|
|
|
|
@@ -3,7 +3,9 @@ package tech.picnic.errorprone.bugpatterns;
|
|
|
|
|
import static com.google.errorprone.BugPattern.LinkType.NONE;
|
|
|
|
|
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
|
|
|
|
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
|
|
|
|
|
import static java.util.function.Predicate.not;
|
|
|
|
|
|
|
|
|
|
import com.google.auto.common.AnnotationMirrors;
|
|
|
|
|
import com.google.auto.service.AutoService;
|
|
|
|
|
import com.google.common.collect.ImmutableSet;
|
|
|
|
|
import com.google.errorprone.BugPattern;
|
|
|
|
|
@@ -14,17 +16,24 @@ import com.google.errorprone.fixes.Fix;
|
|
|
|
|
import com.google.errorprone.fixes.SuggestedFix;
|
|
|
|
|
import com.google.errorprone.matchers.AnnotationMatcherUtils;
|
|
|
|
|
import com.google.errorprone.matchers.Description;
|
|
|
|
|
import com.google.errorprone.predicates.TypePredicate;
|
|
|
|
|
import com.google.errorprone.predicates.TypePredicates;
|
|
|
|
|
import com.google.errorprone.util.ASTHelpers;
|
|
|
|
|
import com.sun.source.tree.AnnotationTree;
|
|
|
|
|
import com.sun.source.tree.AssignmentTree;
|
|
|
|
|
import com.sun.source.tree.ExpressionTree;
|
|
|
|
|
import com.sun.source.tree.NewArrayTree;
|
|
|
|
|
import com.sun.source.tree.Tree.Kind;
|
|
|
|
|
import com.sun.tools.javac.code.Attribute;
|
|
|
|
|
import com.sun.tools.javac.code.Symbol;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Optional;
|
|
|
|
|
import java.util.function.BiFunction;
|
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
import java.util.stream.Stream;
|
|
|
|
|
import javax.lang.model.element.AnnotationValue;
|
|
|
|
|
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
|
|
|
|
|
|
|
|
|
/** A {@link BugChecker} which flags annotations that could be written more concisely. */
|
|
|
|
|
@@ -40,6 +49,7 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
|
|
|
|
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Optional<Fix>>>
|
|
|
|
|
FIX_FACTORIES =
|
|
|
|
|
ImmutableSet.of(
|
|
|
|
|
CanonicalAnnotationSyntax::useSpringAlias,
|
|
|
|
|
CanonicalAnnotationSyntax::dropRedundantParentheses,
|
|
|
|
|
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
|
|
|
|
|
CanonicalAnnotationSyntax::dropRedundantCurlies);
|
|
|
|
|
@@ -54,6 +64,69 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
|
|
|
|
.orElse(Description.NO_MATCH);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Optional<Fix> useSpringAlias(AnnotationTree tree, VisitorState state) {
|
|
|
|
|
boolean canDropAttributeName = tree.getArguments().size() == 1;
|
|
|
|
|
|
|
|
|
|
// XXX: The reduce is repeated. Fix.
|
|
|
|
|
return tree.getArguments().stream()
|
|
|
|
|
.flatMap(arg -> useBetterAlias(arg, canDropAttributeName, state).stream())
|
|
|
|
|
.reduce(SuggestedFix.Builder::merge)
|
|
|
|
|
.map(SuggestedFix.Builder::build);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Optional<SuggestedFix.Builder> useBetterAlias(
|
|
|
|
|
ExpressionTree tree, boolean canDropAttributeName, VisitorState state) {
|
|
|
|
|
if (tree.getKind() != Kind.ASSIGNMENT) {
|
|
|
|
|
return Optional.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AssignmentTree assignment = (AssignmentTree) tree;
|
|
|
|
|
ExpressionTree element = assignment.getVariable();
|
|
|
|
|
if (canDropAttributeName == "value".equals(SourceCode.treeToString(element, state))) {
|
|
|
|
|
return Optional.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return getAlias(element, state)
|
|
|
|
|
.map(
|
|
|
|
|
alias ->
|
|
|
|
|
"value".equals(alias)
|
|
|
|
|
? SuggestedFix.builder()
|
|
|
|
|
.replace(
|
|
|
|
|
assignment, SourceCode.treeToString(assignment.getExpression(), state))
|
|
|
|
|
: SuggestedFix.builder().replace(element, alias));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// XXX: Also add support for non-default `AliasFor#annotation` values!
|
|
|
|
|
private static Optional<String> getAlias(ExpressionTree tree, VisitorState state) {
|
|
|
|
|
Symbol sym = ASTHelpers.getSymbol(tree);
|
|
|
|
|
if (sym == null) {
|
|
|
|
|
return Optional.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// XXX: Extract.
|
|
|
|
|
TypePredicate isAliasFor =
|
|
|
|
|
TypePredicates.isExactType("org.springframework.core.annotation.AliasFor");
|
|
|
|
|
TypePredicate isAnnotation = TypePredicates.isExactType("java.lang.annotation.Annotation");
|
|
|
|
|
|
|
|
|
|
return sym.getAnnotationMirrors().stream()
|
|
|
|
|
.filter(m -> isAliasFor.apply(m.type, state))
|
|
|
|
|
.filter(
|
|
|
|
|
m ->
|
|
|
|
|
isAnnotation.apply(
|
|
|
|
|
((Attribute.Class) AnnotationMirrors.getAnnotationValue(m, "annotation"))
|
|
|
|
|
.classType,
|
|
|
|
|
state))
|
|
|
|
|
.flatMap(
|
|
|
|
|
m ->
|
|
|
|
|
Stream.of(
|
|
|
|
|
AnnotationMirrors.getAnnotationValue(m, "value"),
|
|
|
|
|
AnnotationMirrors.getAnnotationValue(m, "attribute")))
|
|
|
|
|
.map(AnnotationValue::getValue)
|
|
|
|
|
.map(Object::toString)
|
|
|
|
|
.filter(not(String::isEmpty))
|
|
|
|
|
.findFirst();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static Optional<Fix> dropRedundantParentheses(AnnotationTree tree, VisitorState state) {
|
|
|
|
|
if (!tree.getArguments().isEmpty()) {
|
|
|
|
|
/* Parentheses are necessary. */
|
|
|
|
|
|