Compare commits

..

1 Commits

Author SHA1 Message Date
Max Sumrall
56ed83bf49 XXX-XXX Prefer jOOQs fetch().stream() over directly .stream()'ing 2023-02-16 19:00:25 +01:00
202 changed files with 454 additions and 4194 deletions

View File

@@ -42,9 +42,9 @@ Please replace this sentence with log output, if applicable.
<!-- Please complete the following information: -->
- Operating system (e.g. MacOS Monterey).
- Java version (i.e. `java --version`, e.g. `17.0.6`).
- Error Prone version (e.g. `2.18.0`).
- Error Prone Support version (e.g. `0.8.0`).
- Java version (i.e. `java --version`, e.g. `17.0.3`).
- Error Prone version (e.g. `2.15.0`).
- Error Prone Support version (e.g. `0.3.0`).
### Additional context

View File

@@ -10,16 +10,16 @@ jobs:
strategy:
matrix:
os: [ ubuntu-22.04 ]
jdk: [ 11.0.18, 17.0.6, 19.0.2 ]
jdk: [ 11.0.16, 17.0.4, 19 ]
distribution: [ temurin ]
experimental: [ false ]
include:
- os: macos-12
jdk: 17.0.6
jdk: 17.0.4
distribution: temurin
experimental: false
- os: windows-2022
jdk: 17.0.6
jdk: 17.0.4
distribution: temurin
experimental: false
- os: ubuntu-22.04

View File

@@ -19,21 +19,16 @@ jobs:
bundler-cache: true
- name: Configure Github Pages
uses: actions/configure-pages@v2.1.3
- name: Set up JDK
uses: actions/setup-java@v3.5.1
with:
java-version: 17.0.4
distribution: temurin
cache: maven
- name: Compile project and extract data
# XXX: Remove `-Dverification.{skip|warn}` and update `website/README.md`
# once module `docgen` has no errors.
run: |
mvn -T1C clean install -DskipTests -Dverification.warn
mvn -T1C clean install -DskipTests -Dverification.skip -Pdocgen
- name: Generate documentation
run: ./generate-docs.sh
- name: Build website with Jekyll
working-directory: ./website
run: bundle exec ruby generate-docs.rb
run: bundle exec jekyll build
- name: Validate HTML output
working-directory: ./website
# XXX: Drop `--disable_external true` once we fully adopted the
# "Refaster rules" terminology on our website and in the code.
run: bundle exec htmlproofer --disable_external true --check-external-hash false ./_site
- name: Upload website as artifact
uses: actions/upload-pages-artifact@v1.0.5
with:

View File

@@ -18,7 +18,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.6
java-version: 17.0.4
distribution: temurin
cache: maven
- name: Run Pitest

View File

@@ -22,7 +22,7 @@ jobs:
- name: Set up JDK
uses: actions/setup-java@v3.8.0
with:
java-version: 17.0.6
java-version: 17.0.4
distribution: temurin
cache: maven
- name: Download Pitest analysis artifact

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.9.1-SNAPSHOT</version>
<version>0.8.1-SNAPSHOT</version>
</parent>
<artifactId>documentation-support</artifactId>
@@ -27,10 +27,6 @@
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_check_api</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_core</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_test_helpers</artifactId>

View File

