-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
RefasterRuleTestExtractor
for documentation generation
This new `Extractor` implementation collects Refaster example input and output code from rule collection tests. This change also introduces explicit compilation steps for the test code. As a side-effect this produces faster feedback in case of invalid input or output code.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
package tech.picnic.errorprone.documentation; | ||
|
||
import static com.google.common.collect.ImmutableList.toImmutableList; | ||
import static com.google.errorprone.matchers.Matchers.isSubtypeOf; | ||
import static java.util.stream.Collectors.joining; | ||
|
||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; | ||
import com.google.auto.service.AutoService; | ||
import com.google.auto.value.AutoValue; | ||
import com.google.common.base.Splitter; | ||
import com.google.common.base.Supplier; | ||
import com.google.common.base.VerifyException; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.errorprone.VisitorState; | ||
import com.google.errorprone.annotations.FormatMethod; | ||
import com.google.errorprone.annotations.Immutable; | ||
import com.google.errorprone.matchers.Matcher; | ||
import com.sun.source.tree.ClassTree; | ||
import com.sun.source.tree.MethodTree; | ||
import java.net.URI; | ||
import java.util.Optional; | ||
import java.util.regex.Pattern; | ||
import tech.picnic.errorprone.documentation.RefasterRuleCollectionTestExtractor.RefasterTestCases; | ||
import tech.picnic.errorprone.utils.SourceCode; | ||
|
||
/** | ||
* An {@link Extractor} that describes how to extract data from Refaster rule input and output test | ||
* classes. | ||
*/ | ||
// XXX: Drop this extractor if/when the Refaster test framework is reimlemented such that tests can | ||
// be located alongside rules, rather than in two additional resource files as currently required by | ||
// `RefasterRuleCollection`. | ||
@Immutable | ||
@AutoService(Extractor.class) | ||
@SuppressWarnings("rawtypes" /* See https://github.com/google/auto/issues/870. */) | ||
public final class RefasterRuleCollectionTestExtractor implements Extractor<RefasterTestCases> { | ||
private static final Matcher<ClassTree> IS_REFASTER_RULE_COLLECTION_TEST_CASE = | ||
isSubtypeOf("tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase"); | ||
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test"); | ||
private static final Pattern TEST_CLASS_FILE_NAME_PATTERN = | ||
Pattern.compile(".*(Input|Output)\\.java"); | ||
private static final Pattern TEST_METHOD_NAME_PATTERN = Pattern.compile("test(.*)"); | ||
private static final String LINE_SEPARATOR = "\n"; | ||
private static final Splitter LINE_SPLITTER = Splitter.on(LINE_SEPARATOR); | ||
|
||
/** Instantiates a new {@link RefasterRuleCollectionTestExtractor} instance. */ | ||
public RefasterRuleCollectionTestExtractor() {} | ||
|
||
@Override | ||
public String identifier() { | ||
return "refaster-rule-collection-test"; | ||
} | ||
|
||
@Override | ||
public Optional<RefasterTestCases> tryExtract(ClassTree tree, VisitorState state) { | ||
if (!IS_REFASTER_RULE_COLLECTION_TEST_CASE.matches(tree, state)) { | ||
return Optional.empty(); | ||
} | ||
|
||
URI sourceFile = state.getPath().getCompilationUnit().getSourceFile().toUri(); | ||
return Optional.of( | ||
RefasterTestCases.create( | ||
sourceFile, | ||
getRuleCollectionName(tree), | ||
isInputFile(sourceFile), | ||
getRefasterTestCases(tree, state))); | ||
} | ||
|
||
private static String getRuleCollectionName(ClassTree tree) { | ||
String className = tree.getSimpleName().toString(); | ||
|
||
// XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key | ||
// aspects of `RefasterRuleCollectionRefasterTestCase` subtypes. | ||
return tryExtractPatternGroup(className, TEST_CLASS_NAME_PATTERN) | ||
.orElseThrow( | ||
violation( | ||
"Refaster rule collection test class name '%s' does not match '%s'", | ||
className, TEST_CLASS_NAME_PATTERN)); | ||
} | ||
|
||
private static boolean isInputFile(URI sourceFile) { | ||
String path = sourceFile.getPath(); | ||
|
||
// XXX: Instead of throwing an error here, it'd be nicer to have a bug checker validate key | ||
// aspects of `RefasterRuleCollectionRefasterTestCase` subtypes. | ||
return "Input" | ||
.equals( | ||
tryExtractPatternGroup(path, TEST_CLASS_FILE_NAME_PATTERN) | ||
.orElseThrow( | ||
violation( | ||
"Refaster rule collection test file name '%s' does not match '%s'", | ||
path, TEST_CLASS_FILE_NAME_PATTERN))); | ||
} | ||
|
||
private static ImmutableList<RefasterTestCase> getRefasterTestCases( | ||
ClassTree tree, VisitorState state) { | ||
return tree.getMembers().stream() | ||
.filter(MethodTree.class::isInstance) | ||
.map(MethodTree.class::cast) | ||
.flatMap(m -> tryExtractRefasterTestCase(m, state).stream()) | ||
.collect(toImmutableList()); | ||
} | ||
|
||
private static Optional<RefasterTestCase> tryExtractRefasterTestCase( | ||
MethodTree method, VisitorState state) { | ||
return tryExtractPatternGroup(method.getName().toString(), TEST_METHOD_NAME_PATTERN) | ||
.map(name -> RefasterTestCase.create(name, getFormattedSource(method, state))); | ||
} | ||
|
||
/** | ||
* Returns the source code for the specified method. | ||
* | ||
* @implNote This operation attempts to trim leading whitespace, such that the start and end of | ||
* the method declaration are aligned. The implemented heuristic assumes that the code is | ||
* formatted using Google Java Format. | ||
*/ | ||
// XXX: Leading Javadoc and other comments are currently not extracted. Consider fixing this. | ||
private static String getFormattedSource(MethodTree method, VisitorState state) { | ||
String source = SourceCode.treeToString(method, state); | ||
int finalNewline = source.lastIndexOf(LINE_SEPARATOR); | ||
if (finalNewline < 0) { | ||
Check warning on line 121 in documentation-support/src/main/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractor.java GitHub Actions / pitestA change can be made to line 121 without causing a test to fail
|
||
return source; | ||
} | ||
|
||
int indentation = source.substring(finalNewline).lastIndexOf(' '); | ||
String prefixToStrip = " ".repeat(indentation); | ||
Check warning on line 126 in documentation-support/src/main/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractor.java GitHub Actions / pitestA change can be made to line 126 without causing a test to fail
|
||
|
||
return LINE_SPLITTER | ||
.splitToStream(source) | ||
.map(line -> line.startsWith(prefixToStrip) ? line.substring(indentation) : line) | ||
.collect(joining(LINE_SEPARATOR)); | ||
} | ||
|
||
private static Optional<String> tryExtractPatternGroup(String input, Pattern pattern) { | ||
java.util.regex.Matcher matcher = pattern.matcher(input); | ||
return matcher.matches() ? Optional.of(matcher.group(1)) : Optional.empty(); | ||
} | ||
|
||
@FormatMethod | ||
private static Supplier<VerifyException> violation(String format, Object... args) { | ||
return () -> new VerifyException(String.format(format, args)); | ||
} | ||
|
||
@AutoValue | ||
@JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases.class) | ||
abstract static class RefasterTestCases { | ||
static RefasterTestCases create( | ||
URI source, | ||
String ruleCollection, | ||
boolean isInput, | ||
ImmutableList<RefasterTestCase> testCases) { | ||
return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCases( | ||
source, ruleCollection, isInput, testCases); | ||
} | ||
|
||
abstract URI source(); | ||
|
||
abstract String ruleCollection(); | ||
|
||
abstract boolean isInput(); | ||
|
||
abstract ImmutableList<RefasterTestCase> testCases(); | ||
} | ||
|
||
@AutoValue | ||
@JsonDeserialize(as = AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase.class) | ||
abstract static class RefasterTestCase { | ||
static RefasterTestCase create(String name, String content) { | ||
return new AutoValue_RefasterRuleCollectionTestExtractor_RefasterTestCase(name, content); | ||
Check warning on line 169 in documentation-support/src/main/java/tech/picnic/errorprone/documentation/RefasterRuleCollectionTestExtractor.java GitHub Actions / pitestA change can be made to line 169 without causing a test to fail
|
||
} | ||
|
||
abstract String name(); | ||
|
||
abstract String content(); | ||
} | ||
} |