Compare commits

...

32 Commits

Author SHA1 Message Date
Stephan Schroevers
9b4006ba9b Introduce MoreTypes, simplify check 2022-09-11 19:49:00 +02:00
Rick Ossendrijver
ef098fe1f5 Suggestions 2022-09-10 17:23:45 +02:00
Elena Liashenko
668b44be3b Apply suggestion 2022-09-10 17:23:45 +02:00
Elena Liashenko
cb440d2077 Apply suggestion 2022-09-10 17:23:45 +02:00
Elena Liashenko
d3b3d433f9 Fix checkstyle 2022-09-10 17:23:45 +02:00
Elena Liashenko
528c0b4325 Add documentation to utils 2022-09-10 17:23:45 +02:00
Elena Liashenko
3e6b14d8c0 Introduce check for calling flatMap() on Flux<GroupedFlux> 2022-09-10 17:23:45 +02:00
Jeanderson Barros Candido
e34c2baf7c Prefer simple null reference check over calling Objects#{isNull,nonNull} (#228) 2022-09-10 14:37:54 +02:00
Picnic-Bot
184ba8af51 Upgrade NullAway 0.9.10 -> 0.10.0 (#231)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.10...v0.10.0
2022-09-10 14:04:36 +02:00
Stephan Schroevers
63c3aa8259 Improve the RedundantStringConversion check (#193)
- Describe the set of well-known string conversion methods more
  concisely.
- Extend said set to include all relevant `String#valueOf` overloads.
2022-09-10 13:11:44 +02:00
Picnic-Bot
7daabeee48 Upgrade Mockito 4.7.0 -> 4.8.0 (#230)
See:
- https://github.com/mockito/mockito/releases/tag/v4.8.0
- https://github.com/mockito/mockito/compare/v4.7.0...v4.8.0
2022-09-09 10:14:20 +02:00
Picnic-Bot
0acfd8a723 Upgrade Immutables Annotations 2.9.1 -> 2.9.2 (#229)
See:
- https://github.com/immutables/immutables/releases/tag/2.9.2
- https://github.com/immutables/immutables/compare/2.9.1r...2.9.2
2022-09-09 09:04:13 +02:00
Vincent Koeman
000fcefe92 Have RequestMappingAnnotation recognize @RequestPart parameters (#227) 2022-09-08 15:25:46 +02:00
Rick Ossendrijver
1e3ad0fe32 Fix typo in pom.xml (#222) 2022-09-08 12:49:45 +02:00
Stephan Schroevers
b88a668819 Reduce GitHub Actions build workflow permissions (#221) 2022-09-08 08:54:43 +02:00
Stephan Schroevers
4c8e125dcb Drop the dependency on com.google.errorprone:javac (#197)
This new setup matches the upstream Error Prone build configuration and
simplifies development against JDK 11+ internal APIs.
2022-09-05 16:11:06 +02:00
Picnic-Bot
4ab5dc4f32 Upgrade Jackson 2.13.3 -> 2.13.4 (#220)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.13.4
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.13.3...jackson-bom-2.13.4
2022-09-05 09:11:41 +02:00
Picnic-Bot
7c667334cc Upgrade Checker Framework Annotations 3.24.0 -> 3.25.0 (#219)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.25.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.24.0...checker-framework-3.25.0
2022-09-03 13:31:57 +02:00
Picnic-Bot
b4b2afd130 Upgrade NullAway 0.9.9 -> 0.9.10 (#217)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.9.9...v0.9.10
2022-09-01 08:06:51 +02:00
Picnic-Bot
4445c93f6e Upgrade errorprone-slf4j 0.1.13 -> 0.1.15 (#218)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.14
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.15
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.13...v0.1.15
2022-09-01 08:00:59 +02:00
Picnic-Bot
5a7d7ff89b Upgrade Checkstyle 10.3.2 -> 10.3.3 (#216)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.3.3
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.3.2...checkstyle-10.3.3
2022-08-30 10:02:17 +02:00
Picnic-Bot
7ef75e8f07 Upgrade maven-checkstyle-plugin 3.1.2 -> 3.2.0 (#215)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MCHECKSTYLE%20AND%20fixVersion%20%3E%203.1.2%20AND%20fixVersion%20%3C%3D%203.2.0
- https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.1.2...maven-checkstyle-plugin-3.2.0
2022-08-25 09:26:59 +02:00
Rick Ossendrijver
a1f2418805 Introduce release.yml to improve GitHub release notes generation (#213) 2022-08-24 16:31:15 +02:00
Picnic-Bot
39bfcc75ca Upgrade Immutables 2.9.0 -> 2.9.1 (#210)
See:
- https://github.com/immutables/immutables/releases/tag/2.9.1
- https://github.com/immutables/immutables/compare/2.9.0...2.9.1
2022-08-24 09:33:53 +02:00
Stephan Schroevers
753cdce29e [maven-release-plugin] prepare for next development iteration 2022-08-23 08:33:02 +02:00
Stephan Schroevers
36654883e5 [maven-release-plugin] prepare release v0.2.0 2022-08-23 08:33:02 +02:00
Picnic-Bot
d5372934ec Upgrade pitest-maven-plugin 1.9.4 -> 1.9.5 (#211)
See:
- https://github.com/hcoles/pitest/releases/tag/1.9.5
- https://github.com/hcoles/pitest/compare/1.9.4...1.9.5
2022-08-23 07:55:01 +02:00
Stephan Schroevers
f810530599 Fix several RxJava2Adapter Refaster templates (#205)
This reverts some changes from d2bbee3ed9
and instead drops some invalid `Flowable#compose` and `Flux#transform`
rewrite rules.
2022-08-22 11:05:24 +02:00
Stephan Schroevers
6928381403 Drop unnecessary dependency declarations (#208) 2022-08-22 10:49:40 +02:00
Stephan Schroevers
5657a48552 Prefer String#valueOf over Objects#toString (#192)
While there, drop obsolete `NoFunctionalReturnType` warning suppressions.
2022-08-22 10:29:23 +02:00
Stephan Schroevers
50aaf77a9e Enable nohttp-checkstyle (#206)
While this Checkstyle configuration only flags `http://` usages in
Maven-managed source files (thus not in e.g. `pom.xml` or `README.md`
files), this is a low-effort improvement.
2022-08-22 09:39:19 +02:00
Cernat Catalin Stefan
b8e22ffef0 Introduce NonEmptyMono check (#200) 2022-08-20 12:47:56 +02:00
35 changed files with 919 additions and 261 deletions

29
.github/release.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
changelog:
exclude:
labels:
- "ignore-changelog"
categories:
- title: ":rocket: New Error Prone checks and Refaster templates"
labels:
- "new feature"
- title: ":sparkles: Improvements"
labels:
- "improvement"
- title: ":warning: Update considerations and deprecations"
labels:
- "breaking change"
- "deprecation"
- title: ":bug: Bug fixes"
labels:
- "bug"
- "bug fix"
- title: ":books: Documentation, test and build improvements"
labels:
- "documentation"
- "chore"
- title: ":chart_with_upwards_trend: Dependency upgrades"
labels:
- "dependencies"
- title: "Other changes"
labels:
- "*"

View File

@@ -4,6 +4,8 @@ on:
push:
branches:
- 'master'
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-22.04

View File

@@ -2,6 +2,7 @@
-XX:SoftRefLRUPolicyMSPerMB=10
-XX:+UseParallelGC
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.code=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
@@ -9,5 +10,4 @@
--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

@@ -33,9 +33,16 @@ Two other goals that one may find relevant:
`target/pit-reports/index.html` files. For more information check the [PIT
Maven plugin][pitest-maven].
When loading the project in IntelliJ IDEA (and perhaps other IDEs) errors about
the inaccessibility of `com.sun.tools.javac.*` classes may be reported. If this
happens, configure your IDE to enable the `add-exports` profile.
When running the project's tests in IntelliJ IDEA, you might see the following
error:
```
java: exporting a package from system module jdk.compiler is not allowed with --release
```
If this happens, go to _Settings -> Build, Execution, Deployment -> Compiler ->
Java Compiler_ and deselect the option _Use '--release' option for
cross-compilation (Java 9 and later)_. See [IDEA-288052][idea-288052] for
details.
### Contribution guidelines
@@ -335,8 +342,9 @@ Refaster's expressiveness:
[forbidden-apis]: https://github.com/policeman-tools/forbidden-apis
[fossa]: https://fossa.io
[google-java-format]: https://github.com/google/google-java-format
[idea-288052]: https://youtrack.jetbrains.com/issue/IDEA-288052
[maven]: https://maven.apache.org
[modernizer-maven-plugin]: https://github.com/gaul/modernizer-maven-plugin
[sonarcloud]: https://sonarcloud.io
[pitest]: https://pitest.org
[pitest-maven]: https://pitest.org/quickstart/maven
[sonarcloud]: https://sonarcloud.io

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.2.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -69,11 +69,6 @@
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
@@ -128,11 +123,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>

View File

@@ -4,6 +4,10 @@ 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 static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
@@ -16,11 +20,14 @@ import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import java.util.function.Function;
import java.util.function.Supplier;
import reactor.core.publisher.Flux;
/**
@@ -32,11 +39,12 @@ import reactor.core.publisher.Flux;
* former interleaves values as they are emitted, yielding nondeterministic results. In most cases
* {@link Flux#concatMap(Function)} should be preferred, as it produces consistent results and
* avoids potentially saturating the thread pool on which subscription happens. If {@code
* concatMap}'s single-subscription semantics are undesirable one should invoke a {@code flatMap} or
* {@code flatMapSequential} overload with an explicit concurrency level.
* concatMap}'s sequential-subscription semantics are undesirable one should invoke a {@code
* flatMap} or {@code flatMapSequential} overload with an explicit concurrency level.
*
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function, Supplier)} is not flagged
* by this check because there is no clear alternative to point to.
* <p>NB: The rarely-used overload {@link Flux#flatMap(Function, Function,
* java.util.function.Supplier)} is not flagged by this check because there is no clear alternative
* to point to.
*/
@AutoService(BugChecker.class)
@BugPattern(
@@ -50,11 +58,16 @@ public final class FluxFlatMapUsage extends BugChecker
implements MethodInvocationTreeMatcher, MemberReferenceTreeMatcher {
private static final long serialVersionUID = 1L;
private static final String MAX_CONCURRENCY_ARG_NAME = "MAX_CONCURRENCY";
private static final Supplier<Type> FLUX =
Suppliers.typeFromString("reactor.core.publisher.Flux");
private static final Matcher<ExpressionTree> FLUX_FLATMAP =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.onDescendantOf(FLUX)
.namedAnyOf("flatMap", "flatMapSequential")
.withParameters(Function.class.getName());
private static final Supplier<Type> FLUX_OF_PUBLISHERS =
VisitorState.memoize(
generic(FLUX, subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
@@ -62,14 +75,27 @@ public final class FluxFlatMapUsage extends BugChecker
return Description.NO_MATCH;
}
return buildDescription(tree)
.addFix(SuggestedFixes.renameMethodInvocation(tree, "concatMap", state))
.addFix(
SuggestedFix.builder()
.postfixWith(
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
.build())
.build();
SuggestedFix serializationFix = SuggestedFixes.renameMethodInvocation(tree, "concatMap", state);
SuggestedFix concurrencyCapFix =
SuggestedFix.builder()
.postfixWith(
Iterables.getOnlyElement(tree.getArguments()), ", " + MAX_CONCURRENCY_ARG_NAME)
.build();
Description.Builder description = buildDescription(tree);
if (state.getTypes().isSubtype(ASTHelpers.getType(tree), FLUX_OF_PUBLISHERS.get(state))) {
/*
* Nested publishers may need to be subscribed to eagerly in order to avoid a deadlock, e.g.
* if they are produced by `Flux#groupBy`. In this case we suggest specifying an explicit
* concurrently bound in favour of sequential subscriptions using `Flux#concatMap`.
*/
description.addFix(concurrencyCapFix).addFix(serializationFix);
} else {
description.addFix(serializationFix).addFix(concurrencyCapFix);
}
return description.build();
}
@Override

View File

@@ -3,9 +3,11 @@ 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 tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
@@ -15,9 +17,7 @@ import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.List;
import java.util.Optional;
/** A {@link BugChecker} which flags nesting of {@link Optional Optionals}. */
@@ -31,21 +31,13 @@ import java.util.Optional;
public final class NestedOptionals extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Supplier<Type> OPTIONAL = Suppliers.typeFromClass(Optional.class);
private static final Supplier<Type> OPTIONAL_OF_OPTIONAL =
VisitorState.memoize(generic(OPTIONAL, subOf(raw(OPTIONAL))));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
return isOptionalOfOptional(tree, state) ? describeMatch(tree) : Description.NO_MATCH;
}
private static boolean isOptionalOfOptional(Tree tree, VisitorState state) {
Type optionalType = OPTIONAL.get(state);
Type type = ASTHelpers.getType(tree);
if (!ASTHelpers.isSubtype(type, optionalType, state)) {
return false;
}
List<Type> typeArguments = type.getTypeArguments();
return !typeArguments.isEmpty()
&& ASTHelpers.isSubtype(Iterables.getOnlyElement(typeArguments), optionalType, state);
return state.getTypes().isSubtype(ASTHelpers.getType(tree), OPTIONAL_OF_OPTIONAL.get(state))
? describeMatch(tree)
: Description.NO_MATCH;
}
}

View File

@@ -0,0 +1,91 @@
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.anyOf;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
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.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import java.util.function.BiFunction;
import reactor.core.publisher.Mono;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} which flags {@link Mono} operations that are known to be vacuous, given that
* they are invoked on a {@link Mono} that is known not to complete empty.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid vacuous operations on known non-empty `Mono`s",
linkType = NONE,
severity = WARNING,
tags = SIMPLIFICATION)
// XXX: This check does not simplify `someFlux.defaultIfEmpty(T).{defaultIfEmpty(T),hasElements()}`,
// as `someFlux.defaultIfEmpty(T)` yields a `Flux` rather than a `Mono`. Consider adding support for
// these cases.
// XXX: Given more advanced analysis many more expressions could be flagged. Consider
// `Mono.just(someValue)`, `Flux.just(someNonEmptySequence)`,
// `someMono.switchIfEmpty(someProvablyNonEmptyMono)` and many other variants.
// XXX: Consider implementing a similar check for `Publisher`s that are known to complete without
// emitting a value (e.g. `Mono.empty()`, `someFlux.then()`, ...), or known not to complete normally
// (`Mono.never()`, `someFlux.repeat()`, `Mono.error(...)`, ...). The latter category could
// potentially be split out further.
public final class NonEmptyMono extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> MONO_SIZE_CHECK =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Mono")
.namedAnyOf("defaultIfEmpty", "single", "switchIfEmpty");
private static final Matcher<ExpressionTree> NON_EMPTY_MONO =
anyOf(
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.namedAnyOf(
"all",
"any",
"collect",
"collectList",
"collectMap",
"collectMultimap",
"collectSortedList",
"count",
"elementAt",
"hasElement",
"hasElements",
"last",
"reduceWith",
"single"),
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.named("reduce")
.withParameters(Object.class.getName(), BiFunction.class.getName()),
instanceMethod()
.onDescendantOf("reactor.core.publisher.Mono")
.namedAnyOf("defaultIfEmpty", "hasElement", "single"));
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!MONO_SIZE_CHECK.matches(tree, state)) {
return Description.NO_MATCH;
}
ExpressionTree receiver = ASTHelpers.getReceiver(tree);
if (!NON_EMPTY_MONO.matches(receiver, state)) {
return Description.NO_MATCH;
}
return describeMatch(
tree, SuggestedFix.replace(tree, SourceCode.treeToString(receiver, state)));
}
}

View File

@@ -1,10 +1,13 @@
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.SUGGESTION;
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.argumentCount;
import static com.google.errorprone.matchers.Matchers.isNonNullUsingDataflow;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isSubtypeOf;
@@ -13,7 +16,9 @@ import static com.google.errorprone.matchers.method.MethodMatchers.instanceMetho
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Primitives;
import com.google.errorprone.BugPattern;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.VisitorState;
@@ -24,6 +29,7 @@ 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.suppliers.Suppliers;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
@@ -41,6 +47,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.MethodMatcherFactory;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
@@ -69,49 +76,30 @@ public final class RedundantStringConversion extends BugChecker
allOf(STRING, isNonNullUsingDataflow());
private static final Matcher<ExpressionTree> NOT_FORMATTABLE =
not(isSubtypeOf(Formattable.class));
private static final Matcher<ExpressionTree> WELL_KNOWN_STRING_CONVERSION_METHODS =
private static final Matcher<MethodInvocationTree> WELL_KNOWN_STRING_CONVERSION_METHODS =
anyOf(
instanceMethod().onDescendantOfAny(Object.class.getName()).named("toString"),
staticMethod()
.onClass(Objects.class.getName())
instanceMethod()
.onDescendantOfAny(Object.class.getName())
.named("toString")
.withParameters(Object.class.getName()),
staticMethod()
.onClass(String.class.getName())
.named("valueOf")
.withParameters(Object.class.getName()),
staticMethod()
.onClass(String.class.getName())
.named("valueOf")
.withParameters(String.class.getName()),
staticMethod()
.onClass(Byte.class.getName())
.named("toString")
.withParameters(byte.class.getName()),
staticMethod()
.onClass(Character.class.getName())
.named("toString")
.withParameters(char.class.getName()),
staticMethod()
.onClass(Short.class.getName())
.named("toString")
.withParameters(short.class.getName()),
staticMethod()
.onClass(Integer.class.getName())
.named("toString")
.withParameters(int.class.getName()),
staticMethod()
.onClass(Long.class.getName())
.named("toString")
.withParameters(long.class.getName()),
staticMethod()
.onClass(Float.class.getName())
.named("toString")
.withParameters(float.class.getName()),
staticMethod()
.onClass(Double.class.getName())
.named("toString")
.withParameters(double.class.getName()));
.withNoParameters(),
allOf(
argumentCount(1),
anyOf(
staticMethod()
.onClassAny(
Stream.concat(
Primitives.allWrapperTypes().stream(), Stream.of(Objects.class))
.map(Class::getName)
.collect(toImmutableSet()))
.named("toString"),
allOf(
staticMethod().onClass(String.class.getName()).named("valueOf"),
not(
anyMethod()
.anyClass()
.withAnyName()
.withParametersOfType(
ImmutableList.of(Suppliers.arrayOf(Suppliers.CHAR_TYPE))))))));
private static final Matcher<ExpressionTree> STRINGBUILDER_APPEND_INVOCATION =
instanceMethod()
.onDescendantOf(StringBuilder.class.getName())
@@ -127,17 +115,10 @@ public final class RedundantStringConversion extends BugChecker
staticMethod().onClass(String.class.getName()).named("format"),
instanceMethod().onDescendantOf(Formatter.class.getName()).named("format"),
instanceMethod()
.onDescendantOf(PrintStream.class.getName())
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
.namedAnyOf("format", "printf"),
instanceMethod()
.onDescendantOf(PrintStream.class.getName())
.namedAnyOf("print", "println")
.withParameters(Object.class.getName()),
instanceMethod()
.onDescendantOf(PrintWriter.class.getName())
.namedAnyOf("format", "printf"),
instanceMethod()
.onDescendantOf(PrintWriter.class.getName())
.onDescendantOfAny(PrintStream.class.getName(), PrintWriter.class.getName())
.namedAnyOf("print", "println")
.withParameters(Object.class.getName()),
staticMethod()
@@ -156,7 +137,7 @@ public final class RedundantStringConversion extends BugChecker
.onDescendantOf("org.slf4j.Logger")
.namedAnyOf("trace", "debug", "info", "warn", "error");
private final Matcher<ExpressionTree> conversionMethodMatcher;
private final Matcher<MethodInvocationTree> conversionMethodMatcher;
/** Instantiates the default {@link RedundantStringConversion}. */
public RedundantStringConversion() {
@@ -186,7 +167,7 @@ public final class RedundantStringConversion extends BugChecker
List<SuggestedFix.Builder> fixes = new ArrayList<>();
// XXX: Not so nice: we try to simplify the RHS twice.
// XXX: Avoid trying to simplify the RHS twice.
ExpressionTree preferredRhs = trySimplify(rhs, state).orElse(rhs);
if (STRING.matches(preferredRhs, state)) {
tryFix(lhs, state, ANY_EXPR).ifPresent(fixes::add);
@@ -276,15 +257,15 @@ public final class RedundantStringConversion extends BugChecker
// XXX: Write another check which checks that SLF4J patterns don't use `%s` and have a matching
// number of arguments of the appropriate type. Also flag explicit conversions from `Throwable` to
// string as the last logger argument. Suggests either dropping the converison or going with
// string as the last logger argument. Suggests either dropping the conversion or going with
// `Throwable#getMessage()` instead.
private Optional<SuggestedFix.Builder> tryFixSlf4jLogger(
List<? extends ExpressionTree> arguments, VisitorState state) {
/*
* SLF4J treats the final argument to a log statement specially if it is a `Throwabe`: it
* SLF4J treats the final argument to a log statement specially if it is a `Throwable`: it
* will always choose to render the associated stacktrace, even if the argument has a
* matching `{}` placeholder. (In this case the `{}` will simply be logged verbatim.) So if
* a log statement's final argument is the string representation of a `Throwble`, then we
* a log statement's final argument is the string representation of a `Throwable`, then we
* must not strip this explicit string conversion, as that would change the statement's
* semantics.
*/
@@ -338,11 +319,15 @@ public final class RedundantStringConversion extends BugChecker
}
private Optional<ExpressionTree> trySimplify(ExpressionTree tree, VisitorState state) {
if (tree.getKind() != Kind.METHOD_INVOCATION || !conversionMethodMatcher.matches(tree, state)) {
if (tree.getKind() != Kind.METHOD_INVOCATION) {
return Optional.empty();
}
MethodInvocationTree methodInvocation = (MethodInvocationTree) tree;
if (!conversionMethodMatcher.matches(methodInvocation, state)) {
return Optional.empty();
}
switch (methodInvocation.getArguments().size()) {
case 0:
return trySimplifyNullaryMethod(methodInvocation, state);
@@ -383,7 +368,8 @@ public final class RedundantStringConversion extends BugChecker
.orElse(Description.NO_MATCH);
}
private static Matcher<ExpressionTree> createConversionMethodMatcher(ErrorProneFlags flags) {
private static Matcher<MethodInvocationTree> createConversionMethodMatcher(
ErrorProneFlags flags) {
// XXX: ErrorProneFlags#getList splits by comma, but method signatures may also contain commas.
// For this class methods accepting more than one argument are not valid, but still: not nice.
return flags

View File

@@ -66,7 +66,8 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
isType(ANN_PACKAGE_PREFIX + "RequestAttribute"),
isType(ANN_PACKAGE_PREFIX + "RequestBody"),
isType(ANN_PACKAGE_PREFIX + "RequestHeader"),
isType(ANN_PACKAGE_PREFIX + "RequestParam"))),
isType(ANN_PACKAGE_PREFIX + "RequestParam"),
isType(ANN_PACKAGE_PREFIX + "RequestPart"))),
isSameType("java.io.InputStream"),
isSameType("java.time.ZoneId"),
isSameType("java.util.Locale"),

View File

@@ -0,0 +1,132 @@
package tech.picnic.errorprone.bugpatterns.util;
import static java.util.stream.Collectors.toCollection;
import com.google.errorprone.VisitorState;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.suppliers.Suppliers;
import com.sun.tools.javac.code.BoundKind;
import com.sun.tools.javac.code.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
/**
* A set of helper methods which together define a DSL for defining {@link Type types}.
*
* <p>These methods are meant to be statically imported. Example usage:
*
* <pre>{@code
* Supplier<Type> type =
* VisitorState.memoize(
* generic(
* type("reactor.core.publisher.Flux"),
* subOf(generic(type("org.reactivestreams.Publisher"), unbound()))));
* }</pre>
*
* This statement produces a memoized supplier of the type {@code Flux<? extends Publisher<?>>}.
*/
public final class MoreTypes {
private MoreTypes() {}
/**
* Creates a supplier of the type with the given fully qualified name.
*
* <p>This method should only be used when building more complex types in combination with other
* {@link MoreTypes} methods. In other cases prefer directly calling {@link
* Suppliers#typeFromString(String)}.
*
* @param typeName The type of interest.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> type(String typeName) {
return Suppliers.typeFromString(typeName);
}
/**
* Creates a supplier of the described generic type.
*
* @param type The base type of interest.
* @param typeArgs The desired type arguments.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
// XXX: The given `type` should be a generic type, so perhaps `withParams` would be a better
// method name. But the DSL wouldn't look as nice that way.
@SafeVarargs
@SuppressWarnings("varargs")
public static Supplier<Type> generic(Supplier<Type> type, Supplier<Type>... typeArgs) {
return propagateNull(
type,
(state, baseType) -> {
List<Type> params =
Arrays.stream(typeArgs).map(s -> s.get(state)).collect(toCollection(ArrayList::new));
if (params.stream().anyMatch(Objects::isNull)) {
return null;
}
return state.getType(baseType, /* isArray= */ false, params);
});
}
/**
* Creates a raw (erased, non-generic) variant of the given type.
*
* @param type The base type of interest.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> raw(Supplier<Type> type) {
return propagateNull(type, (state, baseType) -> baseType.tsym.erasure(state.getTypes()));
}
/**
* Creates a {@code ? super T} wildcard type, with {@code T} bound to the given type.
*
* @param type The base type of interest.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> supOf(Supplier<Type> type) {
return propagateNull(
type,
(state, baseType) ->
new Type.WildcardType(baseType, BoundKind.SUPER, state.getSymtab().boundClass));
}
/**
* Creates a {@code ? extends T} wildcard type, with {@code T} bound to the given type.
*
* @param type The base type of interest.
* @return A supplier which returns the described type if available in the given state, and {@code
* null} otherwise.
*/
public static Supplier<Type> subOf(Supplier<Type> type) {
return propagateNull(
type,
(state, baseType) ->
new Type.WildcardType(
type.get(state), BoundKind.EXTENDS, state.getSymtab().boundClass));
}
/**
* Creates an unbound wildcard type ({@code ?}).
*
* @return A supplier which returns the described type.
*/
public static Supplier<Type> unbound() {
return state ->
new Type.WildcardType(
state.getSymtab().objectType, BoundKind.UNBOUND, state.getSymtab().boundClass);
}
private static Supplier<Type> propagateNull(
Supplier<Type> type, BiFunction<VisitorState, Type, Type> transformer) {
return state ->
Optional.ofNullable(type.get(state)).map(t -> transformer.apply(state, t)).orElse(null);
}
}

View File

@@ -41,13 +41,11 @@ final class EqualityTemplates {
// non-null.
static final class EqualsPredicate<T> {
@BeforeTemplate
@SuppressWarnings("NoFunctionalReturnType")
Predicate<T> before(T v) {
return e -> v.equals(e);
}
@AfterTemplate
@SuppressWarnings("NoFunctionalReturnType")
Predicate<T> after(T v) {
return v::equals;
}

View File

@@ -9,11 +9,38 @@ import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Objects;
import java.util.function.Predicate;
import javax.annotation.Nullable;
/** Refaster templates related to expressions dealing with (possibly) null values. */
final class NullTemplates {
private NullTemplates() {}
/** Prefer the {@code ==} operator over {@link Objects#isNull(Object)}. */
static final class IsNull {
@BeforeTemplate
boolean before(@Nullable Object object) {
return Objects.isNull(object);
}
@AfterTemplate
boolean after(@Nullable Object object) {
return object == null;
}
}
/** Prefer the {@code !=} operator over {@link Objects#nonNull(Object)}. */
static final class IsNotNull {
@BeforeTemplate
boolean before(@Nullable Object object) {
return Objects.nonNull(object);
}
@AfterTemplate
boolean after(@Nullable Object object) {
return object != null;
}
}
/** Prefer {@link Objects#requireNonNullElse(Object, Object)} over the Guava alternative. */
// XXX: This rule is not valid in case `second` is `@Nullable`: in that case the Guava variant
// will return `null`, while the JDK variant will throw an NPE.
@@ -33,13 +60,11 @@ final class NullTemplates {
/** Prefer {@link Objects#isNull(Object)} over the equivalent lambda function. */
static final class IsNullFunction<T> {
@BeforeTemplate
@SuppressWarnings("NoFunctionalReturnType")
Predicate<T> before() {
return o -> o == null;
}
@AfterTemplate
@SuppressWarnings("NoFunctionalReturnType")
Predicate<T> after() {
return Objects::isNull;
}
@@ -48,13 +73,11 @@ final class NullTemplates {
/** Prefer {@link Objects#nonNull(Object)} over the equivalent lambda function. */
static final class NonNullFunction<T> {
@BeforeTemplate
@SuppressWarnings("NoFunctionalReturnType")
Predicate<T> before() {
return o -> o != null;
}
@AfterTemplate
@SuppressWarnings("NoFunctionalReturnType")
Predicate<T> after() {
return Objects::nonNull;
}

View File

@@ -81,13 +81,11 @@ final class OptionalTemplates {
// generalization. If/when Refaster is extended to understand this, delete the template above.
static final class OptionalOrElseThrowMethodReference<T> {
@BeforeTemplate
@SuppressWarnings("NoFunctionalReturnType")
Function<Optional<T>, T> before() {
return Optional::get;
}
@AfterTemplate
@SuppressWarnings("NoFunctionalReturnType")
Function<Optional<T>, T> after() {
return Optional::orElseThrow;
}

View File

@@ -9,7 +9,6 @@ import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import org.reactivestreams.Publisher;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -39,12 +38,12 @@ final class RxJava2AdapterTemplates {
*/
static final class FlowableToFlux<T> {
@BeforeTemplate
Publisher<T> before(Flowable<T> flowable) {
Flux<T> before(Flowable<T> flowable) {
return Refaster.anyOf(
flowable.compose(Flux::from),
Flux.from(flowable),
flowable.to(Flux::from),
flowable.as(Flux::from),
flowable.compose(RxJava2Adapter::flowableToFlux),
RxJava2Adapter.flowableToFlux(flowable),
flowable.to(RxJava2Adapter::flowableToFlux));
}
@@ -60,12 +59,11 @@ final class RxJava2AdapterTemplates {
*/
static final class FluxToFlowable<T> {
@BeforeTemplate
Publisher<T> before(Flux<T> flux) {
Flowable<T> before(Flux<T> flux) {
return Refaster.anyOf(
Flowable.fromPublisher(flux),
flux.transform(Flowable::fromPublisher),
flux.as(Flowable::fromPublisher),
flux.transform(RxJava2Adapter::fluxToFlowable));
RxJava2Adapter.fluxToFlowable(flux));
}
@AfterTemplate
@@ -132,12 +130,11 @@ final class RxJava2AdapterTemplates {
*/
static final class MonoToFlowable<T> {
@BeforeTemplate
Publisher<T> before(Mono<T> mono) {
Flowable<T> before(Mono<T> mono) {
return Refaster.anyOf(
Flowable.fromPublisher(mono),
mono.transform(Flowable::fromPublisher),
mono.as(Flowable::fromPublisher),
mono.transform(RxJava2Adapter::monoToFlowable));
RxJava2Adapter.monoToFlowable(mono));
}
@AfterTemplate

View File

@@ -13,7 +13,9 @@ import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import javax.annotation.Nullable;
/** Refaster templates related to expressions dealing with {@link String}s. */
@@ -106,6 +108,40 @@ final class StringTemplates {
}
}
/**
* Prefer direct invocation of {@link String#valueOf(Object)} over the indirection introduced by
* {@link Objects#toString(Object)}.
*/
static final class StringValueOf {
@BeforeTemplate
String before(Object object) {
return Objects.toString(object);
}
@AfterTemplate
String after(Object object) {
return String.valueOf(object);
}
}
/**
* Prefer direct delegation to {@link String#valueOf(Object)} over the indirection introduced by
* {@link Objects#toString(Object)}.
*/
// XXX: This template is analogous to `StringValueOf` above. Arguably this is its generalization.
// If/when Refaster is extended to understand this, delete the template above.
static final class StringValueOfMethodReference<T> {
@BeforeTemplate
Function<Object, String> before() {
return Objects::toString;
}
@AfterTemplate
Function<Object, String> after() {
return String::valueOf;
}
}
/** Don't unnecessarily use the two-argument {@link String#substring(int, int)}. */
static final class SubstringRemainder {
@BeforeTemplate

View File

@@ -4,6 +4,7 @@ import static com.google.errorprone.BugCheckerRefactoringTestHelper.newInstance;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
@@ -33,6 +34,14 @@ final class FluxFlatMapUsageTest {
" Flux.just(1).flatMapSequential(Flux::just);",
" // BUG: Diagnostic contains:",
" Flux.just(1).<String>flatMapSequential(i -> Flux.just(String.valueOf(i)));",
" // BUG: Diagnostic contains:",
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
" // BUG: Diagnostic contains:",
" Flux.just(1, 2).groupBy(i -> i).<String>flatMap(i -> Flux.just(String.valueOf(i)));",
" // BUG: Diagnostic contains:",
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
" // BUG: Diagnostic contains:",
" Flux.just(1, 2).groupBy(i -> i).<String>flatMapSequential(i -> Flux.just(String.valueOf(i)));",
"",
" Mono.just(1).flatMap(Mono::just);",
" Flux.just(1).concatMap(Flux::just);",
@@ -71,9 +80,13 @@ final class FluxFlatMapUsageTest {
"import reactor.core.publisher.Flux;",
"",
"class A {",
" private static final int MAX_CONCURRENCY = 8;",
"",
" void m() {",
" Flux.just(1).flatMap(Flux::just);",
" Flux.just(1).flatMapSequential(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
" }",
"}")
.addOutputLines(
@@ -81,12 +94,16 @@ final class FluxFlatMapUsageTest {
"import reactor.core.publisher.Flux;",
"",
"class A {",
" private static final int MAX_CONCURRENCY = 8;",
"",
" void m() {",
" Flux.just(1).concatMap(Flux::just);",
" Flux.just(1).concatMap(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just, MAX_CONCURRENCY);",
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just, MAX_CONCURRENCY);",
" }",
"}")
.doTest();
.doTest(TestMode.TEXT_MATCH);
}
@Test
@@ -103,6 +120,8 @@ final class FluxFlatMapUsageTest {
" void m() {",
" Flux.just(1).flatMap(Flux::just);",
" Flux.just(1).flatMapSequential(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMap(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).flatMapSequential(Flux::just);",
" }",
"}")
.addOutputLines(
@@ -115,8 +134,10 @@ final class FluxFlatMapUsageTest {
" void m() {",
" Flux.just(1).flatMap(Flux::just, MAX_CONCURRENCY);",
" Flux.just(1).flatMapSequential(Flux::just, MAX_CONCURRENCY);",
" Flux.just(1, 2).groupBy(i -> i).concatMap(Flux::just);",
" Flux.just(1, 2).groupBy(i -> i).concatMap(Flux::just);",
" }",
"}")
.doTest();
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,155 @@
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 NonEmptyMonoTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(NonEmptyMono.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(NonEmptyMono.class, getClass());
@Test
void identification() {
compilationTestHelper
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
"import static java.util.function.Function.identity;",
"",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableMap;",
"import java.util.ArrayList;",
"import java.util.HashMap;",
"import java.util.List;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"class A {",
" void m() {",
" Mono.just(1).defaultIfEmpty(2);",
" Mono.just(1).single();",
" Mono.just(1).switchIfEmpty(Mono.just(2));",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).all(x -> true).defaultIfEmpty(true);",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).any(x -> true).single();",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).collect(toImmutableList()).switchIfEmpty(Mono.just(ImmutableList.of()));",
" // BUG: Diagnostic contains:",
" Flux.just(1).collect(ArrayList::new, List::add).defaultIfEmpty(new ArrayList<>());",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectList().single();",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectMap(identity()).switchIfEmpty(Mono.just(ImmutableMap.of()));",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectMap(identity(), identity()).defaultIfEmpty(ImmutableMap.of());",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectMap(identity(), identity(), HashMap::new).single();",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectMultimap(identity()).switchIfEmpty(Mono.just(ImmutableMap.of()));",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectMultimap(identity(), identity()).defaultIfEmpty(ImmutableMap.of());",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectMultimap(identity(), identity(), HashMap::new).single();",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectSortedList().defaultIfEmpty(ImmutableList.of());",
" // BUG: Diagnostic contains:",
" Flux.just(1).collectSortedList((o1, o2) -> 0).single();",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).count().switchIfEmpty(Mono.just(2L));",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).elementAt(0).defaultIfEmpty(1);",
" // BUG: Diagnostic contains:",
" Flux.just(1).elementAt(0, 2).single();",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).hasElement(2).switchIfEmpty(Mono.just(true));",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).hasElements().defaultIfEmpty(true);",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).last().single();",
" // BUG: Diagnostic contains:",
" Flux.just(1).last(2).switchIfEmpty(Mono.just(3));",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).reduceWith(() -> 0, Integer::sum).defaultIfEmpty(2);",
"",
" // BUG: Diagnostic contains:",
" Flux.just(1).single().single();",
" // BUG: Diagnostic contains:",
" Flux.just(1).single(2).switchIfEmpty(Mono.just(3));",
"",
" Flux.just(1).reduce(Integer::sum).defaultIfEmpty(2);",
" // BUG: Diagnostic contains:",
" Flux.just(1).reduce(2, Integer::sum).single();",
"",
" // BUG: Diagnostic contains:",
" Mono.just(1).defaultIfEmpty(1).switchIfEmpty(Mono.just(2));",
" // BUG: Diagnostic contains:",
" Mono.just(1).hasElement().defaultIfEmpty(true);",
" // BUG: Diagnostic contains:",
" Mono.just(1).single().single();",
" }",
"}")
.doTest();
}
@Test
void replacement() {
refactoringTestHelper
.addInputLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
"",
"import com.google.common.collect.ImmutableList;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toImmutableList()).single();",
" Flux.just(1).collect(toImmutableList()).defaultIfEmpty(ImmutableList.of());",
" Flux.just(1).collect(toImmutableList()).switchIfEmpty(Mono.just(ImmutableList.of()));",
"",
" Mono.just(2).hasElement().single();",
" Mono.just(2).hasElement().defaultIfEmpty(true);",
" Mono.just(2).hasElement().switchIfEmpty(Mono.just(true));",
" }",
"}")
.addOutputLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
"",
"import com.google.common.collect.ImmutableList;",
"import reactor.core.publisher.Flux;",
"import reactor.core.publisher.Mono;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toImmutableList());",
" Flux.just(1).collect(toImmutableList());",
" Flux.just(1).collect(toImmutableList());",
"",
" Mono.just(2).hasElement();",
" Mono.just(2).hasElement();",
" Mono.just(2).hasElement();",
" }",
"}")
.doTest(TEXT_MATCH);
}
}

View File

@@ -69,9 +69,14 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" s += String.valueOf(i);",
" // BUG: Diagnostic contains:",
" s += String.valueOf(0);",
" // BUG: Diagnostic contains:",
" s += String.valueOf((String) null);",
" s += String.valueOf(null);",
" s += String.valueOf(new char[0]);",
" s += String.valueOf(new char[0], 0, 0);",
" // BUG: Diagnostic contains:",
" s += Boolean.toString(false);",
" // BUG: Diagnostic contains:",
" s += Byte.toString((byte) 0);",
" // BUG: Diagnostic contains:",
@@ -121,9 +126,12 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" s + String.valueOf(i),",
" // BUG: Diagnostic contains:",
" s + String.valueOf(0),",
" // BUG: Diagnostic contains:",
" s + String.valueOf((String) null),",
" s + String.valueOf(null),",
" s + String.valueOf(new char[0]),",
" s + String.valueOf(new char[0], 0, 0),",
" //",
" 42 + this.toString(),",
" 42 + super.toString(),",
@@ -134,6 +142,7 @@ final class RedundantStringConversionTest {
" 42 + String.valueOf((String) null),",
" 42 + String.valueOf(null),",
" 42 + String.valueOf(new char[0]),",
" 42 + String.valueOf(new char[0], 0, 0),",
"",
" // BUG: Diagnostic contains:",
" this.toString() + s,",
@@ -144,19 +153,24 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" String.valueOf(i) + s,",
" // BUG: Diagnostic contains:",
" String.valueOf(0) + s,",
" // BUG: Diagnostic contains:",
" String.valueOf((String) null) + s,",
" String.valueOf(null) + s,",
" String.valueOf(new char[0]) + s,",
" String.valueOf(new char[0], 0, 0) + s,",
" //",
" this.toString() + 42,",
" super.toString() + 42,",
" i.toString() + 42,",
" i.toString(16) + 42,",
" String.valueOf(i) + 42,",
" String.valueOf(0) + 42,",
" // BUG: Diagnostic contains:",
" String.valueOf((String) null) + 42,",
" String.valueOf(null) + 42,",
" String.valueOf(new char[0]) + 42,",
" String.valueOf(new char[0], 0, 0) + 42,",
"",
" // BUG: Diagnostic contains:",
" this.toString() + this.toString(),",
@@ -167,9 +181,12 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" String.valueOf(i) + String.valueOf(i),",
" // BUG: Diagnostic contains:",
" String.valueOf(0) + String.valueOf(0),",
" // BUG: Diagnostic contains:",
" String.valueOf((String) null) + String.valueOf((String) null),",
" String.valueOf(null) + String.valueOf(null),",
" String.valueOf(new char[0]) + String.valueOf(new char[0]),",
" String.valueOf(new char[0], 0, 0) + String.valueOf(new char[0], 0, 0),",
" };",
" }",
"",
@@ -204,9 +221,12 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" sb.append(String.valueOf(i));",
" // BUG: Diagnostic contains:",
" sb.append(String.valueOf(0));",
" // BUG: Diagnostic contains:",
" sb.append(String.valueOf((String) null));",
" sb.append(String.valueOf(null));",
" sb.append(String.valueOf(new char[0]));",
" sb.append(String.valueOf(new char[0], 0, 0));",
" sb.append(s);",
" sb.append(\"constant\");",
"",
@@ -218,9 +238,12 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" sb.insert(0, String.valueOf(i));",
" // BUG: Diagnostic contains:",
" sb.insert(0, String.valueOf(0));",
" // BUG: Diagnostic contains:",
" sb.insert(0, String.valueOf((String) null));",
" sb.insert(0, String.valueOf(null));",
" sb.insert(0, String.valueOf(new char[0]));",
" sb.insert(0, String.valueOf(new char[0], 0, 0));",
" sb.insert(0, s);",
" sb.insert(0, \"constant\");",
"",
@@ -434,6 +457,8 @@ final class RedundantStringConversionTest {
" // BUG: Diagnostic contains:",
" s + String.valueOf(b),",
" // BUG: Diagnostic contains:",
" s + Boolean.toString(false),",
" // BUG: Diagnostic contains:",
" s + Byte.toString((byte) 0),",
" // BUG: Diagnostic contains:",
" s + Character.toString((char) 0),",

View File

@@ -30,6 +30,7 @@ final class RequestMappingAnnotationTest {
"import org.springframework.web.bind.annotation.RequestHeader;",
"import org.springframework.web.bind.annotation.RequestMapping;",
"import org.springframework.web.bind.annotation.RequestParam;",
"import org.springframework.web.bind.annotation.RequestPart;",
"import org.springframework.web.context.request.NativeWebRequest;",
"import org.springframework.web.context.request.WebRequest;",
"import org.springframework.web.server.ServerWebExchange;",
@@ -60,6 +61,9 @@ final class RequestMappingAnnotationTest {
" A properRequestParam(@RequestParam String param);",
"",
" @RequestMapping",
" A properRequestPart(@RequestPart String part);",
"",
" @RequestMapping",
" A properInputStream(InputStream input);",
"",
" @RequestMapping",

View File

@@ -0,0 +1,185 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.generic;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.raw;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.subOf;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.supOf;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.type;
import static tech.picnic.errorprone.bugpatterns.util.MoreTypes.unbound;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.suppliers.Supplier;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.Signatures;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
final class MoreTypesTest {
private static final ImmutableSet<Supplier<Type>> TYPES =
ImmutableSet.of(
type("java.lang.Nonexistent"),
type("java.lang.String"),
type("java.lang.Number"),
supOf(type("java.lang.Number")),
subOf(type("java.lang.Number")),
type("java.lang.Integer"),
supOf(type("java.lang.Integer")),
subOf(type("java.lang.Integer")),
type("java.util.Optional"),
raw(type("java.util.Optional")),
generic(type("java.util.Optional"), unbound()),
generic(type("java.util.Optional"), type("java.lang.Number")),
type("java.util.Collection"),
raw(type("java.util.Collection")),
generic(type("java.util.Collection"), unbound()),
generic(type("java.util.Collection"), type("java.lang.Number")),
generic(type("java.util.Collection"), supOf(type("java.lang.Number"))),
generic(type("java.util.Collection"), subOf(type("java.lang.Number"))),
generic(type("java.util.Collection"), type("java.lang.Integer")),
generic(type("java.util.Collection"), supOf(type("java.lang.Integer"))),
generic(type("java.util.Collection"), subOf(type("java.lang.Integer"))),
type("java.util.List"),
raw(type("java.util.List")),
generic(type("java.util.List"), unbound()),
generic(type("java.util.List"), type("java.lang.Number")),
generic(type("java.util.List"), supOf(type("java.lang.Number"))),
generic(type("java.util.List"), subOf(type("java.lang.Number"))),
generic(type("java.util.List"), type("java.lang.Integer")),
generic(type("java.util.List"), supOf(type("java.lang.Integer"))),
generic(type("java.util.List"), subOf(type("java.lang.Integer"))),
generic(
type("java.util.Map"),
type("java.lang.String"),
subOf(generic(type("java.util.Collection"), supOf(type("java.lang.Short"))))));
@Test
void matcher() {
CompilationTestHelper.newInstance(SubtypeFlagger.class, getClass())
.addSourceLines(
"/A.java",
"import java.util.Collection;",
"import java.util.List;",
"import java.util.Map;",
"import java.util.Optional;",
"import java.util.Set;",
"",
"class A<S, T> {",
" void m() {",
" Object object = factory();",
" A a = factory();",
"",
" // BUG: Diagnostic contains: [Number, ? super Number, Integer, ? super Integer]",
" int integer = factory();",
"",
" // BUG: Diagnostic contains: [String]",
" String string = factory();",
"",
" // BUG: Diagnostic contains: [Optional]",
" Optional rawOptional = factory();",
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
" Optional<S> optionalOfS = factory();",
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
" Optional<T> optionalOfT = factory();",
" // BUG: Diagnostic contains: [Optional, Optional<?>, Optional<Number>]",
" Optional<Number> optionalOfNumber = factory();",
" // BUG: Diagnostic contains: [Optional, Optional<?>]",
" Optional<Integer> optionalOfInteger = factory();",
"",
" // BUG: Diagnostic contains: [Collection]",
" Collection rawCollection = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
" // Number>, Collection<? extends Number>, Collection<? super Integer>]",
" Collection<Number> collectionOfNumber = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>]",
" Collection<Integer> collectionOfInteger = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>]",
" Collection<Short> collectionOfShort = factory();",
"",
" // BUG: Diagnostic contains: [Collection, List]",
" List rawList = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
" // Number>, Collection<? extends Number>, Collection<? super Integer>, List, List<?>,",
" // List<Number>, List<? super Number>, List<? extends Number>, List<? super Integer>]",
" List<Number> listOfNumber = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>, List,",
" // List<?>, List<? extends Number>, List<Integer>, List<? super Integer>, List<? extends",
" // Integer>]",
" List<Integer> listOfInteger = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>, List,",
" // List<?>, List<? extends Number>]",
" List<Short> listOfShort = factory();",
"",
" // BUG: Diagnostic contains: [Collection]",
" Set rawSet = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<Number>, Collection<? super",
" // Number>, Collection<? extends Number>, Collection<? super Integer>]",
" Set<Number> setOfNumber = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>,",
" // Collection<Integer>, Collection<? super Integer>, Collection<? extends Integer>]",
" Set<Integer> setOfInteger = factory();",
" // BUG: Diagnostic contains: [Collection, Collection<?>, Collection<? extends Number>]",
" Set<Short> setOfShort = factory();",
"",
" Map rawMap = factory();",
" Map<Number, Collection<Number>> mapFromNumberToCollectionOfNumber = factory();",
" Map<Number, Collection<Short>> mapFromNumberToCollectionOfShort = factory();",
" Map<Number, Collection<Integer>> mapFromNumberToCollectionOfInteger = factory();",
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
" Map<String, Collection<Number>> mapFromStringToCollectionOfNumber = factory();",
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
" Map<String, Collection<Short>> mapFromStringToCollectionOfShort = factory();",
" Map<String, Collection<Integer>> mapFromStringToCollectionOfInteger = factory();",
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
" Map<String, List<Number>> mapFromStringToListOfNumber = factory();",
" // BUG: Diagnostic contains: [Map<String, ? extends Collection<? super Short>>]",
" Map<String, List<Short>> mapFromStringToListOfShort = factory();",
" Map<String, List<Integer>> mapFromStringToListOfInteger = factory();",
" }",
"",
" private <T> T factory() {",
" return null;",
" }",
"}")
.doTest();
}
/**
* A {@link BugChecker} which flags method invocations that are a subtype of any type contained in
* {@link #TYPES}.
*/
@BugPattern(summary = "Flags invocations of methods with select return types", severity = ERROR)
public static final class SubtypeFlagger extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
Type treeType = ASTHelpers.getType(tree);
List<String> matches = new ArrayList<>();
for (Supplier<Type> type : TYPES) {
Type testType = type.get(state);
if (testType != null && state.getTypes().isSubtype(treeType, testType)) {
matches.add(Signatures.prettyType(testType));
}
}
return matches.isEmpty()
? Description.NO_MATCH
: buildDescription(tree).setMessage(matches.toString()).build();
}
}
}

View File

@@ -2,6 +2,7 @@ package tech.picnic.errorprone.refastertemplates;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
import java.util.Objects;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
@@ -11,6 +12,14 @@ final class NullTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(MoreObjects.class);
}
boolean testIsNull() {
return Objects.isNull("foo");
}
boolean testIsNotNull() {
return Objects.nonNull("foo");
}
String testRequireNonNullElse() {
return MoreObjects.firstNonNull("foo", "bar");
}

View File

@@ -14,6 +14,14 @@ final class NullTemplatesTest implements RefasterTemplateTestCase {
return ImmutableSet.of(MoreObjects.class);
}
boolean testIsNull() {
return "foo" == null;
}
boolean testIsNotNull() {
return "foo" != null;
}
String testRequireNonNullElse() {
return requireNonNullElse("foo", "bar");
}

View File

@@ -7,8 +7,6 @@ import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.util.Arrays;
import org.reactivestreams.Publisher;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -21,24 +19,20 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Completable.complete().to(RxJava2Adapter::completableToMono));
}
ImmutableSet<Publisher<Integer>> testFlowableToFlux() {
// The `Arrays.asList` is to avoid confusing `javac`; `ImmutableSet.of` uses varargs from the
// seventh parameter onwards.
return ImmutableSet.copyOf(
Arrays.asList(
Flowable.just(1).compose(Flux::from),
Flowable.just(2).to(Flux::from),
Flowable.just(3).as(Flux::from),
Flowable.just(4).compose(RxJava2Adapter::flowableToFlux),
Flowable.just(5).<Publisher<Integer>>to(RxJava2Adapter::flowableToFlux)));
ImmutableSet<Flux<Integer>> testFlowableToFlux() {
return ImmutableSet.of(
Flux.from(Flowable.just(1)),
Flowable.just(2).to(Flux::from),
Flowable.just(3).as(Flux::from),
RxJava2Adapter.flowableToFlux(Flowable.just(4)),
Flowable.just(5).to(RxJava2Adapter::flowableToFlux));
}
ImmutableSet<Publisher<String>> testFluxToFlowable() {
ImmutableSet<Flowable<String>> testFluxToFlowable() {
return ImmutableSet.of(
Flowable.fromPublisher(Flux.just("foo")),
Flux.just("bar").transform(Flowable::fromPublisher),
Flux.just("baz").as(Flowable::fromPublisher),
Flux.just("qux").transform(RxJava2Adapter::fluxToFlowable));
Flux.just("bar").as(Flowable::fromPublisher),
RxJava2Adapter.fluxToFlowable(Flux.just("baz")));
}
ImmutableSet<Observable<Integer>> testFluxToObservable() {
@@ -61,12 +55,11 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
RxJava2Adapter.monoToCompletable(Mono.empty()));
}
ImmutableSet<Publisher<Integer>> testMonoToFlowable() {
ImmutableSet<Flowable<Integer>> testMonoToFlowable() {
return ImmutableSet.of(
Flowable.fromPublisher(Mono.just(1)),
Mono.just(2).transform(Flowable::fromPublisher),
Mono.just(3).as(Flowable::fromPublisher),
Mono.just(4).transform(RxJava2Adapter::monoToFlowable));
Mono.just(2).as(Flowable::fromPublisher),
RxJava2Adapter.monoToFlowable(Mono.just(3)));
}
Maybe<String> testMonoToMaybe() {

View File

@@ -7,8 +7,6 @@ import io.reactivex.Flowable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import java.util.Arrays;
import org.reactivestreams.Publisher;
import reactor.adapter.rxjava.RxJava2Adapter;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -21,24 +19,20 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Completable.complete().as(RxJava2Adapter::completableToMono));
}
ImmutableSet<Publisher<Integer>> testFlowableToFlux() {
// The `Arrays.asList` is to avoid confusing `javac`; `ImmutableSet.of` uses varargs from the
// seventh parameter onwards.
return ImmutableSet.copyOf(
Arrays.asList(
Flowable.just(1).as(RxJava2Adapter::flowableToFlux),
Flowable.just(2).as(RxJava2Adapter::flowableToFlux),
Flowable.just(3).as(RxJava2Adapter::flowableToFlux),
Flowable.just(4).as(RxJava2Adapter::flowableToFlux),
Flowable.just(5).as(RxJava2Adapter::flowableToFlux)));
ImmutableSet<Flux<Integer>> testFlowableToFlux() {
return ImmutableSet.of(
Flowable.just(1).as(RxJava2Adapter::flowableToFlux),
Flowable.just(2).as(RxJava2Adapter::flowableToFlux),
Flowable.just(3).as(RxJava2Adapter::flowableToFlux),
Flowable.just(4).as(RxJava2Adapter::flowableToFlux),
Flowable.just(5).as(RxJava2Adapter::flowableToFlux));
}
ImmutableSet<Publisher<String>> testFluxToFlowable() {
ImmutableSet<Flowable<String>> testFluxToFlowable() {
return ImmutableSet.of(
Flux.just("foo").as(RxJava2Adapter::fluxToFlowable),
Flux.just("bar").as(RxJava2Adapter::fluxToFlowable),
Flux.just("baz").as(RxJava2Adapter::fluxToFlowable),
Flux.just("qux").as(RxJava2Adapter::fluxToFlowable));
Flux.just("baz").as(RxJava2Adapter::fluxToFlowable));
}
ImmutableSet<Observable<Integer>> testFluxToObservable() {
@@ -61,12 +55,11 @@ final class RxJava2AdapterTemplatesTest implements RefasterTemplateTestCase {
Mono.empty().as(RxJava2Adapter::monoToCompletable));
}
ImmutableSet<Publisher<Integer>> testMonoToFlowable() {
ImmutableSet<Flowable<Integer>> testMonoToFlowable() {
return ImmutableSet.of(
Mono.just(1).as(RxJava2Adapter::monoToFlowable),
Mono.just(2).as(RxJava2Adapter::monoToFlowable),
Mono.just(3).as(RxJava2Adapter::monoToFlowable),
Mono.just(4).as(RxJava2Adapter::monoToFlowable));
Mono.just(3).as(RxJava2Adapter::monoToFlowable));
}
Maybe<String> testMonoToMaybe() {

View File

@@ -10,7 +10,9 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
@@ -18,7 +20,7 @@ final class StringTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class, Joiner.class, Stream.class, Streams.class, joining(), UTF_8);
Arrays.class, Joiner.class, Objects.class, Stream.class, Streams.class, joining(), UTF_8);
}
ImmutableSet<Boolean> testStringIsEmpty() {
@@ -59,6 +61,14 @@ final class StringTemplatesTest implements RefasterTemplateTestCase {
ImmutableList.of("foo", "bar").stream().collect(joining("f")));
}
String testStringValueOf() {
return Objects.toString("foo");
}
Function<Object, String> testStringValueOfMethodReference() {
return Objects::toString;
}
String testSubstringRemainder() {
return "foo".substring(1, "foo".length());
}

View File

@@ -11,7 +11,9 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.test.RefasterTemplateTestCase;
@@ -19,7 +21,7 @@ final class StringTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(
Arrays.class, Joiner.class, Stream.class, Streams.class, joining(), UTF_8);
Arrays.class, Joiner.class, Objects.class, Stream.class, Streams.class, joining(), UTF_8);
}
ImmutableSet<Boolean> testStringIsEmpty() {
@@ -59,6 +61,14 @@ final class StringTemplatesTest implements RefasterTemplateTestCase {
String.join("f", ImmutableList.of("foo", "bar")));
}
String testStringValueOf() {
return String.valueOf("foo");
}
Function<Object, String> testStringValueOfMethodReference() {
return String::valueOf;
}
String testSubstringRemainder() {
return "foo".substring(1);
}

91
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.1.1-SNAPSHOT</version>
<version>0.2.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Picnic :: Error Prone Support</name>
@@ -87,6 +87,7 @@
-XX:SoftRefLRUPolicyMSPerMB=10
-XX:+UseParallelGC
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports=jdk.compiler/com.sun.tools.javac.code=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
@@ -94,7 +95,6 @@
--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. -->
@@ -146,14 +146,13 @@
<version.error-prone>${version.error-prone-orig}</version.error-prone>
<version.error-prone-fork>v${version.error-prone-orig}-picnic-2</version.error-prone-fork>
<version.error-prone-orig>2.14.0</version.error-prone-orig>
<version.error-prone-slf4j>0.1.13</version.error-prone-slf4j>
<version.findbugs-format-string>3.0.0</version.findbugs-format-string>
<version.error-prone-slf4j>0.1.15</version.error-prone-slf4j>
<version.guava-beta-checker>1.0</version.guava-beta-checker>
<version.jdk>11</version.jdk>
<version.maven>3.8.6</version.maven>
<version.mockito>4.7.0</version.mockito>
<version.mockito>4.8.0</version.mockito>
<version.nopen-checker>1.0.1</version.nopen-checker>
<version.nullaway>0.9.9</version.nullaway>
<version.nullaway>0.10.0</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
@@ -216,7 +215,7 @@
<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>2.13.3</version>
<version>2.13.4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@@ -240,23 +239,11 @@
<artifactId>auto-value-annotations</artifactId>
<version>${version.auto-value}</version>
</dependency>
<!-- Specified as a workaround for
https://github.com/mojohaus/versions-maven-plugin/issues/244. -->
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jFormatString</artifactId>
<version>${version.findbugs-format-string}</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<version>9+181-r4173-1</version>
</dependency>
<dependency>
<groupId>com.google.googlejavaformat</groupId>
<artifactId>google-java-format</artifactId>
@@ -379,7 +366,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<version>3.24.0</version>
<version>3.25.0</version>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
@@ -389,7 +376,7 @@
<dependency>
<groupId>org.immutables</groupId>
<artifactId>value-annotations</artifactId>
<version>2.9.0</version>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.junit</groupId>
@@ -405,11 +392,6 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@@ -527,7 +509,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.1.2</version>
<version>3.2.0</version>
<configuration>
<checkstyleRules>
<!-- We only enable rules that are not enforced by
@@ -746,6 +728,7 @@
<module name="VisibilityModifier" />
</module>
<module name="UniqueProperties" />
<module name="io.spring.nohttp.checkstyle.check.NoHttpCheck" />
</module>
</checkstyleRules>
<failOnViolation>false</failOnViolation>
@@ -769,7 +752,12 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.3.2</version>
<version>10.3.3</version>
</dependency>
<dependency>
<groupId>io.spring.nohttp</groupId>
<artifactId>nohttp-checkstyle</artifactId>
<version>0.0.10</version>
</dependency>
</dependencies>
<executions>
@@ -791,7 +779,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<!-- XXX: MCOMPILER-503: The plugin contructs a highly
<!-- XXX: MCOMPILER-503: The plugin constructs a highly
unintuitive annotation processor classpath, in which
some indirect dependencies take precedence over
explicitly defined dependencies. This can largely be
@@ -851,13 +839,19 @@
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
<arg>-Xmaxerrs</arg>
<arg>10000</arg>
<arg>-Xmaxwarns</arg>
<arg>10000</arg>
</compilerArgs>
<parameters>true</parameters>
<release>${version.jdk}</release>
<source>${version.jdk}</source>
<target>${version.jdk}</target>
<!-- Erroneously inverted logic... for details, see
https://issues.apache.org/jira/browse/MCOMPILER-209. -->
<useIncrementalCompilation>false</useIncrementalCompilation>
@@ -1245,7 +1239,7 @@
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>1.9.4</version>
<version>1.9.5</version>
<configuration>
<!-- Use multiple threads to speed things up. Extend
timeouts to prevent false positives as a result of
@@ -1719,41 +1713,6 @@
</pluginManagement>
</build>
</profile>
<profile>
<!-- Some code in this project interfaces directly with the Java
compiler. The following `add-exports` arguments are not necessary
for the code to compile because `com.google.errorprone:javac` is on
the classpath. In fact, enabling this profile when building with
Maven on the command line will cause a build failure, because these
flags are incompatible with the `release` flag. This profile exists
solely to be enabled within an IDE: without them IntelliJ IDEA
reports compilation errors. -->
<id>add-exports</id>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs combine.children="append">
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
<arg>--add-exports</arg>
<arg>jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
<profile>
<!-- This profile is auto-activated when performing a release. -->
<id>release</id>

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.2.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-compiler</artifactId>
@@ -37,11 +37,6 @@
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

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.2.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-runner</artifactId>
@@ -47,11 +47,6 @@
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

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.2.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-support</artifactId>
@@ -51,11 +51,6 @@
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>

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.2.1-SNAPSHOT</version>
</parent>
<artifactId>refaster-test-support</artifactId>
@@ -50,11 +50,6 @@
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>javac</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>

View File

@@ -7,7 +7,6 @@ import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Objects;
import java.util.Set;
import javax.annotation.Nullable;
@@ -30,7 +29,7 @@ final class ValidTemplates {
static final class StaticImportStringLength {
@BeforeTemplate
boolean before(@Nullable String string) {
return Objects.isNull(string) || string.isEmpty();
return string == null || string.toCharArray().length == 0;
}
@AfterTemplate

View File

@@ -3,14 +3,13 @@ package tech.picnic.errorprone.refaster.test;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/** Code to test the Refaster templates from {@link ValidTemplates}. */
final class ValidTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class, Strings.class);
return ImmutableSet.of(Strings.class);
}
boolean testStringIsEmpty2() {
@@ -18,7 +17,7 @@ final class ValidTemplatesTest implements RefasterTemplateTestCase {
}
boolean testStaticImportStringLength() {
return Objects.isNull("foo") || "foo".isEmpty();
return "foo" == null || "foo".toCharArray().length == 0;
}
void testBlockTemplateSetAddElement() {

View File

@@ -5,14 +5,13 @@ import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/** Code to test the Refaster templates from {@link ValidTemplates}. */
final class ValidTemplatesTest implements RefasterTemplateTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(Objects.class, Strings.class);
return ImmutableSet.of(Strings.class);
}
boolean testStringIsEmpty2() {