/*
* Copyright 2017 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.errorprone.BugPattern.Category.JDK;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import static com.google.errorprone.matchers.Description.NO_MATCH;
import static com.google.errorprone.util.ASTHelpers.findSuperMethods;
import static com.google.errorprone.util.ASTHelpers.hasAnnotation;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.DoNotCall;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import javax.lang.model.element.Modifier;
/** @author cushon@google.com (Liam Miller-Cushon) */
// TODO(cushon): this should subsume ImmutableModification and LocalizableWrongToString
@BugPattern(
name = "DoNotCall",
category = JDK,
summary = "This method should not be called.",
severity = ERROR
)
public class DoNotCallChecker extends BugChecker
implements MethodTreeMatcher, MethodInvocationTreeMatcher {
@Override
public Description matchMethod(MethodTree tree, VisitorState state) {
MethodSymbol symbol = ASTHelpers.getSymbol(tree);
if (symbol == null) {
return NO_MATCH;
}
if (hasAnnotation(tree, DoNotCall.class, state)) {
if (symbol.getModifiers().contains(Modifier.ABSTRACT)) {
return NO_MATCH;
}
if (symbol.getModifiers().contains(Modifier.FINAL)) {
return NO_MATCH;
}
if (symbol.owner.enclClass().getModifiers().contains(Modifier.FINAL)) {
return NO_MATCH;
}
return buildDescription(tree)
.setMessage("Methods annotated with @DoNotCall should be final.")
.addFix(SuggestedFixes.addModifiers(tree, state, Modifier.FINAL))
.build();
}
return findSuperMethods(symbol, state.getTypes())
.stream()
.filter(s -> hasAnnotation(s, DoNotCall.class, state))
.findAny()
.map(
s -> {
String message =
String.format(
"Method overrides %s in %s which is annotated @DoNotCall,"
+ " it should also be annotated.",
s.getSimpleName(), s.owner.getSimpleName());
return buildDescription(tree)
.setMessage(message)
.addFix(
SuggestedFix.builder()
.addImport(DoNotCall.class.getName())
.prefixWith(tree, "@DoNotCall ")
.build())
.build();
})
.orElse(NO_MATCH);
}
@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
DoNotCall doNotCall = ASTHelpers.getAnnotation(tree, DoNotCall.class);
if (doNotCall == null) {
return NO_MATCH;
}
StringBuilder message = new StringBuilder("This method should not be called");
if (doNotCall.value().isEmpty()) {
message.append(", see its documentation for details.");
} else {
message.append(": ").append(doNotCall.value());
}
return buildDescription(tree).setMessage(message.toString()).build();
}
}