Compare commits

...

33 Commits

Author SHA1 Message Date
Rick Ossendrijver
4974f5afb4 Add support for renaming setup and teardown methods 2021-12-27 15:34:41 +01:00
Rick Ossendrijver
00012c6aa8 Extend StaticImportCheck test coverage (#21) 2021-11-14 14:39:15 +01:00
Rick Ossendrijver
193589d193 Require static import of SpringBootTest.WebEnvironment constants (#19) 2021-11-14 14:26:17 +01:00
Stephan Schroevers
1a1588d413 Upgrade Error Prone 2.9.0 -> 2.10.0 2021-11-14 13:50:26 +01:00
Stephan Schroevers
6656bd8186 Assorted upgrades 2021-11-14 13:50:26 +01:00
Rick Ossendrijver
26a9b46de7 Add Refaster rules for StepVerifier creation (#18) 2021-11-14 13:48:39 +01:00
Phil Werli
17e74f778b Add Refaster rules for redundant Flux#concat invocations (#20) 2021-11-13 18:04:49 +01:00
Stephan Schroevers
e1bdb098de Upgrade AssertJ Core 3.20.2 -> 3.21.0
See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://github.com/joel-costigliola/assertj-core/compare/assertj-core-3.20.2...assertj-core-3.21.0
2021-09-25 13:53:58 +02:00
Stephan Schroevers
0be162c837 Assorted upgrades 2021-09-25 13:53:31 +02:00
Stephan Schroevers
edfb2a70e8 Upgrade Guava 30.1.1-jre -> 31.0-jre
And drop Refaster rules for APIs that are now `@Deprecated` or have an
`@InlineMe` annotation.

See:
- https://guava.dev/releases/31.0-jre/api/diffs/
- https://github.com/google/guava/releases/tag/v31.0
- https://github.com/google/guava/compare/v30.1.1...v31.0
2021-09-25 13:17:10 +02:00
Stephan Schroevers
c1e5dc4339 Assorted upgrades 2021-09-19 21:07:40 +02:00
Stephan Schroevers
7218006b3c Assorted upgrades 2021-09-03 07:42:57 +02:00
Rick Ossendrijver
a3f599ba1b Introduce WebClientTemplates (#16) 2021-08-30 21:54:28 +02:00
Stephan Schroevers
f819a3e42f Assorted upgrades 2021-08-20 08:50:15 +02:00
Stephan Schroevers
7fc865c769 Light RefasterRuleResourceCompilerTaskListener cleanup 2021-08-19 22:43:10 +02:00
Stephan Schroevers
0287060d96 Fix and optimize FormatStringConcatenationCheck 2021-08-19 22:42:41 +02:00
Stephan Schroevers
ac4e81f2c7 Support running the build using JDK 16+ 2021-08-14 19:29:55 +02:00
Stephan Schroevers
350f028781 Sync with PSM parent 2021-08-14 19:13:31 +02:00
Stephan Schroevers
299ce7b800 Assorted upgrades 2021-08-14 19:13:31 +02:00
Stephan Schroevers
f3a929a3bb Upgrade fmt-maven-plugin, reformat 2021-08-04 10:59:23 +02:00
Stephan Schroevers
625a55add5 Add extra Refaster rule, document possible FormatStringConcatenation extension 2021-08-04 10:58:16 +02:00
Stephan Schroevers
ef59340987 Assorted upgrades 2021-08-04 10:58:05 +02:00
Stephan Schroevers
f2aea38b17 Introduce AbstractOptionalAssertContainsSame Refaster template 2021-08-03 13:53:44 +02:00
Stephan Schroevers
5cd8863eb5 Slightly optimize MethodReferenceUsage check 2021-08-01 19:10:46 +02:00
Stephan Schroevers
5b57b4720b Introduce MockitoStubbing check 2021-08-01 18:29:54 +02:00
Stephan Schroevers
5224571407 Assorted FormatStringConcatenationCheck improvements 2021-08-01 15:56:32 +02:00
Stephan Schroevers
c8189e9431 Document RefasterCheck follow-up task 2021-08-01 12:41:00 +02:00
Stephan Schroevers
0fe42f1ea6 Introduce FormatStringConcatenation check 2021-08-01 12:32:08 +02:00
Stephan Schroevers
78b29107a3 Upgrade dependencies 2021-07-29 12:09:19 +02:00
Stephan Schroevers
7ae7d5105a Document StreamMapFirst gotcha, courtesy of Nathan 2021-07-29 11:51:11 +02:00
Stephan Schroevers
0976b33d61 Support Refaster templates without an @AfterTemplate method 2021-07-29 11:48:51 +02:00
Stephan Schroevers
4b6f81c4fa imeZoneUsageCheck: dDon't flag likely overrides 2021-07-27 17:44:04 +02:00
Stephan Schroevers
1e879c175e Don't flag empty test (helper) methods 2021-07-27 17:43:08 +02:00
39 changed files with 1328 additions and 157 deletions

10
.mvn/jvm.config Normal file
View File

@@ -0,0 +1,10 @@
--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.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

@@ -171,10 +171,25 @@
<artifactId>spring-context</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<scope>test</scope>
<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>
</dependency>
<dependency>
<groupId>org.testng</groupId>

View File

@@ -17,9 +17,11 @@ 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 com.sun.tools.javac.code.Symbol.MethodSymbol;
import java.util.Optional;
/** A {@link BugChecker} which flags empty methods that seemingly can simply be deleted. */
@AutoService(BugChecker.class)
@@ -41,7 +43,8 @@ public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatc
if (tree.getBody() == null
|| !tree.getBody().getStatements().isEmpty()
|| ASTHelpers.containsComments(tree, state)
|| PERMITTED_ANNOTATION.matches(tree, state)) {
|| PERMITTED_ANNOTATION.matches(tree, state)
|| isInPossibleTestHelperClass(state)) {
return Description.NO_MATCH;
}
@@ -52,4 +55,11 @@ public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatc
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

@@ -0,0 +1,253 @@
package tech.picnic.errorprone.bugpatterns;
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.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;
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;
/**
* 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(
name = "FormatStringConcatenation",
summary = "Defer string concatenation to the invoked method",
linkType = LinkType.NONE,
severity = SeverityLevel.WARNING,
tags = StandardTags.SIMPLIFICATION)
public final class FormatStringConcatenationCheck 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;
}
@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;
}
@Override
public Void visitParenthesized(ParenthesizedTree tree, VisitorState state) {
return tree.getExpression().accept(this, state);
}
@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 -> Util.treeToString(tree, state))
.collect(joining(", "));
}
}
}

View File

@@ -7,6 +7,7 @@ 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.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
@@ -22,6 +23,7 @@ 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.Objects;
import java.util.Optional;
import javax.lang.model.element.Modifier;
@@ -59,6 +61,13 @@ public final class JUnitMethodDeclarationCheck extends BugChecker implements Met
isType("org.junit.jupiter.api.AfterEach"),
isType("org.junit.jupiter.api.BeforeAll"),
isType("org.junit.jupiter.api.BeforeEach")));
private static final ImmutableMap<String, String> REPLACEMENTS =
ImmutableMap.<String, String>builder()
.put("org.junit.jupiter.api.AfterAll", "afterAll")
.put("org.junit.jupiter.api.AfterEach", "tearDown")
.put("org.junit.jupiter.api.BeforeAll", "beforeAll")
.put("org.junit.jupiter.api.BeforeEach", "setUp")
.build();
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
@@ -83,9 +92,32 @@ public final class JUnitMethodDeclarationCheck extends BugChecker implements Met
tryCanonicalizeMethodName(tree, state).ifPresent(builder::merge);
}
if (SETUP_OR_TEARDOWN_METHOD.matches(tree, state)) {
tryCanonicalizeSetupOrTeardownMethod(tree, state).ifPresent(builder::merge);
}
return builder.isEmpty() ? Description.NO_MATCH : describeMatch(tree, builder.build());
}
private static Optional<SuggestedFix> tryCanonicalizeSetupOrTeardownMethod(
MethodTree tree, VisitorState state) {
Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(tree);
return symbol.getAnnotationMirrors().stream()
.map(compound -> compound.getAnnotationType().toString())
.filter(REPLACEMENTS::containsKey)
.filter(
e ->
!symbol
.getSimpleName()
.toString()
.startsWith(
Objects.requireNonNull(
REPLACEMENTS.get(e)))) // , tree.getName().toString()))
.findFirst()
.map(e -> SuggestedFixes.renameMethod(tree, REPLACEMENTS.get(e), state));
}
private static Optional<SuggestedFix> tryCanonicalizeMethodName(
MethodTree tree, VisitorState state) {
return Optional.ofNullable(ASTHelpers.getSymbol(tree))

View File

@@ -69,7 +69,10 @@ public final class MethodReferenceUsageCheck extends BugChecker
*/
return constructMethodRef(tree, tree.getBody())
.map(SuggestedFix.Builder::build)
.filter(fix -> SuggestedFixes.compilesWithFix(fix, state))
.filter(
fix ->
SuggestedFixes.compilesWithFix(
fix, state, ImmutableList.of(), /* onlyInSameCompilationUnit= */ true))
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}

View File

@@ -0,0 +1,58 @@
package tech.picnic.errorprone.bugpatterns;
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.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;
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;
/**
* 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(
name = "MockitoStubbing",
summary = "Don't unnecessarily use Mockito's `eq(...)`",
linkType = LinkType.NONE,
severity = SeverityLevel.SUGGESTION,
tags = StandardTags.SIMPLIFICATION)
public final class MockitoStubbingCheck 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,
Util.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

@@ -98,6 +98,8 @@ public final class RefasterCheck extends BugChecker implements CompilationUnitTr
// 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;
}
/* Then apply them. */

View File

@@ -80,6 +80,7 @@ public final class StaticImportCheck extends BugChecker implements MemberSelectT
"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",

View File

@@ -1,7 +1,11 @@
package tech.picnic.errorprone.bugpatterns;
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;
@@ -35,7 +39,11 @@ public final class TimeZoneUsageCheck extends BugChecker implements MethodInvoca
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> BANNED_TIME_METHOD =
anyOf(
instanceMethod().onDescendantOf(Clock.class.getName()).namedAnyOf("getZone", "withZone"),
allOf(
instanceMethod()
.onDescendantOf(Clock.class.getName())
.namedAnyOf("getZone", "withZone"),
not(enclosingClass(isSubtypeOf(Clock.class)))),
staticMethod()
.onClass(Clock.class.getName())
.namedAnyOf(

View File

@@ -97,12 +97,24 @@ 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) {

View File

@@ -39,6 +39,7 @@ 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;
@@ -357,6 +358,18 @@ 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
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) {
@@ -517,7 +530,7 @@ final class AssertJTemplates {
static final class AssertThatSetsAreEqual<S, T extends S> {
@BeforeTemplate
IterableAssert<S> before(Set<S> set1, Set<T> set2) {
AbstractCollectionAssert<?, ?, S, ?> before(Set<S> set1, Set<T> set2) {
return Refaster.anyOf(
assertThat(set1).isEqualTo(set2),
assertThat(set1).containsExactlyInAnyOrderElementsOf(set2));
@@ -525,7 +538,7 @@ final class AssertJTemplates {
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Set<S> set1, Set<T> set2) {
AbstractCollectionAssert<?, ?, S, ?> after(Set<S> set1, Set<T> set2) {
return assertThat(set1).hasSameElementsAs(set2);
}
}
@@ -536,13 +549,13 @@ final class AssertJTemplates {
static final class AssertThatMultisetsAreEqual<S, T extends S> {
@BeforeTemplate
IterableAssert<S> before(Multiset<S> multiset1, Multiset<T> multiset2) {
AbstractCollectionAssert<?, ?, S, ?> before(Multiset<S> multiset1, Multiset<T> multiset2) {
return assertThat(multiset1).isEqualTo(multiset2);
}
@AfterTemplate
@UseImportPolicy(ImportPolicy.STATIC_IMPORT_ALWAYS)
IterableAssert<S> after(Multiset<S> multiset1, Multiset<T> multiset2) {
AbstractCollectionAssert<?, ?, S, ?> after(Multiset<S> multiset1, Multiset<T> multiset2) {
return assertThat(multiset1).containsExactlyInAnyOrderElementsOf(multiset2);
}
}
@@ -922,7 +935,7 @@ final class AssertJTemplates {
}
@BeforeTemplate
IterableAssert<T> before2(
AbstractCollectionAssert<?, ?, T, ?> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, Iterable<U> iterable) {
return assertThat(stream.collect(collector)).containsExactlyInAnyOrderElementsOf(iterable);
}
@@ -943,7 +956,7 @@ final class AssertJTemplates {
}
@BeforeTemplate
IterableAssert<T> before2(
AbstractCollectionAssert<?, ?, T, ?> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, U[] array) {
return assertThat(stream.collect(collector)).containsExactlyInAnyOrder(array);
}
@@ -967,7 +980,7 @@ final class AssertJTemplates {
@BeforeTemplate
@SuppressWarnings("AssertThatStreamContainsExactlyInAnyOrder" /* Varargs converted to array. */)
IterableAssert<T> before2(
AbstractCollectionAssert<?, ?, T, ?> before2(
Stream<S> stream, Collector<S, ?, ? extends Multiset<T>> collector, @Repeated U elements) {
return assertThat(stream.collect(collector))
.containsExactlyInAnyOrder(Refaster.asVarargs(elements));

View File

@@ -128,19 +128,6 @@ final class ImmutableListTemplates {
}
}
/** 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

View File

@@ -14,6 +14,7 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import javax.annotation.Nullable;
/** Refaster templates related to expressions dealing with {@link Optional}s. */
final class OptionalTemplates {
@@ -24,7 +25,7 @@ final class OptionalTemplates {
// parentheses around the null check, but that's currently not the case. Try to fix that.
@BeforeTemplate
@SuppressWarnings("TernaryOperatorOptionalNegativeFiltering" /* Special case. */)
Optional<T> before(T object) {
Optional<T> before(@Nullable T object) {
return object == null ? Optional.empty() : Optional.of(object);
}
@@ -91,19 +92,6 @@ 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}.

View File

@@ -161,6 +161,32 @@ 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();
}
}
/** Don't unnecessarily invoke {@link Flux#concat(Publisher)}. */
static final class FluxIdentity<T> {
@BeforeTemplate
Flux<T> before(Flux<T> flux) {
return Flux.concat(flux);
}
@AfterTemplate
Flux<T> after(Flux<T> flux) {
return flux;
}
}
/**
* Prefer a collection using {@link MoreCollectors#toOptional()} over more contrived alternatives.
*/
@@ -194,6 +220,32 @@ 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

View File

@@ -148,6 +148,8 @@ 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) {

View File

@@ -14,6 +14,7 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
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)`?
@@ -37,7 +38,7 @@ final class StringTemplates {
/** Prefer {@link Strings#isNullOrEmpty(String)} over the more verbose alternative. */
static final class StringIsNullOrEmpty {
@BeforeTemplate
boolean before(String str) {
boolean before(@Nullable String str) {
return str == null || str.isEmpty();
}

View File

@@ -0,0 +1,36 @@
package tech.picnic.errorprone.refastertemplates;
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
/**
* 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<?> before2(
WebTestClient.RequestBodySpec requestBodySpec, T value) {
return requestBodySpec.body(fromValue(value));
}
@AfterTemplate
RequestHeadersSpec<?> after(RequestBodySpec requestBodySpec, T value) {
return requestBodySpec.bodyValue(value);
}
}
}

View File

@@ -33,6 +33,10 @@ public final class EmptyMethodCheckTest {
" interface F {",
" void fun();",
" }",
"",
" final class MyTestClass {",
" void helperMethod() {}",
" }",
"}")
.addSourceLines(
"B.java",

View File

@@ -0,0 +1,414 @@
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 FormatStringConcatenationCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(FormatStringConcatenationCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(FormatStringConcatenationCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.base.Preconditions.checkArgument;",
"import static com.google.common.base.Preconditions.checkNotNull;",
"import static com.google.common.base.Preconditions.checkState;",
"import static com.google.common.base.Verify.verify;",
"import static org.assertj.core.api.Assertions.assertThat;",
"import static org.assertj.core.api.SoftAssertions.assertSoftly;",
"",
"import java.util.Locale;",
"import java.util.Formatter;",
"import org.assertj.core.api.Assertions;",
"import org.assertj.core.api.BDDAssertions;",
"import org.assertj.core.api.Fail;",
"import org.assertj.core.api.ThrowableAssertAlternative;",
"import org.assertj.core.api.WithAssertions;",
"import org.slf4j.Logger;",
"import org.slf4j.LoggerFactory;",
"import org.slf4j.Marker;",
"",
"class A {",
" private static final Logger LOG = LoggerFactory.getLogger(A.class);",
"",
" void negative() {",
" hashCode();",
" equals(new A());",
" equals(toString());",
" equals(0);",
" equals(\"str\");",
" equals(\"str\" + 0);",
" equals(0 + 0);",
" equals(0 - 0);",
" equals(\"str \" + toString());",
" }",
"",
" void assertj() {",
" assertThat(0).overridingErrorMessage(toString());",
" assertThat(0).overridingErrorMessage(\"str\");",
" assertThat(0).overridingErrorMessage(\"str \" + 0);",
" assertThat(0).overridingErrorMessage(\"str %s\", 2 * 3);",
" assertThat(0).overridingErrorMessage(\"str %s\", toString());",
" // BUG: Diagnostic contains:",
" assertThat(0).overridingErrorMessage(\"str \" + hashCode() / 2);",
" // BUG: Diagnostic contains:",
" assertThat(0).overridingErrorMessage((\"str \" + toString()));",
" // BUG: Diagnostic contains:",
" assertThat(0).overridingErrorMessage(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(0).overridingErrorMessage(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(0).withFailMessage(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(0).withFailMessage(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertSoftly(softly -> softly.fail(\"str \" + toString()));",
" // BUG: Diagnostic contains:",
" assertSoftly(softly -> softly.fail(\"%s \" + toString(), \"arg\"));",
" assertSoftly(softly -> softly.fail(\"str \" + toString(), new Throwable()));",
"",
" // BUG: Diagnostic contains:",
" assertThat(\"\").isEqualTo(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(\"\").isEqualTo(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasMessage(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasMessage(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasMessageContaining(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasMessageContaining(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasMessageEndingWith(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasMessageEndingWith(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasMessageStartingWith(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasMessageStartingWith(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasRootCauseMessage(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasRootCauseMessage(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasStackTraceContaining(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(new Error()).hasStackTraceContaining(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(0).as(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(0).as(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" assertThat(0).describedAs(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" assertThat(0).describedAs(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withMessage(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withMessage(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withMessageContaining(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withMessageContaining(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withMessageEndingWith(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withMessageEndingWith(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withMessageStartingWith(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withMessageStartingWith(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withStackTraceContaining(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" ((ThrowableAssertAlternative) null).withStackTraceContaining(\"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" ((WithAssertions) null).fail(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" ((WithAssertions) null).fail(\"%s \" + toString(), \"arg\");",
" ((WithAssertions) null).fail(\"str \" + toString(), new Throwable());",
"",
" // BUG: Diagnostic contains:",
" Assertions.fail(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" Assertions.fail(\"%s \" + toString(), \"arg\");",
" Assertions.fail(\"str \" + toString(), new Throwable());",
"",
" // BUG: Diagnostic contains:",
" BDDAssertions.fail(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" BDDAssertions.fail(\"%s \" + toString(), \"arg\");",
" BDDAssertions.fail(\"str \" + toString(), new Throwable());",
"",
" // BUG: Diagnostic contains:",
" Fail.fail(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" Fail.fail(\"%s \" + toString(), \"arg\");",
" Fail.fail(\"str \" + toString(), new Throwable());",
" }",
"",
" void guava() {",
" checkArgument(true);",
" checkArgument(true, toString());",
" checkArgument(true, \"str\");",
" checkArgument(true, \"str \" + 0);",
" checkArgument(true, \"str %s\", 2 * 3);",
" checkArgument(true, \"str %s\", toString());",
" // BUG: Diagnostic contains:",
" checkArgument(true, \"str \" + hashCode() / 2);",
" // BUG: Diagnostic contains:",
" checkArgument(true, (\"str \" + toString()));",
" // BUG: Diagnostic contains:",
" checkArgument(true, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" checkArgument(true, \"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" checkNotNull(true, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" checkNotNull(true, \"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" checkState(true, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" checkState(true, \"%s \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" verify(true, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" verify(true, \"%s \" + toString(), \"arg\");",
" }",
"",
" void jdk() {",
" String.format(\"str\");",
" String.format(\"str \" + 0);",
" String.format(\"str {}\", 2 * 3);",
" String.format(\"str {}\", toString());",
" // BUG: Diagnostic contains:",
" String.format(\"str \" + hashCode() / 2);",
" // BUG: Diagnostic contains:",
" String.format((\"str \" + toString()));",
" // BUG: Diagnostic contains:",
" String.format(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" String.format(\"{} \" + toString(), \"arg\");",
"",
" String.format(Locale.ROOT, \"str\");",
" String.format(Locale.ROOT, \"str \" + 0);",
" String.format(Locale.ROOT, \"str {}\", 2 * 3);",
" String.format(Locale.ROOT, \"str {}\", toString());",
" // BUG: Diagnostic contains:",
" String.format(Locale.ROOT, (\"str \" + toString()));",
" // BUG: Diagnostic contains:",
" String.format(Locale.ROOT, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" String.format(Locale.ROOT, \"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" new Formatter().format(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" new Formatter().format(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" new Formatter().format(Locale.ROOT, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" new Formatter().format(Locale.ROOT, \"{} \" + toString(), \"arg\");",
" }",
"",
" void slf4j() {",
" LOG.debug(\"str\");",
" LOG.debug(\"str \" + 0);",
" LOG.debug(\"str {}\", 2 * 3);",
" LOG.debug(\"str {}\", toString());",
" // BUG: Diagnostic contains:",
" LOG.debug(\"str \" + hashCode() / 2);",
" // BUG: Diagnostic contains:",
" LOG.debug((\"str \" + toString()));",
" // BUG: Diagnostic contains:",
" LOG.debug(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.debug(\"{} \" + toString(), \"arg\");",
"",
" LOG.debug((Marker) null, \"str\");",
" LOG.debug((Marker) null, \"str \" + 0);",
" LOG.debug((Marker) null, \"str {}\", 2 * 3);",
" LOG.debug((Marker) null, \"str {}\", toString());",
" // BUG: Diagnostic contains:",
" LOG.debug((Marker) null, (\"str \" + toString()));",
" // BUG: Diagnostic contains:",
" LOG.debug((Marker) null, \"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.debug((Marker) null, \"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.error(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.error(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.error((Marker) null,\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.error((Marker) null,\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.info(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.info(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.info((Marker) null,\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.info((Marker) null,\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.trace(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.trace(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.trace((Marker) null,\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.trace((Marker) null,\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.warn(\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.warn(\"{} \" + toString(), \"arg\");",
"",
" // BUG: Diagnostic contains:",
" LOG.warn((Marker) null,\"str \" + toString());",
" // BUG: Diagnostic contains:",
" LOG.warn((Marker) null,\"{} \" + toString(), \"arg\");",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import static com.google.common.base.Preconditions.checkArgument;",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"import java.util.Locale;",
"import java.util.Formatter;",
"import org.slf4j.Logger;",
"import org.slf4j.LoggerFactory;",
"import org.slf4j.Marker;",
"",
"class A {",
" private static final Logger LOG = LoggerFactory.getLogger(A.class);",
"",
" void assertj() {",
" assertThat(0).overridingErrorMessage(toString() + \" str\");",
" assertThat(0).overridingErrorMessage(\"str \" + toString());",
" assertThat(0).overridingErrorMessage(toString() + toString());",
" assertThat(0).overridingErrorMessage(\"str \" + toString() + \" word \" + new A().hashCode());",
" assertThat(0).overridingErrorMessage(\"str \" + (toString() + \" word \") + (hashCode() / 2));",
"",
" // Flagged but not auto-fixed.",
" assertThat(0).overridingErrorMessage(\"%s \" + toString(), \"arg\");",
" }",
"",
" void guava() {",
" checkArgument(true, \"str \" + toString());",
"",
" // Flagged but not auto-fixed.",
" checkArgument(true, \"%s \" + toString(), \"arg\");",
" }",
"",
" void jdk() {",
" String.format(\"str \" + toString());",
" String.format(Locale.ROOT, \"str \" + toString());",
"",
" // Flagged but not auto-fixed.",
" String.format(\"{} \" + toString(), \"arg\");",
" String.format(Locale.ROOT, \"{} \" + toString(), \"arg\");",
" }",
"",
" void slf4j() {",
" LOG.debug(\"str \" + toString());",
" LOG.debug((Marker) null, \"str \" + toString());",
"",
" // Flagged but not auto-fixed.",
" LOG.debug(\"{} \" + toString(), \"arg\");",
" LOG.debug((Marker) null, \"{} \" + toString(), \"arg\");",
" }",
"}")
.addOutputLines(
"out/A.java",
"import static com.google.common.base.Preconditions.checkArgument;",
"import static org.assertj.core.api.Assertions.assertThat;",
"",
"import java.util.Locale;",
"import java.util.Formatter;",
"import org.slf4j.Logger;",
"import org.slf4j.LoggerFactory;",
"import org.slf4j.Marker;",
"",
"class A {",
" private static final Logger LOG = LoggerFactory.getLogger(A.class);",
"",
" void assertj() {",
" assertThat(0).overridingErrorMessage(\"%s str\", toString());",
" assertThat(0).overridingErrorMessage(\"str %s\", toString());",
" assertThat(0).overridingErrorMessage(\"%s%s\", toString(), toString());",
" assertThat(0).overridingErrorMessage(\"str %s word %s\", toString(), new A().hashCode());",
" assertThat(0).overridingErrorMessage(\"str %s word %s\", toString(), hashCode() / 2);",
"",
" // Flagged but not auto-fixed.",
" assertThat(0).overridingErrorMessage(\"%s \" + toString(), \"arg\");",
" }",
"",
" void guava() {",
" checkArgument(true, \"str %s\", toString());",
"",
" // Flagged but not auto-fixed.",
" checkArgument(true, \"%s \" + toString(), \"arg\");",
" }",
"",
" void jdk() {",
" String.format(\"str %s\", toString());",
" String.format(Locale.ROOT, \"str %s\", toString());",
"",
" // Flagged but not auto-fixed.",
" String.format(\"{} \" + toString(), \"arg\");",
" String.format(Locale.ROOT, \"{} \" + toString(), \"arg\");",
" }",
" void slf4j() {",
" LOG.debug(\"str {}\", toString());",
" LOG.debug((Marker) null, \"str {}\", toString());",
"",
" // Flagged but not auto-fixed.",
" LOG.debug(\"{} \" + toString(), \"arg\");",
" LOG.debug((Marker) null, \"{} \" + toString(), \"arg\");",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -24,15 +24,15 @@ public final class JUnitMethodDeclarationCheckTest {
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" @BeforeAll void setUp1() {}",
" @BeforeAll void beforeAll1() {}",
" // BUG: Diagnostic contains:",
" @BeforeAll public void setUp2() {}",
" @BeforeAll public void beforeAll2() {}",
" // BUG: Diagnostic contains:",
" @BeforeAll protected void setUp3() {}",
" @BeforeAll protected void beforeAll3() {}",
" // BUG: Diagnostic contains:",
" @BeforeAll private void setUp4() {}",
" @BeforeAll private void beforeAll4() {}",
"",
" @BeforeEach void setup5() {}",
" @BeforeEach void setUp5() {}",
" // BUG: Diagnostic contains:",
" @BeforeEach public void setUp6() {}",
" // BUG: Diagnostic contains:",
@@ -48,13 +48,13 @@ public final class JUnitMethodDeclarationCheckTest {
" // BUG: Diagnostic contains:",
" @AfterEach private void tearDown4() {}",
"",
" @AfterAll void tearDown5() {}",
" @AfterAll void afterAll5() {}",
" // BUG: Diagnostic contains:",
" @AfterAll public void tearDown6() {}",
" @AfterAll public void afterAll6() {}",
" // BUG: Diagnostic contains:",
" @AfterAll protected void tearDown7() {}",
" @AfterAll protected void afterAll7() {}",
" // BUG: Diagnostic contains:",
" @AfterAll private void tearDown8() {}",
" @AfterAll private void afterAll8() {}",
"",
" @Test void method1() {}",
" // BUG: Diagnostic contains:",
@@ -92,11 +92,11 @@ public final class JUnitMethodDeclarationCheckTest {
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class B extends A {",
" @Override @BeforeAll void setUp1() {}",
" @Override @BeforeAll public void setUp2() {}",
" @Override @BeforeAll protected void setUp3() {}",
" @Override @BeforeAll void beforeAll1() {}",
" @Override @BeforeAll public void beforeAll2() {}",
" @Override @BeforeAll protected void beforeAll3() {}",
"",
" @Override @BeforeEach void setup5() {}",
" @Override @BeforeEach void setUp5() {}",
" @Override @BeforeEach public void setUp6() {}",
" @Override @BeforeEach protected void setUp7() {}",
"",
@@ -104,9 +104,9 @@ public final class JUnitMethodDeclarationCheckTest {
" @Override @AfterEach public void tearDown2() {}",
" @Override @AfterEach protected void tearDown3() {}",
"",
" @Override @AfterAll void tearDown5() {}",
" @Override @AfterAll public void tearDown6() {}",
" @Override @AfterAll protected void tearDown7() {}",
" @Override @AfterAll void afterAll5() {}",
" @Override @AfterAll public void afterAll6() {}",
" @Override @AfterAll protected void afterAll7() {}",
"",
" @Override @Test void method1() {}",
" @Override @Test void testMethod2() {}",
@@ -163,10 +163,10 @@ public final class JUnitMethodDeclarationCheckTest {
"import org.junit.jupiter.params.ParameterizedTest;",
"",
"class A {",
" @BeforeAll void setUp1() {}",
" @BeforeEach void setUp2() {}",
" @AfterEach void setUp3() {}",
" @AfterAll void setUp4() {}",
" @BeforeAll void beforeAll() {}",
" @BeforeEach void setUp() {}",
" @AfterEach void tearDown() {}",
" @AfterAll void afterAll() {}",
"",
" @Test void foo() {}",
" @ParameterizedTest void bar() {}",
@@ -177,4 +177,36 @@ public final class JUnitMethodDeclarationCheckTest {
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replaceSetupAndTeardownMethods() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"",
"class A {",
" @BeforeAll public void setUp1() {}",
" @BeforeEach protected void setUp2() {}",
" @AfterEach private void setUp3() {}",
" @AfterAll private void setUp4() {}",
"}")
.addOutputLines(
"out/A.java",
"import org.junit.jupiter.api.AfterAll;",
"import org.junit.jupiter.api.AfterEach;",
"import org.junit.jupiter.api.BeforeAll;",
"import org.junit.jupiter.api.BeforeEach;",
"",
"class A {",
" @BeforeAll void beforeAll() {}",
" @BeforeEach void setUp() {}",
" @AfterEach void tearDown() {}",
" @AfterAll void afterAll() {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,101 @@
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 MockitoStubbingCheckTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MockitoStubbingCheck.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(MockitoStubbingCheck.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.ArgumentMatchers.notNull;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Runnable runnable = mock(Runnable.class);",
" doAnswer(inv -> null).when(runnable).run();",
"",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(\"foo\");",
" doAnswer(inv -> null).when(consumer).accept(notNull());",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(consumer).accept(ArgumentMatchers.eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(consumer).accept(eq(toString()));",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null).when(biConsumer).accept(0, \"foo\");",
" doAnswer(inv -> null).when(biConsumer).accept(eq(0), notNull());",
" doAnswer(inv -> null).when(biConsumer).accept(notNull(), eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(biConsumer).accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" // BUG: Diagnostic contains:",
" doAnswer(inv -> null).when(biConsumer).accept(eq(hashCode()), eq(toString()));",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"in/A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null).when(consumer).accept(eq(toString()));",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null).when(biConsumer).accept(ArgumentMatchers.eq(0), ArgumentMatchers.eq(\"foo\"));",
" doAnswer(inv -> null).when(biConsumer).accept(eq(hashCode()), eq(toString()));",
" }",
"}")
.addOutputLines(
"out/A.java",
"import static org.mockito.ArgumentMatchers.eq;",
"import static org.mockito.Mockito.doAnswer;",
"import static org.mockito.Mockito.mock;",
"",
"import java.util.function.BiConsumer;",
"import java.util.function.Consumer;",
"import org.mockito.ArgumentMatchers;",
"",
"class A {",
" void m() {",
" Consumer<String> consumer = mock(Consumer.class);",
" doAnswer(inv -> null).when(consumer).accept(\"foo\");",
" doAnswer(inv -> null).when(consumer).accept(toString());",
"",
" BiConsumer<Integer, String> biConsumer = mock(BiConsumer.class);",
" doAnswer(inv -> null).when(biConsumer).accept(0, \"foo\");",
" doAnswer(inv -> null).when(biConsumer).accept(hashCode(), toString());",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -67,7 +67,8 @@ public final class RefasterCheckTest {
"Stream",
"String",
"TestNGToAssertJ",
"Time");
"Time",
"WebClient");
/**
* Matches the parts of the fully-qualified name of a template class that should be removed in
* order to produce the associated {@link #TEMPLATE_GROUPS template group name}.

View File

@@ -27,6 +27,7 @@ public final class StaticImportCheckTest {
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static java.nio.charset.StandardCharsets.UTF_8;",
"import static java.util.function.Predicate.not;",
"import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;",
"",
"import com.google.common.base.Predicates;",
"import com.google.common.collect.ImmutableMap;",
@@ -35,6 +36,8 @@ public final class StaticImportCheckTest {
"import java.nio.charset.StandardCharsets;",
"import java.util.Optional;",
"import java.util.function.Predicate;",
"import org.springframework.boot.test.context.SpringBootTest;",
"import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;",
"",
"class A {",
" void m() {",
@@ -68,6 +71,10 @@ public final class StaticImportCheckTest {
" Object o1 = StandardCharsets.UTF_8;",
" Object o2 = UTF_8;",
"",
" // BUG: Diagnostic contains:",
" Object e1 = WebEnvironment.RANDOM_PORT;",
" Object e2 = RANDOM_PORT;",
"",
" Optional.empty();",
" }",
"",
@@ -87,8 +94,12 @@ public final class StaticImportCheckTest {
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableSet;",
"import java.nio.charset.StandardCharsets;",
"import java.util.Objects;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.springframework.format.annotation.DateTimeFormat;",
"import org.springframework.format.annotation.DateTimeFormat.ISO;",
"import org.springframework.boot.test.context.SpringBootTest;",
"import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;",
"",
"class A {",
" void m1() {",
@@ -101,6 +112,10 @@ public final class StaticImportCheckTest {
" Predicates.not(null);",
" not(null);",
"",
" Arguments.arguments(\"foo\");",
"",
" Objects.requireNonNull(\"bar\");",
"",
" Object o = StandardCharsets.UTF_8;",
" }",
"",
@@ -113,13 +128,19 @@ public final class StaticImportCheckTest {
" @DateTimeFormat(iso = ISO.DATE) String date,",
" @DateTimeFormat(iso = ISO.DATE_TIME) String dateTime,",
" @DateTimeFormat(iso = ISO.TIME) String time) {}",
"",
" @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)",
" final class Test {}",
"}")
.addOutputLines(
"out/A.java",
"import static com.google.common.collect.ImmutableMap.toImmutableMap;",
"import static com.google.common.collect.ImmutableSet.toImmutableSet;",
"import static java.nio.charset.StandardCharsets.UTF_8;",
"import static java.util.Objects.requireNonNull;",
"import static java.util.function.Predicate.not;",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;",
"import static org.springframework.format.annotation.DateTimeFormat.ISO.DATE;",
"import static org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME;",
"import static org.springframework.format.annotation.DateTimeFormat.ISO.TIME;",
@@ -128,6 +149,10 @@ public final class StaticImportCheckTest {
"import com.google.common.collect.ImmutableMap;",
"import com.google.common.collect.ImmutableSet;",
"import java.nio.charset.StandardCharsets;",
"import java.util.Objects;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.springframework.boot.test.context.SpringBootTest;",
"import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;",
"import org.springframework.format.annotation.DateTimeFormat;",
"import org.springframework.format.annotation.DateTimeFormat.ISO;",
"",
@@ -142,6 +167,10 @@ public final class StaticImportCheckTest {
" Predicates.not(null);",
" not(null);",
"",
" arguments(\"foo\");",
"",
" requireNonNull(\"bar\");",
"",
" Object o = UTF_8;",
" }",
"",
@@ -154,6 +183,9 @@ public final class StaticImportCheckTest {
" @DateTimeFormat(iso = DATE) String date,",
" @DateTimeFormat(iso = DATE_TIME) String dateTime,",
" @DateTimeFormat(iso = TIME) String time) {}",
"",
" @SpringBootTest(webEnvironment = RANDOM_PORT)",
" final class Test {}",
"}")
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
}

View File

@@ -26,6 +26,7 @@ public final class TimeZoneUsageCheckTest {
"import java.time.LocalDate;",
"import java.time.LocalDateTime;",
"import java.time.LocalTime;",
"import java.time.ZoneId;",
"",
"class A {",
" void m() {",
@@ -78,6 +79,24 @@ public final class TimeZoneUsageCheckTest {
" // BUG: Diagnostic matches: X",
" LocalTime.now(UTC);",
" }",
"",
" abstract class ForwardingClock extends Clock {",
" private final Clock clock;",
"",
" ForwardingClock(Clock clock) {",
" this.clock = clock;",
" }",
"",
" @Override",
" public ZoneId getZone() {",
" return clock.getZone();",
" }",
"",
" @Override",
" public Clock withZone(ZoneId zone) {",
" return clock.withZone(zone);",
" }",
" }",
"}")
.doTest();
}

View File

@@ -44,6 +44,12 @@ final class AssertJOptionalTemplatesTest implements RefasterTemplateTestCase {
assertThat(Optional.of(4)).isPresent().hasValue(4));
}
ImmutableSet<AbstractAssert<?, ?>> testAbstractOptionalAssertContainsSame() {
return ImmutableSet.of(
assertThat(Optional.of(1)).get().isSameAs(1),
assertThat(Optional.of(2)).isPresent().isSameAs(2));
}
AbstractAssert<?, ?> testAssertThatOptionalHasValueMatching() {
return assertThat(Optional.of("foo").filter(String::isEmpty)).isPresent();
}

View File

@@ -40,6 +40,11 @@ final class AssertJOptionalTemplatesTest implements RefasterTemplateTestCase {
assertThat(Optional.of(4)).hasValue(4));
}
ImmutableSet<AbstractAssert<?, ?>> testAbstractOptionalAssertContainsSame() {
return ImmutableSet.of(
assertThat(Optional.of(1)).containsSame(1), assertThat(Optional.of(2)).containsSame(2));
}
AbstractAssert<?, ?> testAssertThatOptionalHasValueMatching() {
return assertThat(Optional.of("foo")).get().matches(String::isEmpty);
}

View File

@@ -94,7 +94,8 @@ final class ImmutableListMultimapTemplatesTest implements RefasterTemplateTestCa
.collect(
flatteningToImmutableListMultimap(
Map.Entry::getKey, e -> e.getValue().stream().map(Math::toIntExact))),
Multimaps.asMap((Multimap<String, Long>) ImmutableSetMultimap.of("bar", 2L)).entrySet()
Multimaps.asMap((Multimap<String, Long>) ImmutableSetMultimap.of("bar", 2L))
.entrySet()
.stream()
.collect(
flatteningToImmutableListMultimap(

View File

@@ -59,10 +59,6 @@ final class ImmutableListTemplatesTest implements RefasterTemplateTestCase {
Stream.of(2).collect(collectingAndThen(toList(), ImmutableList::copyOf)));
}
ImmutableList<Integer> testImmutableListAsList() {
return ImmutableList.of(1, 2, 3).asList();
}
ImmutableSet<ImmutableList<Integer>> testImmutableListSortedCopyOf() {
return ImmutableSet.of(
ImmutableList.sortedCopyOf(naturalOrder(), ImmutableSet.of(1)),

View File

@@ -57,10 +57,6 @@ final class ImmutableListTemplatesTest implements RefasterTemplateTestCase {
Stream.of(1).collect(toImmutableList()), Stream.of(2).collect(toImmutableList()));
}
ImmutableList<Integer> testImmutableListAsList() {
return ImmutableList.of(1, 2, 3);
}
ImmutableSet<ImmutableList<Integer>> testImmutableListSortedCopyOf() {
return ImmutableSet.of(
ImmutableList.sortedCopyOf(ImmutableSet.of(1)),

View File

@@ -72,7 +72,8 @@ final class ImmutableSetMultimapTemplatesTest implements RefasterTemplateTestCas
.collect(
flatteningToImmutableSetMultimap(
Map.Entry::getKey, e -> e.getValue().stream().map(Math::toIntExact))),
Multimaps.asMap((Multimap<String, Long>) ImmutableSetMultimap.of("bar", 2L)).entrySet()
Multimaps.asMap((Multimap<String, Long>) ImmutableSetMultimap.of("bar", 2L))
.entrySet()
.stream()
.collect(
flatteningToImmutableSetMultimap(

View File

@@ -34,10 +34,6 @@ final class OptionalTemplatesTest implements RefasterTemplateTestCase {
return Optional::get;
}
Stream<Object> testOptionalToStream() {
return Stream.concat(Streams.stream(Optional.empty()), Streams.stream(Optional.of("foo")));
}
ImmutableSet<Optional<String>> testOptionalFirstIteratorElement() {
return ImmutableSet.of(
ImmutableSet.of("foo").iterator().hasNext()

View File

@@ -34,10 +34,6 @@ final class OptionalTemplatesTest implements RefasterTemplateTestCase {
return Optional::orElseThrow;
}
Stream<Object> testOptionalToStream() {
return Stream.concat(Optional.empty().stream(), Optional.of("foo").stream());
}
ImmutableSet<Optional<String>> testOptionalFirstIteratorElement() {
return ImmutableSet.of(
stream(ImmutableSet.of("foo").iterator()).findFirst(),

View File

@@ -49,6 +49,14 @@ final class ReactorTemplatesTest implements RefasterTemplateTestCase {
return Mono.just("foo").flatMapMany(s -> Mono.just(s + s));
}
Flux<String> testMonoFlux() {
return Flux.concat(Mono.just("foo"));
}
Flux<String> testFluxIdentity() {
return Flux.concat(Flux.just("foo"));
}
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
return ImmutableSet.of(
Mono.just("foo").map(Optional::of).defaultIfEmpty(Optional.empty()),
@@ -59,6 +67,14 @@ final class ReactorTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(PublisherProbe.of(Mono.empty()), PublisherProbe.of(Flux.empty()));
}
StepVerifier.FirstStep<Integer> testStepVerifierFromMono() {
return StepVerifier.create(Mono.just(1));
}
StepVerifier.FirstStep<Integer> testStepVerifierFromFlux() {
return StepVerifier.create(Flux.just(1));
}
StepVerifier.Step<Integer> testStepVerifierStepExpectNextEmpty() {
return StepVerifier.create(Mono.just(0)).expectNext();
}

View File

@@ -50,6 +50,14 @@ final class ReactorTemplatesTest implements RefasterTemplateTestCase {
return Mono.just("foo").flatMap(s -> Mono.just(s + s)).flux();
}
Flux<String> testMonoFlux() {
return Mono.just("foo").flux();
}
Flux<String> testFluxIdentity() {
return Flux.just("foo");
}
ImmutableSet<Mono<Optional<String>>> testMonoCollectToOptional() {
return ImmutableSet.of(
Mono.just("foo").flux().collect(toOptional()),
@@ -60,6 +68,14 @@ final class ReactorTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(PublisherProbe.empty(), PublisherProbe.empty());
}
StepVerifier.FirstStep<Integer> testStepVerifierFromMono() {
return Mono.just(1).as(StepVerifier::create);
}
StepVerifier.FirstStep<Integer> testStepVerifierFromFlux() {
return Flux.just(1).as(StepVerifier::create);
}
StepVerifier.Step<Integer> testStepVerifierStepExpectNextEmpty() {
return StepVerifier.create(Mono.just(0));
}

View File

@@ -0,0 +1,20 @@
package tech.picnic.errorprone.bugpatterns;
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
import com.google.common.collect.ImmutableSet;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.WebClient;
final class WebClientTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(fromValue(""));
}
ImmutableSet<?> testBodyValue() {
return ImmutableSet.of(
WebClient.create("foo").post().body(fromValue("bar")),
WebTestClient.bindToServer().build().post().body(fromValue("bar")));
}
}

View File

@@ -0,0 +1,20 @@
package tech.picnic.errorprone.bugpatterns;
import static org.springframework.web.reactive.function.BodyInserters.fromValue;
import com.google.common.collect.ImmutableSet;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.client.WebClient;
final class WebClientTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(fromValue(""));
}
ImmutableSet<?> testBodyValue() {
return ImmutableSet.of(
WebClient.create("foo").post().bodyValue("bar"),
WebTestClient.bindToServer().build().post().bodyValue("bar"));
}
}

148
pom.xml
View File

@@ -69,6 +69,21 @@
arguments. In particular, JaCoCo relies on this for the configuration
of its Java agent. -->
<argLine>
<!-- Error Prone makes extensive use of unsupported
`javac`-internal APIs, in some cases even reflectively inspecting
them. These APIs are accessed at compile time and when running
tests. The former case is covered by flags in `.mvn/jvm.config`;
the latter is covered here. -->
--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.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
<!-- The test JVMs are short-running. By disabling certain
expensive JIT optimizations we actually speed up most tests. -->
-XX:TieredStopAtLevel=1
@@ -110,18 +125,19 @@
<!-- Dependency and plugin versions that are referenced in more than
one place. We use these to keep dependencies in sync. Version numbers
that need to be referenced only once should *not* be listed here. -->
<version.auto-service>1.0</version.auto-service>
<version.auto-service>1.0.1</version.auto-service>
<version.auto-value>1.8.2</version.auto-value>
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
<version.error-prone-orig>2.8.0</version.error-prone-orig>
<version.error-prone-orig>2.10.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.4</version.error-prone-slf4j>
<version.findbugs-format-string>3.0.0</version.findbugs-format-string>
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>11</version.jdk>
<version.maven>3.6.3</version.maven>
<version.mockito>3.11.2</version.mockito>
<version.mockito>4.0.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.9.1</version.nullaway>
<version.nullaway>0.9.2</version.nullaway>
<!-- XXX: Two other dependencies are potentially of interest:
`com.palantir.assertj-automation:assertj-refaster-rules` and
`com.palantir.baseline:baseline-refaster-rules` contain Refaster rules
@@ -129,8 +145,8 @@
`RefasterRuleBuilderScanner` to convert those to `.refaster` files so
that we can pick them up. (But in case of `baseline-refaster-rules`
perhaps we can simply incorporate all of them.) -->
<version.palantir-assertj-automation>0.2.1</version.palantir-assertj-automation>
<version.palantir-baseline>4.7.0</version.palantir-baseline>
<version.palantir-assertj-automation>0.3.0</version.palantir-assertj-automation>
<version.palantir-baseline>4.42.0</version.palantir-baseline>
<version.surefire>2.22.2</version.surefire>
</properties>
@@ -174,24 +190,29 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.12.4</version>
<version>2.13.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
<version>1.1</version>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<version>${version.auto-service}</version>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${version.auto-value}</version>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
<version>1.8.2</version>
<version>${version.auto-value}</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
@@ -220,7 +241,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-bom</artifactId>
<version>30.1.1-jre</version>
<version>31.0.1-jre</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -260,7 +281,7 @@
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2020.0.9</version>
<version>2020.0.13</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -272,12 +293,12 @@
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.6.2</version>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.1.10</version>
<version>2.1.11</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
@@ -309,12 +330,12 @@
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.20.2</version>
<version>3.21.0</version>
</dependency>
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.16.0</version>
<version>3.19.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -324,7 +345,7 @@
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.7.2</version>
<version>5.8.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -346,10 +367,15 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.9</version>
<version>5.3.13</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -364,7 +390,7 @@
<plugin>
<groupId>com.coveo</groupId>
<artifactId>fmt-maven-plugin</artifactId>
<version>2.9.1</version>
<version>2.12</version>
</plugin>
<plugin>
<groupId>com.github.ekryd.sortpom</groupId>
@@ -385,6 +411,7 @@
</configuration>
<executions>
<execution>
<id>verify-pom-sorting</id>
<goals>
<goal>verify</goal>
</goals>
@@ -392,27 +419,10 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.lewisd</groupId>
<artifactId>lint-maven-plugin</artifactId>
<version>0.0.11</version>
<configuration>
<failOnViolation>false</failOnViolation>
</configuration>
<executions>
<execution>
<id>validate-pom</id>
<goals>
<goal>check</goal>
</goals>
<phase>validate</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapis</artifactId>
<version>3.1</version>
<version>3.2</version>
<configuration>
<bundledSignatures>
<bundledSignature>jdk-internal</bundledSignature>
@@ -605,6 +615,10 @@
<!-- Instead, please use
`com.google.common.base.Preconditions`. -->
</property>
<property name="illegalClasses" value="org\.assertj\.core\.util\.Streams(\..*?)?">
<!-- Instead, please use
`com.google.common.collect.Streams`. -->
</property>
<property name="illegalClasses" value="org\.junit\.jupiter\.api\.Assertions(\..*?)?">
<!-- Instead, please use
`org.assertj.core.api.Assertions`. -->
@@ -622,7 +636,7 @@
</property>
<property name="illegalClasses" value="org\.testng\.AssertJUnit(\..*?)?">
<!-- Instead, please use
`com.google.common.base.Preconditions`. -->
`org.assertj.core.api.Assertions`. -->
</property>
<property name="regexp" value="true" />
</module>
@@ -631,6 +645,10 @@
<module name="InnerAssignment" />
<module name="InvalidJavadocPosition" />
<module name="JavadocBlockTagLocation" />
<module name="MatchXpath">
<property name="query" value="//BLOCK_COMMENT_BEGIN[./COMMENT_CONTENT[contains(@text, '@author')]]" />
<message key="matchxpath.match" value="Avoid the `@author` Javadoc tag; Git history suffices." />
</module>
<module name="MissingDeprecated" />
<module name="MutableException" />
<module name="NeedBraces" />
@@ -648,13 +666,6 @@
<module name="SimplifyBooleanReturn" />
<module name="SuppressWarningsHolder" />
<module name="TrailingComment" />
<module name="WriteTag">
<property name="tag" value="@author" />
<property name="tagFormat" value="\S" />
<property name="tagSeverity" value="error" />
<property name="severity" value="ignore" />
</module>
<module name="UnnecessaryParentheses" />
<module name="UnnecessarySemicolonAfterTypeMemberDeclaration" />
<module name="UnnecessarySemicolonInEnumeration" />
<module name="UnnecessarySemicolonInTryWithResources" />
@@ -675,7 +686,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>8.41.1</version>
<version>9.1</version>
</dependency>
</dependencies>
<executions>
@@ -736,11 +747,14 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
<configuration>
<retryFailedDeploymentCount>3</retryFailedDeploymentCount>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<version>3.0.0</version>
<configuration>
<fail>false</fail>
<rules>
@@ -777,7 +791,7 @@
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>extra-enforcer-rules</artifactId>
<version>1.3</version>
<version>1.4</version>
</dependency>
</dependencies>
<executions>
@@ -836,7 +850,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
<configuration>
<!-- All relevant doclint checks are performed during
the compilation phase; no need to recheck during
@@ -1050,7 +1064,7 @@
<plugin>
<groupId>org.gaul</groupId>
<artifactId>modernizer-maven-plugin</artifactId>
<version>2.2.0</version>
<version>2.3.0</version>
<configuration>
<exclusionPatterns>
<!-- The plugin suggests replacing Guava's
@@ -1059,6 +1073,12 @@
methods don't allow one to convey immutability at
the type system level. -->
<exclusionPattern>com/google/common/collect/Immutable(List|Map|Set)\.(copyOf|of):.*</exclusionPattern>
<!-- The plugin suggests replacing usages of
Guava's `Iterables` class with
`java.util.stream.Stream` equivalents, but the
alternative is often more verbose, requiring
unnecessary conversions to and from streams. -->
<exclusionPattern>com/google/common/collect/Iterables\..*</exclusionPattern>
</exclusionPatterns>
<failOnViolations>false</failOnViolations>
<ignorePackages>
@@ -1071,7 +1091,7 @@
</configuration>
<executions>
<execution>
<id>modernize</id>
<id>run-modernizer</id>
<goals>
<goal>modernizer</goal>
</goals>
@@ -1087,7 +1107,7 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.6.7</version>
<version>1.7.3</version>
<configuration>
<!-- Use multiple threads to speed things up. Extend
timeouts to prevent false positives as a result of
@@ -1100,7 +1120,7 @@
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>
<version>0.14</version>
<version>0.15</version>
</dependency>
</dependencies>
<executions>
@@ -1115,7 +1135,7 @@
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.0.2155</version>
<version>3.9.1.2184</version>
</plugin>
</plugins>
</pluginManagement>
@@ -1250,10 +1270,6 @@
<groupId>com.github.ekryd.sortpom</groupId>
<artifactId>sortpom-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.lewisd</groupId>
<artifactId>lint-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapis</artifactId>
@@ -1435,8 +1451,13 @@
-Xep:BetaApi:OFF
<!-- We don't target JDK 7. -->
-Xep:Java7ApiChecker:OFF
<!-- We don't target JDK 8. -->
-Xep:Java8ApiChecker:OFF
<!-- We don't target Android. -->
-Xep:StaticOrDefaultInterfaceMethod:OFF
<!-- XXX: This check flags false positives.
See https://github.com/google/error-prone/issues/2679. -->
-Xep:VoidMissingNullable:OFF
-XepOpt:NullAway:AnnotatedPackages=tech.picnic
-XepOpt:NullAway:AssertsEnabled=true
-XepOpt:NullAway:CheckOptionalEmptiness=true
@@ -1473,9 +1494,9 @@
</profile>
<profile>
<!-- The `build-checks` and `error-prone` profiles enable a whole
bunch of additional compile checks. This profile ensures that by
default those warnings break the build. Disabling this profile
allows one to collect all build warnings without failing the build. -->
bunch of additional compile checks. By default those warnings break
the build. This profile allows one to collect all build warnings
without failing the build. -->
<id>disallow-warnings</id>
<activation>
<property>
@@ -1492,13 +1513,6 @@
<verifyFail>stop</verifyFail>
</configuration>
</plugin>
<plugin>
<groupId>com.lewisd</groupId>
<artifactId>lint-maven-plugin</artifactId>
<configuration>
<failOnViolation>true</failOnViolation>
</configuration>
</plugin>
<plugin>
<groupId>de.thetaphi</groupId>
<artifactId>forbiddenapis</artifactId>

View File

@@ -7,7 +7,7 @@ import com.google.common.collect.Multimaps;
import com.google.errorprone.CodeTransformer;
import com.google.errorprone.CompositeCodeTransformer;
import com.google.errorprone.refaster.RefasterRuleBuilderScanner;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
@@ -23,6 +23,7 @@ import com.sun.tools.javac.main.JavaCompiler;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Name;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.UncheckedIOException;
import java.util.List;
@@ -62,7 +63,7 @@ final class RefasterRuleResourceCompilerTaskListener implements TaskListener {
try {
outputCodeTransformers(rule.getValue(), getOutputFile(taskEvent, rule.getKey()));
} catch (IOException e) {
throw new UncheckedIOException(e);
throw new UncheckedIOException("Failed to persist compiled Refaster templates", e);
}
}
}
@@ -71,11 +72,12 @@ final class RefasterRuleResourceCompilerTaskListener implements TaskListener {
return Boolean.TRUE.equals(
new TreeScanner<Boolean, Void>() {
@Override
public Boolean visitAnnotation(AnnotationTree node, Void ctx) {
public Boolean visitAnnotation(AnnotationTree node, Void v) {
Symbol sym = ASTHelpers.getSymbol(node);
return (sym != null
&& sym.getQualifiedName().contentEquals(AfterTemplate.class.getCanonicalName()))
|| super.visitAnnotation(node, ctx);
&& sym.getQualifiedName()
.contentEquals(BeforeTemplate.class.getCanonicalName()))
|| super.visitAnnotation(node, v);
}
@Override
@@ -88,13 +90,13 @@ final class RefasterRuleResourceCompilerTaskListener implements TaskListener {
private ImmutableListMultimap<ClassTree, CodeTransformer> compileRefasterTemplates(
ClassTree tree) {
ListMultimap<ClassTree, CodeTransformer> rules = ArrayListMultimap.create();
new TreeScanner<Void, Context>() {
new TreeScanner<Void, Void>() {
@Override
public Void visitClass(ClassTree node, Context ctx) {
rules.putAll(node, RefasterRuleBuilderScanner.extractRules(node, ctx));
return super.visitClass(node, ctx);
public Void visitClass(ClassTree node, Void v) {
rules.putAll(node, RefasterRuleBuilderScanner.extractRules(node, context));
return super.visitClass(node, null);
}
}.scan(tree, context);
}.scan(tree, null);
return ImmutableListMultimap.copyOf(rules);
}
@@ -123,7 +125,7 @@ final class RefasterRuleResourceCompilerTaskListener implements TaskListener {
private static void outputCodeTransformers(List<CodeTransformer> rules, FileObject target)
throws IOException {
try (ObjectOutputStream output = new ObjectOutputStream(target.openOutputStream())) {
try (ObjectOutput output = new ObjectOutputStream(target.openOutputStream())) {
output.writeObject(CompositeCodeTransformer.compose(rules));
}
}