tech.picnic.errorprone.bugpatterns.CanonicalAnnotationSyntax Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of error-prone-contrib Show documentation
Show all versions of error-prone-contrib Show documentation
Extra Error Prone plugins by Picnic.
package tech.picnic.errorprone.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
import static com.google.errorprone.BugPattern.StandardTags.SIMPLIFICATION;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
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.sun.source.tree.AnnotationTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.NewArrayTree;
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 tech.picnic.errorprone.utils.SourceCode;
/** A {@link BugChecker} that flags annotations that could be written more concisely. */
@AutoService(BugChecker.class)
@BugPattern(
summary = "Omit redundant syntax from annotation declarations",
link = BUG_PATTERNS_BASE_URL + "CanonicalAnnotationSyntax",
linkType = CUSTOM,
severity = SUGGESTION,
tags = SIMPLIFICATION)
public final class CanonicalAnnotationSyntax extends BugChecker implements AnnotationTreeMatcher {
private static final long serialVersionUID = 1L;
private static final Pattern TRAILING_ARRAY_COMMA = Pattern.compile(",\\s*}$");
private static final ImmutableSet>>
FIX_FACTORIES =
ImmutableSet.of(
CanonicalAnnotationSyntax::dropRedundantParentheses,
CanonicalAnnotationSyntax::dropRedundantValueAttribute,
CanonicalAnnotationSyntax::dropRedundantCurlies);
/** Instantiates a new {@link CanonicalAnnotationSyntax} instance. */
public CanonicalAnnotationSyntax() {}
@Override
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
return FIX_FACTORIES.stream()
.map(op -> op.apply(tree, state))
.flatMap(Optional::stream)
.findFirst()
.map(fix -> describeMatch(tree, fix))
.orElse(Description.NO_MATCH);
}
private static Optional dropRedundantParentheses(AnnotationTree tree, VisitorState state) {
if (!tree.getArguments().isEmpty()) {
/* Parentheses are necessary. */
return Optional.empty();
}
String src = state.getSourceForNode(tree);
if (src == null) {
/* Without the source code there's not much we can do. */
return Optional.empty();
}
int parenIndex = src.indexOf('(');
if (parenIndex < 0) {
/* There are no redundant parentheses. */
return Optional.empty();
}
return Optional.of(SuggestedFix.replace(tree, src.substring(0, parenIndex)));
}
private static Optional dropRedundantValueAttribute(
AnnotationTree tree, VisitorState state) {
List extends ExpressionTree> args = tree.getArguments();
if (args.size() != 1) {
/* The `value` attribute, if specified, cannot be dropped. */
return Optional.empty();
}
ExpressionTree arg = args.get(0);
if (state.getSourceForNode(arg) == null) {
/*
* The annotation argument doesn't have a source representation, e.g. because `value` isn't
* assigned explicitly.
*/
return Optional.empty();
}
ExpressionTree expr = AnnotationMatcherUtils.getArgument(tree, "value");
if (expr == null) {
/* This is not an explicit assignment to the `value` attribute. */
return Optional.empty();
}
/* Replace the assignment with (the simplified representation of) just its value. */
return Optional.of(
SuggestedFix.replace(
arg,
simplifyAttributeValue(expr, state)
.orElseGet(() -> SourceCode.treeToString(expr, state))));
}
private static Optional dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
List fixes = new ArrayList<>();
for (ExpressionTree arg : tree.getArguments()) {
/*
* We'll try to simplify each assignment's RHS; for non-assignment we'll try to simplify
* the expression as a whole.
*/
ExpressionTree value =
(arg instanceof AssignmentTree assignment) ? assignment.getExpression() : arg;
/* Store a fix for each expression that was successfully simplified. */
simplifyAttributeValue(value, state)
.ifPresent(expr -> fixes.add(SuggestedFix.builder().replace(value, expr)));
}
return fixes.stream().reduce(SuggestedFix.Builder::merge).map(SuggestedFix.Builder::build);
}
private static Optional simplifyAttributeValue(ExpressionTree expr, VisitorState state) {
/* Drop curly braces or commas if possible. */
return expr instanceof NewArrayTree newArray
? simplifySingletonArray(newArray, state).or(() -> dropTrailingComma(newArray, state))
: Optional.empty();
}
/** Returns the expression describing the array's sole element, if any. */
private static Optional simplifySingletonArray(NewArrayTree array, VisitorState state) {
return Optional.of(array.getInitializers())
.filter(initializers -> initializers.size() == 1)
.map(initializers -> SourceCode.treeToString(initializers.get(0), state));
}
private static Optional dropTrailingComma(NewArrayTree array, VisitorState state) {
String src = SourceCode.treeToString(array, state);
return Optional.of(TRAILING_ARRAY_COMMA.matcher(src))
.filter(Matcher::find)
.map(m -> src.substring(0, m.start()) + '}');
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy