mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
X
This commit is contained in:
@@ -277,6 +277,11 @@
|
||||
<artifactId>refaster-compiler</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-rule-benchmark-generator</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
@@ -285,6 +290,7 @@
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs combine.children="append">
|
||||
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
|
||||
<arg>-Xplugin:RefasterRuleBenchmarkGenerator -XoutputDirectory=${project.build.directory}/generated-test-sources/test-annotations</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
@@ -58,6 +58,12 @@ public class ReactorRulesBenchmarks {
|
||||
Mono<T> after(Optional<T> optional, Mono<T> mono) {
|
||||
return Mono.justOrEmpty(optional).switchIfEmpty(mono);
|
||||
}
|
||||
|
||||
// XXX: Methods such as this one could perhaps be inferred in the common case.
|
||||
@Benchmarked.OnResult
|
||||
T subscribe(Mono<T> mono) {
|
||||
return mono.block();
|
||||
}
|
||||
}
|
||||
|
||||
// XXX: Variations like this would be generated.
|
||||
|
||||
@@ -120,6 +120,7 @@
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs combine.children="append">
|
||||
<arg>-Xplugin:DocumentationGenerator -XoutputDirectory=${project.build.directory}/docs</arg>
|
||||
<!-- XXX: Enable `refaster-rule-benchmark-generator` here. -->
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
1
pom.xml
1
pom.xml
@@ -45,6 +45,7 @@
|
||||
<module>error-prone-guidelines</module>
|
||||
<module>error-prone-utils</module>
|
||||
<module>refaster-compiler</module>
|
||||
<module>refaster-rule-benchmark-generator</module>
|
||||
<module>refaster-runner</module>
|
||||
<module>refaster-support</module>
|
||||
<module>refaster-test-support</module>
|
||||
|
||||
117
refaster-rule-benchmark-generator/pom.xml
Normal file
117
refaster-rule-benchmark-generator/pom.xml
Normal file
@@ -0,0 +1,117 @@
|
||||
<?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.16.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>refaster-rule-benchmark-generator</artifactId>
|
||||
|
||||
<name>Picnic :: Error Prone Support :: Refaster Rule Benchmark Generator</name>
|
||||
<!-- XXX: Review description: can this be an annotation processor? -->
|
||||
<description>Compiler plugin that derives JMH benchmarks from Refaster Rules.</description>
|
||||
<url>https://error-prone.picnic.tech</url>
|
||||
|
||||
<dependencies>
|
||||
<!-- XXX: Prune this list. -->
|
||||
<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_core</artifactId>
|
||||
<!-- XXX: Review scope. -->
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${groupId.error-prone}</groupId>
|
||||
<artifactId>error_prone_test_helpers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
<!-- XXX: Review scope. -->
|
||||
<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.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.module</groupId>
|
||||
<artifactId>jackson-module-parameter-names</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>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<scope>provided</scope>
|
||||
</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>
|
||||
<!-- XXX: Explicitly declared as a workaround for
|
||||
https://github.com/pitest/pitest-junit5-plugin/issues/105. -->
|
||||
<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>
|
||||
@@ -1,4 +1,4 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
package tech.picnic.errorprone.refaster.benchmark;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
@@ -13,16 +13,15 @@ import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** A compiler {@link Plugin} that generates JMH benchmarks for Refaster rules. */
|
||||
// XXX: Move logic to separate package and Maven module.
|
||||
// XXX: Review whether this can be an annotation processor instead.
|
||||
@AutoService(Plugin.class)
|
||||
public final class RefasterBenchmarkGenerator implements Plugin {
|
||||
public final class RefasterRuleBenchmarkGenerator 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 RefasterBenchmarkGenerator} instance. */
|
||||
public RefasterBenchmarkGenerator() {}
|
||||
/** Instantiates a new {@link RefasterRuleBenchmarkGenerator} instance. */
|
||||
public RefasterRuleBenchmarkGenerator() {}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
@@ -34,9 +33,9 @@ public final class RefasterBenchmarkGenerator implements Plugin {
|
||||
checkArgument(args.length == 1, "Precisely one path must be provided");
|
||||
|
||||
// XXX: Drop all path logic: instead generate in the same package as the Refaster rule
|
||||
// collection.
|
||||
// collection. (But how do we then determine the base directory?)
|
||||
javacTask.addTaskListener(
|
||||
new RefasterBenchmarkGeneratorTaskListener(
|
||||
new RefasterRuleBenchmarkGeneratorTaskListener(
|
||||
((BasicJavacTask) javacTask).getContext(), getOutputPath(args[0])));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
package tech.picnic.errorprone.refaster.benchmark;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.matchers.Matchers.anyOf;
|
||||
import static com.google.errorprone.matchers.Matchers.hasAnnotation;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.matchers.Matchers;
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TaskEvent;
|
||||
import com.sun.source.util.TaskEvent.Kind;
|
||||
@@ -14,19 +22,22 @@ import com.sun.source.util.TreePathScanner;
|
||||
import com.sun.tools.javac.api.JavacTrees;
|
||||
import com.sun.tools.javac.util.Context;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import javax.tools.JavaFileObject;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
// XXX: Document.
|
||||
final class RefasterBenchmarkGeneratorTaskListener implements TaskListener {
|
||||
final class RefasterRuleBenchmarkGeneratorTaskListener implements TaskListener {
|
||||
private static final Matcher<Tree> IS_TEMPLATE =
|
||||
anyOf(hasAnnotation(BeforeTemplate.class), hasAnnotation(AfterTemplate.class));
|
||||
private static final Matcher<Tree> IS_BENCHMARKED =
|
||||
Matchers.hasAnnotation("tech.picnic.errorprone.refaster.annotation.Benchmarked");
|
||||
|
||||
private final Context context;
|
||||
private final Path outputPath;
|
||||
|
||||
RefasterBenchmarkGeneratorTaskListener(Context context, Path outputPath) {
|
||||
RefasterRuleBenchmarkGeneratorTaskListener(Context context, Path outputPath) {
|
||||
this.context = context;
|
||||
this.outputPath = outputPath;
|
||||
}
|
||||
@@ -55,18 +66,15 @@ final class RefasterBenchmarkGeneratorTaskListener implements TaskListener {
|
||||
VisitorState.createForUtilityPurposes(context)
|
||||
.withPath(new TreePath(new TreePath(compilationUnit), classTree));
|
||||
|
||||
// XXX: Make static.
|
||||
Matcher<Tree> isBenchmarked =
|
||||
Matchers.hasAnnotation("tech.picnic.errorprone.refaster.annotation.Benchmarked");
|
||||
|
||||
new TreePathScanner<@Nullable Void, Boolean>() {
|
||||
@Override
|
||||
public @Nullable Void visitClass(ClassTree classTree, Boolean doBenchmark) {
|
||||
// XXX: Validate that `@Benchmarked` is only placed in contexts with at least one Refaster
|
||||
// rule.
|
||||
boolean inspectClass = doBenchmark || isBenchmarked.matches(classTree, state);
|
||||
boolean inspectClass = doBenchmark || IS_BENCHMARKED.matches(classTree, state);
|
||||
|
||||
if (inspectClass) {
|
||||
System.out.println(handle(classTree, state));
|
||||
// XXX: If this class has a `@BeforeTemplate` method, generate a benchmark for it.
|
||||
}
|
||||
|
||||
@@ -75,6 +83,29 @@ final class RefasterBenchmarkGeneratorTaskListener implements TaskListener {
|
||||
}.scan(compilationUnit, false);
|
||||
}
|
||||
|
||||
// XXX: Name? Scope?
|
||||
private static Rule handle(ClassTree classTree, VisitorState state) {
|
||||
ImmutableList<Rule.Method> methods =
|
||||
classTree.getMembers().stream()
|
||||
.filter(m -> IS_TEMPLATE.matches(m, state))
|
||||
.map(m -> process((MethodTree) m, state))
|
||||
.collect(toImmutableList());
|
||||
|
||||
Rule rule = new Rule(classTree, methods);
|
||||
return rule;
|
||||
}
|
||||
|
||||
private static Rule.Method process(MethodTree methodTree, VisitorState state) {
|
||||
// XXX: Initially, disallow `Refaster.x` usages.
|
||||
// XXX: Initially, disallow references to `@Placeholder` methods.
|
||||
return new Rule.Method(methodTree);
|
||||
}
|
||||
|
||||
// XXX: Move types down.
|
||||
record Rule(ClassTree tree, ImmutableList<Rule.Method> methods) {
|
||||
record Method(MethodTree tree) {}
|
||||
}
|
||||
|
||||
private void createOutputDirectory() {
|
||||
try {
|
||||
Files.createDirectories(outputPath);
|
||||
@@ -83,12 +114,4 @@ final class RefasterBenchmarkGeneratorTaskListener implements TaskListener {
|
||||
String.format("Error while creating directory with path '%s'", outputPath), e);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> void writeToFile(String identifier, String className, T data) {
|
||||
Json.write(outputPath.resolve(String.format("%s-%s.json", identifier, className)), data);
|
||||
}
|
||||
|
||||
private static String getSimpleClassName(URI path) {
|
||||
return Paths.get(path).getFileName().toString().replace(".java", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package tech.picnic.errorprone.refaster.benchmark;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
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 java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
// XXX: This code is a near-duplicate of the identically named class in `documentation-support`.
|
||||
public final class Compilation {
|
||||
private Compilation() {}
|
||||
|
||||
public static void compileWithRefasterRuleBenchmarkGenerator(
|
||||
Path outputDirectory, String path, String... lines) {
|
||||
compileWithRefasterRuleBenchmarkGenerator(
|
||||
outputDirectory.toAbsolutePath().toString(), path, lines);
|
||||
}
|
||||
|
||||
public static void compileWithRefasterRuleBenchmarkGenerator(
|
||||
String outputDirectory, String path, String... lines) {
|
||||
/*
|
||||
* The compiler options specified here largely match those used by Error Prone's
|
||||
* `CompilationTestHelper`. A key difference is the stricter linting configuration. When
|
||||
* compiling using JDK 21+, these lint options also require that certain JDK modules are
|
||||
* explicitly exported.
|
||||
*/
|
||||
compile(
|
||||
ImmutableList.of(
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
|
||||
"--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
|
||||
"-encoding",
|
||||
"UTF-8",
|
||||
"-parameters",
|
||||
"-proc:none",
|
||||
"-Werror",
|
||||
"-Xlint:all,-serial",
|
||||
"-Xplugin:RefasterRuleBenchmarkGenerator -XoutputDirectory=" + outputDirectory,
|
||||
"-XDdev",
|
||||
"-XDcompilePolicy=simple"),
|
||||
FileObjects.forSourceLines(path, lines));
|
||||
}
|
||||
|
||||
private static void compile(ImmutableList<String> options, JavaFileObject javaFileObject) {
|
||||
JavacFileManager javacFileManager = FileManagers.testFileManager();
|
||||
JavaCompiler compiler = JavacTool.create();
|
||||
|
||||
List<Diagnostic<?>> diagnostics = new ArrayList<>();
|
||||
JavacTaskImpl task =
|
||||
(JavacTaskImpl)
|
||||
compiler.getTask(
|
||||
null,
|
||||
javacFileManager,
|
||||
diagnostics::add,
|
||||
options,
|
||||
ImmutableList.of(),
|
||||
ImmutableList.of(javaFileObject));
|
||||
|
||||
Boolean result = task.call();
|
||||
assertThat(diagnostics).isEmpty();
|
||||
assertThat(result).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package tech.picnic.errorprone.refaster.benchmark;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
// XXX: Add relevant tests also present in `DocumentationGeneratorTaskListenerTest`.
|
||||
// XXX: Test package placement.
|
||||
final class RefasterRuleBenchmarkGeneratorTaskListenerTest {
|
||||
// XXX: Rename!
|
||||
@Test
|
||||
void xxx(@TempDir Path outputDirectory) {
|
||||
Compilation.compileWithRefasterRuleBenchmarkGenerator(
|
||||
outputDirectory,
|
||||
"TestCheckerWithoutAnnotation.java",
|
||||
"""
|
||||
import com.google.errorprone.refaster.annotation.AfterTemplate;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import java.util.Optional;
|
||||
import reactor.core.publisher.Mono;
|
||||
import tech.picnic.errorprone.refaster.annotation.Benchmarked;
|
||||
|
||||
@Benchmarked
|
||||
final class MonoFromOptionalSwitchIfEmpty<T> {
|
||||
@BeforeTemplate
|
||||
Mono<T> before(Optional<T> optional, Mono<T> mono) {
|
||||
return optional.map(Mono::just).orElse(mono);
|
||||
}
|
||||
|
||||
@AfterTemplate
|
||||
Mono<T> after(Optional<T> optional, Mono<T> mono) {
|
||||
return Mono.justOrEmpty(optional).switchIfEmpty(mono);
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
assertThat(outputDirectory.toAbsolutePath()).isEmptyDirectory();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package tech.picnic.errorprone.refaster.benchmark;
|
||||
|
||||
final class RefasterRuleBenchmarkGeneratorTest {
|
||||
// XXX: TBD. See `DocumentationGeneratorTest`.
|
||||
}
|
||||
@@ -18,10 +18,14 @@ public @interface Benchmarked {
|
||||
// - Specify warmup and measurement iterations.
|
||||
// - Specify output time unit.
|
||||
// - Value generation hints.f
|
||||
// Once configuration is supported, annotations on nested classes should override the configuration specified by outer classes.
|
||||
// Once configuration is supported, annotations on nested classes should override the
|
||||
// configuration specified by outer classes.
|
||||
|
||||
// XXX: Explain use. Allow restriction by name?
|
||||
public @interface Param {
|
||||
@Target(ElementType.FIELD)
|
||||
@interface Param {}
|
||||
|
||||
}
|
||||
// XXX: Explain use
|
||||
@Target(ElementType.METHOD)
|
||||
@interface OnResult {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user