| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * 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.android.tools.lint.checks; |
| |
| import static com.android.SdkConstants.FQCN_SUPPRESS_LINT; |
| import static com.android.SdkConstants.SUPPRESS_LINT; |
| |
| import com.android.annotations.NonNull; |
| import com.android.tools.lint.client.api.IssueRegistry; |
| import com.android.tools.lint.detector.api.Category; |
| import com.android.tools.lint.detector.api.Context; |
| import com.android.tools.lint.detector.api.Detector; |
| import com.android.tools.lint.detector.api.Implementation; |
| import com.android.tools.lint.detector.api.Issue; |
| import com.android.tools.lint.detector.api.JavaContext; |
| import com.android.tools.lint.detector.api.Scope; |
| import com.android.tools.lint.detector.api.Severity; |
| import com.android.tools.lint.detector.api.Speed; |
| |
| import java.io.File; |
| import java.util.Collections; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import lombok.ast.Annotation; |
| import lombok.ast.AnnotationElement; |
| import lombok.ast.AnnotationValue; |
| import lombok.ast.ArrayInitializer; |
| import lombok.ast.AstVisitor; |
| import lombok.ast.Block; |
| import lombok.ast.ConstructorDeclaration; |
| import lombok.ast.Expression; |
| import lombok.ast.ForwardingAstVisitor; |
| import lombok.ast.MethodDeclaration; |
| import lombok.ast.Modifiers; |
| import lombok.ast.Node; |
| import lombok.ast.Select; |
| import lombok.ast.StrictListAccessor; |
| import lombok.ast.StringLiteral; |
| import lombok.ast.TypeBody; |
| import lombok.ast.VariableDefinition; |
| import lombok.ast.VariableDefinitionEntry; |
| |
| /** |
| * Checks annotations to make sure they are valid |
| */ |
| public class AnnotationDetector extends Detector implements Detector.JavaScanner { |
| /** Placing SuppressLint on a local variable doesn't work for class-file based checks */ |
| public static final Issue ISSUE = Issue.create( |
| "LocalSuppress", //$NON-NLS-1$ |
| "@SuppressLint on invalid element", |
| |
| "The `@SuppressAnnotation` is used to suppress Lint warnings in Java files. However, " + |
| "while many lint checks analyzes the Java source code, where they can find " + |
| "annotations on (for example) local variables, some checks are analyzing the " + |
| "`.class` files. And in class files, annotations only appear on classes, fields " + |
| "and methods. Annotations placed on local variables disappear. If you attempt " + |
| "to suppress a lint error for a class-file based lint check, the suppress " + |
| "annotation not work. You must move the annotation out to the surrounding method.", |
| |
| Category.CORRECTNESS, |
| 3, |
| Severity.ERROR, |
| new Implementation( |
| AnnotationDetector.class, |
| Scope.JAVA_FILE_SCOPE)); |
| |
| /** Constructs a new {@link AnnotationDetector} check */ |
| public AnnotationDetector() { |
| } |
| |
| @Override |
| public boolean appliesTo(@NonNull Context context, @NonNull File file) { |
| return true; |
| } |
| |
| @NonNull |
| @Override |
| public Speed getSpeed() { |
| return Speed.FAST; |
| } |
| |
| // ---- Implements JavaScanner ---- |
| |
| @Override |
| public List<Class<? extends Node>> getApplicableNodeTypes() { |
| return Collections.<Class<? extends Node>>singletonList(Annotation.class); |
| } |
| |
| @Override |
| public AstVisitor createJavaVisitor(@NonNull JavaContext context) { |
| return new AnnotationChecker(context); |
| } |
| |
| private static class AnnotationChecker extends ForwardingAstVisitor { |
| private final JavaContext mContext; |
| |
| public AnnotationChecker(JavaContext context) { |
| mContext = context; |
| } |
| |
| @Override |
| public boolean visitAnnotation(Annotation node) { |
| String type = node.astAnnotationTypeReference().getTypeName(); |
| if (SUPPRESS_LINT.equals(type) || FQCN_SUPPRESS_LINT.equals(type)) { |
| Node parent = node.getParent(); |
| if (parent instanceof Modifiers) { |
| parent = parent.getParent(); |
| if (parent instanceof VariableDefinition) { |
| for (AnnotationElement element : node.astElements()) { |
| AnnotationValue valueNode = element.astValue(); |
| if (valueNode == null) { |
| continue; |
| } |
| if (valueNode instanceof StringLiteral) { |
| StringLiteral literal = (StringLiteral) valueNode; |
| String id = literal.astValue(); |
| if (!checkId(node, id)) { |
| return super.visitAnnotation(node); |
| } |
| } else if (valueNode instanceof ArrayInitializer) { |
| ArrayInitializer array = (ArrayInitializer) valueNode; |
| StrictListAccessor<Expression, ArrayInitializer> expressions = |
| array.astExpressions(); |
| if (expressions == null) { |
| continue; |
| } |
| Iterator<Expression> arrayIterator = expressions.iterator(); |
| while (arrayIterator.hasNext()) { |
| Expression arrayElement = arrayIterator.next(); |
| if (arrayElement instanceof StringLiteral) { |
| String id = ((StringLiteral) arrayElement).astValue(); |
| if (!checkId(node, id)) { |
| return super.visitAnnotation(node); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| return super.visitAnnotation(node); |
| } |
| |
| private boolean checkId(Annotation node, String id) { |
| IssueRegistry registry = mContext.getDriver().getRegistry(); |
| Issue issue = registry.getIssue(id); |
| // Special-case the ApiDetector issue, since it does both source file analysis |
| // only on field references, and class file analysis on the rest, so we allow |
| // annotations outside of methods only on fields |
| if (issue != null && !issue.getImplementation().getScope().contains(Scope.JAVA_FILE) |
| || issue == ApiDetector.UNSUPPORTED) { |
| // Ensure that this isn't a field |
| Node parent = node.getParent(); |
| while (parent != null) { |
| if (parent instanceof MethodDeclaration |
| || parent instanceof ConstructorDeclaration |
| || parent instanceof Block) { |
| break; |
| } else if (parent instanceof TypeBody) { // It's a field |
| return true; |
| } else if (issue == ApiDetector.UNSUPPORTED |
| && parent instanceof VariableDefinition) { |
| VariableDefinition definition = (VariableDefinition) parent; |
| for (VariableDefinitionEntry entry : definition.astVariables()) { |
| Expression initializer = entry.astInitializer(); |
| if (initializer instanceof Select) { |
| return true; |
| } |
| } |
| } |
| parent = parent.getParent(); |
| if (parent == null) { |
| return true; |
| } |
| } |
| |
| // This issue doesn't have AST access: annotations are not |
| // available for local variables or parameters |
| mContext.report(ISSUE, node, mContext.getLocation(node), String.format( |
| "The `@SuppressLint` annotation cannot be used on a local " + |
| "variable with the lint check '%1$s': move out to the " + |
| "surrounding method", id)); |
| return false; |
| } |
| |
| return true; |
| } |
| } |
| } |