@@ -5,19 +5,17 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;
import com.google.auto.common.AnnotationMirrors;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import java.util.Optional;
import com.sun.tools.javac.util.Context;
import javax.lang.model.element.AnnotationValue;
import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation;
@@ -25,38 +23,29 @@ import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocume
* An {@link Extractor} that describes how to extract data from a {@code @BugPattern} annotation.
*/
@Immutable
@AutoService(Extractor.class)
public final class BugPatternExtractor implements Extractor<BugPatternDocumentation> {
/** Instantiates a new {@link BugPatternExtractor} instance. */
public BugPatternExtractor() {}
final class BugPatternExtractor implements Extractor<BugPatternDocumentation> {
@Override
public String identifier() {
return "bugpattern";
public BugPatternDocumentation extract(ClassTree tree, Context context) {
ClassSymbol symbol = ASTHelpers.getSymbol(tree);
BugPattern annotation = symbol.getAnnotation(BugPattern.class);
requireNonNull(annotation, "BugPattern annotation must be present");
return new AutoValue_BugPatternExtractor_BugPatternDocumentation(
symbol.getQualifiedName().toString(),
annotation.name().isEmpty() ? tree.getSimpleName().toString() : annotation.name(),
ImmutableList.copyOf(annotation.altNames()),
annotation.link(),
ImmutableList.copyOf(annotation.tags()),
annotation.summary(),
annotation.explanation(),
annotation.severity(),
annotation.disableable(),
annotation.documentSuppression() ? getSuppressionAnnotations(tree) : ImmutableList.of());
}
@Override
public Optional<BugPatternDocumentation> tryExtract(ClassTree tree, VisitorState state) {
ClassSymbol symbol = ASTHelpers.getSymbol(tree);
BugPattern annotation = symbol.getAnnotation(BugPattern.class);
if (annotation == null) {
return Optional.empty();
}
return Optional.of(
new AutoValue_BugPatternExtractor_BugPatternDocumentation(
symbol.getQualifiedName().toString(),
annotation.name().isEmpty() ? tree.getSimpleName().toString() : annotation.name(),
ImmutableList.copyOf(annotation.altNames()),
annotation.link(),
ImmutableList.copyOf(annotation.tags()),
annotation.summary(),
annotation.explanation(),
annotation.severity(),
annotation.disableable(),
annotation.documentSuppression()
? getSuppressionAnnotations(tree)
: ImmutableList.of()));
public boolean canExtract(ClassTree tree) {
return ASTHelpers.hasDirectAnnotationWithSimpleName(tree, BugPattern.class.getSimpleName());
}
/**

View File

@@ -1,200 +0,0 @@
package tech.picnic.errorprone.documentation;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anything;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.classLiteral;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.TreeScanner;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestDocumentation;
/**
* An {@link Extractor} that describes how to extract data from classes that test a {@code
* BugChecker}.
*/
@Immutable
@AutoService(Extractor.class)
public final class BugPatternTestExtractor implements Extractor<BugPatternTestDocumentation> {
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test");
private static final Matcher<Tree> JUNIT_TEST_METHOD =
toType(MethodTree.class, hasAnnotation("org.junit.jupiter.api.Test"));
private static final Matcher<MethodInvocationTree> BUG_PATTERN_TEST_METHOD =
allOf(
staticMethod()
.onDescendantOfAny(
"com.google.errorprone.CompilationTestHelper",
"com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("newInstance"),
argument(0, classLiteral(anything())));
/** Instantiates a new {@link BugPatternTestExtractor} instance. */
public BugPatternTestExtractor() {}
@Override
public String identifier() {
return "bugpattern-test";
}
// XXX: Improve support for correctly extracting multiple sources from a single
// `{BugCheckerRefactoring,Compilation}TestHelper` test.
@Override
public Optional<BugPatternTestDocumentation> tryExtract(ClassTree tree, VisitorState state) {
return getClassUnderTest(tree)
.filter(bugPatternName -> testsBugPattern(bugPatternName, tree, state))
.map(
bugPatternName -> {
CollectBugPatternTests scanner = new CollectBugPatternTests();
for (Tree m : tree.getMembers()) {
if (JUNIT_TEST_METHOD.matches(m, state)) {
scanner.scan(m, state);
}
}
return new AutoValue_BugPatternTestExtractor_BugPatternTestDocumentation(
bugPatternName, scanner.getIdentificationTests(), scanner.getReplacementTests());
});
}
private static boolean testsBugPattern(
String bugPatternName, ClassTree tree, VisitorState state) {
AtomicBoolean result = new AtomicBoolean(false);
new TreeScanner<@Nullable Void, @Nullable Void>() {
@Override
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, @Nullable Void v) {
if (BUG_PATTERN_TEST_METHOD.matches(node, state)) {
MemberSelectTree firstArgumentTree = (MemberSelectTree) node.getArguments().get(0);
result.compareAndSet(
/* expectedValue= */ false,
bugPatternName.equals(firstArgumentTree.getExpression().toString()));
}
return super.visitMethodInvocation(node, v);
}
}.scan(tree, null);
return result.get();
}
private static Optional<String> getClassUnderTest(ClassTree tree) {
return Optional.of(TEST_CLASS_NAME_PATTERN.matcher(tree.getSimpleName().toString()))
.filter(java.util.regex.Matcher::matches)
.map(m -> m.group(1));
}
private static final class CollectBugPatternTests
extends TreeScanner<@Nullable Void, VisitorState> {
private static final Matcher<ExpressionTree> IDENTIFICATION_SOURCE_LINES =
instanceMethod()
.onDescendantOf("com.google.errorprone.CompilationTestHelper")
.named("addSourceLines");
private static final Matcher<ExpressionTree> REPLACEMENT_INPUT =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("addInputLines");
private static final Matcher<ExpressionTree> REPLACEMENT_OUTPUT =
instanceMethod()
.onDescendantOf("com.google.errorprone.BugCheckerRefactoringTestHelper.ExpectOutput")
.named("addOutputLines");
private final List<String> identificationTests = new ArrayList<>();
private final List<BugPatternReplacementTestDocumentation> replacementTests = new ArrayList<>();
public ImmutableList<String> getIdentificationTests() {
return ImmutableList.copyOf(identificationTests);
}
public ImmutableList<BugPatternReplacementTestDocumentation> getReplacementTests() {
return ImmutableList.copyOf(replacementTests);
}
// XXX: Consider:
// - Whether to omit or handle differently identification tests without `// BUG: Diagnostic
// (contains|matches)` markers.
// - Whether to omit or handle differently replacement tests with identical input and output.
// (Though arguably we should have a separate checker which replaces such cases with
// `.expectUnchanged()`.)
// - Whether to track `.expectUnchanged()` test cases.
@Override
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, VisitorState state) {
if (IDENTIFICATION_SOURCE_LINES.matches(node, state)) {
getSourceCode(node).ifPresent(identificationTests::add);
} else if (REPLACEMENT_OUTPUT.matches(node, state)) {
ExpressionTree receiver = ASTHelpers.getReceiver(node);
// XXX: Make this code nicer.
if (REPLACEMENT_INPUT.matches(receiver, state)) {
getSourceCode(node)
.ifPresent(
output ->
getSourceCode((MethodInvocationTree) receiver)
.ifPresent(
input ->
replacementTests.add(
new AutoValue_BugPatternTestExtractor_BugPatternReplacementTestDocumentation(
input, output))));
}
}
return super.visitMethodInvocation(node, state);
}
// XXX: Duplicated from `ErrorProneTestSourceFormat`. Can we do better?
private static Optional<String> getSourceCode(MethodInvocationTree tree) {
List<? extends ExpressionTree> sourceLines =
tree.getArguments().subList(1, tree.getArguments().size());
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
String value = ASTHelpers.constValue(sourceLine, String.class);
if (value == null) {
return Optional.empty();
}
source.append(value).append('\n');
}
return Optional.of(source.toString());
}
}
// XXX: Rename?
@AutoValue
abstract static class BugPatternTestDocumentation {
abstract String name();
abstract ImmutableList<String> identificationTests();
abstract ImmutableList<BugPatternReplacementTestDocumentation> replacementTests();
}
// XXX: Rename?
@AutoValue
abstract static class BugPatternReplacementTestDocumentation {
abstract String inputLines();
abstract String outputLines();
}
}

View File

@@ -5,14 +5,10 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.util.Context;
import java.io.File;
@@ -23,7 +19,6 @@ import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ServiceLoader;
import javax.tools.JavaFileObject;
/**
@@ -32,13 +27,6 @@ import javax.tools.JavaFileObject;
*/
// XXX: Find a better name for this class; it doesn't generate documentation per se.
final class DocumentationGeneratorTaskListener implements TaskListener {
@SuppressWarnings({"rawtypes", "unchecked"})
private static final ImmutableList<Extractor<?>> EXTRACTORS =
(ImmutableList)
ImmutableList.copyOf(
ServiceLoader.load(
Extractor.class, DocumentationGeneratorTaskListener.class.getClassLoader()));
private static final ObjectMapper OBJECT_MAPPER =
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
@@ -63,25 +51,19 @@ final class DocumentationGeneratorTaskListener implements TaskListener {
return;
}
JavaFileObject sourceFile = taskEvent.getSourceFile();
CompilationUnitTree compilationUnit = taskEvent.getCompilationUnit();
ClassTree classTree = JavacTrees.instance(context).getTree(taskEvent.getTypeElement());
if (sourceFile == null || compilationUnit == null || classTree == null) {
JavaFileObject sourceFile = taskEvent.getSourceFile();
if (classTree == null || sourceFile == null) {
return;
}
VisitorState state =
VisitorState.createForUtilityPurposes(context)
.withPath(new TreePath(new TreePath(compilationUnit), classTree));
for (Extractor<?> extractor : EXTRACTORS) {
extractor
.tryExtract(classTree, state)
.ifPresent(
data ->
writeToFile(
extractor.identifier(), getSimpleClassName(sourceFile.toUri()), data));
}
ExtractorType.findMatchingType(classTree)
.ifPresent(
extractorType ->
writeToFile(
extractorType.getIdentifier(),
getSimpleClassName(sourceFile.toUri()),
extractorType.getExtractor().extract(classTree, context)));
}
private void createDocsDirectory() {

View File

@@ -1,9 +1,8 @@
package tech.picnic.errorprone.documentation;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import java.util.Optional;
import com.sun.tools.javac.util.Context;
/**
* Interface implemented by classes that define how to extract data of some type {@link T} from a
@@ -14,20 +13,21 @@ import java.util.Optional;
@Immutable
interface Extractor<T> {
/**
* Returns the unique identifier of this extractor.
* Extracts and returns an instance of {@link T} using the provided arguments.
*
* @return A non-{@code null} string.
* @param tree The {@link ClassTree} to analyze and from which to extract instances of {@link T}.
* @param context The {@link Context} in which the current compilation takes place.
* @return A non-null instance of {@link T}.
*/
String identifier();
// XXX: Drop `Context` parameter unless used.
T extract(ClassTree tree, Context context);
/**
* Attempts to extract an instance of type {@link T} using the provided arguments.
* Tells whether this {@link Extractor} can extract documentation content from the given {@link
* ClassTree}.
*
* @param tree The {@link ClassTree} to analyze and from which to extract an instance of type
* {@link T}.
* @param state A {@link VisitorState} describing the context in which the given {@link ClassTree}
* is found.
* @return An instance of type {@link T}, if possible.
* @param tree The {@link ClassTree} of interest.
* @return {@code true} iff data extraction is supported.
*/
Optional<T> tryExtract(ClassTree tree, VisitorState state);
boolean canExtract(ClassTree tree);
}

View File

@@ -0,0 +1,35 @@
package tech.picnic.errorprone.documentation;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.sun.source.tree.ClassTree;
import java.util.EnumSet;
import java.util.Optional;
/** An enumeration of {@link Extractor} types. */
enum ExtractorType {
BUG_PATTERN("bugpattern", new BugPatternExtractor());
private static final ImmutableSet<ExtractorType> TYPES =
Sets.immutableEnumSet(EnumSet.allOf(ExtractorType.class));
private final String identifier;
private final Extractor<?> extractor;
ExtractorType(String identifier, Extractor<?> extractor) {
this.identifier = identifier;
this.extractor = extractor;
}
String getIdentifier() {
return identifier;
}
Extractor<?> getExtractor() {
return extractor;
}
static Optional<ExtractorType> findMatchingType(ClassTree tree) {
return TYPES.stream().filter(type -> type.getExtractor().canExtract(tree)).findFirst();
}
}

View File

@@ -1,61 +0,0 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import java.util.Optional;
import java.util.regex.Pattern;
import tech.picnic.errorprone.documentation.models.RefasterTemplateCollectionTestData;
import tech.picnic.errorprone.documentation.models.RefasterTemplateTestData;
@Immutable
@AutoService(Extractor.class)
public final class RefasterTestInputExtractor
implements Extractor<RefasterTemplateCollectionTestData> {
private static final Pattern TEST_INPUT_CLASS_NAME_PATTERN = Pattern.compile("(.*)TestInput");
@Override
public String identifier() {
return "refaster-test-input";
}
@Override
public Optional<RefasterTemplateCollectionTestData> tryExtract(
ClassTree tree, VisitorState state) {
Optional<String> className = getClassUnderTest(tree);
if (className.isEmpty()) {
return Optional.empty();
}
ImmutableList<RefasterTemplateTestData> templateTests =
tree.getMembers().stream()
.filter(m -> m instanceof MethodTree)
.map(MethodTree.class::cast)
.filter(m -> m.getName().toString().startsWith("test"))
.map(
m ->
RefasterTemplateTestData.create(
m.getName().toString().replace("test", ""), getSourceCode(m, state)))
.collect(toImmutableList());
return Optional.of(
RefasterTemplateCollectionTestData.create(className.orElseThrow(), true, templateTests));
}
private static Optional<String> getClassUnderTest(ClassTree tree) {
return Optional.of(TEST_INPUT_CLASS_NAME_PATTERN.matcher(tree.getSimpleName().toString()))
.filter(java.util.regex.Matcher::matches)
.map(m -> m.group(1));
}
// XXX: Duplicated from `SourceCode`. Can we do better?
private String getSourceCode(MethodTree tree, VisitorState state) {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}
}

View File

@@ -1,61 +0,0 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import java.util.Optional;
import java.util.regex.Pattern;
import tech.picnic.errorprone.documentation.models.RefasterTemplateCollectionTestData;
import tech.picnic.errorprone.documentation.models.RefasterTemplateTestData;
@Immutable
@AutoService(Extractor.class)
public final class RefasterTestOutputExtractor
implements Extractor<RefasterTemplateCollectionTestData> {
private static final Pattern TEST_INPUT_CLASS_NAME_PATTERN = Pattern.compile("(.*)TestOutput");
@Override
public String identifier() {
return "refaster-test-output";
}
@Override
public Optional<RefasterTemplateCollectionTestData> tryExtract(
ClassTree tree, VisitorState state) {
Optional<String> className = getClassUnderTest(tree);
if (className.isEmpty()) {
return Optional.empty();
}
ImmutableList<RefasterTemplateTestData> templateTests =
tree.getMembers().stream()
.filter(m -> m instanceof MethodTree)
.map(MethodTree.class::cast)
.filter(m -> m.getName().toString().startsWith("test"))
.map(
m ->
RefasterTemplateTestData.create(
m.getName().toString().replace("test", ""), getSourceCode(m, state)))
.collect(toImmutableList());
return Optional.of(
RefasterTemplateCollectionTestData.create(className.orElseThrow(), true, templateTests));
}
private static Optional<String> getClassUnderTest(ClassTree tree) {
return Optional.of(TEST_INPUT_CLASS_NAME_PATTERN.matcher(tree.getSimpleName().toString()))
.filter(java.util.regex.Matcher::matches)
.map(m -> m.group(1));
}
// XXX: Duplicated from `SourceCode`. Can we do better?
private String getSourceCode(MethodTree tree, VisitorState state) {
String src = state.getSourceForNode(tree);
return src != null ? src : tree.toString();
}
}

View File

@@ -1,25 +0,0 @@
package tech.picnic.errorprone.documentation.models;
import com.google.auto.value.AutoValue;
import java.util.List;
import tech.picnic.errorprone.documentation.models.AutoValue_RefasterTemplateCollectionData;
/**
* Object containing all data related to a Refaster template collection. This is solely used for
* serialization.
*/
@AutoValue
public abstract class RefasterTemplateCollectionData {
public static RefasterTemplateCollectionData create(
String name, String description, String link, List<RefasterTemplateData> templates) {
return new AutoValue_RefasterTemplateCollectionData(name, description, link, templates);
}
abstract String name();
abstract String description();
abstract String link();
abstract List<RefasterTemplateData> templates();
}

View File

@@ -1,20 +0,0 @@
package tech.picnic.errorprone.documentation.models;
import com.google.auto.value.AutoValue;
import java.util.List;
import tech.picnic.errorprone.documentation.models.AutoValue_RefasterTemplateCollectionTestData;
@AutoValue
public abstract class RefasterTemplateCollectionTestData {
public static RefasterTemplateCollectionTestData create(
String templateCollection, boolean isInput, List<RefasterTemplateTestData> templatesTests) {
return new AutoValue_RefasterTemplateCollectionTestData(
templateCollection, isInput, templatesTests);
}
abstract String templateCollection();
abstract boolean isInput();
abstract List<RefasterTemplateTestData> templateTests();
}

View File

@@ -1,21 +0,0 @@
package tech.picnic.errorprone.documentation.models;
import com.google.auto.value.AutoValue;
import com.google.errorprone.BugPattern.SeverityLevel;
import tech.picnic.errorprone.documentation.models.AutoValue_RefasterTemplateData;
@AutoValue
public abstract class RefasterTemplateData {
public static RefasterTemplateData create(
String name, String description, String link, SeverityLevel severityLevel) {
return new AutoValue_RefasterTemplateData(name, description, link, severityLevel);
}
abstract String name();
abstract String description();
abstract String link();
abstract SeverityLevel severityLevel();
}

View File

@@ -1,15 +0,0 @@
package tech.picnic.errorprone.documentation.models;
import com.google.auto.value.AutoValue;
import tech.picnic.errorprone.documentation.models.AutoValue_RefasterTemplateTestData;
@AutoValue
public abstract class RefasterTemplateTestData {
public static RefasterTemplateTestData create(String templateName, String templateTestContent) {
return new AutoValue_RefasterTemplateTestData(templateName, templateTestContent);
}
abstract String templateName();
abstract String templateTestContent();
}

View File

@@ -3,6 +3,7 @@ package tech.picnic.errorprone.documentation;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.google.common.io.Resources;
import com.google.errorprone.BugPattern;
@@ -13,6 +14,7 @@ import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.ClassTree;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -118,8 +120,7 @@ final class BugPatternExtractorTest {
private static void verifyFileMatchesResource(
Path outputDirectory, String fileName, String resourceName) throws IOException {
assertThat(outputDirectory.resolve(fileName))
.content(UTF_8)
assertThat(Files.readString(outputDirectory.resolve(fileName)))
.isEqualToIgnoringWhitespace(getResource(resourceName));
}
@@ -138,10 +139,14 @@ final class BugPatternExtractorTest {
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
BugPatternExtractor extractor = new BugPatternExtractor();
assertThatThrownBy(() -> extractor.extract(tree, state.context))
.isInstanceOf(NullPointerException.class)
.hasMessage("BugPattern annotation must be present");
return buildDescription(tree)
.setMessage(
String.format(
"Can extract: %s", new BugPatternExtractor().tryExtract(tree, state).isPresent()))
.setMessage(String.format("Can extract: %s", extractor.canExtract(tree)))
.build();
}
}

View File

@@ -1,352 +0,0 @@
package tech.picnic.errorprone.documentation;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.io.Resources;
import java.io.IOException;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
final class BugPatternTestExtractorTest {
@Test
void noBugPatternTest(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerWithoutAnnotation.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"public final class TestCheckerWithoutAnnotation extends BugChecker {}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void bugPatternTestFileWithoutTestSuffix(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerWithWrongSuffix.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"",
"final class TestCheckerWithoutTestSuffix {",
" private static class TestCheckerWithout extends BugChecker {}",
"",
" CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(TestCheckerWithout.class, getClass());",
"}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void minimalBugPatternTest(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(TestChecker.class, getClass());",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-minimal.json");
}
@Test
void differentBugPatternAsClassVariableTest(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"",
"final class TestCheckerTest {",
" private static class DifferentChecker extends BugChecker {}",
"",
" CompilationTestHelper compilationTestHelper = CompilationTestHelper.newInstance(DifferentChecker.class, getClass());",
"}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void differentBugPatternAsLocalVariable(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class DifferentChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(DifferentChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .doTest();",
" }",
"}");
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
}
@Test
void bugPatternTestSingleIdentification(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .doTest();",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-identification.json");
}
@Test
void bugPatternTestIdentificationMultipleSourceLines(@TempDir Path outputDirectory)
throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"package pkg;",
"",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .addSourceLines(\"B.java\", \"class B {}\")",
" .doTest();",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-identification-two-sources.json");
}
@Test
void bugPatternTestSingleReplacement(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void replacement() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .addOutputLines(\"A.java\", \"class A {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-replacement.json");
}
@Test
void bugPatternTestMultipleReplacementSources(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void replacement() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .addOutputLines(\"A.java\", \"class A {}\")",
" .addInputLines(\"B.java\", \"class B {}\")",
" .addOutputLines(\"B.java\", \"class B {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-replacement-two-sources.json");
}
@Test
void bugPatternReplacementExpectUnchanged(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void replacement() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .expectUnchanged()",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-replacement-expect-unchanged.json");
}
@Test
void bugPatternTestIdentificationAndReplacement(@TempDir Path outputDirectory)
throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .doTest();",
" }",
"",
" @Test",
" void replacement() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .addOutputLines(\"A.java\", \"class A {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-identification-and-replacement.json");
}
@Test
void bugPatternTestMultipleIdentificationAndReplacement(@TempDir Path outputDirectory)
throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"package pkg;",
"",
"import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.SECOND;",
"",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import com.google.errorprone.CompilationTestHelper;",
"import org.junit.jupiter.api.Test;",
"",
"final class TestCheckerTest {",
" private static class TestChecker extends BugChecker {}",
"",
" @Test",
" void identification() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"A.java\", \"class A {}\")",
" .doTest();",
" }",
"",
" @Test",
" void identification2() {",
" CompilationTestHelper.newInstance(TestChecker.class, getClass())",
" .addSourceLines(\"B.java\", \"class B {}\")",
" .doTest();",
" }",
"",
" @Test",
" void replacementFirstSuggestedFix() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .addInputLines(\"A.java\", \"class A {}\")",
" .addOutputLines(\"A.java\", \"class A {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"",
" @Test",
" void replacementSecondSuggestedFix() {",
" BugCheckerRefactoringTestHelper.newInstance(TestChecker.class, getClass())",
" .setFixChooser(SECOND)",
" .addInputLines(\"B.java\", \"class B {}\")",
" .addOutputLines(\"B.java\", \"class B {}\")",
" .doTest(TestMode.TEXT_MATCH);",
" }",
"}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-test-TestCheckerTest.json",
"bugpattern-test-documentation-multiple-identification-and-replacement.json");
}
private static void verifyFileMatchesResource(
Path outputDirectory, String fileName, String resourceName) throws IOException {
assertThat(outputDirectory.resolve(fileName))
.content(UTF_8)
.isEqualToIgnoringWhitespace(getResource(resourceName));
}
// XXX: Once we support only JDK 15+, drop this method in favour of including the resources as
// text blocks in this class. (This also requires renaming the `verifyFileMatchesResource`
// method.)
private static String getResource(String resourceName) throws IOException {
return Resources.toString(
Resources.getResource(BugPatternTestExtractorTest.class, resourceName), UTF_8);
}
}

