Compare commits

...

47 Commits

Author SHA1 Message Date
Pieter Dirk Soels
5bf0d75850 Generate website content 2023-02-13 14:32:13 +01:00
Rick Ossendrijver
2dad97451e Further simplify testing setup 2023-02-13 13:21:43 +01:00
Rick Ossendrijver
ca901c6286 Simplify tests 2023-02-13 13:21:43 +01:00
Rick Ossendrijver
ec2b3fb434 Introduce BugPatternTestExtractor with tests 2023-02-13 13:21:43 +01:00
Eric Staffas
29469cbbfd Introduce ConflictDetection utility class (#478) 2023-02-13 12:43:17 +01:00
Picnic-Bot
da9a6dd270 Upgrade Byte Buddy 1.12.23 -> 1.13.0 (#496)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.13.0
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.12.23...byte-buddy-1.13.0
2023-02-13 11:35:27 +01:00
Gökhun Çelik
0cb03aa132 Add Gradle installation instructions to README (#430) 2023-02-13 11:18:36 +01:00
Rick Ossendrijver
0f15070883 Introduce documentation-support module (#428)
This new module provides the initial version of a framework for the extraction 
of data from bug checkers and Refaster rules, to be used as input for website
generation.
2023-02-13 09:27:08 +01:00
Stephan Schroevers
14b5fa1feb Update .mvn/maven.config for compatibility with Maven 3.9.0 (#493)
See https://issues.apache.org/jira/browse/MNG-7684
2023-02-08 09:08:09 +01:00
Stephan Schroevers
d1f513373f Enable additional maven-enforcer-plugin rules (#489) 2023-02-06 14:16:05 +01:00
Picnic-Bot
cd1593009b Upgrade Pitest Git plugins 1.0.3 -> 1.0.4 (#490) 2023-02-06 11:49:37 +01:00
Picnic-Bot
0d52414c04 Upgrade Byte Buddy 1.12.22 -> 1.12.23 (#492)
See:
- https://github.com/raphw/byte-buddy/releases/tag/byte-buddy-1.12.23
- https://github.com/raphw/byte-buddy/compare/byte-buddy-1.12.22...byte-buddy-1.12.23
2023-02-06 08:17:38 +01:00
Picnic-Bot
a55ed9cea9 Upgrade maven-enforcer-plugin 3.1.0 -> 3.2.1 (#487)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MENFORCER%20AND%20fixVersion%20%3E%203.1.0%20AND%20fixVersion%20%3C%3D%203.2.1%20
- https://github.com/apache/maven-enforcer/releases/tag/enforcer-3.2.1
- https://github.com/apache/maven-enforcer/compare/enforcer-3.1.0...enforcer-3.2.1
2023-02-04 10:33:11 +01:00
Picnic-Bot
9b191f46aa Upgrade Checker Framework Annotations 3.29.0 -> 3.30.0 (#488)
See:
- https://github.com/typetools/checker-framework/releases/tag/checker-framework-3.30.0
- https://github.com/typetools/checker-framework/compare/checker-framework-3.29.0...checker-framework-3.30.0
2023-02-03 09:37:00 +01:00
Picnic-Bot
6ea756f3ce Upgrade sortpom-maven-plugin 3.2.0 -> 3.2.1 (#481)
See:
- https://github.com/Ekryd/sortpom/wiki/Versions
- https://github.com/Ekryd/sortpom/releases/tag/sortpom-parent-3.2.1
- https://github.com/Ekryd/sortpom/compare/sortpom-parent-3.2.0...sortpom-parent-3.2.1
2023-02-03 09:06:17 +01:00
Picnic-Bot
adbcc4a94f Upgrade Checkstyle 10.6.0 -> 10.7.0 (#486)
See:
- https://checkstyle.sourceforge.io/releasenotes.html
- https://github.com/checkstyle/checkstyle/releases/tag/checkstyle-10.7.0
- https://github.com/checkstyle/checkstyle/compare/checkstyle-10.6.0...checkstyle-10.7.0
2023-02-02 10:19:11 +01:00
Picnic-Bot
0ed2788dbd Upgrade NullAway 0.10.8 -> 0.10.9 (#485)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.8...v0.10.9
2023-02-02 09:22:00 +01:00
Picnic-Bot
04749ffcf5 Upgrade pitest-maven-plugin 1.10.4 -> 1.11.0 (#483)
See https://github.com/hcoles/pitest/compare/1.10.4...1.11.0
2023-01-31 08:49:11 +01:00
Picnic-Bot
37077bd03c Upgrade Mockito 5.1.0 -> 5.1.1 (#482)
See:
- https://github.com/mockito/mockito/releases/tag/v5.1.1
- https://github.com/mockito/mockito/compare/v5.1.0...v5.1.1
2023-01-31 08:30:51 +01:00
Picnic-Bot
4798f7cf5f Upgrade Jackson 2.14.1 -> 2.14.2 (#479)
See:
- https://github.com/FasterXML/jackson/wiki/Jackson-Release-2.14.2
- https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.14.1...jackson-bom-2.14.2
2023-01-30 09:36:53 +01:00
Picnic-Bot
ac285f0c50 Upgrade Mockito 5.0.0 -> 5.1.0 (#480)
See:
- https://github.com/mockito/mockito/releases/tag/v5.1.0
- https://github.com/mockito/mockito/compare/v5.0.0...v5.1.0
2023-01-30 09:25:08 +01:00
Picnic-Bot
1f3fb08082 Upgrade New Relic Java Agent 7.11.1 -> 8.0.0 (#477)
See:
- https://github.com/newrelic/newrelic-java-agent/releases/tag/v8.0.0
- https://github.com/newrelic/newrelic-java-agent/compare/v7.11.1...v8.0.0
2023-01-28 10:08:14 +01:00
Stephan Schroevers
9a397aa047 [maven-release-plugin] prepare for next development iteration 2023-01-27 09:20:57 +01:00
Stephan Schroevers
60e74332de [maven-release-plugin] prepare release v0.8.0 2023-01-27 09:20:54 +01:00
Stephan Schroevers
3a94aad3b0 Document MonoFlatMapToFlux Refaster rule limitation (#473) 2023-01-24 15:38:52 +01:00
Picnic-Bot
a5b5f43974 Upgrade TestNG 7.4.0 -> 7.7.1 (#469)
See:
- https://github.com/cbeust/testng/blob/master/CHANGES.txt
- https://github.com/cbeust/testng/releases/tag/7.6.0
- https://github.com/cbeust/testng/releases/tag/7.6.1
- https://github.com/cbeust/testng/releases/tag/7.7.0
- https://github.com/cbeust/testng/releases/tag/7.7.1
- https://github.com/cbeust/testng/compare/7.4.0...7.7.1
2023-01-24 09:45:13 +01:00
Rick Ossendrijver
c212b9a171 Enable Checkstyle's JavadocStyle module (#451)
See: 
- https://checkstyle.org/config_javadoc.html#JavadocStyle
- https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocStyleCheck.html
2023-01-23 11:16:12 +01:00
Picnic-Bot
499f922328 Upgrade Spring 5.3.24 -> 5.3.25 (#460)
See:
- https://github.com/spring-projects/spring-framework/releases/tag/v5.3.25
- https://github.com/spring-projects/spring-framework/compare/v5.3.24...v5.3.25
2023-01-20 09:59:49 +01:00
Picnic-Bot
5720732b48 Upgrade Spring Boot 2.7.7 -> 2.7.8 (#471)
See:
- https://github.com/spring-projects/spring-boot/releases/tag/v2.7.8
- https://github.com/spring-projects/spring-boot/compare/v2.7.7...v2.7.8
2023-01-20 08:47:55 +01:00
Phil Werli
81ffd04fe4 Extend MonoIdentity Refaster rule (#470)
By flagging expressions of the form `mono.flux().singleOrEmpty()`.
2023-01-19 13:49:57 +01:00
Picnic-Bot
b2f514f0a5 Upgrade Mockito 4.11.0 -> 5.0.0 (#463)
See:
- https://github.com/mockito/mockito/releases/tag/v5.0.0
- https://github.com/mockito/mockito/compare/v4.11.0...v5.0.0
2023-01-19 10:59:24 +01:00
Picnic-Bot
dad92b5fa6 Upgrade maven-dependency-plugin 3.4.0 -> 3.5.0 (#461)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MDEP%20AND%20fixVersion%20%3E%203.4.0%20AND%20fixVersion%20%3C%3D%203.5.0%20AND%20statusCategory%20%3D%20Done%20
- https://github.com/apache/maven-dependency-plugin/compare/maven-dependency-plugin-3.4.0...maven-dependency-plugin-3.5.0
2023-01-18 17:13:44 +01:00
Picnic-Bot
6d699f75ad Upgrade maven-checkstyle-plugin 3.2.0 -> 3.2.1 (#459)
See:
- https://issues.apache.org/jira/issues/?jql=project%20%3D%20MCHECKSTYLE%20AND%20fixVersion%20%3E%203.2.0%20AND%20fixVersion%20%3C%3D%203.2.1
- https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.2.0...maven-checkstyle-plugin-3.2.1
2023-01-18 16:38:54 +01:00
Picnic-Bot
cad0c74dc0 Upgrade nohttp-checkstyle 0.0.10 -> 0.0.11 (#458)
See https://github.com/spring-io/nohttp/compare/0.0.10...0.0.11
2023-01-17 09:15:00 +01:00
Picnic-Bot
a3a1f495f1 Upgrade errorprone-slf4j 0.1.17 -> 0.1.18 (#466)
See:
- https://github.com/KengoTODA/errorprone-slf4j/releases/tag/v0.1.18
- https://github.com/KengoTODA/errorprone-slf4j/compare/v0.1.17...v0.1.18
2023-01-17 08:48:31 +01:00
Picnic-Bot
b2a646fc21 Upgrade AssertJ 3.24.1 -> 3.24.2 (#467)
See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://assertj.github.io/doc/#assertj-guava-release-notes
- https://github.com/assertj/assertj/compare/assertj-build-3.24.1...assertj-build-3.24.2
2023-01-17 08:15:38 +01:00
Picnic-Bot
1194b0c83c Upgrade NullAway 0.10.7 -> 0.10.8 (#464)
See:
- https://github.com/uber/NullAway/blob/master/CHANGELOG.md
- https://github.com/uber/NullAway/compare/v0.10.7...v0.10.8
2023-01-17 08:01:48 +01:00
Phil Werli
82a07fde25 Extend MonoIdentity Refaster rule (#465)
By flagging expressions of the form `mono.flux().next()`.
2023-01-17 07:33:56 +01:00
Picnic-Bot
dec3220b5b Upgrade pitest-junit5-plugin 1.1.1 -> 1.1.2 (#462)
See https://github.com/pitest/pitest-junit5-plugin/compare/1.1.1...1.1.2
2023-01-16 08:42:14 +01:00
Stephan Schroevers
79356ac553 Upgrade Error Prone 2.17.0 -> 2.18.0 (#455)
See:
- https://github.com/google/error-prone/releases/tag/v2.18.0
- https://github.com/google/error-prone/compare/v2.17.0...v2.18.0
- https://github.com/PicnicSupermarket/error-prone/compare/v2.17.0-picnic-1...v2.18.0-picnic-1
2023-01-11 19:12:39 +01:00
Picnic-Bot
f079c53914 Upgrade Project Reactor 2022.0.1 -> 2022.0.2 (#456)
See:
- https://github.com/reactor/reactor/releases/tag/2022.0.2
- https://github.com/reactor/reactor/compare/2022.0.1...2022.0.2
2023-01-11 13:58:41 +01:00
Picnic-Bot
8e24da907d Upgrade JUnit Jupiter 5.9.1 -> 5.9.2 (#457)
See:
- https://junit.org/junit5/docs/current/release-notes/index.html
- https://github.com/junit-team/junit5/releases/tag/r5.9.2
- https://github.com/junit-team/junit5/compare/r5.9.1...r5.9.2
2023-01-11 11:16:10 +01:00
Picnic-Bot
3c89a1c80d Upgrade AssertJ 3.24.0 -> 3.24.1 (#453)
See:
- https://assertj.github.io/doc/#assertj-core-release-notes
- https://github.com/assertj/assertj/compare/assertj-build-3.24.0...assertj-build-3.24.1
2023-01-09 09:05:59 +01:00
Rick Ossendrijver
9bd4b16001 Inline most {BugCheckerRefactoring,Compilation}TestHelper fields (#442) 2023-01-08 12:40:20 +01:00
Picnic-Bot
6370452803 Upgrade swagger-annotations 2.2.7 -> 2.2.8 (#452)
See:
- https://github.com/swagger-api/swagger-core/releases/tag/v2.2.8
- https://github.com/swagger-api/swagger-core/compare/v2.2.7...v2.2.8
2023-01-08 12:16:52 +01:00
Benedek Halasi
feb9abfa91 Introduce MapGetOrDefault Refaster rule (#439)
Fixes #431.
2023-01-06 14:57:12 +01:00
Stephan Schroevers
560f52bad0 [maven-release-plugin] prepare for next development iteration 2023-01-06 11:29:12 +01:00
211 changed files with 2542 additions and 570 deletions

View File

@@ -19,16 +19,21 @@ 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 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
run: bundle exec ruby generate-docs.rb
- name: Upload website as artifact
uses: actions/upload-pages-artifact@v1.0.5
with:

View File

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

View File

@@ -36,7 +36,9 @@ code_][picnic-blog-ep-post].
### Installation
This library is built on top of [Error Prone][error-prone-orig-repo]. To use
it:
it, read the installation guide for Maven or Gradle below.
#### Maven
1. First, follow Error Prone's [installation
guide][error-prone-installation-guide].
@@ -94,6 +96,31 @@ it:
Prone Support. Alternatively reference this project's `self-check` profile
definition. -->
#### Gradle
1. First, follow the [installation guide]
[error-prone-gradle-installation-guide] of the `gradle-errorprone-plugin`.
2. Next, edit your `build.gradle` file to add one or more Error Prone Support
modules:
```groovy
dependencies {
// Error Prone itself.
errorprone("com.google.errorprone:error_prone_core:${errorProneVersion}")
// Error Prone Support's additional bug checkers.
errorprone("tech.picnic.error-prone-support:error-prone-contrib:${errorProneSupportVersion}")
// Error Prone Support's Refaster rules.
errorprone("tech.picnic.error-prone-support:refaster-runner:${errorProneSupportVersion}")
}
tasks.withType(JavaCompile).configureEach {
options.errorprone.disableWarningsInGeneratedCode = true
// Add other Error Prone flags here. See:
// - https://github.com/tbroyer/gradle-errorprone-plugin#configuration
// - https://errorprone.info/docs/flags
}
```
### Seeing it in action
Consider the following example code:
@@ -207,6 +234,7 @@ guidelines][contributing].
[error-prone-bugchecker]: https://github.com/google/error-prone/blob/master/check_api/src/main/java/com/google/errorprone/bugpatterns/BugChecker.java
[error-prone-fork-jitpack]: https://jitpack.io/#PicnicSupermarket/error-prone
[error-prone-fork-repo]: https://github.com/PicnicSupermarket/error-prone
[error-prone-gradle-installation-guide]: https://github.com/tbroyer/gradle-errorprone-plugin
[error-prone-installation-guide]: https://errorprone.info/docs/installation#maven
[error-prone-orig-repo]: https://github.com/google/error-prone
[error-prone-pull-3301]: https://github.com/google/error-prone/pull/3301

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>tech.picnic.error-prone-support</groupId>
<artifactId>error-prone-support</artifactId>
<version>0.8.1-SNAPSHOT</version>
</parent>
<artifactId>documentation-support</artifactId>
<name>Picnic :: Error Prone Support :: Documentation Support</name>
<description>Data extraction support for the purpose of documentation generation.</description>
<dependencies>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotation</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_check_api</artifactId>
</dependency>
<dependency>
<groupId>${groupId.error-prone}</groupId>
<artifactId>error_prone_test_helpers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto</groupId>
<artifactId>auto-common</artifactId>
</dependency>
<dependency>
<groupId>com.google.auto.service</groupId>
<artifactId>auto-service-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,105 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Objects.requireNonNull;
import com.google.auto.common.AnnotationMirrors;
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.source.util.TaskEvent;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.util.Context;
import javax.lang.model.element.AnnotationValue;
import tech.picnic.errorprone.documentation.BugPatternExtractor.BugPatternDocumentation;
/**
* An {@link Extractor} that describes how to extract data from a {@code @BugPattern} annotation.
*/
@Immutable
final class BugPatternExtractor implements Extractor<BugPatternDocumentation> {
@Override
public BugPatternDocumentation extract(ClassTree tree, Context context, TaskEvent taskEvent) {
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 boolean canExtract(ClassTree tree, VisitorState state) {
return ASTHelpers.hasDirectAnnotationWithSimpleName(tree, BugPattern.class.getSimpleName());
}
/**
* Returns the fully-qualified class names of suppression annotations specified by the {@link
* BugPattern} annotation located on the given tree.
*
* @implNote This method cannot simply invoke {@link BugPattern#suppressionAnnotations()}, as that
* will yield an "Attempt to access Class objects for TypeMirrors" exception.
*/
private static ImmutableList<String> getSuppressionAnnotations(ClassTree tree) {
AnnotationTree annotationTree =
ASTHelpers.getAnnotationWithSimpleName(
ASTHelpers.getAnnotations(tree), BugPattern.class.getSimpleName());
requireNonNull(annotationTree, "BugPattern annotation must be present");
Attribute.Array types =
doCast(
AnnotationMirrors.getAnnotationValue(
ASTHelpers.getAnnotationMirror(annotationTree), "suppressionAnnotations"),
Attribute.Array.class);
return types.getValue().stream()
.map(v -> doCast(v, Attribute.Class.class).classType.toString())
.collect(toImmutableList());
}
@SuppressWarnings("unchecked")
private static <T extends AnnotationValue> T doCast(AnnotationValue value, Class<T> target) {
verify(target.isInstance(value), "Value '%s' is not of type '%s'", value, target);
return (T) value;
}
@AutoValue
abstract static class BugPatternDocumentation {
abstract String fullyQualifiedName();
abstract String name();
abstract ImmutableList<String> altNames();
abstract String link();
abstract ImmutableList<String> tags();
abstract String summary();
abstract String explanation();
abstract SeverityLevel severityLevel();
abstract boolean canDisable();
abstract ImmutableList<String> suppressionAnnotations();
}
}

View File

@@ -0,0 +1,180 @@
package tech.picnic.errorprone.documentation;
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
import static com.google.errorprone.matchers.Matchers.instanceMethod;
import static com.google.errorprone.matchers.method.MethodMatchers.staticMethod;
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.annotations.Var;
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.util.TaskEvent;
import com.sun.source.util.TreeScanner;
import com.sun.tools.javac.util.Context;
import java.util.ArrayList;
import java.util.List;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestDocumentation;
/**
* An {@link Extractor} that describes how to extract data from a test that tests a {@code
* BugChecker}.
*/
@Immutable
final class BugPatternTestExtractor implements Extractor<BugPatternTestDocumentation> {
private static final Matcher<MethodTree> JUNIT_TEST_METHOD =
hasAnnotation("org.junit.jupiter.api.Test");
// XXX: Improve support for correctly extracting multiple sources from a single
// `{BugCheckerRefactoring,Compilation}TestHelper` test.
@Override
public BugPatternTestDocumentation extract(ClassTree tree, Context context, TaskEvent event) {
VisitorState state = VisitorState.createForUtilityPurposes(context);
CollectBugPatternTests scanner = new CollectBugPatternTests(state);
tree.getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(m -> JUNIT_TEST_METHOD.matches(m, state))
.forEach(m -> scanner.scan(m, null));
String className = tree.getSimpleName().toString();
return new AutoValue_BugPatternTestExtractor_BugPatternTestDocumentation(
className.substring(0, className.lastIndexOf("Test")),
scanner.getIdentificationTests(),
scanner.getReplacementTests());
}
@Override
public boolean canExtract(ClassTree tree, VisitorState state) {
String className = tree.getSimpleName().toString();
if (!className.endsWith("Test")) {
return false;
}
ScanBugPatternTest scanBugPatternTest = new ScanBugPatternTest();
scanBugPatternTest.scan(tree, state);
String bugPatternName = className.substring(0, className.lastIndexOf("Test"));
return scanBugPatternTest.hasTestUsingClassInstance(bugPatternName);
}
private static final class ScanBugPatternTest extends TreeScanner<@Nullable Void, VisitorState> {
private static final Matcher<ExpressionTree> BUG_PATTERN_TEST_METHOD =
staticMethod()
.onDescendantOfAny(
"com.google.errorprone.CompilationTestHelper",
"com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("newInstance");
private final List<String> encounteredClasses = new ArrayList<>();
boolean hasTestUsingClassInstance(String clazz) {
return encounteredClasses.contains(clazz);
}
@Override
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, VisitorState state) {
if (BUG_PATTERN_TEST_METHOD.matches(node, state)) {
MemberSelectTree firstArgumentTree = (MemberSelectTree) node.getArguments().get(0);
encounteredClasses.add(firstArgumentTree.getExpression().toString());
}
return super.visitMethodInvocation(node, state);
}
}
private static final class CollectBugPatternTests
extends TreeScanner<@Nullable Void, @Nullable Void> {
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 VisitorState state;
private final List<String> identificationTests = new ArrayList<>();
private final List<BugPatternReplacementTestDocumentation> replacementTests = new ArrayList<>();
@Var private String replacementOutputLines = "";
CollectBugPatternTests(VisitorState state) {
this.state = state;
}
public ImmutableList<String> getIdentificationTests() {
return ImmutableList.copyOf(identificationTests);
}
public ImmutableList<BugPatternReplacementTestDocumentation> getReplacementTests() {
return ImmutableList.copyOf(replacementTests);
}
@Override
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, @Nullable Void unused) {
if (IDENTIFICATION_SOURCE_LINES.matches(node, state)) {
identificationTests.add(getSourceLines(node));
} else if (REPLACEMENT_INPUT.matches(node, state)) {
/* The visitor starts with `addOutputLines` and in the next visit it will go over the `addInputLines`. */
replacementTests.add(
BugPatternReplacementTestDocumentation.create(
getSourceLines(node), replacementOutputLines));
} else if (REPLACEMENT_OUTPUT.matches(node, state)) {
replacementOutputLines = getSourceLines(node);
}
return super.visitMethodInvocation(node, unused);
}
// XXX: Duplicate from `ErrorProneTestSourceFormat`, should we move this to `SourceCode` util?
private static String getSourceLines(MethodInvocationTree tree) {
List<? extends ExpressionTree> sourceLines =
tree.getArguments().subList(1, tree.getArguments().size());
StringBuilder source = new StringBuilder();
for (ExpressionTree sourceLine : sourceLines) {
Object value = ASTHelpers.constValue(sourceLine);
if (value == null) {
return "";
}
source.append(value).append('\n');
}
return source.toString();
}
}
@AutoValue
abstract static class BugPatternTestDocumentation {
abstract String name();
abstract ImmutableList<String> identificationTests();
abstract ImmutableList<BugPatternReplacementTestDocumentation> replacementTests();
}
@AutoValue
abstract static class BugPatternReplacementTestDocumentation {
static BugPatternReplacementTestDocumentation create(String sourceLines, String outputLines) {
return new AutoValue_BugPatternTestExtractor_BugPatternReplacementTestDocumentation(
sourceLines, outputLines);
}
abstract String inputLines();
abstract String outputLines();
}
}

View File

@@ -0,0 +1,56 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.base.Preconditions.checkArgument;
import com.google.auto.service.AutoService;
import com.google.common.annotations.VisibleForTesting;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.tools.javac.api.BasicJavacTask;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A compiler {@link Plugin} that analyzes and extracts relevant information for documentation
* purposes from processed files.
*/
// XXX: Find a better name for this class; it doesn't generate documentation per se.
@AutoService(Plugin.class)
public final class DocumentationGenerator implements Plugin {
@VisibleForTesting static final String OUTPUT_DIRECTORY_FLAG = "-XoutputDirectory";
private static final Pattern OUTPUT_DIRECTORY_FLAG_PATTERN =
Pattern.compile(Pattern.quote(OUTPUT_DIRECTORY_FLAG) + "=(.*)");
/** Instantiates a new {@link DocumentationGenerator} instance. */
public DocumentationGenerator() {}
@Override
public String getName() {
return getClass().getSimpleName();
}
@Override
public void init(JavacTask javacTask, String... args) {
checkArgument(args.length == 1, "Precisely one path must be provided");
javacTask.addTaskListener(
new DocumentationGeneratorTaskListener(
((BasicJavacTask) javacTask).getContext(), getOutputPath(args[0])));
}
@VisibleForTesting
static Path getOutputPath(String pathArg) {
Matcher matcher = OUTPUT_DIRECTORY_FLAG_PATTERN.matcher(pathArg);
checkArgument(
matcher.matches(), "'%s' must be of the form '%s=<value>'", pathArg, OUTPUT_DIRECTORY_FLAG);
String path = matcher.group(1);
try {
return Path.of(path);
} catch (InvalidPathException e) {
throw new IllegalArgumentException(String.format("Invalid path '%s'", path), e);
}
}
}

View File

@@ -0,0 +1,92 @@
package tech.picnic.errorprone.documentation;
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.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.source.util.TaskListener;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.util.Context;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.tools.JavaFileObject;
/**
* A {@link TaskListener} that identifies and extracts relevant content for documentation generation
* and writes it to disk.
*/
// XXX: Find a better name for this class; it doesn't generate documentation per se.
final class DocumentationGeneratorTaskListener implements TaskListener {
private static final ObjectMapper OBJECT_MAPPER =
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
private final Context context;
private final Path docsPath;
DocumentationGeneratorTaskListener(Context context, Path path) {
this.context = context;
this.docsPath = path;
}
@Override
public void started(TaskEvent taskEvent) {
if (taskEvent.getKind() == Kind.ANALYZE) {
createDocsDirectory();
}
}
@Override
public void finished(TaskEvent taskEvent) {
if (taskEvent.getKind() != Kind.ANALYZE) {
return;
}
ClassTree classTree = JavacTrees.instance(context).getTree(taskEvent.getTypeElement());
JavaFileObject sourceFile = taskEvent.getSourceFile();
if (classTree == null || sourceFile == null) {
return;
}
ExtractorType.findMatchingType(classTree, VisitorState.createForUtilityPurposes(context))
.ifPresent(
extractorType ->
writeToFile(
extractorType.getIdentifier(),
getSimpleClassName(sourceFile.toUri()),
extractorType.getExtractor().extract(classTree, context, taskEvent)));
}
private void createDocsDirectory() {
try {
Files.createDirectories(docsPath);
} catch (IOException e) {
throw new IllegalStateException(
String.format("Error while creating directory with path '%s'", docsPath), e);
}
}
private <T> void writeToFile(String identifier, String className, T data) {
File file = docsPath.resolve(String.format("%s-%s.json", identifier, className)).toFile();
try (FileWriter fileWriter = new FileWriter(file, UTF_8)) {
OBJECT_MAPPER.writeValue(fileWriter, data);
} catch (IOException e) {
throw new UncheckedIOException(String.format("Cannot write to file '%s'", file.getPath()), e);
}
}
private static String getSimpleClassName(URI path) {
return Paths.get(path).getFileName().toString().replace(".java", "");
}
}

View File

@@ -0,0 +1,38 @@
package tech.picnic.errorprone.documentation;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Immutable;
import com.sun.source.tree.ClassTree;
import com.sun.source.util.TaskEvent;
import com.sun.tools.javac.util.Context;
/**
* Interface implemented by classes that define how to extract data of some type {@link T} from a
* given {@link ClassTree}.
*
* @param <T> The type of data that is extracted.
*/
@Immutable
interface Extractor<T> {
/**
* Extracts and returns an instance of {@link T} using the provided arguments.
*
* @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.
* @param taskEvent The {@link TaskEvent} in which the current compilation takes place.
* @return A non-null instance of {@link T}.
*/
// XXX: Drop `Context` parameter unless used.
T extract(ClassTree tree, Context context, TaskEvent taskEvent);
/**
* Tells whether this {@link Extractor} can extract documentation content from the given {@link
* ClassTree}.
*
* @param tree The {@link ClassTree} of interest.
* @param state A {@link VisitorState} describes the context in which the given {@link ClassTree}
* is found.
* @return {@code true} iff data extraction is supported.
*/
boolean canExtract(ClassTree tree, VisitorState state);
}

View File

@@ -0,0 +1,40 @@
package tech.picnic.errorprone.documentation;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.errorprone.VisitorState;
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()),
BUG_PATTERN_TEST("bugpattern-test", new BugPatternTestExtractor()),
// REFASTER("refaster", new RefasterExtractor()),
REFASTER_TEMPLATE_TEST_INPUT("refaster-test-input", new RefasterTestExtractor()),
REFASTER_TEMPLATE_TEST_OUTPUT("refaster-test-output", new RefasterTestExtractor());
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, VisitorState state) {
return TYPES.stream().filter(type -> type.getExtractor().canExtract(tree, state)).findFirst();
}
}

View File

@@ -0,0 +1,66 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.VisitorState;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.util.Context;
import java.util.List;
public final class RefasterTestExtractor
implements Extractor<RefasterTestExtractor.RefasterTemplateCollectionTestData> {
@Override
public RefasterTemplateCollectionTestData extract(
ClassTree tree, Context context, TaskEvent taskEvent) {
String templateCollectionName = tree.getSimpleName().toString().replace("Test", "");
boolean isInput = taskEvent.getSourceFile().getName().contains("Input");
VisitorState stateWithPath =
VisitorState.createForUtilityPurposes(context)
.withPath(TreePath.getPath(taskEvent.getCompilationUnit(), tree));
ImmutableList<RefasterTemplateTestData> templateTests =
tree.getMembers().stream()
.filter(MethodTree.class::isInstance)
.map(MethodTree.class::cast)
.filter(m -> m.getName().toString().startsWith("test"))
.map(
m -> {
String src = stateWithPath.getSourceForNode(tree);
return new AutoValue_RefasterTestExtractor_RefasterTemplateTestData(
m.getName().toString().replace("test", ""),
src != null ? src : tree.toString());
})
.collect(toImmutableList());
return new AutoValue_RefasterTestExtractor_RefasterTemplateCollectionTestData(
templateCollectionName, isInput, templateTests);
}
@Override
public boolean canExtract(ClassTree tree, VisitorState state) {
String className = tree.getSimpleName().toString();
return className.endsWith("TestInput") || className.endsWith("TestOutput");
}
@AutoValue
abstract static class RefasterTemplateTestData {
abstract String templateName();
abstract String templateTestContent();
}
@AutoValue
abstract static class RefasterTemplateCollectionTestData {
abstract String templateCollection();
abstract boolean isInput();
abstract List<RefasterTemplateTestData> templateTests();
}
}

View File

@@ -0,0 +1,7 @@
/**
* A Java compiler plugin that extracts data from compiled classes, in support of the Error Prone
* Support documentation.
*/
@com.google.errorprone.annotations.CheckReturnValue
@org.jspecify.annotations.NullMarked
package tech.picnic.errorprone.documentation;

View File

@@ -0,0 +1,153 @@
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;
import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.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;
final class BugPatternExtractorTest {
@Test
void noBugPattern(@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 minimalBugPattern(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"MinimalBugChecker.java",
"package pkg;",
"",
"import com.google.errorprone.BugPattern;",
"import com.google.errorprone.BugPattern.SeverityLevel;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"@BugPattern(summary = \"MinimalBugChecker summary\", severity = SeverityLevel.ERROR)",
"public final class MinimalBugChecker extends BugChecker {}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-MinimalBugChecker.json",
"bugpattern-documentation-minimal.json");
}
@Test
void completeBugPattern(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"CompleteBugChecker.java",
"package pkg;",
"",
"import com.google.errorprone.BugPattern;",
"import com.google.errorprone.BugPattern.SeverityLevel;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"import org.junit.jupiter.api.Test;",
"",
"@BugPattern(",
" name = \"OtherName\",",
" summary = \"CompleteBugChecker summary\",",
" linkType = BugPattern.LinkType.CUSTOM,",
" link = \"https://error-prone.picnic.tech\",",
" explanation = \"Example explanation\",",
" severity = SeverityLevel.SUGGESTION,",
" altNames = \"Check\",",
" tags = BugPattern.StandardTags.SIMPLIFICATION,",
" disableable = false,",
" suppressionAnnotations = {BugPattern.class, Test.class})",
"public final class CompleteBugChecker extends BugChecker {}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-CompleteBugChecker.json",
"bugpattern-documentation-complete.json");
}
@Test
void undocumentedSuppressionBugPattern(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"UndocumentedSuppressionBugPattern.java",
"package pkg;",
"",
"import com.google.errorprone.BugPattern;",
"import com.google.errorprone.BugPattern.SeverityLevel;",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"@BugPattern(",
" summary = \"UndocumentedSuppressionBugPattern summary\",",
" severity = SeverityLevel.WARNING,",
" documentSuppression = false)",
"public final class UndocumentedSuppressionBugPattern extends BugChecker {}");
verifyFileMatchesResource(
outputDirectory,
"bugpattern-UndocumentedSuppressionBugPattern.json",
"bugpattern-documentation-undocumented-suppression.json");
}
@Test
void bugPatternAnnotationIsAbsent() {
CompilationTestHelper.newInstance(TestChecker.class, getClass())
.addSourceLines(
"TestChecker.java",
"import com.google.errorprone.bugpatterns.BugChecker;",
"",
"// BUG: Diagnostic contains: Can extract: false",
"public final class TestChecker extends BugChecker {}")
.doTest();
}
private static void verifyFileMatchesResource(
Path outputDirectory, String fileName, String resourceName) throws IOException {
assertThat(Files.readString(outputDirectory.resolve(fileName)))
.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(BugPatternExtractorTest.class, resourceName), UTF_8);
}
/** A {@link BugChecker} that validates the {@link BugPatternExtractor}. */
@BugPattern(summary = "Validates `BugPatternExtractor` extraction", severity = ERROR)
public static final class TestChecker extends BugChecker implements ClassTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchClass(ClassTree tree, VisitorState state) {
BugPatternExtractor extractor = new BugPatternExtractor();
assertThatThrownBy(() -> extractor.extract(tree, state.context, null))
.isInstanceOf(NullPointerException.class)
.hasMessage("BugPattern annotation must be present");
return buildDescription(tree)
.setMessage(String.format("Can extract: %s", extractor.canExtract(tree, state)))
.build();
}
}
}

View File

@@ -0,0 +1,337 @@
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.Files;
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 minimalBugPatternTest(@TempDir Path outputDirectory) throws IOException {
Compilation.compileWithDocumentationGenerator(
outputDirectory,
"TestCheckerTest.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
"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.BugCheckerRefactoringTestHelper;",
"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(Files.readString(outputDirectory.resolve(fileName)))
.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

@@ -0,0 +1,45 @@
package tech.picnic.errorprone.documentation;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.FileManagers;
import com.google.errorprone.FileObjects;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.file.JavacFileManager;
import java.nio.file.Path;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
// XXX: Generalize and move this class so that it can also be used by `refaster-compiler`.
// XXX: Add support for this class to the `ErrorProneTestHelperSourceFormat` check.
public final class Compilation {
private Compilation() {}
public static void compileWithDocumentationGenerator(
Path outputDirectory, String fileName, String... lines) {
compileWithDocumentationGenerator(outputDirectory.toAbsolutePath().toString(), fileName, lines);
}
public static void compileWithDocumentationGenerator(
String outputDirectory, String fileName, String... lines) {
compile(
ImmutableList.of("-Xplugin:DocumentationGenerator -XoutputDirectory=" + outputDirectory),
FileObjects.forSourceLines(fileName, lines));
}
private static void compile(ImmutableList<String> options, JavaFileObject javaFileObject) {
JavacFileManager javacFileManager = FileManagers.testFileManager();
JavaCompiler compiler = JavacTool.create();
JavacTaskImpl task =
(JavacTaskImpl)
compiler.getTask(
null,
javacFileManager,
null,
options,
ImmutableList.of(),
ImmutableList.of(javaFileObject));
task.call();
}
}

View File

@@ -0,0 +1,77 @@
package tech.picnic.errorprone.documentation;
import static com.google.common.collect.ImmutableList.toImmutableList;
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.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
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 org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.EnabledOnOs;
import org.junit.jupiter.api.io.TempDir;
final class DocumentationGeneratorTaskListenerTest {
@EnabledOnOs(WINDOWS)
@Test
void readOnlyFileSystemWindows(@TempDir Path outputDirectory) throws IOException {
AclFileAttributeView view =
Files.getFileAttributeView(outputDirectory, AclFileAttributeView.class);
view.setAcl(
view.getAcl().stream()
.map(
entry ->
AclEntry.newBuilder(entry)
.setPermissions(
Sets.difference(entry.permissions(), ImmutableSet.of(ADD_SUBDIRECTORY)))
.build())
.collect(toImmutableList()));
readOnlyFileSystemFailsToWrite(outputDirectory.resolve("nonexistent"));
}
@DisabledOnOs(WINDOWS)
@Test
void readOnlyFileSystemNonWindows(@TempDir Path outputDirectory) {
assertThat(outputDirectory.toFile().setWritable(false))
.describedAs("Failed to make test directory unwritable")
.isTrue();
readOnlyFileSystemFailsToWrite(outputDirectory.resolve("nonexistent"));
}
private static void readOnlyFileSystemFailsToWrite(Path outputDirectory) {
assertThatThrownBy(
() ->
Compilation.compileWithDocumentationGenerator(
outputDirectory, "A.java", "class A {}"))
.hasRootCauseInstanceOf(FileSystemException.class)
.hasCauseInstanceOf(IllegalStateException.class)
.hasMessageEndingWith("Error while creating directory with path '%s'", outputDirectory);
}
@Test
void noClassNoOutput(@TempDir Path outputDirectory) {
Compilation.compileWithDocumentationGenerator(outputDirectory, "A.java", "package pkg;");
assertThat(outputDirectory).isEmptyDirectory();
}
@Test
void excessArguments(@TempDir Path outputDirectory) {
assertThatThrownBy(
() ->
Compilation.compileWithDocumentationGenerator(
outputDirectory.toAbsolutePath() + " extra-arg", "A.java", "package pkg;"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Precisely one path must be provided");
}
}

View File

@@ -0,0 +1,38 @@
package tech.picnic.errorprone.documentation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static tech.picnic.errorprone.documentation.DocumentationGenerator.OUTPUT_DIRECTORY_FLAG;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
final class DocumentationGeneratorTest {
@ParameterizedTest
@ValueSource(strings = {"bar", "foo"})
void getOutputPath(String path) {
assertThat(DocumentationGenerator.getOutputPath(OUTPUT_DIRECTORY_FLAG + '=' + path))
.isEqualTo(Path.of(path));
}
@ParameterizedTest
@ValueSource(strings = {"", "-XoutputDirectory", "invalidOption=Test", "nothing"})
void getOutputPathWithInvalidArgument(String pathArg) {
assertThatThrownBy(() -> DocumentationGenerator.getOutputPath(pathArg))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("'%s' must be of the form '%s=<value>'", pathArg, OUTPUT_DIRECTORY_FLAG);
}
@Test
void getOutputPathWithInvalidPath() {
String basePath = "path-with-null-char-\0";
assertThatThrownBy(
() -> DocumentationGenerator.getOutputPath(OUTPUT_DIRECTORY_FLAG + '=' + basePath))
.isInstanceOf(IllegalArgumentException.class)
.hasCauseInstanceOf(InvalidPathException.class)
.hasMessageEndingWith("Invalid path '%s'", basePath);
}
}

View File

@@ -0,0 +1,19 @@
{
"fullyQualifiedName": "pkg.CompleteBugChecker",
"name": "OtherName",
"altNames": [
"Check"
],
"link": "https://error-prone.picnic.tech",
"tags": [
"Simplification"
],
"summary": "CompleteBugChecker summary",
"explanation": "Example explanation",
"severityLevel": "SUGGESTION",
"canDisable": false,
"suppressionAnnotations": [
"com.google.errorprone.BugPattern",
"org.junit.jupiter.api.Test"
]
}

View File

@@ -0,0 +1,14 @@
{
"fullyQualifiedName": "pkg.MinimalBugChecker",
"name": "MinimalBugChecker",
"altNames": [],
"link": "",
"tags": [],
"summary": "MinimalBugChecker summary",
"explanation": "",
"severityLevel": "ERROR",
"canDisable": true,
"suppressionAnnotations": [
"java.lang.SuppressWarnings"
]
}

View File

@@ -0,0 +1,12 @@
{
"fullyQualifiedName": "pkg.UndocumentedSuppressionBugPattern",
"name": "UndocumentedSuppressionBugPattern",
"altNames": [],
"link": "",
"tags": [],
"summary": "UndocumentedSuppressionBugPattern summary",
"explanation": "",
"severityLevel": "WARNING",
"canDisable": true,
"suppressionAnnotations": []
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,17 @@
{
"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

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

View File

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

View File

@@ -0,0 +1,10 @@
{
"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.7.0</version>
<version>0.8.1-SNAPSHOT</version>
</parent>
<artifactId>error-prone-contrib</artifactId>
@@ -39,6 +39,14 @@
<artifactId>error_prone_test_helpers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>documentation-support</artifactId>
<!-- This dependency is declared only as a hint to Maven that
compilation depends on it; see the `maven-compiler-plugin`'s
`annotationProcessorPaths` configuration below. -->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-support</artifactId>
@@ -213,6 +221,11 @@
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths combine.children="append">
<path>
<groupId>${project.groupId}</groupId>
<artifactId>documentation-support</artifactId>
<version>${project.version}</version>
</path>
<path>
<groupId>${project.groupId}</groupId>
<artifactId>refaster-compiler</artifactId>
@@ -226,6 +239,7 @@
</annotationProcessorPaths>
<compilerArgs combine.children="append">
<arg>-Xplugin:RefasterRuleCompiler</arg>
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
</compilerArgs>
</configuration>
</plugin>

View File

@@ -9,7 +9,6 @@ import static com.google.errorprone.matchers.Matchers.hasModifier;
import static com.google.errorprone.matchers.Matchers.not;
import static java.util.function.Predicate.not;
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isValidIdentifier;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.SETUP_OR_TEARDOWN_METHOD;
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD;
@@ -25,15 +24,11 @@ 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.ImportTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import java.util.Optional;
import javax.lang.model.element.Modifier;
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
import tech.picnic.errorprone.bugpatterns.util.ConflictDetection;
/** A {@link BugChecker} that flags non-canonical JUnit method declarations. */
// XXX: Consider introducing a class-level check that enforces that test classes:
@@ -90,7 +85,7 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
tryCanonicalizeMethodName(symbol)
.ifPresent(
newName ->
findMethodRenameBlocker(symbol, newName, state)
ConflictDetection.findMethodRenameBlocker(symbol, newName, state)
.ifPresentOrElse(
blocker -> reportMethodRenameBlocker(tree, blocker, state),
() -> fixBuilder.merge(SuggestedFixes.renameMethod(tree, newName, state))));
@@ -106,61 +101,7 @@ public final class JUnitMethodDeclaration extends BugChecker implements MethodTr
.build());
}
/**
* If applicable, returns a human-readable argument against assigning the given name to an
* existing method.
*
* <p>This method implements imperfect heuristics. Things it currently does not consider include
* the following:
*
* <ul>
* <li>Whether the rename would merely introduce a method overload, rather than clashing with an
* existing method declaration.
* <li>Whether the rename would cause a method in a superclass to be overridden.
* <li>Whether the rename would in fact clash with a static import. (It could be that a static
* import of the same name is only referenced from lexical scopes in which the method under
* consideration cannot be referenced directly.)
* </ul>
*/
private static Optional<String> findMethodRenameBlocker(
MethodSymbol method, String newName, VisitorState state) {
if (isExistingMethodName(method.owner.type, newName, state)) {
return Optional.of(
String.format(
"a method named `%s` is already defined in this class or a supertype", newName));
}
if (isSimpleNameStaticallyImported(newName, state)) {
return Optional.of(String.format("`%s` is already statically imported", newName));
}
if (!isValidIdentifier(newName)) {
return Optional.of(String.format("`%s` is not a valid identifier", newName));
}
return Optional.empty();
}
private static boolean isExistingMethodName(Type clazz, String name, VisitorState state) {
return ASTHelpers.matchingMethods(state.getName(name), method -> true, clazz, state.getTypes())
.findAny()
.isPresent();
}
private static boolean isSimpleNameStaticallyImported(String simpleName, VisitorState state) {
return state.getPath().getCompilationUnit().getImports().stream()
.filter(ImportTree::isStatic)
.map(ImportTree::getQualifiedIdentifier)
.map(tree -> getStaticImportSimpleName(tree, state))
.anyMatch(simpleName::contentEquals);
}
private static CharSequence getStaticImportSimpleName(Tree tree, VisitorState state) {
String source = SourceCode.treeToString(tree, state);
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
private static Optional<String> tryCanonicalizeMethodName(Symbol symbol) {
private static Optional<String> tryCanonicalizeMethodName(MethodSymbol symbol) {
return Optional.of(symbol.getQualifiedName().toString())
.filter(name -> name.startsWith(TEST_PREFIX))
.map(name -> name.substring(TEST_PREFIX.length()))

View File

@@ -0,0 +1,75 @@
package tech.picnic.errorprone.bugpatterns.util;
import static tech.picnic.errorprone.bugpatterns.util.JavaKeywords.isValidIdentifier;
import com.google.errorprone.VisitorState;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Type;
import java.util.Optional;
/** A set of helper methods for detecting conflicts that would be caused when applying fixes. */
public final class ConflictDetection {
private ConflictDetection() {}
/**
* If applicable, returns a human-readable argument against assigning the given name to an
* existing method.
*
* <p>This method implements imperfect heuristics. Things it currently does not consider include
* the following:
*
* <ul>
* <li>Whether the rename would merely introduce a method overload, rather than clashing with an
* existing method declaration in its class or a supertype.
* <li>Whether the rename would in fact clash with a static import. (It could be that a static
* import of the same name is only referenced from lexical scopes in which the method under
* consideration cannot be referenced directly.)
* </ul>
*
* @param method The method considered for renaming.
* @param newName The newly proposed name for the method.
* @param state The {@link VisitorState} to use when searching for blockers.
* @return A human-readable argument against assigning the proposed name to the given method, or
* {@link Optional#empty()} if no blocker was found.
*/
public static Optional<String> findMethodRenameBlocker(
MethodSymbol method, String newName, VisitorState state) {
if (isExistingMethodName(method.owner.type, newName, state)) {
return Optional.of(
String.format(
"a method named `%s` is already defined in this class or a supertype", newName));
}
if (isSimpleNameStaticallyImported(newName, state)) {
return Optional.of(String.format("`%s` is already statically imported", newName));
}
if (!isValidIdentifier(newName)) {
return Optional.of(String.format("`%s` is not a valid identifier", newName));
}
return Optional.empty();
}
private static boolean isExistingMethodName(Type clazz, String name, VisitorState state) {
return ASTHelpers.matchingMethods(state.getName(name), method -> true, clazz, state.getTypes())
.findAny()
.isPresent();
}
private static boolean isSimpleNameStaticallyImported(String simpleName, VisitorState state) {
return state.getPath().getCompilationUnit().getImports().stream()
.filter(ImportTree::isStatic)
.map(ImportTree::getQualifiedIdentifier)
.map(tree -> getStaticImportSimpleName(tree, state))
.anyMatch(simpleName::contentEquals);
}
private static CharSequence getStaticImportSimpleName(Tree tree, VisitorState state) {
String source = SourceCode.treeToString(tree, state);
return source.subSequence(source.lastIndexOf('.') + 1, source.length());
}
}

View File

@@ -1,5 +1,7 @@
package tech.picnic.errorprone.refasterrules;
import static java.util.Objects.requireNonNullElse;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
@@ -43,6 +45,21 @@ final class MapRules {
}
}
/** Prefer {@link Map#getOrDefault(Object, Object)} over more contrived alternatives. */
// XXX: Note that `requireNonNullElse` throws an NPE if the second argument is `null`, while the
// alternative does not.
static final class MapGetOrDefault<K, V, T> {
@BeforeTemplate
V before(Map<K, V> map, T key, V defaultValue) {
return requireNonNullElse(map.get(key), defaultValue);
}
@AfterTemplate
V after(Map<K, V> map, T key, V defaultValue) {
return map.getOrDefault(key, defaultValue);
}
}
/** Prefer {@link Map#isEmpty()} over more contrived alternatives. */
static final class MapIsEmpty<K, V> {
@BeforeTemplate

View File

@@ -369,7 +369,8 @@ final class ReactorRules {
static final class MonoIdentity<T> {
@BeforeTemplate
Mono<T> before(Mono<T> mono) {
return mono.switchIfEmpty(Mono.empty());
return Refaster.anyOf(
mono.switchIfEmpty(Mono.empty()), mono.flux().next(), mono.flux().singleOrEmpty());
}
@BeforeTemplate
@@ -473,6 +474,11 @@ final class ReactorRules {
* Flux}.
*/
abstract static class MonoFlatMapToFlux<T, S> {
// XXX: It would be more expressive if this `@Placeholder` were replaced with a `Function<?
// super T, ? extends Mono<? extends S>>` parameter, so that compatible non-lambda expression
// arguments to `flatMapMany` are also matched. However, the type inferred for lambda and method
// reference expressions passed to `flatMapMany` appears to always be `Function<T, Publisher<?
// extends S>>`, which doesn't match. Find a solution.
@Placeholder(allowsIdentity = true)
abstract Mono<S> transformation(@MayOptionallyUse T value);

View File

@@ -3,21 +3,16 @@ package tech.picnic.errorprone.bugpatterns;
import static com.google.common.base.Predicates.containsPattern;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AmbiguousJsonCreatorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
.expectErrorMessage(
"X",
containsPattern("`JsonCreator.Mode` should be set for single-argument creators"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AmbiguousJsonCreator.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
.expectErrorMessage(
"X", containsPattern("`JsonCreator.Mode` should be set for single-argument creators"))
.addSourceLines(
"Container.java",
"import com.fasterxml.jackson.annotation.JsonCreator;",
@@ -118,7 +113,7 @@ final class AmbiguousJsonCreatorTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(AmbiguousJsonCreator.class, getClass())
.addInputLines(
"A.java",
"import com.fasterxml.jackson.annotation.JsonCreator;",
@@ -143,6 +138,6 @@ final class AmbiguousJsonCreatorTest {
" return FOO;",
" }",
"}")
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -7,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AssertJIsNullTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AssertJIsNull.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AssertJIsNull.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(AssertJIsNull.class, getClass())
.addSourceLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",
@@ -38,7 +33,7 @@ final class AssertJIsNullTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(AssertJIsNull.class, getClass())
.addInputLines(
"A.java",
"import static org.assertj.core.api.Assertions.assertThat;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class AutowiredConstructorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(AutowiredConstructor.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(AutowiredConstructor.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(AutowiredConstructor.class, getClass())
.addSourceLines(
"Container.java",
"import com.google.errorprone.annotations.Immutable;",
@@ -71,7 +66,7 @@ final class AutowiredConstructorTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(AutowiredConstructor.class, getClass())
.addInputLines(
"Container.java",
"import org.springframework.beans.factory.annotation.Autowired;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class CanonicalAnnotationSyntaxTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass())
.addSourceLines(
"pkg/A.java",
"package pkg;",
@@ -133,7 +128,7 @@ final class CanonicalAnnotationSyntaxTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass())
.addInputLines(
"pkg/A.java",
"package pkg;",

View File

@@ -8,14 +8,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class CollectorMutabilityTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(CollectorMutability.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(CollectorMutability.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(CollectorMutability.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
@@ -67,7 +62,7 @@ final class CollectorMutabilityTest {
@Test
void identificationWithoutGuavaOnClasspath() {
compilationTestHelper
CompilationTestHelper.newInstance(CollectorMutability.class, getClass())
.withClasspath()
.addSourceLines(
"A.java",
@@ -84,7 +79,7 @@ final class CollectorMutabilityTest {
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(CollectorMutability.class, getClass())
.addInputLines(
"A.java",
"import static java.util.stream.Collectors.toList;",
@@ -141,7 +136,7 @@ final class CollectorMutabilityTest {
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(CollectorMutability.class, getClass())
.setFixChooser(SECOND)
.addInputLines(
"A.java",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class EmptyMethodTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(EmptyMethod.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(EmptyMethod.class, getClass())
.addSourceLines(
"A.java",
"class A {",
@@ -69,7 +64,7 @@ final class EmptyMethodTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(EmptyMethod.class, getClass())
.addInputLines(
"A.java",
"final class A {",

View File

@@ -6,15 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ErrorProneTestHelperSourceFormatTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
ErrorProneTestHelperSourceFormat.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",
@@ -63,7 +57,7 @@ final class ErrorProneTestHelperSourceFormatTest {
* Verifies that import sorting and code formatting is performed unconditionally, while unused
* imports are removed unless part of a `BugCheckerRefactoringTestHelper` expected output file.
*/
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(ErrorProneTestHelperSourceFormat.class, getClass())
.addInputLines(
"A.java",
"import com.google.errorprone.BugCheckerRefactoringTestHelper;",

View File

@@ -4,12 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ExplicitEnumOrderingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ExplicitEnumOrdering.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(ExplicitEnumOrdering.class, getClass())
.addSourceLines(
"A.java",
"import static java.lang.annotation.RetentionPolicy.CLASS;",

View File

@@ -1,7 +1,5 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.newInstance;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
@@ -9,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class FluxFlatMapUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(FluxFlatMapUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
newInstance(FluxFlatMapUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(FluxFlatMapUsage.class, getClass())
.addSourceLines(
"A.java",
"import java.util.function.BiFunction;",
@@ -73,7 +66,7 @@ final class FluxFlatMapUsageTest {
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(FluxFlatMapUsage.class, getClass())
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
@@ -108,7 +101,7 @@ final class FluxFlatMapUsageTest {
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(FluxFlatMapUsage.class, getClass())
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"A.java",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class FormatStringConcatenationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(FormatStringConcatenation.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(FormatStringConcatenation.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(FormatStringConcatenation.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.base.Preconditions.checkArgument;",
@@ -309,7 +304,7 @@ final class FormatStringConcatenationTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(FormatStringConcatenation.class, getClass())
.addInputLines(
"A.java",
"import static com.google.common.base.Preconditions.checkArgument;",

View File

@@ -7,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class IdentityConversionTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(IdentityConversion.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(IdentityConversion.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.errorprone.matchers.Matchers.instanceMethod;",
@@ -184,7 +179,7 @@ final class IdentityConversionTest {
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
@@ -291,7 +286,7 @@ final class IdentityConversionTest {
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(IdentityConversion.class, getClass())
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"A.java",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class ImmutablesSortedSetComparatorTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass())
.addSourceLines(
"A.java",
"import com.google.common.collect.ContiguousSet;",
@@ -109,7 +104,7 @@ final class ImmutablesSortedSetComparatorTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass())
.addInputLines(
"A.java",
"import com.google.common.collect.ImmutableSortedSet;",
@@ -147,7 +142,7 @@ final class ImmutablesSortedSetComparatorTest {
@Test
void replacementWithImportClash() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(ImmutablesSortedSetComparator.class, getClass())
.addInputLines(
"MySpringService.java",
"import com.google.common.collect.ImmutableSortedSet;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class IsInstanceLambdaUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass())
.addSourceLines(
"A.java",
"import java.util.stream.Stream;",
@@ -46,7 +41,7 @@ final class IsInstanceLambdaUsageTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(IsInstanceLambdaUsage.class, getClass())
.addInputLines(
"A.java",
"import java.util.stream.Stream;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class JUnitClassModifiersTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(JUnitClassModifiers.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(JUnitClassModifiers.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(JUnitClassModifiers.class, getClass())
.addSourceLines(
"Container.java",
"import org.junit.jupiter.api.Test;",
@@ -98,7 +93,7 @@ final class JUnitClassModifiersTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(JUnitClassModifiers.class, getClass())
.addInputLines(
"A.java",
"import org.junit.jupiter.api.Test;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class JUnitMethodDeclarationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(JUnitMethodDeclaration.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(JUnitMethodDeclaration.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(JUnitMethodDeclaration.class, getClass())
.addSourceLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",
@@ -345,7 +340,7 @@ final class JUnitMethodDeclarationTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(JUnitMethodDeclaration.class, getClass())
.addInputLines(
"A.java",
"import static org.junit.jupiter.params.provider.Arguments.arguments;",

View File

@@ -7,22 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class LexicographicalAnnotationAttributeListingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(
LexicographicalAnnotationAttributeListing.class, getClass());
private final CompilationTestHelper restrictedCompilationTestHelper =
CompilationTestHelper.newInstance(LexicographicalAnnotationAttributeListing.class, getClass())
.setArgs(
ImmutableList.of(
"-XepOpt:LexicographicalAnnotationAttributeListing:Includes=pkg.A.Foo,pkg.A.Bar",
"-XepOpt:LexicographicalAnnotationAttributeListing:Excludes=pkg.A.Bar#value"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationAttributeListing.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(LexicographicalAnnotationAttributeListing.class, getClass())
.addSourceLines(
"A.java",
"import static java.math.RoundingMode.DOWN;",
@@ -165,7 +152,8 @@ final class LexicographicalAnnotationAttributeListingTest {
// `CanonicalAnnotationSyntax` checker correct the situation in a subsequent run.
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationAttributeListing.class, getClass())
.addInputLines(
"A.java",
"import static java.math.RoundingMode.DOWN;",
@@ -246,7 +234,11 @@ final class LexicographicalAnnotationAttributeListingTest {
@Test
void filtering() {
/* Some violations are not flagged because they are not in- or excluded. */
restrictedCompilationTestHelper
CompilationTestHelper.newInstance(LexicographicalAnnotationAttributeListing.class, getClass())
.setArgs(
ImmutableList.of(
"-XepOpt:LexicographicalAnnotationAttributeListing:Includes=pkg.A.Foo,pkg.A.Bar",
"-XepOpt:LexicographicalAnnotationAttributeListing:Excludes=pkg.A.Bar#value"))
.addSourceLines(
"pkg/A.java",
"package pkg;",

View File

@@ -6,15 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class LexicographicalAnnotationListingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(
LexicographicalAnnotationListing.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass())
.addSourceLines(
"A.java",
"import java.lang.annotation.ElementType;",
@@ -133,7 +127,7 @@ final class LexicographicalAnnotationListingTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(LexicographicalAnnotationListing.class, getClass())
.addInputLines(
"A.java",
"import java.lang.annotation.ElementType;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MethodReferenceUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MethodReferenceUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(MethodReferenceUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(MethodReferenceUsage.class, getClass())
.addSourceLines(
"A.java",
"import com.google.common.collect.Streams;",
@@ -323,7 +318,7 @@ final class MethodReferenceUsageTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(MethodReferenceUsage.class, getClass())
.addInputLines(
"A.java",
"import static java.util.Collections.emptyList;",

View File

@@ -6,16 +6,12 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MissingRefasterAnnotationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MissingRefasterAnnotation.class, getClass())
.expectErrorMessage(
"X",
containsPattern(
"The Refaster rule contains a method without any Refaster annotations"));
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(MissingRefasterAnnotation.class, getClass())
.expectErrorMessage(
"X",
containsPattern("The Refaster rule contains a method without any Refaster annotations"))
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.annotation.AfterTemplate;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class MockitoStubbingTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MockitoStubbing.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(MockitoStubbing.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(MockitoStubbing.class, getClass())
.addSourceLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",
@@ -55,7 +50,7 @@ final class MockitoStubbingTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(MockitoStubbing.class, getClass())
.addInputLines(
"A.java",
"import static org.mockito.ArgumentMatchers.eq;",

View File

@@ -4,12 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class NestedOptionalsTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(NestedOptionals.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(NestedOptionals.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Optional;",
@@ -42,7 +39,7 @@ final class NestedOptionalsTest {
@Test
void identificationOptionalTypeNotLoaded() {
compilationTestHelper
CompilationTestHelper.newInstance(NestedOptionals.class, getClass())
.addSourceLines(
"A.java",
"import java.time.Duration;",

View File

@@ -7,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class NonEmptyMonoTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(NonEmptyMono.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(NonEmptyMono.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(NonEmptyMono.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",
@@ -111,7 +106,7 @@ final class NonEmptyMonoTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(NonEmptyMono.class, getClass())
.addInputLines(
"A.java",
"import static com.google.common.collect.ImmutableList.toImmutableList;",

View File

@@ -5,22 +5,21 @@ import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
// XXX: There are no tests for multiple replacements within the same expression:
// - Error Prone doesn't currently support this, it seems.
// - The `BugCheckerRefactoringTestHelper` throws an exception in this case.
// - During actual compilation only the first replacement is applied.
// XXX: Can we perhaps work-around this by describing the fixes in reverse order?
final class PrimitiveComparisonTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass());
// XXX: There are no tests for multiple replacements within the same expression:
// - Error Prone doesn't currently support this, it seems.
// - The `BugCheckerRefactoringTestHelper` throws an exception in this case.
// - During actual compilation only the first replacement is applied.
// XXX: Can we perhaps work-around this by describing the fixes in reverse order?
// The logic for `char` and `short` is exactly analogous to the `byte` case.
/**
* Tests the identification of {@code byte} comparisons for which boxing can be avoided.
*
* @implNote The logic for {@code char} and {@code short} is exactly analogous to the {@code byte}
* case.
*/
@Test
void byteComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -120,7 +119,7 @@ final class PrimitiveComparisonTest {
@Test
void intComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -227,7 +226,7 @@ final class PrimitiveComparisonTest {
@Test
void longComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -318,7 +317,7 @@ final class PrimitiveComparisonTest {
@Test
void floatComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -386,7 +385,7 @@ final class PrimitiveComparisonTest {
@Test
void doubleComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -461,7 +460,7 @@ final class PrimitiveComparisonTest {
@Test
void stringComparison() {
compilationTestHelper
CompilationTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addSourceLines(
"A.java",
"import java.util.Comparator;",
@@ -497,7 +496,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithPrimitiveVariants() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import java.util.Comparator;",
@@ -577,7 +576,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithBoxedVariants() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import java.util.Comparator;",
@@ -643,7 +642,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithPrimitiveVariantsUsingStaticImports() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import static java.util.Comparator.comparing;",
@@ -682,7 +681,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithBoxedVariantsUsingStaticImports() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import static java.util.Comparator.comparingDouble;",
@@ -723,7 +722,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithPrimitiveVariantsInComplexSyntacticalContext() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import java.util.Comparator;",
@@ -755,7 +754,7 @@ final class PrimitiveComparisonTest {
@Test
void replacementWithBoxedVariantsInComplexSyntacticalContext() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(PrimitiveComparison.class, getClass())
.addInputLines(
"A.java",
"import java.util.Comparator;",

View File

@@ -12,13 +12,6 @@ import org.junit.jupiter.api.Test;
final class RedundantStringConversionTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass());
private final CompilationTestHelper customizedCompilationTestHelper =
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.setArgs(
ImmutableList.of(
"-XepOpt:RedundantStringConversion:ExtraConversionMethods=java.lang.Enum#name(),A#name(),A.B#toString(int)"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(RedundantStringConversion.class, getClass());
@Test
void identificationOfIdentityTransformation() {
@@ -416,7 +409,10 @@ final class RedundantStringConversionTest {
@Test
void identificationOfCustomConversionMethod() {
customizedCompilationTestHelper
CompilationTestHelper.newInstance(RedundantStringConversion.class, getClass())
.setArgs(
ImmutableList.of(
"-XepOpt:RedundantStringConversion:ExtraConversionMethods=java.lang.Enum#name(),A#name(),A.B#toString(int)"))
.addSourceLines(
"A.java",
"import java.math.RoundingMode;",
@@ -509,7 +505,7 @@ final class RedundantStringConversionTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(RedundantStringConversion.class, getClass())
.addInputLines(
"A.java",
"class A {",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RefasterAnyOfUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RefasterAnyOfUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(RefasterAnyOfUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(RefasterAnyOfUsage.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.Refaster;",
@@ -38,7 +33,7 @@ final class RefasterAnyOfUsageTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(RefasterAnyOfUsage.class, getClass())
.addInputLines(
"A.java",
"import com.google.errorprone.refaster.Refaster;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RefasterRuleModifiersTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RefasterRuleModifiers.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(RefasterRuleModifiers.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(RefasterRuleModifiers.class, getClass())
.addSourceLines(
"A.java",
"import com.google.errorprone.refaster.annotation.BeforeTemplate;",
@@ -136,7 +131,7 @@ final class RefasterRuleModifiersTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(RefasterRuleModifiers.class, getClass())
.addInputLines(
"A.java",
"import com.google.errorprone.refaster.annotation.BeforeTemplate;",

View File

@@ -4,12 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RequestMappingAnnotationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RequestMappingAnnotation.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(RequestMappingAnnotation.class, getClass())
.addSourceLines(
"A.java",
"import java.io.InputStream;",

View File

@@ -4,16 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class RequestParamTypeTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(RequestParamType.class, getClass());
private final CompilationTestHelper restrictedCompilationTestHelper =
CompilationTestHelper.newInstance(RequestParamType.class, getClass())
.setArgs(
"-XepOpt:RequestParamType:SupportedCustomTypes=com.google.common.collect.ImmutableSet,com.google.common.collect.ImmutableSortedMultiset");
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(RequestParamType.class, getClass())
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableBiMap;",
@@ -70,7 +63,9 @@ final class RequestParamTypeTest {
@Test
void identificationRestricted() {
restrictedCompilationTestHelper
CompilationTestHelper.newInstance(RequestParamType.class, getClass())
.setArgs(
"-XepOpt:RequestParamType:SupportedCustomTypes=com.google.common.collect.ImmutableSet,com.google.common.collect.ImmutableSortedMultiset")
.addSourceLines(
"A.java",
"import com.google.common.collect.ImmutableBiMap;",

View File

@@ -7,14 +7,9 @@ import org.junit.jupiter.api.Test;
import org.springframework.scheduling.annotation.Scheduled;
final class ScheduledTransactionTraceTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(ScheduledTransactionTrace.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(ScheduledTransactionTrace.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(ScheduledTransactionTrace.class, getClass())
.addSourceLines(
"A.java",
"import com.newrelic.api.agent.Trace;",
@@ -46,7 +41,7 @@ final class ScheduledTransactionTraceTest {
@Test
void identificationWithoutNewRelicAgentApiOnClasspath() {
compilationTestHelper
CompilationTestHelper.newInstance(ScheduledTransactionTrace.class, getClass())
.withClasspath(Scheduled.class)
.addSourceLines(
"A.java",
@@ -61,7 +56,7 @@ final class ScheduledTransactionTraceTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(ScheduledTransactionTrace.class, getClass())
.addInputLines(
"A.java",
"import com.newrelic.api.agent.Trace;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class Slf4jLogStatementTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(Slf4jLogStatement.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(Slf4jLogStatement.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(Slf4jLogStatement.class, getClass())
.addSourceLines(
"A.java",
"import org.slf4j.Logger;",
@@ -96,7 +91,7 @@ final class Slf4jLogStatementTest {
// XXX: Drop what's unused.
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(Slf4jLogStatement.class, getClass())
.addInputLines(
"A.java",
"import org.slf4j.Logger;",

View File

@@ -6,14 +6,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class SpringMvcAnnotationTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(SpringMvcAnnotation.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(SpringMvcAnnotation.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(SpringMvcAnnotation.class, getClass())
.addSourceLines(
"A.java",
"import static org.springframework.web.bind.annotation.RequestMethod.DELETE;",
@@ -85,7 +80,7 @@ final class SpringMvcAnnotationTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(SpringMvcAnnotation.class, getClass())
.addInputLines(
"A.java",
"import static org.springframework.web.bind.annotation.RequestMethod.PATCH;",

View File

@@ -8,11 +8,6 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class StaticImportTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(StaticImport.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(StaticImport.class, getClass());
@Test
void candidateMethodsAreNotRedundant() {
assertThat(StaticImport.STATIC_IMPORT_CANDIDATE_MEMBERS.keySet())
@@ -33,7 +28,7 @@ final class StaticImportTest {
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(StaticImport.class, getClass())
.addSourceLines(
"A.java",
"import static com.google.common.collect.ImmutableMap.toImmutableMap;",
@@ -118,7 +113,7 @@ final class StaticImportTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(StaticImport.class, getClass())
.addInputLines(
"A.java",
"import static java.util.function.Predicate.not;",

View File

@@ -1,7 +1,5 @@
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugCheckerRefactoringTestHelper.newInstance;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
@@ -9,14 +7,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class StringCaseLocaleUsageTest {
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(StringCaseLocaleUsage.class, getClass());
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
newInstance(StringCaseLocaleUsage.class, getClass());
@Test
void identification() {
compilationTestHelper
CompilationTestHelper.newInstance(StringCaseLocaleUsage.class, getClass())
.addSourceLines(
"A.java",
"import static java.util.Locale.ROOT;",
@@ -54,7 +47,7 @@ final class StringCaseLocaleUsageTest {
@Test
void replacementFirstSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(StringCaseLocaleUsage.class, getClass())
.setFixChooser(FixChoosers.FIRST)
.addInputLines(
"A.java",
@@ -93,7 +86,7 @@ final class StringCaseLocaleUsageTest {
@Test
void replacementSecondSuggestedFix() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(StringCaseLocaleUsage.class, getClass())
.setFixChooser(FixChoosers.SECOND)
.addInputLines(
"A.java",

View File

@@ -8,17 +8,12 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class StringJoinTest {
private final CompilationTestHelper compilationHelper =
CompilationTestHelper.newInstance(StringJoin.class, getClass())
.expectErrorMessage(
"valueOf", containsPattern("Prefer `String#valueOf` over `String#format`"))
.expectErrorMessage("join", containsPattern("Prefer `String#join` over `String#format`"));
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
BugCheckerRefactoringTestHelper.newInstance(StringJoin.class, getClass());
@Test
void identification() {
compilationHelper
CompilationTestHelper.newInstance(StringJoin.class, getClass())
.expectErrorMessage(
"valueOf", containsPattern("Prefer `String#valueOf` over `String#format`"))
.expectErrorMessage("join", containsPattern("Prefer `String#join` over `String#format`"))
.addSourceLines(
"A.java",
"import java.util.Formattable;",
@@ -63,7 +58,7 @@ final class StringJoinTest {
@Test
void replacement() {
refactoringTestHelper
BugCheckerRefactoringTestHelper.newInstance(StringJoin.class, getClass())
.addInputLines(
"A.java",
"class A {",

View File

@@ -4,12 +4,9 @@ import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;
final class TimeZoneUsageTest {
private final CompilationTestHelper compilationHelper =
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass());
@Test
void identification() {
compilationHelper
CompilationTestHelper.newInstance(TimeZoneUsage.class, getClass())
.addSourceLines(
"A.java",
"import static java.time.ZoneOffset.UTC;",

View File

@@ -0,0 +1,73 @@
package tech.picnic.errorprone.bugpatterns.util;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
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.MethodTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import org.junit.jupiter.api.Test;
final class ConflictDetectionTest {
@Test
void matcher() {
CompilationTestHelper.newInstance(RenameBlockerFlagger.class, getClass())
.addSourceLines(
"pkg/A.java",
"package pkg;",
"",
"import static pkg.A.B.method3t;",
"",
"import pkg.A.method4t;",
"",
"class A {",
" void method1() {",
" method3t();",
" method4(method4t.class);",
" }",
"",
" // BUG: Diagnostic contains: a method named `method2t` is already defined in this class or a",
" // supertype",
" void method2() {}",
"",
" void method2t() {}",
"",
" // BUG: Diagnostic contains: `method3t` is already statically imported",
" void method3() {}",
"",
" void method4(Object o) {}",
"",
" // BUG: Diagnostic contains: `int` is not a valid identifier",
" void in() {}",
"",
" static class B {",
" static void method3t() {}",
" }",
"",
" class method4t {}",
"}")
.doTest();
}
/**
* A {@link BugChecker} that uses {@link ConflictDetection#findMethodRenameBlocker(MethodSymbol,
* String, VisitorState)} to flag methods of which the name cannot be suffixed with a {@code t}.
*/
@BugPattern(summary = "Interacts with `ConflictDetection` for testing purposes", severity = ERROR)
public static final class RenameBlockerFlagger extends BugChecker implements MethodTreeMatcher {
private static final long serialVersionUID = 1L;
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
return ConflictDetection.findMethodRenameBlocker(
ASTHelpers.getSymbol(tree), tree.getName() + "t", state)
.map(blocker -> buildDescription(tree).setMessage(blocker).build())
.orElse(Description.NO_MATCH);
}
}
}

View File

@@ -40,9 +40,6 @@ final class MethodMatcherFactoryTest {
"com.example.A#m2(java.lang.String)",
"com.example.sub.B#m3(int,int)"));
private final CompilationTestHelper compilationTestHelper =
CompilationTestHelper.newInstance(MatchedMethodsFlagger.class, getClass());
@Test
void createWithMalformedSignatures() {
MethodMatcherFactory factory = new MethodMatcherFactory();
@@ -58,7 +55,7 @@ final class MethodMatcherFactoryTest {
@Test
void matcher() {
compilationTestHelper
CompilationTestHelper.newInstance(MatchedMethodsFlagger.class, getClass())
.addSourceLines(
"com/example/A.java",
"package com.example;",

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
import static org.assertj.core.api.Assertions.assertThat;

View File

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

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
import static org.assertj.core.api.Assertions.assertThat;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
import static org.assertj.core.api.Assertions.assertThat;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
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;
package tech.picnic.errorprone.refasterrules.input;
import static com.google.common.collect.ImmutableSet.toImmutableSet;

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.util.Comparator.naturalOrder;

View File

@@ -1,4 +1,4 @@
package tech.picnic.errorprone.refasterrules;
package tech.picnic.errorprone.refasterrules.input;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static java.util.function.Function.identity;

View File

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

View File

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

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