com.google.errorprone.bugpatterns.RedundantOverride.java Source code

Java tutorial

Introduction

Here is the source code for com.google.errorprone.bugpatterns.RedundantOverride.java

Source

/*
 * Copyright 2019 The Error Prone Authors.
 *
 * 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.collect.ImmutableSet.toImmutableSet;
import static com.google.errorprone.BugPattern.ProvidesFix.REQUIRES_HUMAN_ATTENTION;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.util.ASTHelpers.findSuperMethod;
import static com.google.errorprone.util.ASTHelpers.getReceiver;
import static com.google.errorprone.util.ASTHelpers.getSymbol;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ErrorProneTokens;
import com.sun.source.doctree.DocCommentTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.util.SimpleTreeVisitor;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.lang.model.element.Modifier;

/** Removes overrides which purely pass through to the method in the super class. */
@BugPattern(name = "RedundantOverride", summary = "This override is redundant, and can be removed.", severity = WARNING, providesFix = REQUIRES_HUMAN_ATTENTION)
public final class RedundantOverride extends BugChecker implements MethodTreeMatcher {

    @Override
    public Description matchMethod(MethodTree tree, VisitorState state) {
        MethodSymbol methodSymbol = getSymbol(tree);
        if (methodSymbol == null) {
            return NO_MATCH;
        }
        Optional<MethodSymbol> maybeSuperMethod = findSuperMethod(methodSymbol, state.getTypes());
        if (!maybeSuperMethod.isPresent()) {
            return NO_MATCH;
        }
        MethodSymbol superMethod = maybeSuperMethod.get();
        if (tree.getBody() == null || tree.getBody().getStatements().size() != 1) {
            return NO_MATCH;
        }
        StatementTree statement = tree.getBody().getStatements().get(0);
        ExpressionTree expression = getSingleInvocation(statement);
        if (expression == null) {
            return NO_MATCH;
        }
        MethodInvocationTree methodInvocationTree = (MethodInvocationTree) expression;
        if (!getSymbol(methodInvocationTree).equals(superMethod)) {
            return NO_MATCH;
        }
        ExpressionTree receiver = getReceiver(methodInvocationTree);
        if (!(receiver instanceof IdentifierTree)) {
            return NO_MATCH;
        }
        if (!((IdentifierTree) receiver).getName().contentEquals("super")) {
            return NO_MATCH;
        }
        // Exempt Javadocs; the override might be here to add documentation.
        DocCommentTree docCommentTree = JavacTrees.instance(state.context).getDocCommentTree(state.getPath());
        if (docCommentTree != null) {
            return NO_MATCH;
        }
        // Exempt broadening of visibility.
        if (!methodSymbol.getModifiers().equals(superMethod.getModifiers())) {
            return NO_MATCH;
        }
        // Overriding a protected member in another package broadens the visibility to the new package.
        if (methodSymbol.getModifiers().contains(Modifier.PROTECTED)
                && !Objects.equals(superMethod.packge(), methodSymbol.packge())) {
            return NO_MATCH;
        }
        // Exempt any change in annotations (aside from @Override).
        ImmutableSet<Symbol> superAnnotations = getAnnotations(superMethod);
        ImmutableSet<Symbol> methodAnnotations = getAnnotations(methodSymbol);
        if (!Sets.difference(Sets.symmetricDifference(superAnnotations, methodAnnotations),
                ImmutableSet.of(state.getSymtab().overrideType.tsym)).isEmpty()) {
            return NO_MATCH;
        }
        for (int i = 0; i < tree.getParameters().size(); ++i) {
            if (!(methodInvocationTree.getArguments().get(i) instanceof IdentifierTree)) {
                return NO_MATCH;
            }
            if (!getSymbol(tree.getParameters().get(i))
                    .equals(getSymbol(methodInvocationTree.getArguments().get(i)))) {
                return NO_MATCH;
            }
        }
        // Exempt if there are comments within the body. (Do this last, as it's expensive.)
        if (ErrorProneTokens.getTokens(state.getSourceForNode(tree.getBody()), state.context).stream()
                .anyMatch(t -> !t.comments().isEmpty())) {
            return NO_MATCH;
        }

        return describeMatch(tree, SuggestedFix.delete(tree));
    }

    @Nullable
    private static MethodInvocationTree getSingleInvocation(StatementTree statement) {
        return statement.accept(new SimpleTreeVisitor<MethodInvocationTree, Void>() {
            @Override
            public MethodInvocationTree visitReturn(ReturnTree returnTree, Void unused) {
                return visit(returnTree.getExpression(), null);
            }

            @Override
            public MethodInvocationTree visitExpressionStatement(ExpressionStatementTree expressionStatement,
                    Void unused) {
                return visit(expressionStatement.getExpression(), null);
            }

            @Override
            public MethodInvocationTree visitMethodInvocation(MethodInvocationTree methodInvocationTree,
                    Void unused) {
                return methodInvocationTree;
            }
        }, null);
    }

    private static ImmutableSet<Symbol> getAnnotations(MethodSymbol symbol) {
        return symbol.getRawAttributes().stream().map(a -> a.type.tsym).collect(toImmutableSet());
    }
}