/*
* Copyright 2013 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.errorprone.bugpatterns;
import static com.google.common.base.MoreObjects.firstNonNull;
import com.google.common.base.Preconditions;
import com.google.errorprone.BugCheckerInfo;
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.BugPattern.Suppressibility;
import com.google.errorprone.VisitorState;
import com.google.errorprone.fixes.Fix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Suppressible;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.DoWhileLoopTree;
import com.sun.source.tree.EmptyStatementTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.IfTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.IntersectionTypeTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.LambdaExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ModifiersTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.PrimitiveTypeTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.SwitchTree;
import com.sun.source.tree.SynchronizedTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TryTree;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WhileLoopTree;
import com.sun.source.tree.WildcardTree;
import com.sun.tools.javac.code.Symbol;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.CheckReturnValue;
/**
* A base class for implementing bug checkers. The {@code BugChecker} supplies a Scanner
* implementation for this checker, making it easy to use a single checker. Subclasses should also
* implement one or more of the {@code *Matcher} interfaces in this class to declare which tree node
* types to match against.
*
* @author Colin Decker
* @author Eddie Aftandilian (eaftan@google.com)
*/
public abstract class BugChecker implements Suppressible, Serializable {
private final BugCheckerInfo info;
public BugChecker() {
info = BugCheckerInfo.create(getClass());
}
/**
* Helper to create a Description for the common case where there is a fix.
*/
@CheckReturnValue
protected Description describeMatch(Tree node, Fix fix) {
return buildDescription(node)
.addFix(fix)
.build();
}
/**
* Helper to create a Description for the common case where there is no fix.
*/
@CheckReturnValue
protected Description describeMatch(Tree node) {
return buildDescription(node).build();
}
/**
* Returns a Description builder, which allows you to customize the diagnostic with a custom
* message or multiple fixes.
*/
@CheckReturnValue
protected Description.Builder buildDescription(Tree node) {
return buildDescriptionFromChecker(node, this);
}
/**
* Returns a new builder for {@link Description}s.
*
* @param node the node where the error is
* @param checker the {@code BugChecker} instance that is producing this {@code Description}
*/
@CheckReturnValue
public static Description.Builder buildDescriptionFromChecker(Tree node, BugChecker checker) {
return Description.builder(
Preconditions.checkNotNull(node),
checker.canonicalName(),
checker.linkUrl(),
checker.defaultSeverity(),
checker.message());
}
@Override
public String canonicalName() {
return info.canonicalName();
}
@Override
public Set<String> allNames() {
return info.allNames();
}
public String message() {
return info.message();
}
public SeverityLevel defaultSeverity() {
return info.defaultSeverity();
}
public SeverityLevel severity(Map<String, SeverityLevel> severities) {
return firstNonNull(severities.get(canonicalName()), defaultSeverity());
}
public String linkUrl() {
return info.linkUrl();
}
@Override
public Suppressibility suppressibility() {
return info.suppressibility();
}
@Override
public Set<Class<? extends Annotation>> customSuppressionAnnotations() {
return info.customSuppressionAnnotations();
}
/**
* Returns true if the given tree is annotated with a {@code @SuppressWarnings} that disables this
* bug checker.
*/
public boolean isSuppressed(Tree tree) {
SuppressWarnings suppression = ASTHelpers.getAnnotation(tree, SuppressWarnings.class);
return suppression != null
&& !Collections.disjoint(Arrays.asList(suppression.value()), allNames());
}
/**
* Returns true if the given symbol is annotated with a {@code @SuppressWarnings} that disables
* this bug checker.
*/
public boolean isSuppressed(Symbol symbol) {
SuppressWarnings suppression = ASTHelpers.getAnnotation(symbol, SuppressWarnings.class);
return suppression != null
&& !Collections.disjoint(Arrays.asList(suppression.value()), allNames());
}
public static interface AnnotationTreeMatcher extends Suppressible {
Description matchAnnotation(AnnotationTree tree, VisitorState state);
}
public static interface AnnotatedTypeTreeMatcher extends Suppressible {
Description matchAnnotatedType(AnnotatedTypeTree tree, VisitorState state);
}
public static interface ArrayAccessTreeMatcher extends Suppressible {
Description matchArrayAccess(ArrayAccessTree tree, VisitorState state);
}
public static interface ArrayTypeTreeMatcher extends Suppressible {
Description matchArrayType(ArrayTypeTree tree, VisitorState state);
}
public static interface AssertTreeMatcher extends Suppressible {
Description matchAssert(AssertTree tree, VisitorState state);
}
public static interface AssignmentTreeMatcher extends Suppressible {
Description matchAssignment(AssignmentTree tree, VisitorState state);
}
public static interface BinaryTreeMatcher extends Suppressible {
Description matchBinary(BinaryTree tree, VisitorState state);
}
public static interface BlockTreeMatcher extends Suppressible {
Description matchBlock(BlockTree tree, VisitorState state);
}
public static interface BreakTreeMatcher extends Suppressible {
Description matchBreak(BreakTree tree, VisitorState state);
}
public static interface CaseTreeMatcher extends Suppressible {
Description matchCase(CaseTree tree, VisitorState state);
}
public static interface CatchTreeMatcher extends Suppressible {
Description matchCatch(CatchTree tree, VisitorState state);
}
public static interface ClassTreeMatcher extends Suppressible {
Description matchClass(ClassTree tree, VisitorState state);
}
public static interface CompilationUnitTreeMatcher extends Suppressible {
Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state);
}
public static interface CompoundAssignmentTreeMatcher extends Suppressible {
Description matchCompoundAssignment(CompoundAssignmentTree tree, VisitorState state);
}
public static interface ConditionalExpressionTreeMatcher
extends Suppressible {
Description matchConditionalExpression(ConditionalExpressionTree tree, VisitorState state);
}
public static interface ContinueTreeMatcher extends Suppressible {
Description matchContinue(ContinueTree tree, VisitorState state);
}
public static interface DoWhileLoopTreeMatcher extends Suppressible {
Description matchDoWhileLoop(DoWhileLoopTree tree, VisitorState state);
}
public static interface EmptyStatementTreeMatcher extends Suppressible {
Description matchEmptyStatement(EmptyStatementTree tree, VisitorState state);
}
public static interface EnhancedForLoopTreeMatcher extends Suppressible {
Description matchEnhancedForLoop(EnhancedForLoopTree tree, VisitorState state);
}
// Intentionally skip ErroneousTreeMatcher -- we don't analyze malformed expressions.
public static interface ExpressionStatementTreeMatcher extends Suppressible {
Description matchExpressionStatement(ExpressionStatementTree tree, VisitorState state);
}
public static interface ForLoopTreeMatcher extends Suppressible {
Description matchForLoop(ForLoopTree tree, VisitorState state);
}
public static interface IdentifierTreeMatcher extends Suppressible {
Description matchIdentifier(IdentifierTree tree, VisitorState state);
}
public static interface IfTreeMatcher extends Suppressible {
Description matchIf(IfTree tree, VisitorState state);
}
public static interface ImportTreeMatcher extends Suppressible {
Description matchImport(ImportTree tree, VisitorState state);
}
public static interface InstanceOfTreeMatcher extends Suppressible {
Description matchInstanceOf(InstanceOfTree tree, VisitorState state);
}
public static interface IntersectionTypeTreeMatcher extends Suppressible {
Description matchIntersectionType(IntersectionTypeTree tree, VisitorState state);
}
public static interface LabeledStatementTreeMatcher extends Suppressible {
Description matchLabeledStatement(LabeledStatementTree tree, VisitorState state);
}
public static interface LambdaExpressionTreeMatcher extends Suppressible {
Description matchLambdaExpression(LambdaExpressionTree tree, VisitorState state);
}
public static interface LiteralTreeMatcher extends Suppressible {
Description matchLiteral(LiteralTree tree, VisitorState state);
}
public static interface MemberReferenceTreeMatcher extends Suppressible {
Description matchMemberReference(MemberReferenceTree tree, VisitorState state);
}
public static interface MemberSelectTreeMatcher extends Suppressible {
Description matchMemberSelect(MemberSelectTree tree, VisitorState state);
}
public static interface MethodTreeMatcher extends Suppressible {
Description matchMethod(MethodTree tree, VisitorState state);
}
public static interface MethodInvocationTreeMatcher extends Suppressible {
Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state);
}
public static interface ModifiersTreeMatcher extends Suppressible {
Description matchModifiers(ModifiersTree tree, VisitorState state);
}
public static interface NewArrayTreeMatcher extends Suppressible {
Description matchNewArray(NewArrayTree tree, VisitorState state);
}
public static interface NewClassTreeMatcher extends Suppressible {
Description matchNewClass(NewClassTree tree, VisitorState state);
}
// Intentionally skip OtherTreeMatcher. It seems to be used only for let expressions, which are
// generated by javac to implement autoboxing. We are only interested in source-level constructs.
public static interface ParameterizedTypeTreeMatcher extends Suppressible {
Description matchParameterizedType(ParameterizedTypeTree tree, VisitorState state);
}
public static interface ParenthesizedTreeMatcher extends Suppressible {
Description matchParenthesized(ParenthesizedTree tree, VisitorState state);
}
public static interface PrimitiveTypeTreeMatcher extends Suppressible {
Description matchPrimitiveType(PrimitiveTypeTree tree, VisitorState state);
}
public static interface ReturnTreeMatcher extends Suppressible {
Description matchReturn(ReturnTree tree, VisitorState state);
}
public static interface SwitchTreeMatcher extends Suppressible {
Description matchSwitch(SwitchTree tree, VisitorState state);
}
public static interface SynchronizedTreeMatcher extends Suppressible {
Description matchSynchronized(SynchronizedTree tree, VisitorState state);
}
public static interface ThrowTreeMatcher extends Suppressible {
Description matchThrow(ThrowTree tree, VisitorState state);
}
public static interface TryTreeMatcher extends Suppressible {
Description matchTry(TryTree tree, VisitorState state);
}
public static interface TypeCastTreeMatcher extends Suppressible {
Description matchTypeCast(TypeCastTree tree, VisitorState state);
}
public static interface TypeParameterTreeMatcher extends Suppressible {
Description matchTypeParameter(TypeParameterTree tree, VisitorState state);
}
public static interface UnaryTreeMatcher extends Suppressible {
Description matchUnary(UnaryTree tree, VisitorState state);
}
public static interface UnionTypeTreeMatcher extends Suppressible {
Description matchUnionType(UnionTypeTree tree, VisitorState state);
}
public static interface VariableTreeMatcher extends Suppressible {
Description matchVariable(VariableTree tree, VisitorState state);
}
public static interface WhileLoopTreeMatcher extends Suppressible {
Description matchWhileLoop(WhileLoopTree tree, VisitorState state);
}
public static interface WildcardTreeMatcher extends Suppressible {
Description matchWildcard(WildcardTree tree, VisitorState state);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BugChecker)) {
return false;
}
BugChecker that = (BugChecker) obj;
return this.canonicalName().equals(that.canonicalName())
&& this.defaultSeverity().equals(that.defaultSeverity())
&& this.suppressibility().equals(that.suppressibility());
}
@Override
public int hashCode() {
return Objects.hash(canonicalName(), defaultSeverity(), suppressibility());
}
}