Skip to content

Commit

Permalink
Suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephan202 committed Mar 3, 2023
1 parent 215cdb2 commit 2ad41dd
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import org.jspecify.annotations.Nullable;
import tech.picnic.errorprone.documentation.BugPatternTestExtractor.BugPatternTestDocumentation;

Expand All @@ -34,8 +36,17 @@
*/
@Immutable
final class BugPatternTestExtractor implements Extractor<BugPatternTestDocumentation> {
private static final Pattern TEST_CLASS_NAME_PATTERN = Pattern.compile("(.*)Test");
private static final Matcher<Tree> JUNIT_TEST_METHOD =
toType(MethodTree.class, hasAnnotation("org.junit.jupiter.api.Test"));
private static final Matcher<MethodInvocationTree> BUG_PATTERN_TEST_METHOD =
allOf(
staticMethod()
.onDescendantOfAny(
"com.google.errorprone.CompilationTestHelper",
"com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("newInstance"),
argument(0, classLiteral(anything())));

// XXX: Improve support for correctly extracting multiple sources from a single
// `{BugCheckerRefactoring,Compilation}TestHelper` test.
Expand All @@ -49,53 +60,49 @@ public BugPatternTestDocumentation extract(ClassTree tree, VisitorState state) {
}
}

String className = tree.getSimpleName().toString();
String bugPatternName =
getClassUnderTest(tree)
.orElseThrow(
() ->
new IllegalArgumentException(
String.format(
"Name of given class does not match '%s'", TEST_CLASS_NAME_PATTERN)));
return new AutoValue_BugPatternTestExtractor_BugPatternTestDocumentation(
className.substring(0, className.lastIndexOf("Test")),
scanner.getIdentificationTests(),
scanner.getReplacementTests());
bugPatternName, 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);
return getClassUnderTest(tree)
.filter(bugPatternName -> testsBugPattern(bugPatternName, tree, state))
.isPresent();
}

// XXX: Consider replacing this type with an anonymous class in a method. Possibly also below.
private static final class ScanBugPatternTest extends TreeScanner<@Nullable Void, VisitorState> {
private static final Matcher<MethodInvocationTree> BUG_PATTERN_TEST_METHOD =
allOf(
staticMethod()
.onDescendantOfAny(
"com.google.errorprone.CompilationTestHelper",
"com.google.errorprone.BugCheckerRefactoringTestHelper")
.named("newInstance"),
argument(0, classLiteral(anything())));

private final List<String> encounteredClasses = new ArrayList<>();

boolean hasTestUsingClassInstance(String clazz) {
return encounteredClasses.contains(clazz);
}
private static boolean testsBugPattern(
String bugPatternName, ClassTree tree, VisitorState state) {
AtomicBoolean result = new AtomicBoolean(false);

new TreeScanner<@Nullable Void, @Nullable Void>() {
@Override
public @Nullable Void visitMethodInvocation(MethodInvocationTree node, @Nullable Void v) {
if (BUG_PATTERN_TEST_METHOD.matches(node, state)) {
MemberSelectTree firstArgumentTree = (MemberSelectTree) node.getArguments().get(0);
result.compareAndSet(
/* expectedValue= */ false,
bugPatternName.equals(firstArgumentTree.getExpression().toString()));
}

@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, v);
}
}.scan(tree, null);

return super.visitMethodInvocation(node, state);
}
return result.get();
}

private static Optional<String> getClassUnderTest(ClassTree tree) {
return Optional.of(TEST_CLASS_NAME_PATTERN.matcher(tree.getSimpleName().toString()))
.filter(java.util.regex.Matcher::matches)
.map(m -> m.group(1));
}

private static final class CollectBugPatternTests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
*
* @param <T> The type of data that is extracted.
*/
// XXX: Here and in the implementations, either:
// 1. Swap `canExtract` and `extract`.
// 2. Combine the methods into a single `Optional<T> tryExtract`.
@Immutable
interface Extractor<T> {
/**
Expand Down

0 comments on commit 2ad41dd

Please sign in to comment.