View File

@@ -1,29 +1,19 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.attribute.AclEntryPermission.ADD_SUBDIRECTORY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.condition.OS.WINDOWS;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.Tree;
import java.io.IOException;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.AclEntry;
import java.nio.file.attribute.AclFileAttributeView;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
@@ -84,55 +74,4 @@ final class DocumentationGeneratorTaskListenerTest {
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Precisely one path must be provided");
}
@Test
void extraction(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"DocumentationGeneratorTaskListenerTestClass.java",
"class DocumentationGeneratorTaskListenerTestClass {}");
// XXX: Once we support only JDK 15+, use a text block for the `expected` string.
assertThat(
outputDirectory.resolve(
"documentation-generator-task-listener-test-DocumentationGeneratorTaskListenerTestClass.json"))
.content(UTF_8)
.isEqualToIgnoringWhitespace(
"{\"className\":\"DocumentationGeneratorTaskListenerTestClass\",\"path\":[\"CLASS: DocumentationGeneratorTaskListenerTestClass\",\"COMPILATION_UNIT\"]}");
}
@Immutable
@AutoService(Extractor.class)
public static final class TestExtractor implements Extractor<ExtractionParameters> {
@Override
public String identifier() {
return "documentation-generator-task-listener-test";
}
@Override
public Optional<ExtractionParameters> tryExtract(ClassTree tree, VisitorState state) {
return Optional.of(tree.getSimpleName().toString())
.filter(n -> n.contains(DocumentationGeneratorTaskListenerTest.class.getSimpleName()))
.map(
className ->
new AutoValue_DocumentationGeneratorTaskListenerTest_ExtractionParameters(
className,
Streams.stream(state.getPath())
.map(TestExtractor::describeTree)
.collect(toImmutableList())));
}
private static String describeTree(Tree tree) {
return (tree instanceof ClassTree)
? String.join(": ", String.valueOf(tree.getKind()), ((ClassTree) tree).getSimpleName())
: tree.getKind().toString();
}
}
@AutoValue
abstract static class ExtractionParameters {
abstract String className();
abstract ImmutableList<String> path();
}
}

View File

@@ -1,12 +0,0 @@
{
"name": "TestChecker",
"identificationTests": [
"class A {}\n"
],
"replacementTests": [
{
"inputLines": "class A {}\n",
"outputLines": "class A {}\n"
}
]
}

View File

@@ -1,8 +0,0 @@
{
"name": "TestChecker",
"identificationTests": [
"class B {}\n",
"class A {}\n"
],
"replacementTests": []
}

View File

@@ -1,7 +0,0 @@
{
"name": "TestChecker",
"identificationTests": [
"class A {}\n"
],
"replacementTests": []
}

View File

@@ -1,5 +0,0 @@
{
"name": "TestChecker",
"identificationTests": [],
"replacementTests": []
}

View File

@@ -1,17 +0,0 @@
{
"name": "TestChecker",
"identificationTests": [
"class A {}\n",
"class B {}\n"
],
"replacementTests": [
{
"inputLines": "class A {}\n",
"outputLines": "class A {}\n"
},
{
"inputLines": "class B {}\n",
"outputLines": "class B {}\n"
}
]
}

View File

@@ -1,5 +0,0 @@
{
"name": "TestChecker",
"identificationTests": [],
"replacementTests": []
}

View File

@@ -1,14 +0,0 @@
{
"name": "TestChecker",
"identificationTests": [],
"replacementTests": [
{
"inputLines": "class B {}\n",
"outputLines": "class B {}\n"
},
{
"inputLines": "class A {}\n",
"outputLines": "class A {}\n"
}
]
}

View File

@@ -1,10 +0,0 @@
{
"name": "TestChecker",
"identificationTests": [],
"replacementTests": [
{
"inputLines": "class A {}\n",
"outputLines": "class A {}\n"
}
]
}

View File

@@ -5,7 +5,7 @@
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.9.1-SNAPSHOT</version>
<version>0.8.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -37,7 +37,7 @@
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_test_helpers</artifactId>
<scope>provided</scope>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
@@ -146,6 +146,10 @@
<artifactId>value-annotations</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
@@ -257,33 +261,4 @@
</plugins>
</pluginManagement>
</build>
<profiles>
<profile>
<id>docgen</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>add-test-source</id>
<goals>
<goal>add-test-source</goal>
</goals>
<phase>generate-test-sources</phase>
<configuration>
<sources>
<source>src/test/resources</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -1,131 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isVariable;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.returnStatement;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.BlockTreeMatcher;
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.AssignmentTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.code.Symbol;
import java.util.List;
import java.util.Optional;
import tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags unnecessary local variable assignments preceding a return
* statement.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Variable assignment is redundant; value can be returned directly",
link = BUG_PATTERNS_BASE_URL + "DirectReturn",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class DirectReturn extends BugChecker implements BlockTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<StatementTree> VARIABLE_RETURN = returnStatement(isVariable());
private static final Matcher<ExpressionTree> MOCKITO_MOCK_OR_SPY_WITH_IMPLICIT_TYPE =
allOf(
not(toType(MethodInvocationTree.class, argument(0, isSameType(Class.class.getName())))),
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
/** Instantiates a new {@link DirectReturn} instance. */
public DirectReturn() {}
@Override
public Description matchBlock(BlockTree tree, VisitorState state) {
List<? extends StatementTree> statements = tree.getStatements();
if (statements.size() < 2) {
return Description.NO_MATCH;
}
StatementTree finalStatement = statements.get(statements.size() - 1);
if (!VARIABLE_RETURN.matches(finalStatement, state)) {
return Description.NO_MATCH;
}
Symbol variableSymbol = ASTHelpers.getSymbol(((ReturnTree) finalStatement).getExpression());
StatementTree precedingStatement = statements.get(statements.size() - 2);
return tryMatchAssignment(variableSymbol, precedingStatement)
.filter(resultExpr -> canInlineToReturnStatement(resultExpr, state))
.map(
resultExpr ->
describeMatch(
precedingStatement,
SuggestedFix.builder()
.replace(
precedingStatement,
String.format("return %s;", SourceCode.treeToString(resultExpr, state)))
.delete(finalStatement)
.build()))
.orElse(Description.NO_MATCH);
}
private static Optional<ExpressionTree> tryMatchAssignment(Symbol targetSymbol, Tree tree) {
if (tree instanceof ExpressionStatementTree) {
return tryMatchAssignment(targetSymbol, ((ExpressionStatementTree) tree).getExpression());
}
if (tree instanceof AssignmentTree) {
AssignmentTree assignment = (AssignmentTree) tree;
return targetSymbol.equals(ASTHelpers.getSymbol(assignment.getVariable()))
? Optional.of(assignment.getExpression())
: Optional.empty();
}
if (tree instanceof VariableTree) {
VariableTree declaration = (VariableTree) tree;
return declaration.getModifiers().getAnnotations().isEmpty()
&& targetSymbol.equals(ASTHelpers.getSymbol(declaration))
? Optional.ofNullable(declaration.getInitializer())
: Optional.empty();
}
return Optional.empty();
}
/**
* Tells whether inlining the given expression to the associated return statement can be done
* safely.
*
* <p>Inlining is generally safe, but in rare cases the operation may have a functional impact.
* The sole case considered here is the inlining of a Mockito mock or spy construction without an
* explicit type. In such a case the type created depends on context, such as the method's return
* type.
*/
private static boolean canInlineToReturnStatement(
ExpressionTree expressionTree, VisitorState state) {
return !MOCKITO_MOCK_OR_SPY_WITH_IMPLICIT_TYPE.matches(expressionTree, state)
|| MoreASTHelpers.findMethodExitedOnReturn(state)
.filter(m -> MoreASTHelpers.areSameType(expressionTree, m.getReturnType(), state))
.isPresent();
}
}

View File

@@ -152,7 +152,7 @@ public final class ErrorProneTestHelperSourceFormat extends BugChecker
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
String value = ASTHelpers.constValue(sourceLine, String.class);
Object value = ASTHelpers.constValue(sourceLine);
if (value == null) {
return Optional.empty();
}

View File

@@ -1,100 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Preconditions.checkState;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.CONCURRENCY;
import static com.google.errorprone.BugPattern.StandardTags.PERFORMANCE;
import static com.google.errorprone.matchers.method.MethodMatchers.instanceMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.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.MethodInvocationTree;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.util.Position;
import java.util.stream.Stream;
import tech.picnic.errorprone.bugpatterns.util.ThirdPartyLibrary;
/**
* A {@link BugChecker} that flags {@link reactor.core.publisher.Flux} operator usages that may
* implicitly cause the calling thread to be blocked.
*
* <p>Note that the methods flagged here are not themselves blocking, but iterating over the
* resulting {@link Iterable} or {@link Stream} may be.
*/
@AutoService(BugChecker.class)
@BugPattern(
summary = "Avoid iterating over `Flux`es in an implicitly blocking manner",
link = BUG_PATTERNS_BASE_URL + "FluxImplicitBlock",
linkType = CUSTOM,
severity = WARNING,
tags = {CONCURRENCY, PERFORMANCE})
public final class FluxImplicitBlock extends BugChecker implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> FLUX_WITH_IMPLICIT_BLOCK =
instanceMethod()
.onDescendantOf("reactor.core.publisher.Flux")
.namedAnyOf("toIterable", "toStream")
.withNoParameters();
private static final Supplier<Type> STREAM = Suppliers.typeFromString(Stream.class.getName());
/** Instantiates a new {@link FluxImplicitBlock} instance. */
public FluxImplicitBlock() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!FLUX_WITH_IMPLICIT_BLOCK.matches(tree, state)) {
return Description.NO_MATCH;
}
Description.Builder description =
buildDescription(tree).addFix(SuggestedFixes.addSuppressWarnings(state, canonicalName()));
if (ThirdPartyLibrary.GUAVA.isIntroductionAllowed(state)) {
description.addFix(
suggestBlockingElementCollection(
tree, "com.google.common.collect.ImmutableList.toImmutableList", state));
}
description.addFix(
suggestBlockingElementCollection(tree, "java.util.stream.Collectors.toList", state));
return description.build();
}
private static SuggestedFix suggestBlockingElementCollection(
MethodInvocationTree tree, String fullyQualifiedCollectorMethod, VisitorState state) {
SuggestedFix.Builder importSuggestion = SuggestedFix.builder();
String replacementMethodInvocation =
SuggestedFixes.qualifyStaticImport(fullyQualifiedCollectorMethod, importSuggestion, state);
boolean isStream =
ASTHelpers.isSubtype(ASTHelpers.getResultType(tree), STREAM.get(state), state);
String replacement =
String.format(
".collect(%s()).block()%s", replacementMethodInvocation, isStream ? ".stream()" : "");
return importSuggestion.merge(replaceMethodInvocation(tree, replacement, state)).build();
}
private static SuggestedFix.Builder replaceMethodInvocation(
MethodInvocationTree tree, String replacement, VisitorState state) {
int startPosition = state.getEndPosition(ASTHelpers.getReceiver(tree));
int endPosition = state.getEndPosition(tree);
checkState(
startPosition != Position.NOPOS && endPosition != Position.NOPOS,
"Cannot locate method to be replaced in source code");
return SuggestedFix.builder().replace(startPosition, endPosition, replacement);
}
}

View File

