![JAR search and dependency download from the Maven repository](/logo.png)
com.android.tools.lint.detector.api.JavaContext Maven / Gradle / Ivy
/*
* Copyright (C) 2011 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.detector.api;
import static com.android.SdkConstants.CLASS_CONTEXT;
import static com.android.tools.lint.client.api.JavaParser.ResolvedNode;
import static com.android.tools.lint.client.api.JavaParser.TypeDescriptor;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.client.api.JavaEvaluator;
import com.android.tools.lint.client.api.JavaParser;
import com.android.tools.lint.client.api.JavaParser.ResolvedClass;
import com.android.tools.lint.client.api.LintDriver;
import com.android.tools.lint.detector.api.Detector.JavaPsiScanner;
import com.google.common.collect.Iterators;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnonymousClass;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiEnumConstant;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiJavaCodeReferenceElement;
import com.intellij.psi.PsiJavaFile;
import com.intellij.psi.PsiLabeledStatement;
import com.intellij.psi.PsiMember;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiNewExpression;
import com.intellij.psi.PsiReferenceExpression;
import com.intellij.psi.PsiSwitchStatement;
import com.intellij.psi.util.PsiTreeUtil;
import java.io.File;
import java.util.Iterator;
import lombok.ast.AnnotationElement;
import lombok.ast.AnnotationMethodDeclaration;
import lombok.ast.ClassDeclaration;
import lombok.ast.ConstructorDeclaration;
import lombok.ast.ConstructorInvocation;
import lombok.ast.EnumConstant;
import lombok.ast.Expression;
import lombok.ast.LabelledStatement;
import lombok.ast.MethodDeclaration;
import lombok.ast.MethodInvocation;
import lombok.ast.Node;
import lombok.ast.Position;
import lombok.ast.TypeDeclaration;
import lombok.ast.VariableReference;
/**
* A {@link Context} used when checking Java files.
*
* NOTE: This is not a public or final API; if you rely on this be prepared
* to adjust your code for the next tools release.
*/
public class JavaContext extends Context {
static final String SUPPRESS_COMMENT_PREFIX = "//noinspection ";
/**
* The parse tree
*
* @deprecated Use {@link #javaFile} instead (see {@link JavaPsiScanner})
*/
@Deprecated
private Node compilationUnit;
/** The parse tree */
private PsiJavaFile javaFile;
/** The parser which produced the parse tree */
private final JavaParser parser;
/** Whether this context is in a test source folder */
private boolean testSource;
/**
* Constructs a {@link JavaContext} for running lint on the given file, with
* the given scope, in the given project reporting errors to the given
* client.
*
* @param driver the driver running through the checks
* @param project the project to run lint on which contains the given file
* @param main the main project if this project is a library project, or
* null if this is not a library project. The main project is
* the root project of all library projects, not necessarily the
* directly including project.
* @param file the file to be analyzed
* @param parser the parser to use
*/
public JavaContext(
@NonNull LintDriver driver,
@NonNull Project project,
@Nullable Project main,
@NonNull File file,
@NonNull JavaParser parser) {
super(driver, project, main, file);
this.parser = parser;
}
/**
* Returns a location for the given node
*
* @param node the AST node to get a location for
* @return a location for the given node
*/
@NonNull
public Location getLocation(@NonNull Node node) {
return parser.getLocation(this, node);
}
/**
* Returns a location for the given node range (from the starting offset of the first node to
* the ending offset of the second node).
*
* @param from the AST node to get a starting location from
* @param fromDelta Offset delta to apply to the starting offset
* @param to the AST node to get a ending location from
* @param toDelta Offset delta to apply to the ending offset
* @return a location for the given node
*/
@NonNull
public Location getRangeLocation(
@NonNull Node from,
int fromDelta,
@NonNull Node to,
int toDelta) {
return parser.getRangeLocation(this, from, fromDelta, to, toDelta);
}
/**
* Returns a location for the given node range (from the starting offset of the first node to
* the ending offset of the second node).
*
* @param from the AST node to get a starting location from
* @param fromDelta Offset delta to apply to the starting offset
* @param to the AST node to get a ending location from
* @param toDelta Offset delta to apply to the ending offset
* @return a location for the given node
*/
@NonNull
public Location getRangeLocation(
@NonNull PsiElement from,
int fromDelta,
@NonNull PsiElement to,
int toDelta) {
return parser.getRangeLocation(this, from, fromDelta, to, toDelta);
}
/**
* Returns a location for the given node range (from the starting offset of the first node to
* the ending offset of the second node).
*
* @param from the AST node to get a starting location from
* @param fromDelta Offset delta to apply to the starting offset
* @param length The number of characters to add from the delta
* @return a location for the given node
*/
@NonNull
public Location getRangeLocation(
@NonNull PsiElement from,
int fromDelta,
int length) {
return parser.getRangeLocation(this, from, fromDelta, fromDelta + length);
}
/**
* Returns a {@link Location} for the given node. This attempts to pick a shorter
* location range than the entire node; for a class or method for example, it picks
* the name node (if found). For statement constructs such as a {@code switch} statement
* it will highlight the keyword, etc.
*
* @param node the AST node to create a location for
* @return a location for the given node
*/
@NonNull
public Location getNameLocation(@NonNull Node node) {
return parser.getNameLocation(this, node);
}
/**
* Returns a {@link Location} for the given node. This attempts to pick a shorter
* location range than the entire node; for a class or method for example, it picks
* the name node (if found). For statement constructs such as a {@code switch} statement
* it will highlight the keyword, etc.
*
* @param element the AST node to create a location for
* @return a location for the given node
*/
@NonNull
public Location getNameLocation(@NonNull PsiElement element) {
if (element instanceof PsiSwitchStatement) {
// Just use keyword
return parser.getRangeLocation(this, element, 0, 6); // 6: "switch".length()
}
return parser.getNameLocation(this, element);
}
@NonNull
public Location getLocation(@NonNull PsiElement node) {
return parser.getLocation(this, node);
}
@NonNull
public JavaParser getParser() {
return parser;
}
@NonNull
public JavaEvaluator getEvaluator() {
return parser.getEvaluator();
}
@Nullable
public Node getCompilationUnit() {
return compilationUnit;
}
/**
* Sets the compilation result. Not intended for client usage; the lint infrastructure
* will set this when a context has been processed
*
* @param compilationUnit the parse tree
*/
public void setCompilationUnit(@Nullable Node compilationUnit) {
this.compilationUnit = compilationUnit;
}
/**
* Returns the {@link PsiJavaFile}.
*
* @return the parsed Java source file
*/
@Nullable
public PsiJavaFile getJavaFile() {
return javaFile;
}
/**
* Sets the compilation result. Not intended for client usage; the lint infrastructure
* will set this when a context has been processed
*
* @param javaFile the parse tree
*/
public void setJavaFile(@Nullable PsiJavaFile javaFile) {
this.javaFile = javaFile;
}
@Override
public void report(@NonNull Issue issue, @NonNull Location location,
@NonNull String message) {
if (driver.isSuppressed(this, issue, javaFile)) {
return;
}
super.report(issue, location, message);
}
/**
* Reports an issue applicable to a given AST node. The AST node is used as the
* scope to check for suppress lint annotations.
*
* @param issue the issue to report
* @param scope the AST node scope the error applies to. The lint infrastructure
* will check whether there are suppress annotations on this node (or its enclosing
* nodes) and if so suppress the warning without involving the client.
* @param location the location of the issue, or null if not known
* @param message the message for this warning
*/
public void report(
@NonNull Issue issue,
@Nullable Node scope,
@NonNull Location location,
@NonNull String message) {
if (scope != null && driver.isSuppressed(this, issue, scope)) {
return;
}
super.report(issue, location, message);
}
public void report(
@NonNull Issue issue,
@Nullable PsiElement scope,
@NonNull Location location,
@NonNull String message) {
if (scope != null && driver.isSuppressed(this, issue, scope)) {
return;
}
super.report(issue, location, message);
}
/**
* Report an error.
* Like {@link #report(Issue, Node, Location, String)} but with
* a now-unused data parameter at the end.
*
* @deprecated Use {@link #report(Issue, Node, Location, String)} instead;
* this method is here for custom rule compatibility
*/
@SuppressWarnings("UnusedDeclaration") // Potentially used by external existing custom rules
@Deprecated
public void report(
@NonNull Issue issue,
@Nullable Node scope,
@NonNull Location location,
@NonNull String message,
@SuppressWarnings("UnusedParameters") @Nullable Object data) {
report(issue, scope, location, message);
}
/**
* @deprecated Use {@link PsiTreeUtil#getParentOfType(PsiElement, Class[])}
* with PsiMethod.class instead
*/
@Deprecated
@Nullable
public static Node findSurroundingMethod(Node scope) {
while (scope != null) {
Class extends Node> type = scope.getClass();
// The Lombok AST uses a flat hierarchy of node type implementation classes
// so no need to do instanceof stuff here.
if (type == MethodDeclaration.class || type == ConstructorDeclaration.class) {
return scope;
}
scope = scope.getParent();
}
return null;
}
/**
* @deprecated Use {@link PsiTreeUtil#getParentOfType(PsiElement, Class[])}
* with PsiMethod.class instead
*/
@Deprecated
@Nullable
public static ClassDeclaration findSurroundingClass(@Nullable Node scope) {
while (scope != null) {
Class extends Node> type = scope.getClass();
// The Lombok AST uses a flat hierarchy of node type implementation classes
// so no need to do instanceof stuff here.
if (type == ClassDeclaration.class) {
return (ClassDeclaration) scope;
}
scope = scope.getParent();
}
return null;
}
@Override
@Nullable
protected String getSuppressCommentPrefix() {
return SUPPRESS_COMMENT_PREFIX;
}
/**
* @deprecated Use {@link #isSuppressedWithComment(PsiElement, Issue)} instead
*/
@Deprecated
public boolean isSuppressedWithComment(@NonNull Node scope, @NonNull Issue issue) {
// Check whether there is a comment marker
CharSequence contents = getContents();
assert contents != null; // otherwise we wouldn't be here
Position position = scope.getPosition();
if (position == null) {
return false;
}
int start = position.getStart();
return isSuppressedWithComment(start, issue);
}
public boolean isSuppressedWithComment(@NonNull PsiElement scope, @NonNull Issue issue) {
// Check whether there is a comment marker
CharSequence contents = getContents();
assert contents != null; // otherwise we wouldn't be here
TextRange textRange = scope.getTextRange();
if (textRange == null) {
return false;
}
int start = textRange.getStartOffset();
return isSuppressedWithComment(start, issue);
}
/**
* @deprecated Location handles aren't needed for AST nodes anymore; just use the
* {@link PsiElement} from the AST
*/
@Deprecated
@NonNull
public Location.Handle createLocationHandle(@NonNull Node node) {
return parser.createLocationHandle(this, node);
}
/**
* @deprecated Use PsiElement resolve methods (varies by AST node type, e.g.
* {@link PsiMethodCallExpression#resolveMethod()}
*/
@Deprecated
@Nullable
public ResolvedNode resolve(@NonNull Node node) {
return parser.resolve(this, node);
}
/**
* @deprecated Use {@link JavaEvaluator#findClass(String)} instead
*/
@Deprecated
@Nullable
public ResolvedClass findClass(@NonNull String fullyQualifiedName) {
return parser.findClass(this, fullyQualifiedName);
}
/**
* @deprecated Use {@link PsiExpression#getType()} )} instead
*/
@Deprecated
@Nullable
public TypeDescriptor getType(@NonNull Node node) {
return parser.getType(this, node);
}
/**
* @deprecated Use {@link #getMethodName(PsiElement)} instead
*/
@Deprecated
@Nullable
public static String getMethodName(@NonNull Node call) {
if (call instanceof MethodInvocation) {
return ((MethodInvocation)call).astName().astValue();
} else if (call instanceof ConstructorInvocation) {
return ((ConstructorInvocation)call).astTypeReference().getTypeName();
} else if (call instanceof EnumConstant) {
return ((EnumConstant)call).astName().astValue();
} else {
return null;
}
}
@Nullable
public static String getMethodName(@NonNull PsiElement call) {
if (call instanceof PsiMethodCallExpression) {
return ((PsiMethodCallExpression)call).getMethodExpression().getReferenceName();
} else if (call instanceof PsiNewExpression) {
PsiJavaCodeReferenceElement classReference = ((PsiNewExpression) call).getClassReference();
if (classReference != null) {
return classReference.getReferenceName();
} else {
return null;
}
} else if (call instanceof PsiEnumConstant) {
return ((PsiEnumConstant)call).getName();
} else {
return null;
}
}
/**
* Searches for a name node corresponding to the given node
* @return the name node to use, if applicable
* @deprecated Use {@link #findNameElement(PsiElement)} instead
*/
@Deprecated
@Nullable
public static Node findNameNode(@NonNull Node node) {
if (node instanceof TypeDeclaration) {
// ClassDeclaration, AnnotationDeclaration, EnumDeclaration, InterfaceDeclaration
return ((TypeDeclaration) node).astName();
} else if (node instanceof MethodDeclaration) {
return ((MethodDeclaration)node).astMethodName();
} else if (node instanceof ConstructorDeclaration) {
return ((ConstructorDeclaration)node).astTypeName();
} else if (node instanceof MethodInvocation) {
return ((MethodInvocation)node).astName();
} else if (node instanceof ConstructorInvocation) {
return ((ConstructorInvocation)node).astTypeReference();
} else if (node instanceof EnumConstant) {
return ((EnumConstant)node).astName();
} else if (node instanceof AnnotationElement) {
return ((AnnotationElement)node).astName();
} else if (node instanceof AnnotationMethodDeclaration) {
return ((AnnotationMethodDeclaration)node).astMethodName();
} else if (node instanceof VariableReference) {
return ((VariableReference)node).astIdentifier();
} else if (node instanceof LabelledStatement) {
return ((LabelledStatement)node).astLabel();
}
return null;
}
/**
* Searches for a name node corresponding to the given node
* @return the name node to use, if applicable
*/
@Nullable
public static PsiElement findNameElement(@NonNull PsiElement element) {
if (element instanceof PsiClass) {
if (element instanceof PsiAnonymousClass) {
return ((PsiAnonymousClass)element).getBaseClassReference();
}
return ((PsiClass) element).getNameIdentifier();
} else if (element instanceof PsiMethod) {
return ((PsiMethod) element).getNameIdentifier();
} else if (element instanceof PsiMethodCallExpression) {
return ((PsiMethodCallExpression) element).getMethodExpression().
getReferenceNameElement();
} else if (element instanceof PsiNewExpression) {
return ((PsiNewExpression) element).getClassReference();
} else if (element instanceof PsiField) {
return ((PsiField)element).getNameIdentifier();
} else if (element instanceof PsiAnnotation) {
return ((PsiAnnotation)element).getNameReferenceElement();
} else if (element instanceof PsiReferenceExpression) {
return ((PsiReferenceExpression) element).getReferenceNameElement();
} else if (element instanceof PsiLabeledStatement) {
return ((PsiLabeledStatement)element).getLabelIdentifier();
}
return null;
}
@Deprecated
@NonNull
public static Iterator getParameters(@NonNull Node call) {
if (call instanceof MethodInvocation) {
return ((MethodInvocation) call).astArguments().iterator();
} else if (call instanceof ConstructorInvocation) {
return ((ConstructorInvocation) call).astArguments().iterator();
} else if (call instanceof EnumConstant) {
return ((EnumConstant) call).astArguments().iterator();
} else {
return Iterators.emptyIterator();
}
}
@Deprecated
@Nullable
public static Node getParameter(@NonNull Node call, int parameter) {
Iterator iterator = getParameters(call);
for (int i = 0; i < parameter - 1; i++) {
if (!iterator.hasNext()) {
return null;
}
iterator.next();
}
return iterator.hasNext() ? iterator.next() : null;
}
/**
* Returns true if the given method invocation node corresponds to a call on a
* {@code android.content.Context}
*
* @param node the method call node
* @return true iff the method call is on a class extending context
* @deprecated use {@link JavaEvaluator#isMemberInSubClassOf(PsiMember, String, boolean)} instead
*/
@Deprecated
public boolean isContextMethod(@NonNull MethodInvocation node) {
// Method name used in many other contexts where it doesn't have the
// same semantics; only use this one if we can resolve types
// and we're certain this is the Context method
ResolvedNode resolved = resolve(node);
if (resolved instanceof JavaParser.ResolvedMethod) {
JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolved;
ResolvedClass containingClass = method.getContainingClass();
if (containingClass.isSubclassOf(CLASS_CONTEXT, false)) {
return true;
}
}
return false;
}
/**
* Returns the first ancestor node of the given type
*
* @param element the element to search from
* @param clz the target node type
* @param the target node type
* @return the nearest ancestor node in the parent chain, or null if not found
* @deprecated Use {@link PsiTreeUtil#getParentOfType} instead
*/
@Deprecated
@Nullable
public static T getParentOfType(
@Nullable Node element,
@NonNull Class clz) {
return getParentOfType(element, clz, true);
}
/**
* Returns the first ancestor node of the given type
*
* @param element the element to search from
* @param clz the target node type
* @param strict if true, do not consider the element itself, only its parents
* @param the target node type
* @return the nearest ancestor node in the parent chain, or null if not found
* @deprecated Use {@link PsiTreeUtil#getParentOfType} instead
*/
@Deprecated
@Nullable
public static T getParentOfType(
@Nullable Node element,
@NonNull Class clz,
boolean strict) {
if (element == null) {
return null;
}
if (strict) {
element = element.getParent();
}
while (element != null) {
if (clz.isInstance(element)) {
//noinspection unchecked
return (T) element;
}
element = element.getParent();
}
return null;
}
/**
* Returns the first ancestor node of the given type, stopping at the given type
*
* @param element the element to search from
* @param clz the target node type
* @param strict if true, do not consider the element itself, only its parents
* @param terminators optional node types to terminate the search at
* @param the target node type
* @return the nearest ancestor node in the parent chain, or null if not found
* @deprecated Use {@link PsiTreeUtil#getParentOfType} instead
*/
@Deprecated
@Nullable
public static T getParentOfType(@Nullable Node element,
@NonNull Class clz,
boolean strict,
@NonNull Class extends Node>... terminators) {
if (element == null) {
return null;
}
if (strict) {
element = element.getParent();
}
while (element != null && !clz.isInstance(element)) {
for (Class> terminator : terminators) {
if (terminator.isInstance(element)) {
return null;
}
}
element = element.getParent();
}
//noinspection unchecked
return (T) element;
}
/**
* Returns the first sibling of the given node that is of the given class
*
* @param sibling the sibling to search from
* @param clz the type to look for
* @param the type
* @return the first sibling of the given type, or null
* @deprecated Use {@link PsiTreeUtil#getNextSiblingOfType(PsiElement, Class)} instead
*/
@Deprecated
@Nullable
public static T getNextSiblingOfType(@Nullable Node sibling,
@NonNull Class clz) {
if (sibling == null) {
return null;
}
Node parent = sibling.getParent();
if (parent == null) {
return null;
}
Iterator iterator = parent.getChildren().iterator();
while (iterator.hasNext()) {
if (iterator.next() == sibling) {
break;
}
}
while (iterator.hasNext()) {
Node child = iterator.next();
if (clz.isInstance(child)) {
//noinspection unchecked
return (T) child;
}
}
return null;
}
/**
* Returns the given argument of the given call
*
* @param call the call containing arguments
* @param index the index of the target argument
* @return the argument at the given index
* @throws IllegalArgumentException if index is outside the valid range
*/
@Deprecated
@NonNull
public static Node getArgumentNode(@NonNull MethodInvocation call, int index) {
int i = 0;
for (Expression parameter : call.astArguments()) {
if (i == index) {
return parameter;
}
i++;
}
throw new IllegalArgumentException(Integer.toString(index));
}
/** Whether this compilation unit is in a test folder */
public boolean isTestSource() {
return testSource;
}
/** Sets whether this compilation unit is in a test folder */
public void setTestSource(boolean testSource) {
this.testSource = testSource;
}
}