-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
16 changed files
with
791 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
103 changes: 103 additions & 0 deletions
103
...ation-support/src/main/java/tech/picnic/errorprone/documentation/BugPatternExtractor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
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.annotations.Immutable; | ||
import com.google.errorprone.util.ASTHelpers; | ||
import com.sun.source.tree.AnnotationTree; | ||
import com.sun.source.tree.ClassTree; | ||
import com.sun.tools.javac.code.Attribute; | ||
import com.sun.tools.javac.code.Symbol.ClassSymbol; | ||
import 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) { | ||
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) { | ||
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(); | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
...on-support/src/main/java/tech/picnic/errorprone/documentation/DocumentationGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
} | ||
} |
91 changes: 91 additions & 0 deletions
91
...rc/main/java/tech/picnic/errorprone/documentation/DocumentationGeneratorTaskListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
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.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) | ||
.ifPresent( | ||
extractorType -> | ||
writeToFile( | ||
extractorType.getIdentifier(), | ||
getSimpleClassName(sourceFile.toUri()), | ||
extractorType.getExtractor().extract(classTree, context))); | ||
} | ||
|
||
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", ""); | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
documentation-support/src/main/java/tech/picnic/errorprone/documentation/Extractor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package tech.picnic.errorprone.documentation; | ||
|
||
import com.google.errorprone.annotations.Immutable; | ||
import com.sun.source.tree.ClassTree; | ||
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. | ||
* @return A non-null instance of {@link T}. | ||
*/ | ||
// XXX: Drop `Context` parameter unless used. | ||
T extract(ClassTree tree, Context context); | ||
|
||
/** | ||
* Tells whether this {@link Extractor} can extract documentation content from the given {@link | ||
* ClassTree}. | ||
* | ||
* @param tree The {@link ClassTree} of interest. | ||
* @return {@code true} iff data extraction is supported. | ||
*/ | ||
boolean canExtract(ClassTree tree); | ||
} |
Oops, something went wrong.