@@ -1,307 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.ALL;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.anyOf;
import static com.google.errorprone.matchers.Matchers.anything;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.classLiteral;
import static com.google.errorprone.matchers.Matchers.hasArguments;
import static com.google.errorprone.matchers.Matchers.isPrimitiveOrBoxedPrimitiveType;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.methodHasParameters;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static com.google.errorprone.matchers.Matchers.toType;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.joining;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.HAS_METHOD_SOURCE;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.getMethodSourceFactoryNames;
import com.google.auto.service.AutoService;
import com.google.common.collect.Iterables;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.code.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
/**
* A {@link BugChecker} that flags JUnit tests with a {@link
* org.junit.jupiter.params.provider.MethodSource} annotation that can be replaced with an
* equivalent {@link org.junit.jupiter.params.provider.ValueSource} annotation.
*/
// XXX: Where applicable, also flag `@MethodSource` annotations that reference multiple value
// factory methods (or that repeat the same value factory method multiple times).
// XXX: Support inlining of overloaded value factory methods.
// XXX: Support inlining of value factory methods referenced by multiple `@MethodSource`
// annotations.
// XXX: Support value factory return expressions of the form `Stream.of(a, b,
// c).map(Arguments::argument)`.
// XXX: Support simplification of test methods that accept additional injected parameters such as
// `TestInfo`; such parameters should be ignored for the purpose of this check.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Prefer `@ValueSource` over a `@MethodSource` where possible and reasonable",
linkType = CUSTOM,
link = BUG_PATTERNS_BASE_URL + "JUnitValueSource",
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class JUnitValueSource extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<ExpressionTree> SUPPORTED_VALUE_FACTORY_VALUES =
anyOf(
isArrayArgumentValueCandidate(),
toType(
MethodInvocationTree.class,
allOf(
staticMethod()
.onClass("org.junit.jupiter.params.provider.Arguments")
.namedAnyOf("arguments", "of"),
argumentCount(1),
argument(0, isArrayArgumentValueCandidate()))));
private static final Matcher<ExpressionTree> ARRAY_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS =
isSingleDimensionArrayCreationWithAllElementsMatching(SUPPORTED_VALUE_FACTORY_VALUES);
private static final Matcher<ExpressionTree> ENUMERATION_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS =
toType(
MethodInvocationTree.class,
allOf(
staticMethod()
.onClassAny(
Stream.class.getName(),
IntStream.class.getName(),
LongStream.class.getName(),
DoubleStream.class.getName(),
List.class.getName(),
Set.class.getName(),
"com.google.common.collect.ImmutableList",
"com.google.common.collect.ImmutableSet")
.named("of"),
hasArguments(AT_LEAST_ONE, anything()),
hasArguments(ALL, SUPPORTED_VALUE_FACTORY_VALUES)));
private static final Matcher<MethodTree> IS_UNARY_METHOD_WITH_SUPPORTED_PARAMETER =
methodHasParameters(
anyOf(
isPrimitiveOrBoxedPrimitiveType(),
isSameType(String.class),
isSameType(state -> state.getSymtab().classType)));
/** Instantiates a new {@link JUnitValueSource} instance. */
public JUnitValueSource() {}
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
if (!IS_UNARY_METHOD_WITH_SUPPORTED_PARAMETER.matches(tree, state)) {
return Description.NO_MATCH;
}
Type parameterType = ASTHelpers.getType(Iterables.getOnlyElement(tree.getParameters()));
return findMethodSourceAnnotation(tree, state)
.flatMap(
methodSourceAnnotation ->
getSoleLocalFactoryName(methodSourceAnnotation, tree)
.filter(factory -> !hasSiblingReferencingValueFactory(tree, factory, state))
.flatMap(factory -> findSiblingWithName(tree, factory, state))
.flatMap(
factoryMethod ->
tryConstructValueSourceFix(
parameterType, methodSourceAnnotation, factoryMethod, state))
.map(fix -> describeMatch(methodSourceAnnotation, fix)))
.orElse(Description.NO_MATCH);
}
/**
* Returns the name of the value factory method pointed to by the given {@code @MethodSource}
* annotation, if it (a) is the only one and (b) is a method in the same class as the annotated
* method.
*/
private static Optional<String> getSoleLocalFactoryName(
AnnotationTree methodSourceAnnotation, MethodTree method) {
return getElementIfSingleton(getMethodSourceFactoryNames(methodSourceAnnotation, method))
.filter(name -> name.indexOf('#') < 0);
}
/**
* Tells whether the given method has a sibling method in the same class that depends on the
* specified value factory method.
*/
private static boolean hasSiblingReferencingValueFactory(
MethodTree tree, String valueFactory, VisitorState state) {
return findMatchingSibling(tree, m -> hasValueFactory(m, valueFactory, state), state)
.isPresent();
}
private static Optional<MethodTree> findSiblingWithName(
MethodTree tree, String methodName, VisitorState state) {
return findMatchingSibling(tree, m -> m.getName().contentEquals(methodName), state);
}
private static Optional<MethodTree> findMatchingSibling(
MethodTree tree, Predicate<? super MethodTree> predicate, VisitorState state) {
return state.findEnclosing(ClassTree.class).getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(not(tree::equals))
.filter(predicate)
.findFirst();
}
private static boolean hasValueFactory(
MethodTree tree, String valueFactoryMethodName, VisitorState state) {
return findMethodSourceAnnotation(tree, state).stream()
.anyMatch(
annotation ->
getMethodSourceFactoryNames(annotation, tree).contains(valueFactoryMethodName));
}
private static Optional<AnnotationTree> findMethodSourceAnnotation(
MethodTree tree, VisitorState state) {
return HAS_METHOD_SOURCE.multiMatchResult(tree, state).matchingNodes().stream().findFirst();
}
private static Optional<SuggestedFix> tryConstructValueSourceFix(
Type parameterType,
AnnotationTree methodSourceAnnotation,
MethodTree valueFactoryMethod,
VisitorState state) {
return getSingleReturnExpression(valueFactoryMethod)
.flatMap(expression -> tryExtractValueSourceAttributeValue(expression, state))
.map(
valueSourceAttributeValue ->
SuggestedFix.builder()
.addImport("org.junit.jupiter.params.provider.ValueSource")
.replace(
methodSourceAnnotation,
String.format(
"@ValueSource(%s = %s)",
toValueSourceAttributeName(parameterType), valueSourceAttributeValue))
.delete(valueFactoryMethod)
.build());
}
// XXX: This pattern also occurs a few times inside Error Prone; contribute upstream.
private static Optional<ExpressionTree> getSingleReturnExpression(MethodTree methodTree) {
List<ExpressionTree> returnExpressions = new ArrayList<>();
new TreeScanner<@Nullable Void, @Nullable Void>() {
@Override
public @Nullable Void visitClass(ClassTree node, @Nullable Void unused) {
/* Ignore `return` statements inside anonymous/local classes. */
return null;
}
@Override
public @Nullable Void visitReturn(ReturnTree node, @Nullable Void unused) {
returnExpressions.add(node.getExpression());
return super.visitReturn(node, unused);
}
@Override
public @Nullable Void visitLambdaExpression(
LambdaExpressionTree node, @Nullable Void unused) {
/* Ignore `return` statements inside lambda expressions. */
return null;
}
}.scan(methodTree, null);
return getElementIfSingleton(returnExpressions);
}
private static Optional<String> tryExtractValueSourceAttributeValue(
ExpressionTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments;
if (ENUMERATION_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS.matches(tree, state)) {
arguments = ((MethodInvocationTree) tree).getArguments();
} else if (ARRAY_OF_SUPPORTED_SINGLE_VALUE_ARGUMENTS.matches(tree, state)) {
arguments = ((NewArrayTree) tree).getInitializers();
} else {
return Optional.empty();
}
/*
* Join the values into a comma-separated string, unwrapping `Arguments` factory method
* invocations if applicable.
*/
return Optional.of(
arguments.stream()
.map(
arg ->
arg instanceof MethodInvocationTree
? Iterables.getOnlyElement(((MethodInvocationTree) arg).getArguments())
: arg)
.map(argument -> SourceCode.treeToString(argument, state))
.collect(joining(", ")))
.map(value -> arguments.size() > 1 ? String.format("{%s}", value) : value);
}
private static String toValueSourceAttributeName(Type type) {
String typeString = type.tsym.name.toString();
switch (typeString) {
case "Class":
return "classes";
case "Character":
return "chars";
case "Integer":
return "ints";
default:
return typeString.toLowerCase(Locale.ROOT) + 's';
}
}
private static <T> Optional<T> getElementIfSingleton(Collection<T> collection) {
return Optional.of(collection)
.filter(elements -> elements.size() == 1)
.map(Iterables::getOnlyElement);
}
private static Matcher<ExpressionTree> isSingleDimensionArrayCreationWithAllElementsMatching(
Matcher<? super ExpressionTree> elementMatcher) {
return (tree, state) -> {
if (!(tree instanceof NewArrayTree)) {
return false;
}
NewArrayTree newArray = (NewArrayTree) tree;
return newArray.getDimensions().isEmpty()
&& !newArray.getInitializers().isEmpty()
&& newArray.getInitializers().stream()
.allMatch(element -> elementMatcher.matches(element, state));
};
}
private static Matcher<ExpressionTree> isArrayArgumentValueCandidate() {
return anyOf(classLiteral(anything()), (tree, state) -> ASTHelpers.constValue(tree) != null);
}
}

View File

@@ -1,86 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static com.google.errorprone.matchers.Matchers.allOf;
import static com.google.errorprone.matchers.Matchers.argument;
import static com.google.errorprone.matchers.Matchers.isSameType;
import static com.google.errorprone.matchers.Matchers.isVariable;
import static com.google.errorprone.matchers.Matchers.not;
import static com.google.errorprone.matchers.Matchers.staticMethod;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import java.util.List;
import tech.picnic.errorprone.bugpatterns.util.MoreASTHelpers;
/**
* A {@link BugChecker} that flags the use of {@link org.mockito.Mockito#mock(Class)} and {@link
* org.mockito.Mockito#spy(Class)} where instead the type to be mocked or spied can be derived from
* context.
*/
// XXX: This check currently does not flag method invocation arguments. When adding support for
// this, consider that in some cases the type to be mocked or spied must be specified explicitly so
// as to disambiguate between method overloads.
// XXX: This check currently does not flag (implicit or explicit) lambda return expressions.
// XXX: This check currently does not drop suppressions that become obsolete after the
// suggested fix is applied; consider adding support for this.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Don't unnecessarily pass a type to Mockito's `mock(Class)` and `spy(Class)` methods",
link = BUG_PATTERNS_BASE_URL + "MockitoMockClassReference",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class MockitoMockClassReference extends BugChecker
implements MethodInvocationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Matcher<MethodInvocationTree> MOCKITO_MOCK_OR_SPY_WITH_HARDCODED_TYPE =
allOf(
argument(0, allOf(isSameType(Class.class.getName()), not(isVariable()))),
staticMethod().onClass("org.mockito.Mockito").namedAnyOf("mock", "spy"));
/** Instantiates a new {@link MockitoMockClassReference} instance. */
public MockitoMockClassReference() {}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
if (!MOCKITO_MOCK_OR_SPY_WITH_HARDCODED_TYPE.matches(tree, state)
|| !isTypeDerivableFromContext(tree, state)) {
return Description.NO_MATCH;
}
List<? extends ExpressionTree> arguments = tree.getArguments();
return describeMatch(tree, SuggestedFixes.removeElement(arguments.get(0), arguments, state));
}
private static boolean isTypeDerivableFromContext(MethodInvocationTree tree, VisitorState state) {
Tree parent = state.getPath().getParentPath().getLeaf();
switch (parent.getKind()) {
case VARIABLE:
return !ASTHelpers.hasNoExplicitType((VariableTree) parent, state)
&& MoreASTHelpers.areSameType(tree, parent, state);
case ASSIGNMENT:
return MoreASTHelpers.areSameType(tree, parent, state);
case RETURN:
return MoreASTHelpers.findMethodExitedOnReturn(state)
.filter(m -> MoreASTHelpers.areSameType(tree, m.getReturnType(), state))
.isPresent();
default:
return false;
}
}
}

View File

@@ -7,7 +7,6 @@ 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.anything;
import static com.google.errorprone.matchers.Matchers.argumentCount;
import static com.google.errorprone.matchers.Matchers.isNonNullUsingDataflow;
import static com.google.errorprone.matchers.Matchers.isSameType;
@@ -68,7 +67,9 @@ public final class RedundantStringConversion extends BugChecker
private static final String EXTRA_STRING_CONVERSION_METHODS_FLAG =
"RedundantStringConversion:ExtraConversionMethods";
private static final Matcher<ExpressionTree> ANY_EXPR = anything();
@SuppressWarnings("UnnecessaryLambda")
private static final Matcher<ExpressionTree> ANY_EXPR = (t, s) -> true;
private static final Matcher<ExpressionTree> LOCALE = isSameType(Locale.class);
private static final Matcher<ExpressionTree> MARKER = isSubtypeOf("org.slf4j.Marker");
private static final Matcher<ExpressionTree> STRING = isSameType(String.class);

View File

@@ -74,13 +74,9 @@ public final class RequestMappingAnnotation extends BugChecker implements Method
isSameType("java.time.ZoneId"),
isSameType("java.util.Locale"),
isSameType("java.util.TimeZone"),
isSameType("jakarta.servlet.http.HttpServletRequest"),
isSameType("jakarta.servlet.http.HttpServletResponse"),
isSameType("javax.servlet.http.HttpServletRequest"),
isSameType("javax.servlet.http.HttpServletResponse"),
isSameType("org.springframework.http.HttpMethod"),
isSameType("org.springframework.ui.Model"),
isSameType("org.springframework.validation.BindingResult"),
isSameType("org.springframework.web.context.request.NativeWebRequest"),
isSameType("org.springframework.web.context.request.WebRequest"),
isSameType("org.springframework.web.server.ServerWebExchange"),

View File

@@ -5,12 +5,8 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import java.util.Optional;
/**
* A collection of helper methods for working with the AST.
@@ -50,33 +46,4 @@ public final class MoreASTHelpers {
public static boolean methodExistsInEnclosingClass(CharSequence methodName, VisitorState state) {
return !findMethods(methodName, state).isEmpty();
}
/**
* Returns the {@link MethodTree} from which control flow would exit if there would be a {@code
* return} statement at the given {@link VisitorState}'s current {@link VisitorState#getPath()
* path}.
*
* @param state The {@link VisitorState} from which to derive the AST location of interest.
* @return A {@link MethodTree}, unless the {@link VisitorState}'s path does not point to an AST
* node located inside a method, or if the (hypothetical) {@code return} statement would exit
* a lambda expression instead.
*/
public static Optional<MethodTree> findMethodExitedOnReturn(VisitorState state) {
return Optional.ofNullable(state.findEnclosing(LambdaExpressionTree.class, MethodTree.class))
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast);
}
/**
* Tells whether the given trees are of the same type, after type erasure.
*
* @param treeA The first tree of interest.
* @param treeB The second tree of interest.
* @param state The {@link VisitorState} describing the context in which the given trees were
* found.
* @return Whether the specified trees have the same erased types.
*/
public static boolean areSameType(Tree treeA, Tree treeB, VisitorState state) {
return ASTHelpers.isSameType(ASTHelpers.getType(treeA), ASTHelpers.getType(treeB), state);
}
}

View File

