net.sourceforge.pmd.lang.java.internal.AnnotationSuppressionUtil Maven / Gradle / Ivy
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.internal;
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.ASTMemberValue;
import net.sourceforge.pmd.lang.java.ast.ASTMemberValuePair;
import net.sourceforge.pmd.lang.java.ast.Annotatable;
import net.sourceforge.pmd.lang.java.rule.errorprone.ImplicitSwitchFallThroughRule;
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
import net.sourceforge.pmd.lang.rule.Rule;
import net.sourceforge.pmd.reporting.Report;
import net.sourceforge.pmd.reporting.Report.SuppressedViolation;
import net.sourceforge.pmd.reporting.RuleViolation;
import net.sourceforge.pmd.reporting.ViolationSuppressor;
/**
* Helper methods to suppress violations based on annotations.
*
* An annotation suppresses a rule if the annotation is a {@link SuppressWarnings},
* and if the set of suppressed warnings ({@link SuppressWarnings#value()})
* contains at least one of those:
*
* - "PMD" (suppresses all rules);
*
- "PMD.rulename", where rulename is the name of the given rule;
*
- "all" (conventional value to suppress all warnings).
*
*
* Additionally, the following values suppress a specific set of rules:
*
* - {@code "unused"}: suppresses rules like UnusedLocalVariable or UnusedPrivateField;
*
- {@code "serial"}: suppresses BeanMembersShouldSerialize, NonSerializableClass and MissingSerialVersionUID;
*
- TODO "fallthrough" #1899
*
*/
final class AnnotationSuppressionUtil {
private static final Set UNUSED_RULES
= new HashSet<>(Arrays.asList("UnusedPrivateField", "UnusedLocalVariable", "UnusedPrivateMethod",
"UnusedFormalParameter", "UnusedAssignment", "SingularField"));
private static final Set SERIAL_RULES =
new HashSet<>(Arrays.asList("BeanMembersShouldSerialize", "NonSerializableClass", "MissingSerialVersionUID"));
static final ViolationSuppressor JAVA_ANNOT_SUPPRESSOR = new ViolationSuppressor() {
@Override
public String getId() {
return "@SuppressWarnings";
}
@Override
public Report.SuppressedViolation suppressOrNull(RuleViolation rv, @NonNull Node node) {
if (contextSuppresses(node, rv.getRule())) {
return new SuppressedViolation(rv, this, null);
}
return null;
}
};
static final List ALL_JAVA_SUPPRESSORS = listOf(JAVA_ANNOT_SUPPRESSOR);
private AnnotationSuppressionUtil() {
}
static boolean contextSuppresses(Node node, Rule rule) {
boolean result = suppresses(node, rule);
if (!result && node instanceof ASTCompilationUnit) {
for (int i = 0; !result && i < node.getNumChildren(); i++) {
result = AnnotationSuppressionUtil.suppresses(node.getChild(i), rule);
}
}
if (!result) {
Node parent = node.getParent();
while (!result && parent != null) {
result = AnnotationSuppressionUtil.suppresses(parent, rule);
parent = parent.getParent();
}
}
return result;
}
/**
* Returns true if the node has an annotation that suppresses the
* given rule.
*/
private static boolean suppresses(final Node node, Rule rule) {
Annotatable suppressor = getSuppressor(node);
return suppressor != null && hasSuppressWarningsAnnotationFor(suppressor, rule);
}
@Nullable
private static Annotatable getSuppressor(Node node) {
if (node instanceof Annotatable) {
return (Annotatable) node;
} else {
return null;
}
}
private static boolean hasSuppressWarningsAnnotationFor(final Annotatable node, Rule rule) {
return node.getDeclaredAnnotations().any(it -> annotationSuppresses(it, rule));
}
// @formatter:on
private static boolean annotationSuppresses(ASTAnnotation annotation, Rule rule) {
if (TypeTestUtil.isA(SuppressWarnings.class, annotation)) {
for (ASTMemberValue value : annotation.getFlatValue(ASTMemberValuePair.VALUE_ATTR)) {
Object constVal = value.getConstValue();
if (constVal instanceof String) {
String stringVal = (String) constVal;
if ("PMD".equals(stringVal)
|| ("PMD." + rule.getName()).equals(stringVal) // NOPMD uselessparentheses false positive
// Check for standard annotations values
|| "all".equals(stringVal)
|| "serial".equals(stringVal) && SERIAL_RULES.contains(rule.getName())
|| "unused".equals(stringVal) && UNUSED_RULES.contains(rule.getName())
|| "fallthrough".equals(stringVal) && rule instanceof ImplicitSwitchFallThroughRule
) {
return true;
}
}
}
}
return false;
}
}