diff --git a/pom.xml b/pom.xml index bffded6a5b..91f966f7fa 100644 --- a/pom.xml +++ b/pom.xml @@ -41,6 +41,7 @@ error-prone-contrib refaster-compiler + refaster-rule-selector refaster-runner refaster-support refaster-test-support @@ -144,7 +145,7 @@ 1.0.1 1.9 ${version.error-prone-orig} - v${version.error-prone-orig}-picnic-2 + v${version.error-prone-orig}-picnic-3 2.14.0 0.1.15 1.0 @@ -197,6 +198,11 @@ refaster-compiler ${project.version} + + ${project.groupId} + refaster-rule-selector + ${project.version} + ${project.groupId} refaster-runner @@ -224,6 +230,11 @@ auto-common 1.2.1 + + com.google.auto.service + auto-service + ${version.auto-service} + com.google.auto.service auto-service-annotations @@ -244,6 +255,13 @@ jsr305 3.0.2 + + + + + + com.google.googlejavaformat google-java-format @@ -417,6 +435,13 @@ + + + jitpack.io + https://jitpack.io + + + @@ -793,6 +818,11 @@ error_prone_core ${version.error-prone} + + com.google.auto.value + auto-value + ${version.auto-value} + com.google.auto.service auto-service @@ -1522,6 +1552,11 @@ Prone bug pattern checkers, so we enable all and then selectively deactivate some. --> -XepAllDisabledChecksAsWarnings + + -XepDisableWarningsInGeneratedCode -Xep:AndroidJdkLibsChecker:OFF + Select rules based on the Error Prone distribution. + + + com.github.PicnicSupermarket.error-prone + v2.14.0-picnic-3 + + + + + ${groupId.error-prone} + error_prone_core + provided + + + com.google.auto.service + auto-service-annotations + provided + + + com.google.auto.value + auto-value-annotations + ${version.auto-value} + + + com.google.code.findbugs + jsr305 + provided + + + com.google.guava + guava + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + ${project.groupId} + refaster-compiler + ${project.version} + + + + -Xplugin:RefasterRuleCompiler + + + + + + + diff --git a/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/DefaultRefasterRuleSelector.java b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/DefaultRefasterRuleSelector.java new file mode 100644 index 0000000000..e4d30577e9 --- /dev/null +++ b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/DefaultRefasterRuleSelector.java @@ -0,0 +1,28 @@ +package tech.picnic.errorprone.rule.selector; + +import com.google.auto.service.AutoService; +import com.google.errorprone.refaster.RefasterRule; +import com.sun.source.tree.CompilationUnitTree; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** XXX: Write this */ +@AutoService(RefasterRuleSelector.class) +public final class DefaultRefasterRuleSelector implements RefasterRuleSelector { + private final List> refasterRules; + + /** + * XXX: Write this. + * + * @param refasterRules XXX: Write this + */ + public DefaultRefasterRuleSelector(List> refasterRules) { + this.refasterRules = refasterRules; + } + + @Override + public Set> selectCandidateRules(CompilationUnitTree tree) { + return new HashSet<>(refasterRules); + } +} diff --git a/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/DefaultRuleSelectorFactory.java b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/DefaultRuleSelectorFactory.java new file mode 100644 index 0000000000..4d2a25742a --- /dev/null +++ b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/DefaultRuleSelectorFactory.java @@ -0,0 +1,48 @@ +package tech.picnic.errorprone.rule.selector; + +import com.google.errorprone.refaster.RefasterRule; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.List; + +/** XXX: Write */ +public final class DefaultRuleSelectorFactory implements RefasterRuleSelectorFactory { + /** + * XXX: Write this. + * + * @param classLoader Test + * @param refasterRules Test + * @return Test + */ + @Override + public RefasterRuleSelector createRefasterRuleSelector( + ClassLoader classLoader, List> refasterRules) { + return isClassPathCompatible(classLoader) + ? new SmartRefasterRuleSelector(refasterRules) + : new DefaultRefasterRuleSelector(refasterRules); + } + + /** + * XXX: Write this + * + * @param classLoader Test + * @return Test + */ + @Override + public boolean isClassPathCompatible(ClassLoader classLoader) { + Class clazz; + try { + clazz = + Class.forName( + "com.google.errorprone.ErrorProneOptions", /* initialize= */ false, classLoader); + } catch (ClassNotFoundException e) { + return false; + // throw new IllegalStateException("Cannot load + // `com.google.errorprone.ErrorProneOptions`", e); + } + + return Arrays.stream(clazz.getDeclaredMethods()) + .filter(m -> Modifier.isPublic(m.getModifiers())) + .anyMatch(m -> m.getName().equals("isSuggestionsAsWarnings")); + } +} diff --git a/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/Node.java b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/Node.java new file mode 100644 index 0000000000..d63041087e --- /dev/null +++ b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/Node.java @@ -0,0 +1,99 @@ +package tech.picnic.errorprone.rule.selector; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Maps; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * A node in an immutable tree. + * + *

The tree's edges are string-labeled, while its leaves store values of type {@code T}. + */ +@AutoValue +abstract class Node { + static Node create(Map> children, ImmutableList values) { + return new AutoValue_Node<>(ImmutableSortedMap.copyOf(children), values); + } + + static Node create( + List values, Function>> pathExtractor) { + BuildNode tree = BuildNode.create(); + tree.register(values, pathExtractor); + return tree.immutable(); + } + + abstract ImmutableMap> children(); + + abstract ImmutableList values(); + + void collectCandidateTemplates(ImmutableList candidateEdges, Consumer sink) { + values().forEach(sink); + + if (candidateEdges.isEmpty() || children().isEmpty()) { + return; + } + + if (children().size() < candidateEdges.size()) { + for (Map.Entry> e : children().entrySet()) { + if (candidateEdges.contains(e.getKey())) { + e.getValue().collectCandidateTemplates(candidateEdges, sink); + } + } + } else { + ImmutableList remainingCandidateEdges = + candidateEdges.subList(1, candidateEdges.size()); + Node child = children().get(candidateEdges.get(0)); + if (child != null) { + child.collectCandidateTemplates(remainingCandidateEdges, sink); + } + collectCandidateTemplates(remainingCandidateEdges, sink); + } + } + + @AutoValue + @SuppressWarnings("AutoValueImmutableFields" /* Type is used only during `Node` construction. */) + abstract static class BuildNode { + static BuildNode create() { + return new AutoValue_Node_BuildNode<>(new HashMap<>(), new ArrayList<>()); + } + + abstract Map> children(); + + abstract List values(); + + private void register( + List values, Function>> pathsExtractor) { + for (T value : values) { + for (ImmutableSet path : pathsExtractor.apply(value)) { + registerPath(value, path.asList()); + } + } + } + + private void registerPath(T value, ImmutableList path) { + path.stream() + .findFirst() + .ifPresentOrElse( + edge -> + children() + .computeIfAbsent(edge, k -> BuildNode.create()) + .registerPath(value, path.subList(1, path.size())), + () -> values().add(value)); + } + + private Node immutable() { + return Node.create( + Maps.transformValues(children(), BuildNode::immutable), ImmutableList.copyOf(values())); + } + } +} diff --git a/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/RefasterRuleSelector.java b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/RefasterRuleSelector.java new file mode 100644 index 0000000000..719add25ed --- /dev/null +++ b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/RefasterRuleSelector.java @@ -0,0 +1,16 @@ +package tech.picnic.errorprone.rule.selector; + +import com.google.errorprone.refaster.RefasterRule; +import com.sun.source.tree.CompilationUnitTree; +import java.util.Set; + +/** XXX: Write this. */ +public interface RefasterRuleSelector { + /** + * XXX: Write this + * + * @param tree XXX: Write this + * @return XXX: Write this + */ + Set> selectCandidateRules(CompilationUnitTree tree); +} diff --git a/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/RefasterRuleSelectorFactory.java b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/RefasterRuleSelectorFactory.java new file mode 100644 index 0000000000..8b72ba415b --- /dev/null +++ b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/RefasterRuleSelectorFactory.java @@ -0,0 +1,25 @@ +package tech.picnic.errorprone.rule.selector; + +import com.google.errorprone.refaster.RefasterRule; +import java.util.List; + +/** XXX: Write this */ +public interface RefasterRuleSelectorFactory { + /** + * XXX: Write this + * + * @param classLoader XXX: Write this + * @param refasterRules XXX: Write this + * @return XXX: Write this + */ + RefasterRuleSelector createRefasterRuleSelector( + ClassLoader classLoader, List> refasterRules); + + /** + * XXX: Write this + * + * @param classLoader XXX: Write this + * @return XXX: Write this + */ + boolean isClassPathCompatible(ClassLoader classLoader); +} diff --git a/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/SmartRefasterRuleSelector.java b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/SmartRefasterRuleSelector.java new file mode 100644 index 0000000000..71cb0c226d --- /dev/null +++ b/refaster-rule-selector/src/main/java/tech/picnic/errorprone/rule/selector/SmartRefasterRuleSelector.java @@ -0,0 +1,363 @@ +package tech.picnic.errorprone.rule.selector; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static java.util.Collections.newSetFromMap; +import static java.util.stream.Collectors.toCollection; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; +import com.google.errorprone.refaster.BlockTemplate; +import com.google.errorprone.refaster.ExpressionTemplate; +import com.google.errorprone.refaster.RefasterRule; +import com.google.errorprone.refaster.UAnyOf; +import com.google.errorprone.refaster.UClassIdent; +import com.google.errorprone.refaster.UExpression; +import com.google.errorprone.refaster.UStatement; +import com.google.errorprone.refaster.UStaticIdent; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.BinaryTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.CompoundAssignmentTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberReferenceTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; +import com.sun.source.tree.UnaryTree; +import com.sun.source.util.TreeScanner; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; + +/** XXX: Write this */ +@AutoService(RefasterRuleSelector.class) +public final class SmartRefasterRuleSelector implements RefasterRuleSelector { + private final Node> treeRules; + + // XXX: Here pass in the Node? Instead of the list? + + /** + * XXX: Write this + * + * @param refasterRules XXX: Write this + */ + public SmartRefasterRuleSelector(List> refasterRules) { + this.treeRules = + Node.create(refasterRules, SmartRefasterRuleSelector::extractTemplateIdentifiers); + } + + @Override + public Set> selectCandidateRules(CompilationUnitTree tree) { + Set> candidateRules = newSetFromMap(new IdentityHashMap<>()); + treeRules.collectCandidateTemplates( + extractSourceIdentifiers(tree).asList(), candidateRules::add); + + return candidateRules; + } + + // XXX: Decompose `RefasterRule`s such that each has exactly one `@BeforeTemplate`. + private static ImmutableSet> extractTemplateIdentifiers( + RefasterRule refasterRule) { + ImmutableSet.Builder> results = ImmutableSet.builder(); + + for (Object template : refasterRule.beforeTemplates()) { + if (template instanceof ExpressionTemplate) { + UExpression expr = ((ExpressionTemplate) template).expression(); + results.addAll(extractTemplateIdentifiers(ImmutableList.of(expr))); + } else if (template instanceof BlockTemplate) { + ImmutableList statements = ((BlockTemplate) template).templateStatements(); + results.addAll(extractTemplateIdentifiers(statements)); + } else { + throw new IllegalStateException( + String.format("Unexpected template type '%s'", template.getClass())); + } + } + + return results.build(); + } + + // XXX: Consider interning the strings (once a benchmark is in place). + private static ImmutableSet> extractTemplateIdentifiers( + ImmutableList trees) { + List> identifierCombinations = new ArrayList<>(); + identifierCombinations.add(new HashSet<>()); + + // XXX: Make the scanner static, then make also its helper methods static. + new TreeScanner>>() { + @Nullable + @Override + public Void visitIdentifier(IdentifierTree node, List> identifierCombinations) { + // XXX: Also include the package name if not `java.lang`; it must be present. + if (node instanceof UClassIdent) { + for (Set ids : identifierCombinations) { + ids.add(getSimpleName(((UClassIdent) node).getTopLevelClass())); + ids.add(getIdentifier(node)); + } + } else if (node instanceof UStaticIdent) { + UClassIdent subNode = ((UStaticIdent) node).classIdent(); + for (Set ids : identifierCombinations) { + ids.add(getSimpleName(subNode.getTopLevelClass())); + ids.add(getIdentifier(subNode)); + ids.add(node.getName().toString()); + } + } + + return null; + } + + private String getIdentifier(IdentifierTree tree) { + return getSimpleName(tree.getName().toString()); + } + + private String getSimpleName(String fcqn) { + int index = fcqn.lastIndexOf('.'); + return index < 0 ? fcqn : fcqn.substring(index + 1); + } + + @Nullable + @Override + public Void visitMemberReference( + MemberReferenceTree node, List> identifierCombinations) { + super.visitMemberReference(node, identifierCombinations); + String id = node.getName().toString(); + identifierCombinations.forEach(ids -> ids.add(id)); + return null; + } + + @Nullable + @Override + public Void visitMemberSelect( + MemberSelectTree node, List> identifierCombinations) { + super.visitMemberSelect(node, identifierCombinations); + String id = node.getIdentifier().toString(); + identifierCombinations.forEach(ids -> ids.add(id)); + return null; + } + + @Nullable + @Override + public Void visitAssignment(AssignmentTree node, List> identifierCombinations) { + registerOperator(node, identifierCombinations); + return super.visitAssignment(node, identifierCombinations); + } + + @Nullable + @Override + public Void visitCompoundAssignment( + CompoundAssignmentTree node, List> identifierCombinations) { + registerOperator(node, identifierCombinations); + return super.visitCompoundAssignment(node, identifierCombinations); + } + + @Nullable + @Override + public Void visitUnary(UnaryTree node, List> identifierCombinations) { + registerOperator(node, identifierCombinations); + return super.visitUnary(node, identifierCombinations); + } + + @Nullable + @Override + public Void visitBinary(BinaryTree node, List> identifierCombinations) { + registerOperator(node, identifierCombinations); + return super.visitBinary(node, identifierCombinations); + } + + // XXX: Rename! + private void registerOperator(ExpressionTree node, List> identifierCombinations) { + identifierCombinations.forEach(ids -> ids.add(treeKindToString(node.getKind()))); + } + + @Nullable + @Override + public Void visitOther(Tree node, List> identifierCombinations) { + if (node instanceof UAnyOf) { + List> base = copy(identifierCombinations); + identifierCombinations.clear(); + + for (UExpression expr : ((UAnyOf) node).expressions()) { + List> branch = copy(base); + scan(expr, branch); + identifierCombinations.addAll(branch); + } + } + + return null; + } + + private List> copy(List> identifierCombinations) { + return identifierCombinations.stream() + .map(HashSet::new) + .collect(toCollection(ArrayList::new)); + } + }.scan(trees, identifierCombinations); + + return identifierCombinations.stream() + .map(ImmutableSortedSet::copyOf) + .collect(toImmutableSet()); + } + + // XXX: Consider interning! + private ImmutableSortedSet extractSourceIdentifiers(Tree tree) { + Set identifiers = new HashSet<>(); + + // XXX: Make the scanner static. + new TreeScanner>() { + @Nullable + @Override + public Void visitIdentifier(IdentifierTree node, Set identifiers) { + identifiers.add(node.getName().toString()); + return null; + } + + @Nullable + @Override + public Void visitMemberReference(MemberReferenceTree node, Set identifiers) { + super.visitMemberReference(node, identifiers); + identifiers.add(node.getName().toString()); + return null; + } + + @Nullable + @Override + public Void visitMemberSelect(MemberSelectTree node, Set identifiers) { + super.visitMemberSelect(node, identifiers); + identifiers.add(node.getIdentifier().toString()); + return null; + } + + @Nullable + @Override + public Void visitAssignment(AssignmentTree node, Set identifiers) { + registerOperator(node, identifiers); + return super.visitAssignment(node, identifiers); + } + + @Nullable + @Override + public Void visitCompoundAssignment(CompoundAssignmentTree node, Set identifiers) { + registerOperator(node, identifiers); + return super.visitCompoundAssignment(node, identifiers); + } + + @Nullable + @Override + public Void visitUnary(UnaryTree node, Set identifiers) { + registerOperator(node, identifiers); + return super.visitUnary(node, identifiers); + } + + @Nullable + @Override + public Void visitBinary(BinaryTree node, Set identifiers) { + registerOperator(node, identifiers); + return super.visitBinary(node, identifiers); + } + + // XXX: Rename! + private void registerOperator(ExpressionTree node, Set identifiers) { + identifiers.add(treeKindToString(node.getKind())); + } + }.scan(tree, identifiers); + + return ImmutableSortedSet.copyOf(identifiers); + } + + /** + * Returns a unique string representation of the given {@link Tree.Kind}. + * + * @return A string representation of the operator, if known + * @throws IllegalArgumentException If the given input is not supported. + */ + // XXX: Extend list to cover remaining cases; at least for any `Kind` that may appear in a + // Refaster template. + private static String treeKindToString(Tree.Kind kind) { + switch (kind) { + case ASSIGNMENT: + return "="; + case POSTFIX_INCREMENT: + return "x++"; + case PREFIX_INCREMENT: + return "++x"; + case POSTFIX_DECREMENT: + return "x--"; + case PREFIX_DECREMENT: + return "--x"; + case UNARY_PLUS: + return "+x"; + case UNARY_MINUS: + return "-x"; + case BITWISE_COMPLEMENT: + return "~"; + case LOGICAL_COMPLEMENT: + return "!"; + case MULTIPLY: + return "*"; + case DIVIDE: + return "/"; + case REMAINDER: + return "%"; + case PLUS: + return "+"; + case MINUS: + return "-"; + case LEFT_SHIFT: + return "<<"; + case RIGHT_SHIFT: + return ">>"; + case UNSIGNED_RIGHT_SHIFT: + return ">>>"; + case LESS_THAN: + return "<"; + case GREATER_THAN: + return ">"; + case LESS_THAN_EQUAL: + return "<="; + case GREATER_THAN_EQUAL: + return ">="; + case EQUAL_TO: + return "=="; + case NOT_EQUAL_TO: + return "!="; + case AND: + return "&"; + case XOR: + return "^"; + case OR: + return "|"; + case CONDITIONAL_AND: + return "&&"; + case CONDITIONAL_OR: + return "||"; + case MULTIPLY_ASSIGNMENT: + return "*="; + case DIVIDE_ASSIGNMENT: + return "/="; + case REMAINDER_ASSIGNMENT: + return "%="; + case PLUS_ASSIGNMENT: + return "+="; + case MINUS_ASSIGNMENT: + return "-="; + case LEFT_SHIFT_ASSIGNMENT: + return "<<="; + case RIGHT_SHIFT_ASSIGNMENT: + return ">>="; + case UNSIGNED_RIGHT_SHIFT_ASSIGNMENT: + return ">>>="; + case AND_ASSIGNMENT: + return "&="; + case XOR_ASSIGNMENT: + return "^="; + case OR_ASSIGNMENT: + return "|="; + default: + throw new IllegalStateException("Cannot convert Tree.Kind to a String: " + kind); + } + } +} diff --git a/refaster-runner/pom.xml b/refaster-runner/pom.xml index 5f88259589..d24decaea2 100644 --- a/refaster-runner/pom.xml +++ b/refaster-runner/pom.xml @@ -29,6 +29,11 @@ error_prone_check_api provided + + ${groupId.error-prone} + error_prone_core + provided + ${project.groupId} refaster-compiler @@ -37,11 +42,21 @@ `annotationProcessorPaths` configuration below. --> provided + + ${project.groupId} + refaster-rule-selector + ${project.version} + com.google.auto.service auto-service-annotations provided + + com.google.auto.value + auto-value-annotations + provided + com.google.code.findbugs jsr305 diff --git a/refaster-runner/src/main/java/tech/picnic/errorprone/refaster/runner/Refaster.java b/refaster-runner/src/main/java/tech/picnic/errorprone/refaster/runner/Refaster.java index b6e7d19600..ccc47de156 100644 --- a/refaster-runner/src/main/java/tech/picnic/errorprone/refaster/runner/Refaster.java +++ b/refaster-runner/src/main/java/tech/picnic/errorprone/refaster/runner/Refaster.java @@ -26,6 +26,7 @@ import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher; import com.google.errorprone.fixes.Replacement; import com.google.errorprone.matchers.Description; +import com.google.errorprone.refaster.RefasterRule; import com.sun.source.tree.CompilationUnitTree; import com.sun.tools.javac.tree.EndPosTable; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; @@ -33,8 +34,12 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.stream.Stream; +import tech.picnic.errorprone.rule.selector.DefaultRuleSelectorFactory; +import tech.picnic.errorprone.rule.selector.RefasterRuleSelector; /** * A {@link BugChecker} which flags code which can be simplified using Refaster templates located on @@ -56,7 +61,7 @@ public final class Refaster extends BugChecker implements CompilationUnitTreeMat private static final long serialVersionUID = 1L; - private final CodeTransformer codeTransformer; + private final List> refasterRules; /** Instantiates the default {@link Refaster}. */ public Refaster() { @@ -69,23 +74,32 @@ public Refaster() { * @param flags Any provided command line flags. */ public Refaster(ErrorProneFlags flags) { - codeTransformer = createCompositeCodeTransformer(flags); + refasterRules = getRefasterRules(flags); } @CanIgnoreReturnValue @Override public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) { + DefaultRuleSelectorFactory ruleSelectorFactory = new DefaultRuleSelectorFactory(); + RefasterRuleSelector selector = + ruleSelectorFactory.createRefasterRuleSelector( + Thread.currentThread().getContextClassLoader(), refasterRules); + Set> candidateRules = selector.selectCandidateRules(tree); + /* First, collect all matches. */ + SubContext context = new SubContext(state.context); List matches = new ArrayList<>(); - try { - codeTransformer.apply(state.getPath(), new SubContext(state.context), matches::add); - } catch (LinkageError e) { - // XXX: This `try/catch` block handles the issue described and resolved in - // https://github.com/google/error-prone/pull/2456. Drop this block once that change is - // released. - // XXX: Find a way to identify that we're running Picnic's Error Prone fork and disable this - // fallback if so, as it might hide other bugs. - return Description.NO_MATCH; + for (RefasterRule rule : candidateRules) { + try { + rule.apply(state.getPath(), context, matches::add); + } catch (LinkageError e) { + // XXX: This `try/catch` block handles the issue described and resolved in + // https://github.com/google/error-prone/pull/2456. Drop this block once that change is + // released. + // XXX: Find a way to identify that we're running Picnic's Error Prone fork and disable this + // fallback if so, as it might hide other bugs. + return Description.NO_MATCH; + } } /* Then apply them. */ applyMatches(matches, ((JCCompilationUnit) tree).endPositions, state); @@ -94,6 +108,28 @@ public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState s return Description.NO_MATCH; } + private static List> getRefasterRules(ErrorProneFlags flags) { + CodeTransformer compositeCodeTransformer = createCompositeCodeTransformer(flags); + + List> refasterRules = new ArrayList<>(); + collectRefasterRules(compositeCodeTransformer, refasterRules::add); + return refasterRules; + } + + private static void collectRefasterRules( + CodeTransformer transformer, Consumer> sink) { + if (transformer instanceof RefasterRule) { + sink.accept((RefasterRule) transformer); + } else if (transformer instanceof CompositeCodeTransformer) { + for (CodeTransformer t : ((CompositeCodeTransformer) transformer).transformers()) { + collectRefasterRules(t, sink); + } + } else { + throw new IllegalStateException( + String.format("Can't handle `CodeTransformer` of type '%s'", transformer.getClass())); + } + } + /** * Reports a subset of the given matches, such that no two reported matches suggest a replacement * of the same part of the source code. @@ -146,6 +182,7 @@ private static Stream getReplacements( return description.fixes.stream().flatMap(fix -> fix.getReplacements(endPositions).stream()); } + // XXX: Instead create an `ImmutableList` private static CodeTransformer createCompositeCodeTransformer(ErrorProneFlags flags) { ImmutableListMultimap allTransformers = CodeTransformers.getAllCodeTransformers();