@@ -1,6 +1,6 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
import static com.google.errorprone.matchers.Matchers.annotations;
import static com.google.errorprone.matchers.Matchers.anyOf;
@@ -9,7 +9,7 @@ import static java.util.Objects.requireNonNullElse;
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.matchers.AnnotationMatcherUtils;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.MultiMatcher;
@@ -55,59 +55,25 @@ public final class MoreJUnitMatchers {
* Returns the names of the JUnit value factory methods specified by the given {@link
* org.junit.jupiter.params.provider.MethodSource} annotation.
*
* <p>This method differs from {@link #getMethodSourceFactoryDescriptors(AnnotationTree,
* MethodTree)} in that it drops any parenthesized method parameter type enumerations. That is,
* method descriptors such as {@code factoryMethod()} and {@code factoryMethod(java.lang.String)}
* are both simplified to just {@code factoryMethod}. This also means that the returned method
* names may not unambiguously reference a single value factory method; in such a case JUnit will
* throw an error at runtime.
*
* @param methodSourceAnnotation The annotation from which to extract value factory method names.
* @param method The method on which the annotation is located.
* @return One or more value factory descriptors, in the order defined.
* @see #getMethodSourceFactoryDescriptors(AnnotationTree, MethodTree)
* @return One or more value factory names.
*/
// XXX: Drop this method in favour of `#getMethodSourceFactoryDescriptors`. That will require
// callers to either explicitly drop information, or perform a more advanced analysis.
public static ImmutableList<String> getMethodSourceFactoryNames(
AnnotationTree methodSourceAnnotation, MethodTree method) {
return getMethodSourceFactoryDescriptors(methodSourceAnnotation, method).stream()
.map(
descriptor -> {
int index = descriptor.indexOf('(');
return index < 0 ? descriptor : descriptor.substring(0, index);
})
.collect(toImmutableList());
}
/**
* Returns the descriptors of the JUnit value factory methods specified by the given {@link
* org.junit.jupiter.params.provider.MethodSource} annotation.
*
* @param methodSourceAnnotation The annotation from which to extract value factory method
* descriptors.
* @param method The method on which the annotation is located.
* @return One or more value factory descriptors, in the order defined.
* @see #getMethodSourceFactoryNames(AnnotationTree, MethodTree)
*/
// XXX: Rather than strings, have this method return instances of a value type capable of
// resolving the value factory method pointed to.
public static ImmutableList<String> getMethodSourceFactoryDescriptors(
static ImmutableSet<String> getMethodSourceFactoryNames(
AnnotationTree methodSourceAnnotation, MethodTree method) {
String methodName = method.getName().toString();
ExpressionTree value = AnnotationMatcherUtils.getArgument(methodSourceAnnotation, "value");
if (!(value instanceof NewArrayTree)) {
return ImmutableList.of(toMethodSourceFactoryDescriptor(value, methodName));
return ImmutableSet.of(toMethodSourceFactoryName(value, methodName));
}
return ((NewArrayTree) value)
.getInitializers().stream()
.map(name -> toMethodSourceFactoryDescriptor(name, methodName))
.collect(toImmutableList());
.map(name -> toMethodSourceFactoryName(name, methodName))
.collect(toImmutableSet());
}
private static String toMethodSourceFactoryDescriptor(
private static String toMethodSourceFactoryName(
@Nullable ExpressionTree tree, String annotatedMethodName) {
return requireNonNullElse(
Strings.emptyToNull(ASTHelpers.constValue(tree, String.class)), annotatedMethodName);

View File

@@ -1,16 +1,11 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractStringAssert;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@@ -94,30 +89,4 @@ final class AssertJStringRules {
return assertThat(string).doesNotMatch(regex);
}
}
static final class AssertThatPathContent {
@BeforeTemplate
AbstractStringAssert<?> before(Path path, Charset charset) throws IOException {
return assertThat(Files.readString(path, charset));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractStringAssert<?> after(Path path, Charset charset) {
return assertThat(path).content(charset);
}
}
static final class AssertThatPathContentUtf8 {
@BeforeTemplate
AbstractStringAssert<?> before(Path path) throws IOException {
return assertThat(Files.readString(path));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
AbstractStringAssert<?> after(Path path) {
return assertThat(path).content(UTF_8);
}
}
}

View File

@@ -1,53 +0,0 @@
package tech.picnic.errorprone.refasterrules;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChooser;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster rules related to {@link com.google.errorprone.bugpatterns.BugChecker} classes. */
@OnlineDocumentation
final class BugCheckerRules {
private BugCheckerRules() {}
/**
* Avoid calling {@link BugCheckerRefactoringTestHelper#setFixChooser(FixChooser)} or {@link
* BugCheckerRefactoringTestHelper#setImportOrder(String)} with their respective default values.
*/
static final class BugCheckerRefactoringTestHelperIdentity {
@BeforeTemplate
BugCheckerRefactoringTestHelper before(BugCheckerRefactoringTestHelper helper) {
return Refaster.anyOf(
helper.setFixChooser(FixChoosers.FIRST), helper.setImportOrder("static-first"));
}
@AfterTemplate
BugCheckerRefactoringTestHelper after(BugCheckerRefactoringTestHelper helper) {
return helper;
}
}
/**
* Prefer {@link BugCheckerRefactoringTestHelper.ExpectOutput#expectUnchanged()} over repeating
* the input.
*/
// XXX: This rule assumes that the full source code is specified as a single string, e.g. using a
// text block. Support for multi-line source code input would require a `BugChecker`
// implementation instead.
static final class BugCheckerRefactoringTestHelperAddInputLinesExpectUnchanged {
@BeforeTemplate
BugCheckerRefactoringTestHelper before(
BugCheckerRefactoringTestHelper helper, String path, String source) {
return helper.addInputLines(path, source).addOutputLines(path, source);
}
@AfterTemplate
BugCheckerRefactoringTestHelper after(
BugCheckerRefactoringTestHelper helper, String path, String source) {
return helper.addInputLines(path, source).expectUnchanged();
}
}
}

View File

@@ -6,7 +6,6 @@ import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.function.Function.identity;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Streams;
@@ -127,8 +126,8 @@ final class ImmutableMapRules {
}
/**
* Don't map a stream's elements to map entries, only to subsequently collect them into an {@link
* ImmutableMap}. The collection can be performed directly.
* Don't map a a stream's elements to map entries, only to subsequently collect them into an
* {@link ImmutableMap}. The collection can be performed directly.
*/
abstract static class StreamOfMapEntriesToImmutableMap<E, K, V> {
@Placeholder(allowsIdentity = true)
@@ -316,48 +315,6 @@ final class ImmutableMapRules {
}
}
/**
* Prefer creation of an immutable submap using {@link Maps#filterKeys(Map, Predicate)} over more
* contrived alternatives.
*/
abstract static class ImmutableMapCopyOfMapsFilterKeys<K, V> {
@Placeholder(allowsIdentity = true)
abstract boolean keyFilter(@MayOptionallyUse K key);
@BeforeTemplate
ImmutableMap<K, V> before(ImmutableMap<K, V> map) {
return map.entrySet().stream()
.filter(e -> keyFilter(e.getKey()))
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
}
@AfterTemplate
ImmutableMap<K, V> after(ImmutableMap<K, V> map) {
return ImmutableMap.copyOf(Maps.filterKeys(map, k -> keyFilter(k)));
}
}
/**
* Prefer creation of an immutable submap using {@link Maps#filterValues(Map, Predicate)} over
* more contrived alternatives.
*/
abstract static class ImmutableMapCopyOfMapsFilterValues<K, V> {
@Placeholder(allowsIdentity = true)
abstract boolean valueFilter(@MayOptionallyUse V value);
@BeforeTemplate
ImmutableMap<K, V> before(ImmutableMap<K, V> map) {
return map.entrySet().stream()
.filter(e -> valueFilter(e.getValue()))
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
}
@AfterTemplate
ImmutableMap<K, V> after(ImmutableMap<K, V> map) {
return ImmutableMap.copyOf(Maps.filterValues(map, v -> valueFilter(v)));
}
}
// XXX: Add a rule for this:
// Maps.transformValues(streamOfEntries.collect(groupBy(fun)), ImmutableMap::copyOf)
// ->
@@ -366,4 +323,9 @@ final class ImmutableMapRules {
// map.entrySet().stream().filter(keyPred).forEach(mapBuilder::put)
// ->
// mapBuilder.putAll(Maps.filterKeys(map, pred))
//
// map.entrySet().stream().filter(entry ->
// pred(entry.getKey())).collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))
// ->
// ImmutableMap.copyOf(Maps.filterKeys(map, pred))
}

View File

@@ -0,0 +1,29 @@
package tech.picnic.errorprone.refasterrules;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import java.util.stream.Stream;
import org.jooq.Record;
import org.jooq.ResultQuery;
import tech.picnic.errorprone.refaster.annotation.Description;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
@OnlineDocumentation
final class JooqRules {
private JooqRules() {}
/** Prefer eagerly fetching data over (lazy) streaming to avoid potentially leaking resources. */
@Description(
"Prefer eagerly fetching data over (lazy) streaming to avoid potentially leaking resources.")
static final class ResultQueryFetchStream {
@BeforeTemplate
Stream<?> before(ResultQuery<? extends Record> resultQuery) {
return resultQuery.stream();
}
@AfterTemplate
Stream<?> after(ResultQuery<? extends Record> resultQuery) {
return resultQuery.fetch().stream();
}
}
}

View File

@@ -21,14 +21,11 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
final class NullRules {
private NullRules() {}
/**
* Prefer the {@code ==} operator (with {@code null} as the second operand) over {@link
* Objects#isNull(Object)}.
*/
/** Prefer the {@code ==} operator over {@link Objects#isNull(Object)}. */
static final class IsNull {
@BeforeTemplate
boolean before(@Nullable Object object) {
return Refaster.anyOf(null == object, Objects.isNull(object));
return Objects.isNull(object);
}
@AfterTemplate
@@ -37,14 +34,11 @@ final class NullRules {
}
}
/**
* Prefer the {@code !=} operator (with {@code null} as the second operand) over {@link
* Objects#nonNull(Object)}.
*/
/** Prefer the {@code !=} operator over {@link Objects#nonNull(Object)}. */
static final class IsNotNull {
@BeforeTemplate
boolean before(@Nullable Object object) {
return Refaster.anyOf(null != object, Objects.nonNull(object));
return Objects.nonNull(object);
}
@AfterTemplate

View File

@@ -6,13 +6,11 @@ import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndex;
import static com.google.common.base.Preconditions.checkState;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Preconditions;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Objects;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
/** Refaster templates related to statements dealing with {@link Preconditions}. */
@@ -74,22 +72,8 @@ final class PreconditionsRules {
}
}
/** Prefer {@link Objects#requireNonNull(Object)} over non-JDK alternatives. */
static final class RequireNonNull<T> {
@BeforeTemplate
T before(T object) {
return checkNotNull(object);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
T after(T object) {
return requireNonNull(object);
}
}
/** Prefer {@link Objects#requireNonNull(Object)} over more verbose alternatives. */
static final class RequireNonNullStatement<T> {
/** Prefer {@link Preconditions#checkNotNull(Object)} over more verbose alternatives. */
static final class CheckNotNull<T> {
@BeforeTemplate
void before(T object) {
if (object == null) {
@@ -100,26 +84,12 @@ final class PreconditionsRules {
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(T object) {
requireNonNull(object);
checkNotNull(object);
}
}
/** Prefer {@link Objects#requireNonNull(Object, String)} over non-JDK alternatives. */
static final class RequireNonNullWithMessage<T> {
@BeforeTemplate
T before(T object, String message) {
return checkNotNull(object, message);
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
T after(T object, String message) {
return requireNonNull(object, message);
}
}
/** Prefer {@link Objects#requireNonNull(Object, String)} over more verbose alternatives. */
static final class RequireNonNullWithMessageStatement<T> {
/** Prefer {@link Preconditions#checkNotNull(Object, Object)} over more verbose alternatives. */
static final class CheckNotNullWithMessage<T> {
@BeforeTemplate
void before(T object, String message) {
if (object == null) {
@@ -130,7 +100,7 @@ final class PreconditionsRules {
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
void after(T object, String message) {
requireNonNull(object, message);
checkNotNull(object, message);
}
}

View File

@@ -1,17 +1,14 @@
package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.MoreCollectors.toOptional;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toCollection;
import static org.assertj.core.api.Assertions.assertThat;
import static reactor.function.TupleUtils.function;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.MoreCollectors;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -20,11 +17,8 @@ import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
@@ -705,21 +699,21 @@ final class ReactorRules {
}
}
/** Prefer {@link Mono#singleOptional()} over more contrived alternatives. */
/**
* Prefer a collection using {@link MoreCollectors#toOptional()} over more contrived alternatives.
*/
// XXX: Consider creating a plugin that flags/discourages `Mono<Optional<T>>` method return
// types, just as we discourage nullable `Boolean`s and `Optional`s.
static final class MonoSingleOptional<T> {
static final class MonoCollectToOptional<T> {
@BeforeTemplate
Mono<Optional<T>> before(Mono<T> mono) {
return Refaster.anyOf(
mono.flux().collect(toOptional()),
mono.map(Optional::of).defaultIfEmpty(Optional.empty()));
return mono.map(Optional::of).defaultIfEmpty(Optional.empty());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Mono<Optional<T>> after(Mono<T> mono) {
return mono.singleOptional();
return mono.flux().collect(toOptional());
}
}
@@ -812,31 +806,6 @@ final class ReactorRules {
}
}
/**
* Prefer {@link Flux#count()} followed by a conversion from {@code long} to {@code int} over
* collecting into a list and counting its elements.
*/
static final class FluxCountMapMathToIntExact<T> {
@BeforeTemplate
Mono<Integer> before(Flux<T> flux) {
return Refaster.anyOf(
flux.collect(toImmutableList())
.map(
Refaster.anyOf(
Collection::size,
List::size,
ImmutableCollection::size,
ImmutableList::size)),
flux.collect(toCollection(ArrayList::new))
.map(Refaster.anyOf(Collection::size, List::size)));
}
@AfterTemplate
Mono<Integer> after(Flux<T> flux) {
return flux.count().map(Math::toIntExact);
}
}
/**
* Prefer {@link Mono#doOnError(Class, Consumer)} over {@link Mono#doOnError(Predicate, Consumer)}
* where possible.
@@ -1204,14 +1173,12 @@ final class ReactorRules {
}
}
/** Don't unnecessarily have {@link StepVerifier.Step} expect no elements. */
// XXX: Given an `IsEmpty` matcher that identifies a wide range of guaranteed-empty `Iterable`
// expressions, consider also simplifying `step.expectNextSequence(someEmptyIterable)`.
static final class StepVerifierStepIdentity<T> {
/** Don't unnecessarily call {@link StepVerifier.Step#expectNext(Object[])}. */
static final class StepVerifierStepExpectNextEmpty<T> {
@BeforeTemplate
@SuppressWarnings("unchecked")
StepVerifier.Step<T> before(StepVerifier.Step<T> step) {
return Refaster.anyOf(step.expectNext(), step.expectNextCount(0));
return step.expectNext();
}
@AfterTemplate

View File

@@ -10,7 +10,6 @@ import com.google.common.collect.Streams;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
@@ -20,14 +19,10 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference;
/** Refaster rules related to expressions dealing with {@link Stream}s. */
@OnlineDocumentation
@@ -384,46 +379,4 @@ final class StreamRules {
return stream.allMatch(e -> test(e));
}
}
static final class StreamMapToIntSum<T> {
@BeforeTemplate
int before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Integer> mapper) {
return stream.map(mapper).reduce(0, Integer::sum);
}
@AfterTemplate
int after(Stream<T> stream, ToIntFunction<T> mapper) {
return stream.mapToInt(mapper).sum();
}
}
static final class StreamMapToDoubleSum<T> {
@BeforeTemplate
double before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Double> mapper) {
return stream.map(mapper).reduce(0.0, Double::sum);
}
@AfterTemplate
double after(Stream<T> stream, ToDoubleFunction<T> mapper) {
return stream.mapToDouble(mapper).sum();
}
}
static final class StreamMapToLongSum<T> {
@BeforeTemplate
long before(
Stream<T> stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function<? super T, Long> mapper) {
return stream.map(mapper).reduce(0L, Long::sum);
}
@AfterTemplate
long after(Stream<T> stream, ToLongFunction<T> mapper) {
return stream.mapToLong(mapper).sum();
}
}
}

View File

@@ -1,174 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class DirectReturnTest {
@Test
void identification() {
CompilationTestHelper.newInstance(DirectReturn.class, getClass())
.addSourceLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"",
"import java.util.function.Supplier;",
"",
"class A {",
" private String field;",
"",
" void emptyMethod() {}",
"",
" void voidMethod() {",
" toString();",
" return;",
" }",
"",
" String directReturnOfParam(String param) {",
" return param;",
" }",
"",
" String assignmentToField() {",
" field = toString();",
" return field;",
" }",
"",
" Object redundantAssignmentToParam(String param) {",
" // BUG: Diagnostic contains:",
" param = toString();",
" return param;",
" }",
"",
" String redundantMockAssignmentToParam(String param) {",
" // BUG: Diagnostic contains:",
" param = mock();",
" return param;",
" }",
"",
" Object redundantMockWithExplicitTypeAssignmentToParam(String param) {",
" // BUG: Diagnostic contains:",
" param = mock(String.class);",
" return param;",
" }",
"",
" Object salientMockAssignmentToParam(String param) {",
" param = mock();",
" return param;",
" }",
"",
" String redundantAssignmentToLocalVariable() {",
" String variable = null;",
" // BUG: Diagnostic contains:",
" variable = toString();",
" return variable;",
" }",
"",
" String unusedAssignmentToLocalVariable(String param) {",
" String variable = null;",
" variable = toString();",
" return param;",
" }",
"",
" String redundantVariableDeclaration() {",
" // BUG: Diagnostic contains:",
" String variable = toString();",
" return variable;",
" }",
"",
" String redundantSpyVariableDeclaration() {",
" // BUG: Diagnostic contains:",
" String variable = spy();",
" return variable;",
" }",
"",
" Object redundantSpyWithExplicitTypeVariableDeclaration() {",
" // BUG: Diagnostic contains:",
" String variable = spy(String.class);",
" return variable;",
" }",
"",
" Object salientSpyTypeVariableDeclaration() {",
" String variable = spy(\"name\");",
" return variable;",
" }",
"",
" String unusedVariableDeclaration(String param) {",
" String variable = toString();",
" return param;",
" }",
"",
" String assignmentToAnnotatedVariable() {",
" @SuppressWarnings(\"HereBeDragons\")",
" String variable = toString();",
" return variable;",
" }",
"",
" String complexReturnStatement() {",
" String variable = toString();",
" return variable + toString();",
" }",
"",
" String assignmentInsideIfClause() {",
" String variable = null;",
" if (true) {",
" variable = toString();",
" }",
" return variable;",
" }",
"",
" String redundantAssignmentInsideElseClause() {",
" String variable = toString();",
" if (true) {",
" return variable;",
" } else {",
" // BUG: Diagnostic contains:",
" variable = \"foo\";",
" return variable;",
" }",
" }",
"",
" Supplier<String> redundantAssignmentInsideLambda() {",
" return () -> {",
" // BUG: Diagnostic contains:",
" String variable = toString();",
" return variable;",
" };",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(DirectReturn.class, getClass())
.addInputLines(
"A.java",
"class A {",
" String m1() {",
" String variable = null;",
" variable = toString();",
" return variable;",
" }",
"",
" String m2() {",
" String variable = toString();",
" return variable;",
" }",
"}")
.addOutputLines(
"A.java",
"class A {",
" String m1() {",
" String variable = null;",
" return toString();",
" }",
"",
" String m2() {",
" return toString();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -67,6 +67,7 @@ final class FluxFlatMapUsageTest {
@Test
void replacementFirstSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(FluxFlatMapUsage.class, getClass())
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
"import reactor.core.publisher.Flux;",

View File

@@ -1,182 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Predicates.and;
import static com.google.common.base.Predicates.containsPattern;
import static com.google.common.base.Predicates.not;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.SECOND;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.THIRD;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.CorePublisher;
import reactor.core.publisher.Flux;
final class FluxImplicitBlockTest {
@Test
void identification() {
CompilationTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.expectErrorMessage(
"X",
and(
containsPattern("SuppressWarnings"),
containsPattern("toImmutableList"),
containsPattern("toList")))
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableList;",
"import java.util.stream.Stream;",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" // BUG: Diagnostic matches: X",
" Flux.just(1).toIterable();",
" // BUG: Diagnostic matches: X",
" Flux.just(2).toStream();",
" // BUG: Diagnostic matches: X",
" long count = Flux.just(3).toStream().count();",
"",
" Flux.just(4).toIterable(1);",
" Flux.just(5).toIterable(2, null);",
" Flux.just(6).toStream(3);",
" new Foo().toIterable();",
" new Foo().toStream();",
" }",
"",
" class Foo<T> {",
" Iterable<T> toIterable() {",
" return ImmutableList.of();",
" }",
"",
" Stream<T> toStream() {",
" return Stream.empty();",
" }",
" }",
"}")
.doTest();
}
@Test
void identificationWithoutGuavaOnClasspath() {
CompilationTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.withClasspath(CorePublisher.class, Flux.class, Publisher.class)
.expectErrorMessage("X", not(containsPattern("toImmutableList")))
.addSourceLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" // BUG: Diagnostic matches: X",
" Flux.just(1).toIterable();",
" // BUG: Diagnostic matches: X",
" Flux.just(2).toStream();",
" }",
"}")
.doTest();
}
@Test
void replacementFirstSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.addInputLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).toIterable();",
" Flux.just(2).toStream();",
" }",
"}")
.addOutputLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" @SuppressWarnings(\"FluxImplicitBlock\")",
" void m() {",
" Flux.just(1).toIterable();",
" Flux.just(2).toStream();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementSecondSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.setFixChooser(SECOND)
.addInputLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).toIterable();",
" Flux.just(2).toStream();",
" Flux.just(3).toIterable().iterator();",
" Flux.just(4).toStream().count();",
" Flux.just(5) /* a */./* b */ toIterable /* c */(/* d */ ) /* e */;",
" Flux.just(6) /* a */./* b */ toStream /* c */(/* d */ ) /* e */;",
" }",
"}")
.addOutputLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
"",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toImmutableList()).block();",
" Flux.just(2).collect(toImmutableList()).block().stream();",
" Flux.just(3).collect(toImmutableList()).block().iterator();",
" Flux.just(4).collect(toImmutableList()).block().stream().count();",
" Flux.just(5).collect(toImmutableList()).block() /* e */;",
" Flux.just(6).collect(toImmutableList()).block().stream() /* e */;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementThirdSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(FluxImplicitBlock.class, getClass())
.setFixChooser(THIRD)
.addInputLines(
"A.java",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).toIterable();",
" Flux.just(2).toStream();",
" Flux.just(3).toIterable().iterator();",
" Flux.just(4).toStream().count();",
" Flux.just(5) /* a */./* b */ toIterable /* c */(/* d */ ) /* e */;",
" Flux.just(6) /* a */./* b */ toStream /* c */(/* d */ ) /* e */;",
" }",
"}")
.addOutputLines(
"A.java",
"import static java.util.stream.Collectors.toList;",
"",
"import reactor.core.publisher.Flux;",
"",
"class A {",
" void m() {",
" Flux.just(1).collect(toList()).block();",
" Flux.just(2).collect(toList()).block().stream();",
" Flux.just(3).collect(toList()).block().iterator();",
" Flux.just(4).collect(toList()).block().stream().count();",
" Flux.just(5).collect(toList()).block() /* e */;",
" Flux.just(6).collect(toList()).block().stream() /* e */;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -180,6 +180,7 @@ final class IdentityConversionTest {
@Test
void replacementFirstSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.staticMethod;",

View File

@@ -1,496 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class JUnitValueSourceTest {
@Test
void identification() {
CompilationTestHelper.newInstance(JUnitValueSource.class, getClass())
.addSourceLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import java.util.Optional;",
"import java.util.stream.Stream;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" private static Stream<Arguments> identificationTestCases() {",
" return Stream.of(arguments(1), Arguments.of(2));",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"identificationTestCases\")",
" void identification(int foo) {}",
"",
" private static int[] identificationWithParensTestCases() {",
" return new int[] {1, 2};",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"identificationWithParensTestCases()\")",
" void identificationWithParens(int foo) {}",
"",
" @ParameterizedTest",
" @MethodSource(\"valueFactoryMissingTestCases\")",
" void valueFactoryMissing(int foo) {}",
"",
" private static Stream<Arguments> multipleUsagesTestCases() {",
" return Stream.of(arguments(1), Arguments.of(2));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"multipleUsagesTestCases\")",
" void multipleUsages1(int foo) {}",
"",
" @ParameterizedTest",
" @MethodSource(\"multipleUsagesTestCases()\")",
" void multipleUsages2(int bar) {}",
"",
" private static Stream<Arguments> valueFactoryRepeatedTestCases() {",
" return Stream.of(arguments(1), arguments(2));",
" }",
"",
" @ParameterizedTest",
" @MethodSource({\"valueFactoryRepeatedTestCases\", \"valueFactoryRepeatedTestCases\"})",
" void valueFactoryRepeated(int foo) {}",
"",
" private static Stream<Arguments> multipleParametersTestCases() {",
" return Stream.of(arguments(1, 2), arguments(3, 4));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"multipleParametersTestCases\")",
" void multipleParameters(int first, int second) {}",
"",
" private static int[] arrayWithoutInitializersTestCases() {",
" return new int[1];",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"arrayWithoutInitializersTestCases\")",
" void arrayWithoutInitializers(int foo) {}",
"",
" private static Stream<Arguments> runtimeValueTestCases() {",
" int second = 2;",
" return Stream.of(arguments(1), arguments(second));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"runtimeValueTestCases\")",
" void runtimeValue(int foo) {}",
"",
" private static Stream<Arguments> streamChainTestCases() {",
" return Stream.of(1, 2).map(Arguments::arguments);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamChainTestCases\")",
" void streamChain(int number) {}",
"",
" private static Stream<Arguments> multipleReturnsTestCases() {",
" if (true) {",
" return Stream.of(arguments(1), arguments(2));",
" } else {",
" return Stream.of(arguments(3), arguments(4));",
" }",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"multipleReturnsTestCases\")",
" void multipleReturns(int number) {}",
"",
" private static Stream<Arguments> multipleFactoriesFooTestCases() {",
" return Stream.of(arguments(1));",
" }",
"",
" private static Stream<Arguments> multipleFactoriesBarTestCases() {",
" return Stream.of(arguments(1));",
" }",
"",
" @ParameterizedTest",
" @MethodSource({\"multipleFactoriesFooTestCases\", \"multipleFactoriesBarTestCases\"})",
" void multipleFactories(int i) {}",
"",
" private static Stream<Arguments> extraArgsTestCases() {",
" return Stream.of(arguments(1), arguments(1, 2));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"extraArgsTestCases\")",
" void extraArgs(int... i) {}",
"",
" private static Stream<Arguments> localClassTestCases() {",
" class Foo {",
" Stream<Arguments> foo() {",
" return Stream.of(arguments(1), arguments(2));",
" }",
" }",
" return Stream.of(arguments(1), arguments(2));",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"localClassTestCases\")",
" void localClass(int i) {}",
"",
" private static Stream<Arguments> lambdaReturnTestCases() {",
" int foo =",
" Optional.of(10)",
" .map(",
" i -> {",
" return i / 2;",
" })",
" .orElse(0);",
" return Stream.of(arguments(1), arguments(1));",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"lambdaReturnTestCases\")",
" void lambdaReturn(int i) {}",
"",
" @ParameterizedTest",
" @MethodSource(\"tech.picnic.errorprone.Foo#fooTestCases\")",
" void staticMethodReference(int foo) {}",
"",
" private static Stream<Arguments> valueFactoryWithArgumentTestCases(int amount) {",
" return Stream.of(arguments(1), arguments(2));",
" }",
"",
" @ParameterizedTest",
" // BUG: Diagnostic contains:",
" @MethodSource(\"valueFactoryWithArgumentTestCases\")",
" void valueFactoryWithArgument(int foo) {}",
"",
" private static Arguments[] emptyArrayValueFactoryTestCases() {",
" return new Arguments[] {};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"emptyArrayValueFactoryTestCases\")",
" void emptyArrayValueFactory(int foo) {}",
"",
" private static Stream<Arguments> emptyStreamValueFactoryTestCases() {",
" return Stream.of();",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"emptyStreamValueFactoryTestCases\")",
" void emptyStreamValueFactory(int foo) {}",
"",
" private static Arguments[] invalidValueFactoryArgumentsTestCases() {",
" return new Arguments[] {arguments(1), arguments(new Object() {})};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"invalidValueFactoryArgumentsTestCases\")",
" void invalidValueFactoryArguments(int foo) {}",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(JUnitValueSource.class, getClass())
.addInputLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.List;",
"import java.util.Set;",
"import java.util.stream.DoubleStream;",
"import java.util.stream.IntStream;",
"import java.util.stream.LongStream;",
"import java.util.stream.Stream;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" private static final boolean CONST_BOOLEAN = false;",
" private static final byte CONST_BYTE = 42;",
" private static final char CONST_CHARACTER = 'a';",
" private static final short CONST_SHORT = 42;",
" private static final int CONST_INTEGER = 42;",
" private static final long CONST_LONG = 42;",
" private static final float CONST_FLOAT = 42;",
" private static final double CONST_DOUBLE = 42;",
" private static final String CONST_STRING = \"foo\";",
"",
" private static Stream<Arguments> streamOfBooleanArguments() {",
" return Stream.of(arguments(false), arguments(true), arguments(CONST_BOOLEAN));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamOfBooleanArguments\")",
" void primitiveBoolean(boolean b) {}",
"",
" private static Stream<Object> streamOfBooleansAndBooleanArguments() {",
" return Stream.of(false, arguments(true), CONST_BOOLEAN);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamOfBooleansAndBooleanArguments\")",
" void boxedBoolean(Boolean b) {}",
"",
" private static List<Arguments> listOfByteArguments() {",
" return List.of(arguments((byte) 0), arguments((byte) 1), arguments(CONST_BYTE));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"listOfByteArguments\")",
" void primitiveByte(byte b) {}",
"",
" private static List<Object> listOfBytesAndByteArguments() {",
" return List.of((byte) 0, arguments((byte) 1), CONST_BYTE);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"listOfBytesAndByteArguments\")",
" void boxedByte(Byte b) {}",
"",
" private static Set<Arguments> setOfCharacterArguments() {",
" return Set.of(arguments((char) 0), arguments((char) 1), arguments(CONST_CHARACTER));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"setOfCharacterArguments\")",
" void primitiveCharacter(char c) {}",
"",
" private static Set<Object> setOfCharactersAndCharacterArguments() {",
" return Set.of((char) 0, arguments((char) 1), CONST_CHARACTER);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"setOfCharactersAndCharacterArguments\")",
" void boxedCharacter(Character c) {}",
"",
" private static Arguments[] arrayOfShortArguments() {",
" return new Arguments[] {arguments((short) 0), arguments((short) 1), arguments(CONST_SHORT)};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"arrayOfShortArguments\")",
" void primitiveShort(short s) {}",
"",
" private static Object[] arrayOfShortsAndShortArguments() {",
" return new Object[] {(short) 0, arguments((short) 1), CONST_SHORT};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"arrayOfShortsAndShortArguments\")",
" void boxedShort(Short s) {}",
"",
" private static IntStream intStream() {",
" return IntStream.of(0, 1, CONST_INTEGER);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"intStream\")",
" void primitiveInteger(int i) {}",
"",
" private static int[] intArray() {",
" return new int[] {0, 1, CONST_INTEGER};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"intArray\")",
" void boxedInteger(Integer i) {}",
"",
" private static LongStream longStream() {",
" return LongStream.of(0, 1, CONST_LONG);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"longStream\")",
" void primitiveLong(long l) {}",
"",
" private static long[] longArray() {",
" return new long[] {0, 1, CONST_LONG};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"longArray\")",
" void boxedLong(Long l) {}",
"",
" private static ImmutableList<Arguments> immutableListOfFloatArguments() {",
" return ImmutableList.of(arguments(0.0F), arguments(1.0F), arguments(CONST_FLOAT));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"immutableListOfFloatArguments\")",
" void primitiveFloat(float f) {}",
"",
" private static Stream<Object> streamOfFloatsAndFloatArguments() {",
" return Stream.of(0.0F, arguments(1.0F), CONST_FLOAT);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamOfFloatsAndFloatArguments\")",
" void boxedFloat(Float f) {}",
"",
" private static DoubleStream doubleStream() {",
" return DoubleStream.of(0, 1, CONST_DOUBLE);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"doubleStream\")",
" void primitiveDouble(double d) {}",
"",
" private static double[] doubleArray() {",
" return new double[] {0, 1, CONST_DOUBLE};",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"doubleArray\")",
" void boxedDouble(Double d) {}",
"",
" private static ImmutableSet<Arguments> immutableSetOfStringArguments() {",
" return ImmutableSet.of(arguments(\"foo\"), arguments(\"bar\"), arguments(CONST_STRING));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"immutableSetOfStringArguments\")",
" void string(String s) {}",
"",
" private static Stream<Class<?>> streamOfClasses() {",
" return Stream.of(Stream.class, java.util.Map.class);",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"streamOfClasses\")",
" void clazz(Class<?> c) {}",
"",
" private static Stream<Arguments> sameNameFactoryTestCases() {",
" return Stream.of(arguments(1));",
" }",
"",
" private static Stream<Arguments> sameNameFactoryTestCases(int overload) {",
" return Stream.of(arguments(overload));",
" }",
"",
" @ParameterizedTest",
" @MethodSource(\"sameNameFactoryTestCases\")",
" void sameNameFactory(int i) {}",
"}")
.addOutputLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
"",
"import com.google.common.collect.ImmutableList;",
"import com.google.common.collect.ImmutableSet;",
"import java.util.List;",
"import java.util.Set;",
"import java.util.stream.DoubleStream;",
"import java.util.stream.IntStream;",
"import java.util.stream.LongStream;",
"import java.util.stream.Stream;",
"import org.junit.jupiter.params.ParameterizedTest;",
"import org.junit.jupiter.params.provider.Arguments;",
"import org.junit.jupiter.params.provider.MethodSource;",
"import org.junit.jupiter.params.provider.ValueSource;",
"",
"class A {",
" private static final boolean CONST_BOOLEAN = false;",
" private static final byte CONST_BYTE = 42;",
" private static final char CONST_CHARACTER = 'a';",
" private static final short CONST_SHORT = 42;",
" private static final int CONST_INTEGER = 42;",
" private static final long CONST_LONG = 42;",
" private static final float CONST_FLOAT = 42;",
" private static final double CONST_DOUBLE = 42;",
" private static final String CONST_STRING = \"foo\";",
"",
" @ParameterizedTest",
" @ValueSource(booleans = {false, true, CONST_BOOLEAN})",
" void primitiveBoolean(boolean b) {}",
"",
" @ParameterizedTest",
" @ValueSource(booleans = {false, true, CONST_BOOLEAN})",
" void boxedBoolean(Boolean b) {}",
"",
" @ParameterizedTest",
" @ValueSource(bytes = {(byte) 0, (byte) 1, CONST_BYTE})",
" void primitiveByte(byte b) {}",
"",
" @ParameterizedTest",
" @ValueSource(bytes = {(byte) 0, (byte) 1, CONST_BYTE})",
" void boxedByte(Byte b) {}",
"",
" @ParameterizedTest",
" @ValueSource(chars = {(char) 0, (char) 1, CONST_CHARACTER})",
" void primitiveCharacter(char c) {}",
"",
" @ParameterizedTest",
" @ValueSource(chars = {(char) 0, (char) 1, CONST_CHARACTER})",
" void boxedCharacter(Character c) {}",
"",
" @ParameterizedTest",
" @ValueSource(shorts = {(short) 0, (short) 1, CONST_SHORT})",
" void primitiveShort(short s) {}",
"",
" @ParameterizedTest",
" @ValueSource(shorts = {(short) 0, (short) 1, CONST_SHORT})",
" void boxedShort(Short s) {}",
"",
" @ParameterizedTest",
" @ValueSource(ints = {0, 1, CONST_INTEGER})",
" void primitiveInteger(int i) {}",
"",
" @ParameterizedTest",
" @ValueSource(ints = {0, 1, CONST_INTEGER})",
" void boxedInteger(Integer i) {}",
"",
" @ParameterizedTest",
" @ValueSource(longs = {0, 1, CONST_LONG})",
" void primitiveLong(long l) {}",
"",
" @ParameterizedTest",
" @ValueSource(longs = {0, 1, CONST_LONG})",
" void boxedLong(Long l) {}",
"",
" @ParameterizedTest",
" @ValueSource(floats = {0.0F, 1.0F, CONST_FLOAT})",
" void primitiveFloat(float f) {}",
"",
" @ParameterizedTest",
" @ValueSource(floats = {0.0F, 1.0F, CONST_FLOAT})",
" void boxedFloat(Float f) {}",
"",
" @ParameterizedTest",
" @ValueSource(doubles = {0, 1, CONST_DOUBLE})",
" void primitiveDouble(double d) {}",
"",
" @ParameterizedTest",
" @ValueSource(doubles = {0, 1, CONST_DOUBLE})",
" void boxedDouble(Double d) {}",
"",
" @ParameterizedTest",
" @ValueSource(strings = {\"foo\", \"bar\", CONST_STRING})",
" void string(String s) {}",
"",
" @ParameterizedTest",
" @ValueSource(classes = {Stream.class, java.util.Map.class})",
" void clazz(Class<?> c) {}",
"",
" private static Stream<Arguments> sameNameFactoryTestCases(int overload) {",
" return Stream.of(arguments(overload));",
" }",
"",
" @ParameterizedTest",
" @ValueSource(ints = 1)",
" void sameNameFactory(int i) {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -1,136 +0,0 @@
package tech.picnic.errorprone.bugpatterns;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MockitoMockClassReferenceTest {
@Test
void identification() {
CompilationTestHelper.newInstance(MockitoMockClassReference.class, getClass())
.addSourceLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"import static org.mockito.Mockito.withSettings;",
"",
"import java.util.List;",
"import java.util.Objects;",
"import org.mockito.invocation.InvocationOnMock;",
"",
"class A {",
" {",
" Double d = Objects.requireNonNullElseGet(null, () -> mock(Double.class));",
" Double d2 =",
" Objects.requireNonNullElseGet(",
" null,",
" () -> {",
" return mock(Double.class);",
" });",
" }",
"",
" void m() {",
" Number variableMock = 42;",
" // BUG: Diagnostic contains:",
" variableMock = mock(Number.class);",
" // BUG: Diagnostic contains:",
" variableMock = mock(Number.class, \"name\");",
" // BUG: Diagnostic contains:",
" variableMock = mock(Number.class, InvocationOnMock::callRealMethod);",
" // BUG: Diagnostic contains:",
" variableMock = mock(Number.class, withSettings());",
" variableMock = mock(Integer.class);",
" variableMock = 42;",
" // BUG: Diagnostic contains:",
" List rawMock = mock(List.class);",
" // BUG: Diagnostic contains:",
" List<String> genericMock = mock(List.class);",
" var varMock = mock(Integer.class);",
" Class<? extends Number> numberType = Integer.class;",
" Number variableTypeMock = mock(numberType);",
" Object subtypeMock = mock(Integer.class);",
"",
" Number variableSpy = 42;",
" // BUG: Diagnostic contains:",
" variableSpy = spy(Number.class);",
" variableSpy = spy(Integer.class);",
" variableSpy = 42;",
" // BUG: Diagnostic contains:",
" List rawSpy = spy(List.class);",
" // BUG: Diagnostic contains:",
" List<String> genericSpy = spy(List.class);",
" var varSpy = spy(Integer.class);",
" Number variableTypeSpy = spy(numberType);",
" Object subtypeSpy = spy(Integer.class);",
" Object objectSpy = spy(new Object());",
"",
" Objects.hash(mock(Integer.class));",
" Integer i = mock(mock(Integer.class));",
" String s = new String(mock(String.class));",
" }",
"",
" Double getDoubleMock() {",
" return Objects.requireNonNullElseGet(",
" null,",
" () -> {",
" return mock(Double.class);",
" });",
" }",
"",
" Integer getIntegerMock() {",
" // BUG: Diagnostic contains:",
" return mock(Integer.class);",
" }",
"",
" <T> T getGenericMock(Class<T> clazz) {",
" return mock(clazz);",
" }",
"",
" Number getSubTypeMock() {",
" return mock(Integer.class);",
" }",
"}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(MockitoMockClassReference.class, getClass())
.addInputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"import static org.mockito.Mockito.withSettings;",
"",
"import org.mockito.invocation.InvocationOnMock;",
"",
"class A {",
" void m() {",
" Number simpleMock = mock(Number.class);",
" Number namedMock = mock(Number.class, \"name\");",
" Number customAnswerMock = mock(Number.class, InvocationOnMock::callRealMethod);",
" Number customSettingsMock = mock(Number.class, withSettings());",
" Number simpleSpy = spy(Number.class);",
" }",
"}")
.addOutputLines(
"A.java",
"import static org.mockito.Mockito.mock;",
"import static org.mockito.Mockito.spy;",
"import static org.mockito.Mockito.withSettings;",
"",
"import org.mockito.invocation.InvocationOnMock;",
"",
"class A {",
" void m() {",
" Number simpleMock = mock();",
" Number namedMock = mock(\"name\");",
" Number customAnswerMock = mock(InvocationOnMock::callRealMethod);",
" Number customSettingsMock = mock(withSettings());",
" Number simpleSpy = spy();",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -16,8 +16,6 @@ final class RequestMappingAnnotationTest {
"import javax.servlet.http.HttpServletRequest;",
"import javax.servlet.http.HttpServletResponse;",
"import org.springframework.http.HttpMethod;",
"import org.springframework.ui.Model;",
"import org.springframework.validation.BindingResult;",
"import org.springframework.web.bind.annotation.DeleteMapping;",
"import org.springframework.web.bind.annotation.GetMapping;",
"import org.springframework.web.bind.annotation.PatchMapping;",
@@ -84,12 +82,6 @@ final class RequestMappingAnnotationTest {
" A properHttpMethod(HttpMethod method);",
"",
" @RequestMapping",
" A properModel(Model model);",
"",
" @RequestMapping",
" A properBindingResult(BindingResult result);",
"",
" @RequestMapping",
" A properNativeWebRequest(NativeWebRequest request);",
"",
" @RequestMapping",

View File

@@ -48,6 +48,7 @@ final class StringCaseLocaleUsageTest {
@Test
void replacementFirstSuggestedFix() {
BugCheckerRefactoringTestHelper.newInstance(StringCaseLocaleUsage.class, getClass())
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
"class A {",

View File

@@ -8,16 +8,9 @@ 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.ExpressionStatementTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.ReturnTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import java.util.List;
import java.util.function.BiFunction;
import org.junit.jupiter.api.Test;
@@ -74,70 +67,7 @@ final class MoreASTHelpersTest {
.doTest();
}
@Test
void findMethodExitedOnReturn() {
CompilationTestHelper.newInstance(FindMethodReturnTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import java.util.stream.Stream;",
"",
"class A {",
" {",
" toString();",
" }",
"",
" String topLevelMethod() {",
" // BUG: Diagnostic contains: topLevelMethod",
" toString();",
" // BUG: Diagnostic contains: topLevelMethod",
" return toString();",
" }",
"",
" Stream<String> anotherMethod() {",
" // BUG: Diagnostic contains: anotherMethod",
" return Stream.of(1)",
" .map(",
" n -> {",
" toString();",
" return toString();",
" });",
" }",
"",
" void recursiveMethod(Runnable r) {",
" // BUG: Diagnostic contains: recursiveMethod",
" recursiveMethod(",
" new Runnable() {",
" @Override",
" public void run() {",
" // BUG: Diagnostic contains: run",
" toString();",
" }",
" });",
" }",
"}")
.doTest();
}
@Test
void areSameType() {
CompilationTestHelper.newInstance(AreSameTypeTestChecker.class, getClass())
.addSourceLines(
"A.java",
"class A {",
" void negative1(String a, Integer b) {}",
"",
" void negative2(Integer a, Number b) {}",
"",
" // BUG: Diagnostic contains:",
" void positive1(String a, String b) {}",
"",
" // BUG: Diagnostic contains:",
" void positive2(Iterable<String> a, Iterable<Integer> b) {}",
"}")
.doTest();
}
private static String createMethodSearchDiagnosticsMessage(
private static String createDiagnosticsMessage(
BiFunction<String, VisitorState, Object> valueFunction, VisitorState state) {
return Maps.toMap(ImmutableSet.of("foo", "bar", "baz"), key -> valueFunction.apply(key, state))
.toString();
@@ -155,7 +85,7 @@ final class MoreASTHelpersTest {
public Description matchMethod(MethodTree tree, VisitorState state) {
return buildDescription(tree)
.setMessage(
createMethodSearchDiagnosticsMessage(
createDiagnosticsMessage(
(methodName, s) -> MoreASTHelpers.findMethods(methodName, s).size(), state))
.build();
}
@@ -173,55 +103,8 @@ final class MoreASTHelpersTest {
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return buildDescription(tree)
.setMessage(
createMethodSearchDiagnosticsMessage(
MoreASTHelpers::methodExistsInEnclosingClass, state))
.setMessage(createDiagnosticsMessage(MoreASTHelpers::methodExistsInEnclosingClass, state))
.build();
}
}
/**
* A {@link BugChecker} that delegates to {@link
* MoreASTHelpers#findMethodExitedOnReturn(VisitorState)}.
*/
@BugPattern(summary = "Interacts with `MoreASTHelpers` for testing purposes", severity = ERROR)
public static final class FindMethodReturnTestChecker extends BugChecker
implements ExpressionStatementTreeMatcher, ReturnTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchExpressionStatement(ExpressionStatementTree tree, VisitorState state) {
return flagMethodReturnLocation(tree, state);
}
@Override
public Description matchReturn(ReturnTree tree, VisitorState state) {
return flagMethodReturnLocation(tree, state);
}
private Description flagMethodReturnLocation(Tree tree, VisitorState state) {
return MoreASTHelpers.findMethodExitedOnReturn(state)
.map(m -> buildDescription(tree).setMessage(m.getName().toString()).build())
.orElse(Description.NO_MATCH);
}
}
/**
* A {@link BugChecker} that delegates to {@link MoreASTHelpers#areSameType(Tree, Tree,
* VisitorState)}.
*/
@BugPattern(summary = "Interacts with `MoreASTHelpers` for testing purposes", severity = ERROR)
public static final class AreSameTypeTestChecker extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
List<? extends VariableTree> parameters = tree.getParameters();
return parameters.stream()
.skip(1)
.allMatch(p -> MoreASTHelpers.areSameType(p, parameters.get(0), state))
? describeMatch(tree)
: Description.NO_MATCH;
}
}
}

View File

@@ -83,40 +83,6 @@ final class MoreJUnitMatchersTest {
@Test
void getMethodSourceFactoryNames() {
CompilationTestHelper.newInstance(MethodSourceFactoryNamesTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import org.junit.jupiter.params.provider.MethodSource;",
"",
"class A {",
" @MethodSource",
" // BUG: Diagnostic contains: [matchingMethodSource]",
" void matchingMethodSource(boolean b) {}",
"",
" @MethodSource(\"myValueFactory\")",
" // BUG: Diagnostic contains: [myValueFactory]",
" void singleCustomMethodSource(boolean b) {}",
"",
" @MethodSource({",
" \"nullary()\",",
" \"nullary()\",",
" \"\",",
" \"withStringParam(java.lang.String)\",",
" \"paramsUnspecified\"",
" })",
" // BUG: Diagnostic contains: [nullary, nullary, multipleMethodSources, withStringParam,",
" // paramsUnspecified]",
" void multipleMethodSources(boolean b) {}",
"",
" @MethodSource({\"foo\", \"()\", \"bar\"})",
" // BUG: Diagnostic contains: [foo, , bar]",
" void methodSourceWithoutName(boolean b) {}",
"}")
.doTest();
}
@Test
void getMethodSourceFactoryDescriptors() {
CompilationTestHelper.newInstance(MethodSourceFactoryDescriptorsTestChecker.class, getClass())
.addSourceLines(
"A.java",
"import org.junit.jupiter.params.provider.MethodSource;",
@@ -153,14 +119,6 @@ final class MoreJUnitMatchersTest {
" @MethodSource({\"myValueFactory\", \"\"})",
" // BUG: Diagnostic contains: [myValueFactory, customAndMatchingMethodSources]",
" void customAndMatchingMethodSources(boolean b) {}",
"",
" @MethodSource({\"factory\", \"\", \"factory\", \"\"})",
" // BUG: Diagnostic contains: [factory, repeatedMethodSources, factory, repeatedMethodSources]",
" void repeatedMethodSources(boolean b) {}",
"",
" @MethodSource({\"nullary()\", \"withStringParam(java.lang.String)\"})",
" // BUG: Diagnostic contains: [nullary(), withStringParam(java.lang.String)]",
" void methodSourcesWithParameterSpecification(boolean b) {}",
"}")
.doTest();
}
@@ -212,25 +170,4 @@ final class MoreJUnitMatchersTest {
.build();
}
}
/**
* A {@link BugChecker} that flags methods with a JUnit {@code @MethodSource} annotation by
* enumerating the associated value factory method descriptors.
*/
@BugPattern(summary = "Interacts with `MoreJUnitMatchers` for testing purposes", severity = ERROR)
public static final class MethodSourceFactoryDescriptorsTestChecker extends BugChecker
implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
AnnotationTree annotation =
Iterables.getOnlyElement(HAS_METHOD_SOURCE.multiMatchResult(tree, state).matchingNodes());
return buildDescription(tree)
.setMessage(
MoreJUnitMatchers.getMethodSourceFactoryDescriptors(annotation, tree).toString())
.build();
}
}
}

View File

@@ -31,6 +31,7 @@ final class SourceCodeTest {
.expectUnchanged()
.addInputLines(
"AnnotationDeletions.java",
"",
"interface AnnotationDeletions {",
" class SoleAnnotation {",
" @AnnotationToBeDeleted",
@@ -65,6 +66,7 @@ final class SourceCodeTest {
"}")
.addOutputLines(
"AnnotationDeletions.java",
"",
"interface AnnotationDeletions {",
" class SoleAnnotation {",
" void m() {}",
@@ -99,6 +101,7 @@ final class SourceCodeTest {
refactoringTestHelper
.addInputLines(
"MethodDeletions.java",
"",
"interface MethodDeletions {",
" class SoleMethod {",
" void methodToBeDeleted() {}",
@@ -138,6 +141,7 @@ final class SourceCodeTest {
"}")
.addOutputLines(
"MethodDeletions.java",
"",
"interface MethodDeletions {",
" class SoleMethod {}",
"",

View File

@@ -35,7 +35,6 @@ final class RefasterRulesTest {
AssertJThrowingCallableRules.class,
AssortedRules.class,
BigDecimalRules.class,
BugCheckerRules.class,
CollectionRules.class,
ComparatorRules.class,
DoubleStreamRules.class,
@@ -50,6 +49,7 @@ final class RefasterRulesTest {
ImmutableSortedMultisetRules.class,
ImmutableSortedSetRules.class,
IntStreamRules.class,
JooqRules.class,
JUnitRules.class,
JUnitToAssertJRules.class,
LongStreamRules.class,

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.Offset.offset;

View File

@@ -1,22 +1,12 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractStringAssert;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class AssertJStringRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(Files.class);
}
void testAbstractStringAssertStringIsEmpty() {
assertThat("foo").isEqualTo("");
}
@@ -40,12 +30,4 @@ final class AssertJStringRulesTest implements RefasterRuleCollectionTestCase {
AbstractAssert<?, ?> testAssertThatDoesNotMatch() {
return assertThat("foo".matches(".*")).isFalse();
}
AbstractStringAssert<?> testAssertThatPathContent() throws IOException {
return assertThat(Files.readString(Paths.get(""), Charset.defaultCharset()));
}
AbstractStringAssert<?> testAssertThatPathContentUtf8() throws IOException {
return assertThat(Files.readString(Paths.get("")));
}
}

View File

@@ -1,23 +1,12 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.AbstractStringAssert;
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase;
final class AssertJStringRulesTest implements RefasterRuleCollectionTestCase {
@Override
public ImmutableSet<?> elidedTypesAndStaticImports() {
return ImmutableSet.of(Files.class);
}
void testAbstractStringAssertStringIsEmpty() {
assertThat("foo").isEmpty();
}
@@ -41,12 +30,4 @@ final class AssertJStringRulesTest implements RefasterRuleCollectionTestCase {
AbstractAssert<?, ?> testAssertThatDoesNotMatch() {
return assertThat("foo").doesNotMatch(".*");
}
AbstractStringAssert<?> testAssertThatPathContent() throws IOException {
return assertThat(Paths.get("")).content(Charset.defaultCharset());
}
AbstractStringAssert<?> testAssertThatPathContentUtf8() throws IOException {
return assertThat(Paths.get("")).content(UTF_8);
}
}

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.ImmutableSet.toImmutableSet;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.toImmutableEnumSet;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.input;
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.math.BigDecimal;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules.output;
package tech.picnic.errorprone.refasterrules;
import com.google.common.collect.ImmutableSet;
import java.math.BigDecimal;

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