public static interface Detector.UastScanner
There are several different common patterns for detecting issues:
getApplicableMethodNames()
and
visitMethod(JavaContext, UCallExpression, PsiMethod)
getApplicableConstructorTypes()
and
visitConstructor(JavaContext, UCallExpression, PsiMethod)
getApplicableReferenceNames()
and
visitReference(JavaContext, UReferenceExpression, PsiElement)
applicableSuperClasses()
and
visitClass(JavaContext, UClass)
getApplicableUastTypes()
method, and then provide a
UElementHandler
from the createUastHandler(JavaContext)
where you override the various applicable handler methods. This is
done rather than a general visitor from the root node to avoid
having to have every single lint detector (there are hundreds) do a full
tree traversal on its own.Detector.UastScanner exposes the UAST API to lint checks. UAST is short for "Universal AST" and is an abstract syntax tree library which abstracts away details about Java versus Kotlin versus other similar languages and lets the client of the library access the AST in a unified way.
UAST isn't actually a full replacement for PSI; it augments PSI. Essentially, UAST is used for the inside of methods (e.g. method bodies), and things like field initializers. PSI continues to be used at the outer level: for packages, classes, and methods (declarations and signatures). There are also wrappers around some of these for convenience.
The Detector.UastScanner interface reflects this fact. For example,
when you indicate that you want to check calls to a method named foo
,
the call site node is a UAST node (in this case, UCallExpression
,
but the called method itself is a PsiMethod
, since that method
might be anywhere (including in a library that we don't have source for,
so UAST doesn't make sense.)
PsiMethod
or PsiField
back.
However, the visitor methods have all changed, generally to change
to UAST types. For example, the signature
Detector.JavaPsiScanner.visitMethod(JavaContext, JavaElementVisitor, PsiMethodCallExpression, PsiMethod)
should be changed to visitMethod(JavaContext, UCallExpression, PsiMethod)
.
There are a bunch of new methods on classes like JavaContext
which lets
you pass in a UElement
to match the existing PsiElement
methods.
If you have code which does something specific with PSI classes, the following mapping table in alphabetical order might be helpful, since it lists the corresponding UAST classes.
PSI | UAST |
---|---|
com.intellij.psi. | org.jetbrains.uast. |
IElementType | UastBinaryOperator |
PsiAnnotation | UAnnotation |
PsiAnonymousClass | UAnonymousClass |
PsiArrayAccessExpression | UArrayAccessExpression |
PsiBinaryExpression | UBinaryExpression |
PsiCallExpression | UCallExpression |
PsiCatchSection | UCatchClause |
PsiClass | UClass |
PsiClassObjectAccessExpression | UClassLiteralExpression |
PsiConditionalExpression | UIfExpression |
PsiDeclarationStatement | UDeclarationsExpression |
PsiDoWhileStatement | UDoWhileExpression |
PsiElement | UElement |
PsiExpression | UExpression |
PsiForeachStatement | UForEachExpression |
PsiIdentifier | USimpleNameReferenceExpression |
PsiIfStatement | UIfExpression |
PsiImportStatement | UImportStatement |
PsiImportStaticStatement | UImportStatement |
PsiJavaCodeReferenceElement | UReferenceExpression |
PsiLiteral | ULiteralExpression |
PsiLocalVariable | ULocalVariable |
PsiMethod | UMethod |
PsiMethodCallExpression | UCallExpression |
PsiNameValuePair | UNamedExpression |
PsiNewExpression | UCallExpression |
PsiParameter | UParameter |
PsiParenthesizedExpression | UParenthesizedExpression |
PsiPolyadicExpression | UPolyadicExpression |
PsiPostfixExpression | UPostfixExpression or UUnaryExpression |
PsiPrefixExpression | UPrefixExpression or UUnaryExpression |
PsiReference | UReferenceExpression |
PsiReference | UResolvable |
PsiReferenceExpression | UReferenceExpression |
PsiReturnStatement | UReturnExpression |
PsiSuperExpression | USuperExpression |
PsiSwitchLabelStatement | USwitchClauseExpression |
PsiSwitchStatement | USwitchExpression |
PsiThisExpression | UThisExpression |
PsiThrowStatement | UThrowExpression |
PsiTryStatement | UTryExpression |
PsiTypeCastExpression | UBinaryExpressionWithType |
PsiWhileStatement | UWhileExpression |
getUastParent
instead of getParent
. This is to avoid
method name clashes on some elements which are both UAST elements
and PSI elements at the same time - such as UMethod
.
PsiMethod.getBody()
. This will only give you the PSI child content,
which won't work for example when dealing with Kotlin methods.
Normally lint passes you the UMethod which you should be procesing
instead. But if for some reason you need to look up the UAST method
body from a PsiMethod, use this:
UastContext context = UastUtils.getUastContext(element); UExpression body = context.getMethodBody(method);Similarly if you have a
PsiField
and you want to look up its field
initializer, use this:
UastContext context = UastUtils.getUastContext(element); UExpression initializer = context.getInitializerBody(field);
< call.getMethodExpression().getReferenceName(); --- > call.getMethodName()
< call.getMethodExpression().getQualifierExpression(); --- > call.getReceiver()
< PsiExpression[] args = call.getArgumentList().getExpressions(); --- > ListTypically you also need to go through your code and replace array access,args = call.getValueArguments();
arg[i]
, with list access, arg.get(i)
. Or in Kotlin, just
arg[i]
...
UastExpressionUtils
- such as UastExpressionUtils.isAssignment(UElement)
.
Take a look at all the methods there now - there are methods for checking whether
a call is a constructor, whether an expression is an array initializer, etc etc.
ResourceReference
. Here's an example of code which has a UExpression
and wants to know it's referencing a R.styleable resource:
ResourceReference reference = ResourceReference.get(expression); if (reference == null || reference.getType() != ResourceType.STYLEABLE) { return; } ...
PsiBinaryExpression
for things like checking comparator
operators or arithmetic combination of operands, you can replace this with
UBinaryExpression
. But you normally shouldn't; you should use
UPolyadicExpression
instead. A polyadic expression is just like a binary
expression, but possibly with more than two terms. With the old parser backend,
an expression like "A + B + C" would be represented by nested binary expressions
(first A + B, then a parent element which combined that binary expression with C).
However, this will now be provided as a UPolyadicExpression
instead. And
the binary case is handled trivially without the need to special case it.
PSI | UAST |
---|---|
getArgumentList | getValueArguments |
getCatchSections | getCatchClauses |
getDeclaredElements | getDeclarations |
getElseBranch | getElseExpression |
getInitializer | getUastInitializer |
getLExpression | getLeftOperand |
getOperationTokenType | getOperator |
getOwner | getUastParent |
getParent | getUastParent |
getRExpression | getRightOperand |
getReturnValue | getReturnExpression |
getText | asSourceString |
getThenBranch | getThenExpression |
getType | getExpressionType |
getTypeParameters | getTypeArguments |
resolveMethod | resolve |
getApplicableUastTypes()
and then providing
a visitor where they implement the corresponding visit methods. However, from
these visitors you should not be calling super.visitX. To remove this
whole confusion, lint now provides a separate class, UElementHandler
.
For the shared traversal, just provide this handler instead and implement the
appropriate visit methods. It will throw an error if you register element types
in getApplicableUastTypes() that you don't override.
Detector.UastScanner
so a lot of the same concepts
apply; then follow the above section.
Modifier and Type | Method and Description |
---|---|
java.util.List<java.lang.String> |
applicableSuperClasses()
Returns a list of fully qualified names for super classes that this
detector cares about.
|
boolean |
appliesToResourceRefs()
Returns whether this detector cares about Android resource references
(such as
R.layout.main or R.string.app_name ). |
UElementHandler |
createUastHandler(JavaContext context)
Create a parse tree visitor to process the parse tree.
|
java.util.List<java.lang.String> |
getApplicableConstructorTypes()
Return the list of constructor types this detector is interested in, or
null.
|
java.util.List<java.lang.String> |
getApplicableMethodNames()
Return the list of method names this detector is interested in, or
null.
|
java.util.List<java.lang.Class<? extends com.intellij.psi.PsiElement>> |
getApplicablePsiTypes() |
java.util.List<java.lang.String> |
getApplicableReferenceNames()
Return the list of reference names types this detector is interested in, or null.
|
java.util.List<java.lang.Class<? extends org.jetbrains.uast.UElement>> |
getApplicableUastTypes()
Return the types of AST nodes that the visitor returned from
Detector.createJavaVisitor(JavaContext) should visit. |
void |
visitClass(JavaContext context,
org.jetbrains.uast.UClass declaration)
Called for each class that extends one of the super classes specified with
applicableSuperClasses() . |
void |
visitClass(JavaContext context,
org.jetbrains.uast.ULambdaExpression lambda)
Like
visitClass(JavaContext, UClass) , but used for lambdas in
SAM (single abstract method) types. |
void |
visitConstructor(JavaContext context,
org.jetbrains.uast.UCallExpression node,
com.intellij.psi.PsiMethod constructor)
Method invoked for any constructor calls found that matches any names
returned by
getApplicableConstructorTypes() . |
void |
visitMethod(JavaContext context,
org.jetbrains.uast.UCallExpression node,
com.intellij.psi.PsiMethod method)
Method invoked for any method calls found that matches any names
returned by
getApplicableMethodNames() . |
void |
visitReference(JavaContext context,
org.jetbrains.uast.UReferenceExpression reference,
com.intellij.psi.PsiElement referenced)
Method invoked for any references found that matches any names returned by
getApplicableReferenceNames() . |
void |
visitResourceReference(JavaContext context,
org.jetbrains.uast.UElement node,
com.android.resources.ResourceType type,
java.lang.String name,
boolean isFramework)
Called for any resource references (such as
R.layout.main
found in Java code, provided this detector returned true from
appliesToResourceRefs() . |
@Nullable UElementHandler createUastHandler(@NonNull JavaContext context)
Detector.UastScanner
detectors must provide a visitor, unless they
either return true from appliesToResourceRefs()
or return
non null from getApplicableMethodNames()
.
If you return specific AST node types from
getApplicableUastTypes()
()}, then the visitor will only
be called for the specific requested node types. This is more
efficient, since it allows many detectors that apply to only a small
part of the AST (such as method call nodes) to share iteration of the
majority of the parse tree.
If you return null from getApplicableUastTypes()
()}, then your
visitor will be called from the top and all node types visited.
Note that a new visitor is created for each separate compilation unit, so you can store per file state in the visitor.
context
- the Context
for the file being analyzed@Nullable java.util.List<java.lang.Class<? extends org.jetbrains.uast.UElement>> getApplicableUastTypes()
Detector.createJavaVisitor(JavaContext)
should visit. See the
documentation for Detector.createJavaVisitor(JavaContext)
for details
on how the shared visitor is used.
If you return null from this method, then the visitor will process the full tree instead.
Note that for the shared visitor, the return codes from the visit methods are ignored: returning true will not prune iteration of the subtree, since there may be other node types interested in the children. If you need to ensure that your visitor only processes a part of the tree, use a full visitor instead. See the OverdrawDetector implementation for an example of this.
@Nullable java.util.List<java.lang.Class<? extends com.intellij.psi.PsiElement>> getApplicablePsiTypes()
@Nullable java.util.List<java.lang.String> getApplicableMethodNames()
Detector.visitMethod(JavaContext, JavaElementVisitor, PsiMethodCallExpression, PsiMethod)
method for processing. The visitor created by
Detector.createPsiVisitor(JavaContext)
is also passed to that
method, although it can be null.
This makes it easy to write detectors that focus on some fixed calls.
For example, the StringFormatDetector uses this mechanism to look for
"format" calls, and when found it looks around (using the AST's
PsiElement.getParent()
method) to see if it's called on
a String class instance, and if so do its normal processing. Note
that since it doesn't need to do any other AST processing, that
detector does not actually supply a visitor.
void visitMethod(@NonNull JavaContext context, @NonNull org.jetbrains.uast.UCallExpression node, @NonNull com.intellij.psi.PsiMethod method)
getApplicableMethodNames()
. This also passes
back the visitor that was created by
Detector.createJavaVisitor(JavaContext)
, but a visitor is not
required. It is intended for detectors that need to do additional AST
processing, but also want the convenience of not having to look for
method names on their own.context
- the context of the lint requestnode
- the PsiMethodCallExpression
node for the invoked methodmethod
- the PsiMethod
being called@Nullable java.util.List<java.lang.String> getApplicableConstructorTypes()
Detector.visitConstructor(JavaContext, JavaElementVisitor, PsiNewExpression, PsiMethod)
method for processing. The visitor created by
Detector.createJavaVisitor(JavaContext)
is also passed to that
method, although it can be null.
This makes it easy to write detectors that focus on some fixed constructors.
void visitConstructor(@NonNull JavaContext context, @NonNull org.jetbrains.uast.UCallExpression node, @NonNull com.intellij.psi.PsiMethod constructor)
getApplicableConstructorTypes()
. This also passes
back the visitor that was created by
Detector.createPsiVisitor(JavaContext)
, but a visitor is not
required. It is intended for detectors that need to do additional AST
processing, but also want the convenience of not having to look for
method names on their own.context
- the context of the lint requestnode
- the PsiNewExpression
node for the invoked methodconstructor
- the called constructor method@Nullable java.util.List<java.lang.String> getApplicableReferenceNames()
Detector.visitReference(JavaContext, JavaElementVisitor,
PsiJavaCodeReferenceElement, PsiElement)
method for processing. The visitor created by
Detector.createJavaVisitor(JavaContext)
is also passed to that method, although it can be
null.
This makes it easy to write detectors that focus on some fixed references.
void visitReference(@NonNull JavaContext context, @NonNull org.jetbrains.uast.UReferenceExpression reference, @NonNull com.intellij.psi.PsiElement referenced)
getApplicableReferenceNames()
. This also passes back the visitor that was created by
Detector.createPsiVisitor(JavaContext)
, but a visitor is not required. It is intended for
detectors that need to do additional AST processing, but also want the convenience of not
having to look for method names on their own.context
- the context of the lint requestreference
- the PsiJavaCodeReferenceElement
elementreferenced
- the referenced elementboolean appliesToResourceRefs()
R.layout.main
or R.string.app_name
). If it
does, then the visitor will look for these patterns, and if found, it
will invoke visitResourceReference(com.android.tools.lint.detector.api.JavaContext, org.jetbrains.uast.UElement, com.android.resources.ResourceType, java.lang.String, boolean)
passing the resource type
and resource name. It also passes the visitor, if any, that was
created by Detector.createJavaVisitor(JavaContext)
, such that a
detector can do more than just look for resources.void visitResourceReference(@NonNull JavaContext context, @NonNull org.jetbrains.uast.UElement node, @NonNull com.android.resources.ResourceType type, @NonNull java.lang.String name, boolean isFramework)
R.layout.main
found in Java code, provided this detector returned true
from
appliesToResourceRefs()
.context
- the lint scanning contextnode
- the variable reference for the resourcetype
- the resource type, such as "layout" or "string"name
- the resource name, such as "main" from R.layout.main
isFramework
- whether the resource is a framework resource (android.R) or a local
project resource (R)@Nullable java.util.List<java.lang.String> applicableSuperClasses()
visitClass(JavaContext, UClass)
(and sometimes
visitClass(JavaContext, ULambdaExpression)
when it encounters
subclasses and lambdas for these types.void visitClass(@NonNull JavaContext context, @NonNull org.jetbrains.uast.UClass declaration)
applicableSuperClasses()
.
Note: This method will not be called for PsiTypeParameter
classes. These
aren't really classes in the sense most lint detectors think of them, so these
are excluded to avoid having lint checks that don't defensively code for these
accidentally report errors on type parameters. If you really need to check these,
use getApplicablePsiTypes()
with PsiTypeParameter.class
instead.
context
- the lint scanning contextdeclaration
- the class declaration node, or null for anonymous classesvoid visitClass(@NonNull JavaContext context, @NonNull org.jetbrains.uast.ULambdaExpression lambda)
visitClass(JavaContext, UClass)
, but used for lambdas in
SAM (single abstract method) types. For example, if you have
have this method:
void enqueue(Runnable runnable) { ... } ... enqueue({ something(); })then the lambda being passed to the call can be thought of as a class implementing the Runnable interface.
The set of target types for the lambda are provided in applicableSuperClasses()
context
- the lint scanning contextlambda
- the lambda