From 968b5341e61c04586e9de6c48b01ce4668e26015 Mon Sep 17 00:00:00 2001
From: Tor Norbye
3.8 Detector
3.9 Detector Test
-4 Publishing a Lint Check
- 4.1 Android
- 4.1.1 AAR Support
- 4.1.2 lintPublish Configuration
- 4.1.3 Local Checks
- 4.1.4 Unpublishing
-5 Lint Check Unit Testing
- 5.1 Creating a Unit Test
- 5.2 Computing the Expected Output
- 5.3 Test Files
- 5.4 Trimming indents?
- 5.5 Dollars in Raw Strings
- 5.6 Quickfixes
- 5.7 Library Dependencies and Stubs
- 5.8 Binary and Compiled Source Files
- 5.9 Base64-encoded gzipped byte code
- 5.10 My Detector Isn't Invoked From a Test!
- 5.11 Language Level
-6 Test Modes
- 6.1 How to debug
- 6.2 Handling Intentional Failures
- 6.3 Source-Modifying Test Modes
- 6.3.1 Fully Qualified Names
- 6.3.2 Import Aliasing
- 6.3.3 Type Aliasing
- 6.3.4 Parenthesis Mode
- 6.3.5 Argument Reordering
- 6.3.6 Body Removal
- 6.3.7 If to When Replacement
- 6.3.8 Whitespace Mode
- 6.3.9 CDATA Mode
- 6.3.10 Suppressible Mode
-7 Adding Quick Fixes
- 7.1 Introduction
- 7.2 The LintFix builder class
- 7.3 Creating a LintFix
- 7.4 Available Fixes
- 7.5 Combining Fixes
- 7.6 Refactoring Java and Kotlin code
- 7.7 Regular Expressions and Back References
- 7.8 Emitting quick fix XML to apply on CI
-8 Partial Analysis
- 8.1 About
- 8.2 The Problem
- 8.3 Overview
- 8.4 Does My Detector Need Work?
- 8.4.1 Catching Mistakes: Blocking Access to Main Project
- 8.4.2 Catching Mistakes: Simulated App Module
- 8.4.3 Catching Mistakes: Diffing Results
- 8.4.4 Catching Mistakes: Remaining Issues
- 8.5 Incidents
- 8.6 Constraints
- 8.7 Incident LintMaps
- 8.8 Module LintMaps
- 8.9 Optimizations
-9 Data Flow Analyzer
- 9.1 Usage
- 9.2 Self-referencing Calls
- 9.3 Kotlin Scoping Functions
- 9.4 Limitations
- 9.5 Escaping Values
- 9.5.1 Returns
- 9.5.2 Parameters
- 9.5.3 Fields
- 9.6 Non Local Analysis
- 9.7 Examples
- 9.7.1 Simple Example
- 9.7.2 Complex Example
- 9.8 TargetMethodDataFlowAnalyzer
-10 Annotations
- 10.1 Basics
- 10.2 Annotation Usage Types and isApplicableAnnotationUsage
- 10.2.1 Method Override
- 10.2.2 Method Return
- 10.2.3 Handling Usage Types
- 10.2.4 Usage Types Filtered By Default
- 10.2.5 Scopes
- 10.2.6 Inherited Annotations
-11 Options
- 11.1 Usage
- 11.2 Creating Options
- 11.3 Reading Options
- 11.4 Specific Configurations
- 11.5 Files
- 11.6 Constraints
- 11.7 Testing Options
- 11.8 Supporting Lint 4.2, 7.0 and 7.1
-12 Error Message Conventions
- 12.1 Length
- 12.2 Formatting
- 12.3 Punctuation
- 12.4 Include Details
- 12.5 Reference Android By Number
- 12.6 Keep Messages Stable
- 12.7 Plurals
- 12.8 Examples
-13 Frequently Asked Questions
- 13.0.1 My detector callbacks aren't invoked
- 13.0.2 My lint check works from the unit test but not in the IDE
- 13.0.3 visitAnnotationUsage
isn't called for annotations
- 13.0.4 How do I check if a UAST or PSI element is for Java or Kotlin?
- 13.0.5 What if I need a PsiElement
and I have a UElement
?
- 13.0.6 How do I get the UMethod
for a PsiMethod
?
- 13.0.7 How do get a JavaEvaluator
?
- 13.0.8 How do I check whether an element is internal?
- 13.0.9 Is element inline, sealed, operator, infix, suspend, data?
- 13.0.10 How do I look up a class if I have its fully qualified name?
- 13.0.11 How do I look up a class if I have a PsiType?
- 13.0.12 How do I look up hierarhcy annotations for an element?
- 13.0.13 How do I look up if a class is a subclass of another?
- 13.0.14 How do I know which parameter a call argument corresponds to?
- 13.0.15 How can my lint checks target two different versions of lint?
- 13.0.16 Can I make my lint check “not suppressible?”
- 13.0.17 Why are overloaded operators not handled?
- 13.0.18 How do I check out the current lint source code?
- 13.0.19 Where do I find examples of lint checks?
-14 Appendix: Recent Changes
-15 Appendix: Environment Variables and System Properties
- 15.1 Environment Variables
- 15.1.1 Detector Configuration Variables
- 15.1.2 Lint Configuration Variables
- 15.1.3 Lint Development Variables
- 15.2 System Properties
+4 AST Analysis
+ 4.1 AST Analysis
+ 4.2 UAST
+ 4.3 UAST: The Java View
+ 4.4 Expressions
+ 4.5 UElement
+ 4.6 Visiting
+ 4.7 UElement to PSI Mapping
+ 4.8 PSI to UElement
+ 4.9 UAST versus PSI
+ 4.10 Kotlin Analysis API
+ 4.10.1 Nothing Type?
+ 4.10.2 Compiled Metadata
+ 4.10.3 Configuring lint to use K2
+ 4.11 Recipes
+ 4.11.1 Resolve a function call
+ 4.11.2 Resolve a variable reference
+ 4.11.3 Get the containing class of a symbol
+ 4.11.4 Get the fully qualified name of a class
+ 4.11.5 Look up the deprecation status of a symbol
+ 4.11.6 Look up visibility
+ 4.11.7 Get the KtType of a class symbol
+ 4.11.8 Resolve a KtType into a class
+ 4.11.9 See if two types refer to the same raw class (erasure):
+ 4.11.10 For an extension method, get the receiver type:
+ 4.11.11 Get the PsiFile containing a symbol declaration
+5 Publishing a Lint Check
+ 5.1 Android
+ 5.1.1 AAR Support
+ 5.1.2 lintPublish Configuration
+ 5.1.3 Local Checks
+ 5.1.4 Unpublishing
+6 Lint Check Unit Testing
+ 6.1 Creating a Unit Test
+ 6.2 Computing the Expected Output
+ 6.3 Test Files
+ 6.4 Trimming indents?
+ 6.5 Dollars in Raw Strings
+ 6.6 Quickfixes
+ 6.7 Library Dependencies and Stubs
+ 6.8 Binary and Compiled Source Files
+ 6.9 Base64-encoded gzipped byte code
+ 6.10 My Detector Isn't Invoked From a Test!
+ 6.11 Language Level
+7 Test Modes
+ 7.1 How to debug
+ 7.2 Handling Intentional Failures
+ 7.3 Source-Modifying Test Modes
+ 7.3.1 Fully Qualified Names
+ 7.3.2 Import Aliasing
+ 7.3.3 Type Aliasing
+ 7.3.4 Parenthesis Mode
+ 7.3.5 Argument Reordering
+ 7.3.6 Body Removal
+ 7.3.7 If to When Replacement
+ 7.3.8 Whitespace Mode
+ 7.3.9 CDATA Mode
+ 7.3.10 Suppressible Mode
+ 7.3.11 @JvmOverloads Test Mode
+8 Adding Quick Fixes
+ 8.1 Introduction
+ 8.2 The LintFix builder class
+ 8.3 Creating a LintFix
+ 8.4 Available Fixes
+ 8.5 Combining Fixes
+ 8.6 Refactoring Java and Kotlin code
+ 8.7 Regular Expressions and Back References
+ 8.8 Emitting quick fix XML to apply on CI
+9 Partial Analysis
+ 9.1 About
+ 9.2 The Problem
+ 9.3 Overview
+ 9.4 Does My Detector Need Work?
+ 9.4.1 Catching Mistakes: Blocking Access to Main Project
+ 9.4.2 Catching Mistakes: Simulated App Module
+ 9.4.3 Catching Mistakes: Diffing Results
+ 9.4.4 Catching Mistakes: Remaining Issues
+ 9.5 Incidents
+ 9.6 Constraints
+ 9.7 Incident LintMaps
+ 9.8 Module LintMaps
+ 9.9 Optimizations
+10 Data Flow Analyzer
+ 10.1 Usage
+ 10.2 Self-referencing Calls
+ 10.3 Kotlin Scoping Functions
+ 10.4 Limitations
+ 10.5 Escaping Values
+ 10.5.1 Returns
+ 10.5.2 Parameters
+ 10.5.3 Fields
+ 10.6 Non Local Analysis
+ 10.7 Examples
+ 10.7.1 Simple Example
+ 10.7.2 Complex Example
+ 10.8 TargetMethodDataFlowAnalyzer
+11 Annotations
+ 11.1 Basics
+ 11.2 Annotation Usage Types and isApplicableAnnotationUsage
+ 11.2.1 Method Override
+ 11.2.2 Method Return
+ 11.2.3 Handling Usage Types
+ 11.2.4 Usage Types Filtered By Default
+ 11.2.5 Scopes
+ 11.2.6 Inherited Annotations
+12 Options
+ 12.1 Usage
+ 12.2 Creating Options
+ 12.3 Reading Options
+ 12.4 Specific Configurations
+ 12.5 Files
+ 12.6 Constraints
+ 12.7 Testing Options
+ 12.8 Supporting Lint 4.2, 7.0 and 7.1
+13 Error Message Conventions
+ 13.1 Length
+ 13.2 Formatting
+ 13.3 Punctuation
+ 13.4 Include Details
+ 13.5 Reference Android By Number
+ 13.6 Keep Messages Stable
+ 13.7 Plurals
+ 13.8 Examples
+14 Frequently Asked Questions
+ 14.0.1 My detector callbacks aren't invoked
+ 14.0.2 My lint check works from the unit test but not in the IDE
+ 14.0.3 visitAnnotationUsage
isn't called for annotations
+ 14.0.4 How do I check if a UAST or PSI element is for Java or Kotlin?
+ 14.0.5 What if I need a PsiElement
and I have a UElement
?
+ 14.0.6 How do I get the UMethod
for a PsiMethod
?
+ 14.0.7 How do get a JavaEvaluator
?
+ 14.0.8 How do I check whether an element is internal?
+ 14.0.9 Is element inline, sealed, operator, infix, suspend, data?
+ 14.0.10 How do I look up a class if I have its fully qualified name?
+ 14.0.11 How do I look up a class if I have a PsiType?
+ 14.0.12 How do I look up hierarchy annotations for an element?
+ 14.0.13 How do I look up if a class is a subclass of another?
+ 14.0.14 How do I know which parameter a call argument corresponds to?
+ 14.0.15 How can my lint checks target two different versions of lint?
+ 14.0.16 Can I make my lint check “not suppressible?”
+ 14.0.17 Why are overloaded operators not handled?
+ 14.0.18 How do I check out the current lint source code?
+ 14.0.19 Where do I find examples of lint checks?
+ 14.0.20 How do I analyze details about Kotlin?
+15 Appendix: Recent Changes
+16 Appendix: Environment Variables and System Properties
+ 16.1 Environment Variables
+ 16.1.1 Detector Configuration Variables
+ 16.1.2 Lint Configuration Variables
+ 16.1.3 Lint Development Variables
+ 16.2 System Properties
@@ -212,14 +240,14 @@
A configuration provides extra information or parameters to lint on a
- per project, or even per directory basis. For example, the lint.xml
+ per-project, or even per-directory, basis. For example, the lint.xml
files can change the severity for issues, or list incidents to ignore
(matched for example by a regular expression), or even provide values
for options read by a specific detector.
An object passed into detectors in many APIs, providing data about (for example) which file is being analyzed (and in which project), - and for specific types of analysis additional information; for + and (for specific types of analysis) additional information; for example, an XmlContext points to the DOM document, a JavaContext includes the AST, and so on. @@ -290,7 +318,7 @@
Typically lint cares about which set of scopes apply,
- so most of the APIs take an EnumSet< Scope>
, but we'll often
+ so most of the APIs take an EnumSet<Scope>
, but we'll often
refer to this as just “the scope” instead of the “scope set”.
For an issue, whether the incident should be an error, or just a @@ -511,7 +539,7 @@
Scope.JAVA_FILE
may make it sound like there should also
be a Scope.KOTLIN_FILE
. However, JAVA_FILE
here really refers to
both Java and Kotlin files since the analysis and APIs are identical
- for both (using “UAST”, a universal abstract syntax tree). However,
+ for both (using “UAST”, a unified abstract syntax tree). However,
at this point we don't want to rename it since it would break a lot
of existing checks. We might introduce an alias and deprecate this
one in the future.Context
, or a more specific
subclass of Context
such as JavaContext
or XmlContext
. This
-allows lint to provide access to the detectors information they may
-need, without passing in a lot of parameters (and allowing lint to add
-additional data over time without breaking signatures).
+allows lint to give the detectors information they may need, without
+passing in a lot of parameters. It also allows lint to add additional data
+over time without breaking signatures.
The Context
classes also provide many convenience APIs. For example,
for XmlContext
there are methods for creating locations for XML tags,
-XML attributes, just the name part of an XML attribute and just the
+XML attributes, just the name part of an XML attribute, and just the
value part of an XML attribute. For a JavaContext
there are also
methods for creating locations, such as for a method call, including
whether to include the receiver and/or the argument list.
@@ -576,7 +604,7 @@
@@ -593,7 +621,7 @@
LintClient
in the IDE
@@ -612,14 +640,13 @@
LintClient
provides a simple way to provide exact responses for
- specific URLs:LintClient
to use configured IDE proxy settings (as is done in the
+ IntelliJ integration of lint). This is also good for testing, because
+ the special unit test implementation of a LintClient
has a simple way
+ to provide exact responses for specific URLs:lint()
.files(...)
@@ -640,8 +667,8 @@
And much, much, more. However, most of the implementation of
LintClient
is intended for integration of lint itself, and as a check
-author you don't need to worry about it. It's the detector API that
-matters, and is also less likely to change than the client API.
+author you don't need to worry about it. The detector API will matter
+more, and it's also less likely to change than the client API.
@@ -661,8 +688,8 @@
for internal lint usage have been made public
such that lint's
code in one package can access it from the other. There's normally a
comment explaining that this is for internal use only, but be aware
- that just because something is public
or not final
it's a good
- idea to call or override it.
+ that even when something is public
or not final
, it might not be a
+ good idea to call or override it.
Creating an Issue
@@ -676,7 +703,7 @@
Issue
is a final class, so unlike Detector
, you don't subclass
-it, you instantiate it via Issue.create
.
+it; you instantiate it via Issue.create
.
@@ -755,7 +782,7 @@
they'll configure and/or suppress issues. For example, to suppress the
warning in the current method, use
-
@Suppress("SdCardPath")
+
@Suppress("SdCardPath")
(or in Java, @SuppressWarnings). Note that there is an IDE quickfix to
suppress an incident which will automatically add these annotations, so
@@ -1106,20 +1133,20 @@
- When implementing
SourceCodeScanner
, in Kotlin and Java files
you can be called back
- - When a method of a given name is invoked (
getApplicableMethodNames
+ - when a method of a given name is invoked (
getApplicableMethodNames
and visitMethodCall
)
- - When a class of the given type is instantiated
+
- when a class of the given type is instantiated
(
getApplicableConstructorTypes
and visitConstructor
)
- - When a new class is declared which extends (possibly indirectly)
+
- when a new class is declared which extends (possibly indirectly)
a given class or interface (
applicableSuperClasses
and
visitClass
)
- - When annotated elements are referenced or combined
+
- when annotated elements are referenced or combined
(
applicableAnnotations
and visitAnnotationUsage
)
- - When any AST nodes of given types appear (
getApplicableUastTypes
+ - when any AST nodes of given types appear (
getApplicableUastTypes
and createUastHandler
)
@@ -1135,7 +1162,7 @@
(getApplicableAsmNodeTypes
and checkInstruction
)
- like with XmlScanner's
visitDocument
, you can perform your own
- ASM bytecode iteration via checkClass
.
+ ASM bytecode iteration via checkClass
@@ -1172,9 +1199,9 @@
-There are some callbacks both before each individual file is analyzed
-(beforeCheckFile
and afterCheckFile
), as well as before and after
-analysis of all the modules (beforeCheckRootProject
and
+There are some callbacks both before and after each individual file is
+analyzed (beforeCheckFile
and afterCheckFile
), as well as before and
+after analysis of all the modules (beforeCheckRootProject
and
afterCheckRootProject
).
@@ -1303,8 +1330,7 @@
- Finally, there are a number of utility methods; for example there is
- an
editDistance
method used to find likely typos used by a number
- of checks.
+ an editDistance
method used to find likely typos.
Scanner Example
@@ -1345,7 +1371,7 @@
}
}
-The second, older form, may seem simpler, but the new API allows a lot +The second (older) form may seem simpler, but the new API allows a lot more metadata to be attached to the report, such as an override severity. You don't have to convert to the builder syntax to do this; you could also have written the second form as @@ -1372,7 +1398,7 @@
This makes it possible to write a single analyzer which works -(”universally“) across all languages supported by UAST. And this is +across all languages supported by UAST. And this is very useful; most lint checks are doing something API or data-flow specific, not something language specific. If however you do need to implement something very language specific, see the next section, @@ -1582,7 +1608,7 @@
checks/build.gradle
-checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
-checks/src/main/java/com/example/lint/checks/SampleIssueRegistry.kt
-checks/src/main/java/com/example/lint/checks/SampleCodeDetector.kt
-checks/src/test/java/com/example/lint/checks/SampleCodeDetectorTest.kt
+
checks/build.gradle
+checks/src/main/resources/META-INF/services/com.android.tools.lint.client.api.IssueRegistry
+checks/src/main/java/com/example/lint/checks/SampleIssueRegistry.kt
+checks/src/main/java/com/example/lint/checks/SampleCodeDetector.kt
+checks/src/test/java/com/example/lint/checks/SampleCodeDetectorTest.kt
First is the build file, which we've discussed above. @@ -2187,7 +2213,758 @@
-+ + +To analyze Kotlin and Java files, lint offers many convenience callbacks +to make it simple to accomplish common tasks: + +
+ +
+
+And more. See the SourceCodeScanner
interface for more information.
+
+
+
+It also has various helpers, such as a ConstantEvaluator
and a
+DataFlowAnalyzer
to help analyze code.
+
+
+ +But in some cases, you'll need to dig in and analyze the “AST” yourself. + +
++ + +AST is short for “Abstract Syntax Tree” — a tree representation of the +source code. Consider the following very simple Java program: + +
// MyTest.java
+package test.pkg;
+
+public class MyTest {
+ String s = "hello";
+}
+ +Here's the AST for the above program, the way it's represented +internally in IntelliJ. + +
+ +
+ +This is actually a simplified view; in reality, there are also +whitespace nodes tracking all the spans of whitespace characters between +these nodes. + +
+ +Anyway, you can see there is quite a bit of detail here — tracking +things like the keywords, the variables, references to for example the +package — and higher level concepts like a class and a field which I've +marked with a thicker border. + +
+ +Here's the corresponding Kotlin program: + +
// MyTest.kt
+package test.pkg
+
+class MyTest {
+ val s: String = "hello"
+}
+ +And here's the corresponding AST in IntelliJ: + +
+ +
+
+This is for a program which is completely equivalent to the Java one.
+But notice that it has a completely different shape! They reference
+different element classes, PsiClass
versus KtClass
, and on and on
+all the way down.
+
+
+ +But there's some commonality — they each have a node for the file, for +the class, for the field, and for the initial value, the string. + +
++ + +We can construct a new AST which represents the same concepts: + +
+ +
+ +This is a unified AST, in something called “UAST”, short for Unified +Abstract Syntax Tree. UAST is the primary AST representation we use for +code in Lint. All the node classes here are prefixed with a capital U, +for UAST. And this is the UAST for the first Java file example above. + +
+ +Here's the UAST for the corresponding Kotlin example: + +
+ +
+
+As you can see, the ASTs are not always identical. For Strings, in
+Kotlin, we often end up with an extra parent UiInjectionHost
. But for
+our purposes, you can see that the ASTs are mostly the same, so if you
+handle the Kotlin scenario, you'll handle the Java ones too.
+
+
+ + +Note that “Unified” in the name here is a bit misleading. From the name +you may assume that this is some sort of superset of the ASTs across +languages — and AST that can represent everything needed by all +languages. But that's not the case! Instead, a better way to think of it +is as the Java view of the AST. + +
+ +If you for example have the following Kotlin data class: + +
data class Person(
+ var id: String,
+ var name: String
+)
+
+This is a Kotlin data class with two properties. So you might expect
+that UAST would have a way to represent these concepts — properties,
+and java classes. This should be a UDataClass
with two UProperty
+children, right?
+
+
+
+But Java doesn't support properties. If you try to access a Person
+instance from Java, you'll notice that it exposes a number of public
+methods that you don't see there in the Kotlin code — in addition to
+getId
, setId
, getName
and setName
, there's also component1
and
+component2
(for destructuring), and copy
.
+
+
+ +These methods are directly callable from Java, so they show up in UAST, +and your analysis can reason about them. + +
+
+Consider another complete Kotlin source file, test.kt
:
+
+
var property = 0
+ +Here's the UAST representation: + +
+ +
+
+Here we have a very simple Kotlin file — for a single Kotlin property.
+But notice at the UAST level, there's no such thing as top level methods
+and properties. In Java, everything is a class, so kotlinc
will create
+a “facade class”, using the filename plus “Kt”. So we see our TestKt
+class. And there are three members here. There's the getter and the
+setter for this property, as getProperty
and setProperty
. And then
+there is the private field itself, where the property is stored.
+
+
+ +This all shows up in UAST. It's the Java view of the Kotlin code. This +may seem limiting, but in practice, for most lint checks, this is +actually what you want. This makes it easy to reason about calls to APIs +and so on. + +
++ + +You may be getting the impression that the UAST tree is very shallow and +only represents high level declarations, like files, classes, methods +and properties. + +
+ +That's not the case. While it does skip low-level, language-specific +details things like whitespace nodes and individual keyword nodes, all +the various expression types are represented and can be reasoned about. +Take the following expression: + +
if (s.length > 3) 0 else s.count { it.isUpperCase() }
+ +This maps to the following UAST tree: + +
+ +
+ +As you can see it's modeling the if, the comparison, the lambda, and so +on. + +
++ + +Every node in UAST is a subclass of a UElement. There's a parent +pointer, which is handy for navigating around in the AST. + +
+ +The real skill you need for writing lint checks is understanding the +AST, and then doing pattern matching on it. And a simple trick for this +is to create the Kotlin or Java code you want, in a unit test, and then +in your detector, recursively print out the UAST as a tree. + +
+
+Or in the debugger, anytime you have a UElement, you can call
+UElement.asRecursiveLogString
on it, evaluate and see what you find.
+
+
+ +For example, for the following Kotlin code: + +
import java.util.Date
+fun test() {
+ val warn1 = Date()
+ val ok = Date(0L)
+}
+
+here's the corresponding UAST asRecursiveLogString
output:
+
+
UFile (package = )
+ UImportStatement (isOnDemand = false)
+ UClass (name = JavaTest)
+ UMethod (name = test)
+ UBlockExpression
+ UDeclarationsExpression
+ ULocalVariable (name = warn1)
+ UCallExpression (kind = UastCallKind(name='constructor_call'), …
+ USimpleNameReferenceExpression (identifier = Date)
+ UDeclarationsExpression
+ ULocalVariable (name = ok)
+ UCallExpression (kind = UastCallKind(name='constructor_call'), …
+ USimpleNameReferenceExpression (identifier = Date)
+ ULiteralExpression (value = 0)
+
+
+
+You generally shouldn't visit a source file on your own. Lint has a
+special UElementHandler
for that which is used to ensure that we don't
+repeat visiting a source file thousands of times, one per detector.
+
+
+ +But when you're doing local analysis, you sometimes need to visit a +subtree. + +
+
+To do that, just extend AbstractUastVisitor
and pass the visitor to
+the accept
method of the corresponding UElement
.
+
+
method.accept(object : AbstractUastVisitor() {
+ override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression): Boolean {
+ // your code here
+ return super.visitSimpleNameReferenceExpression(node)
+ }
+})
+
+In a visitor, you generally want to call super
as shown above. You can
+also return true
if you've “seen enough” and can stop visiting the
+remainder of the AST.
+
+
+
+If you're visiting Java PSI elements, you use a
+JavaRecursiveElementVisitor
, and in Kotlin PSI, use a KtTreeVisitor
.
+
+
+
+
+UAST is built on top of PSI, and each UElement
has a sourcePsi
+property (which may be null). This lets you map from the general UAST
+node, down to the specific PSI elements.
+
+
+ +Here's an illustration of that: + +
+ +
+ +We have our UAST tree in the top right corner. And here's the Java PSI +AST behind the scenes. We can access the underlying PSI node for a +UElement by accessing the sourcePsi element. So when you do need to dip +into something language specific, that's trivial to do. + +
+ +Note that in some cases, these references are null. + +
+
+Each of the UElement nodes point back into the PSI AST - whether a Java
+AST or a Kotlin AST. Here's the same AST, but with the type of the
+sourcePsi
attribute for each node added.
+
+
+ +
+
+You can see that the class generated to represent the top level
+functions here doesn't have a non-null sourcePsi
, because in the
+Kotlin PSI, there is no real KtClass
for a facade class. And for the
+three members, the private field and the getter and the setter, they all
+correspond to the exact same, single KtProperty
instance, the single
+node in the Kotlin PSI that these methods were generated from.
+
+
+
+
+In some cases, we can also map back to UAST from PSI elements, using the toUElement
extension function.
+
+
+
+For example, let's say we resolve a method call. This returns a
+PsiMethod
, not a UMethod
. But we can get the corresponding UMethod
+using the following:
+
+
val resolved = call.resolve() ?: return
+val uDeclaration = resolve.toUElement()
+
+Note however that toUElement
may return null. For example, if you've
+resolved to a method call that is compiled (which you can check using
+resolved is PsiCompiledElement
), UAST cannot convert it.
+
+
+ + +UAST is the preferred AST to use when you're writing lint checks for +Kotlin and Java. It lets you reason about things that are the same +across the languages. Declarations. Function calls. Super classes. +Assignments. If expressions. Return statements. And on and on. + +
+ +There are lint checks which are language specific — for example, if +you write a lint check which forbids the use of companion objects — in +that case, there's no big advantage to using UAST over PSI; it's only +ever going to run on Kotlin code. (Note however that lint's APIs and +convenience callbacks are all targeting UAST, so it's easier to write +UAST lint checks even for the language-specific checks.) + +
+ +The vast majority of lint checks however aren't language specific, +they're API or bug pattern specific. And if the API can be called +from Java, you want your lint check to not only flag problems in Kotlin, +but in Java code as well. You don't want to have to write the lint check +twice — so if you use UAST, a single lint check can work for both. But +while you generally want to use UAST for your analysis (and lint's APIs +are generally oriented around UAST), there are cases where it's +appropriate to dip into PSI. + +
+ +In particular, you should use PSI when you're doing something highly +language specific, and where the language details aren't exposed in UAST. + +
+
+For example, let's say you need to determine if a UClass
is a Kotlin
+“companion object“. You could cheat and look at the class name to see if
+it's ”Companion“. But that's not quite right; in Kotlin you can
+specify a custom companion object name, and of course users are free
+to create classes named ”Companion“ that aren't companion objects:
+
+
class Test {
+ companion object MyName { // Companion object not named "Companion"!
+ }
+
+ object Companion { // Named "Companion" but not a companion object!
+ }
+}
+
+The right way to do this, is using Kotlin PSI, via the
+UElement.sourcePsi
attribute:
+
+
// Skip companion objects
+val source = node.sourcePsi
+if (source is KtObjectDeclaration && source.isCompanion()) {
+ return
+}
+
+(To figure out how to write the above code, use a debugger on a test
+case and look at the UClass.sourcePsi
attribute; you'll discover that
+it's some subclass of KtObjectDeclaration
; look up its most general
+super interface or class, and then use code completion to discover
+available APIs, such as isCompanion()
.)
+
+
+ + +Using Kotlin PSI was the state of the art for correctly analyzing Kotlin +code until recently. But when you look at the PSI, you'll discover that +some things are really hard to accomplish — in particular, resolving +reference, and dealing with Kotlin types. + +
+ +Lint doesn't actually give you access to everything you need if you want +to try to look up types in Kotlin PSI; you need something called the +“binding context”, which is not exposed anywhere! And this omission is +deliberate, because that was an implementation detail of the old +compiler. The future is K2; a complete rewrite of the compiler front +end, which is no longer using the old binding context. And as part of +the tooling support for K2, there's a new API called the “Kotlin +Analysis API” you can use to dig into details about Kotlin. + +
+ +For most lint checks, you should just use UAST if you can. But when you +need to know really detailed Kotlin information, especially around +types, and smart casts, and null inference, and so on, the Kotlin +Analysis API is your best friend (and only option...) + +
+ +
KtAnalysisSession
returned by analyze
, has been renamed
+ KaSession
. Most APIs now have the prefix Ka
.+ + +Here's a simple example: + +
fun testTodo() {
+ if (SDK_INT < 11) {
+ TODO() // never returns
+ }
+ val actionBar = getActionBar() // OK - SDK_INT must be >= 11 !
+}
+ +Here we have a scenario where we know that the TODO call will never +return, and lint can take advantage of that when analyzing the control +flow — in particular, it should understand that after the TODO() call +there's no chance of fallthrough, so it can conclude that SDK_INT must +be at least 11 after the if block. + +
+
+The way the Kotlin compiler can reason about this is that the TODO
+method in the standard library has a return type of Nothing
.
+
+
@kotlin.internal.InlineOnly
+public inline fun TODO(): Nothing = throw NotImplementedError()
+
+The Nothing
return type means it will never return.
+
+
+
+Before the Kotlin lint analysis API, lint didn't have a way to reason
+about the Nothing
type. UAST only returns Java types, which maps to
+void. So instead, lint had an ugly hack which just hardcoded well known
+names of methods that don't return:
+
+
if (nextStatement is UCallExpression) {
+ val methodName = nextStatement.methodName
+ if (methodName == "fail" || methodName == "error" || methodName == "TODO") {
+ return true
+}
+ +However, with the Kotlin analysis API, this is easy: + +
/**
+* Returns true if this [call] node calls a method known to never
+* return, such as Kotlin's standard library method "error".
+*/
+fun callNeverReturns(call: UCallExpression): Boolean {
+ val sourcePsi = call.sourcePsi as? KtCallExpression ?: return false
+ analyze(sourcePsi) {
+ val callInfo = sourcePsi.resolveCall() ?: return false
+ val returnType = callInfo.singleFunctionCallOrNull()?.symbol?.returnType
+ if (returnType != null && returnType.isNothing) {
+ return true
+ }
+}
+
+The entry point to all Kotlin Analysis API usages is to call the
+analyze
method (see line 7) and pass in a Kotlin PSI element. This
+creates an “analysis session”. It's very important that you don't leak
+objects from inside the session out of it — to avoid memory leaks and
+other problems. If you do need to hold on to a symbol and compare later,
+you can create a special symbol pointer.
+
+
+ +Anyway, there's a huge number of extension methods that take an analysis +session as receiver, so inside the lambda on lines 7 to 13, there are +many new methods available. + +
+
+Here, we have a KtCallExpression
, and inside the analyze
block we
+can call resolveCall()
on it to reach the called method's symbol.
+
+
+
+Similarly on a KtDeclaration
(such as a named function or property) I
+can call getSymbol()
to get the symbol for that method or property, to
+for example look up parameter information. And on a KtExpression
(such
+as an if statement) I can call getKtType()
to get the Kotlin type.
+
+
+
+KtSymbol
and KtType
are the basic primitives we're working with in
+the Kotlin Analysis API. There are a number of subclasses of symbol,
+such as KtFileSymbol
, KtFunctionSymbol
, KtClassOrObjectSymbol
, and
+so on.
+
+
+
+In the new implementation of callNeverReturns
, we resolve the call,
+look up the corresponding function which of course is a KtSymbol
+itself, and from that we get the return type, and then we can just check
+if it's the Nothing
type.
+
+
+ +And this API works both with the old Kotlin compiler, used in lint right +now, and K2, which can be turned on via a flag and will soon be the +default (and may well be the default when you read this; we don't always +remember to update the documentation...) + +
++ + +Accessing Kotlin-specific knowledge not available via Kotlin PSI is one +use for the analysis API. + +
+ +Another big advantage of the Kotlin analysis API is that it gives you +access to reason about compiled Kotlin code, in the same way that the +compiler does. + +
+
+Normally, when you resolve with UAST, you just get a plain PsiMethod
+back. For example, if we have a reference to
+kotlin.text.HexFormat.Companion
, and we resolve it in UAST, we get a
+PsiMethod
back. This is not a Kotlin PSI element, so our earlier
+code to check if this is a companion object (source is
+KtObjectDeclaration && source.isCompanion()
) does not work — the first
+instance check fails. These compiled PsiElement
s do not give us access
+to any of the special Kotlin payload we can usually check on
+KtElement
s — modifiers like inline
or infix
, default parameters,
+and so on.
+
+
+
+The analysis API handles this properly, even for compiled code. In fact,
+the earlier implementation of checking for the Nothing
type
+demonstrated this, because the methods it's analyzing from the Kotlin
+standard library (error
, TODO
, and so on), are all compiled classes
+in the Kotlin standard library jar file!
+
+
+ +Therefore, yes, we can use Kotlin PSI to check if a class is a companion +object if we actually have the source code for the class. But if we're +resolving a reference to a class, using the Kotlin analysis API is +better; it will work for both source and compiled: + +
symbol is KtClassOrObjectSymbol && symbol.classKind == KtClassKind.COMPANION_OBJECT
+ + + +When you're using K2 with lint, a lot of UAST's handling of resolve and +types in Kotlin is actually using the analysis API behind the scenes. + +
+
+If you for example have a Kotlin PSI KtThisExpression
, and you want to
+understand how to resolve the this
reference to another PSI element,
+write the following Kotlin UAST code:
+
+
thisReference.toUElement()?.tryResolve()
+
+You can now use a debugger to step into the tryResolve
call, and
+you'll eventually wind up in code using the Kotlin Analysis API to look
+it up, and as it turns out, here's how:
+
+
analyze(expression) {
+ val reference = expression.getTargetLabel()?.mainReference
+ ?: expression.instanceReference.mainReference
+ val psi = reference.resolveToSymbol()?.psi
+ …
+}
+ + + +To use K2 from a unit test, you can use the following lint test task override: + +
override fun lint(): TestLintTask {
+ return super.lint().configureOptions { flags -> flags.setUseK2Uast(true) }
+}
+
+Outside of tests, you can also set the -Dlint.use.fir.uast=true
system property in your run configurations.
+
+
+ +Note that at some point this flag may go away since we'll be switching +over to K2 completely. + +
++ + +Here are various other Kotlin Analysis scenarios and potential solutions: + +
+val call: KtCallExpression
+…
+analyze(call) {
+ val callInfo = call.resolveCall()
+ if (callInfo != null) {
+ val symbol: KtFunctionLikeSymbol = callInfo.singleFunctionCallOrNull()?.symbol
+ ?: callInfo.singleConstructorCallOrNull()?.symbol
+ ?: callInfo.singleCallOrNull<ktannotationcall>()?.symbol
+ …
+}
+
+
+
+Also use resolveCall
, though it's not really a call:
+
+
val expression: KtNameReferenceExpression
+…
+analyze(expression) {
+ val symbol: KtVariableLikeSymbol = expression.resolveCall()?.singleVariableAccessCall()?.symbol
+}
+ val containingSymbol = symbol.getContainingSymbol()
+if (containingSymbol is KtNamedClassOrObjectSymbol) {
+ …
+}
+ val containing = declarationSymbol.getContainingSymbol()
+if (containing is KtClassOrObjectSymbol) {
+ val fqn = containing.classIdIfNonLocal?.asSingleFqName()
+ …
+}
+ if (symbol is KtDeclarationSymbol) {
+ symbol.deprecationStatus?.let { … }
+}
+ if (symbol is KtSymbolWithVisibility) {
+ val visibility = symbol.visibility
+ if (!visibility.isPublicAPI) {
+ …
+ }
+}
+ containingSymbol.buildSelfClassType()
+
+
+
+Example: is this KtParameter
pointing to an interface?
+
+
analyze(ktParameter) {
+ val parameterSymbol = ktParameter.getParameterSymbol()
+ val returnType = parameterSymbol.returnType
+ val typeSymbol = returnType.expandedClassSymbol
+ if (typeSymbol is KtClassOrObjectSymbol) {
+ val classKind = typeSymbol.classKind
+ if (classKind == KtClassKind.INTERFACE) {
+ …
+ }
+}
+ if (type1 is KtNonErrorClassType && type2 is KtNonErrorClassType &&
+ type1.classId == type2.classId) {
+ …
+}
+ if (declarationSymbol is KtFunctionSymbol) {
+ val declarationReceiverType = declarationSymbol.receiverParameter?.type
+ val file = symbol.getContainingFileSymbol()
+if (file is KtFileSymbol) {
+val psi = file.psi
+if (psi is PsiFile) {
+ …
+}
+ + + +
+@@ -2209,9 +2986,9 @@ libraries, such as the internal Google build system.
-@@ -2244,7 +3021,7 @@ authors a way to provide their own additional checks enforcing usage.
-@@ -2287,7 +3064,7 @@ emitting the custom lint check.
-@@ -2319,7 +3096,7 @@ tracker for this.
@@ -2335,7 +3112,7 @@ * and is no longer registered, any existing mentions of the issue * id in baselines, lint.xml files etc are gracefully handled. */ -open val deletedIssues: List<string> = emptyList()
+open val deletedIssues: List<String> = emptyList()
The reason you'll want to do this is listed right there in the doc: If you don't do this, and if users have for example listed your issue id @@ -2344,17 +3121,13 @@ is done to catch issue id typos. And if the user has a baseline file listing incidents from your check, then if your issue id is not registered as deleted, lint will think this is an issue that has been -“fixed“ since it's no longer reported, and lint will issue an +“fixed” since it's no longer reported, and lint will issue an informational message that the baseline contains issues no longer reported (which is done such that users can update their baseline files, to ensure that the fixed issues aren't reintroduced again.) -
- - -
-@@ -2370,7 +3143,7 @@ until it passes.
-@@ -2456,7 +3229,7 @@ tweaks here and there.
-@@ -2471,7 +3244,7 @@ output, provided of course that it's correct!
-@@ -2577,12 +3350,12 @@ modes chapter.
-
Notice how in the above Kotlin unit tests we used raw strings, and
-we indented the sources to be flush with the opening “”“ string
+we indented the sources to be flush with the opening """
string
delimiter.
@@ -2598,7 +3371,7 @@
will automatically call trimIndent()
on the string passed in to it.
@@ -2614,7 +3387,7 @@ the opposite direction when creating the test sources on disk).
-@@ -2667,7 +3440,7 @@ + "+ android:importantForAutofill=\"no\"\n" + " android:inputType=\"password\" >\n" + " \n"); -
@@ -2763,7 +3536,7 @@ (This check only enforces import references, not all references, so if it doesn't matter to the detector, you can just remove the import but leave references to the class in the code.) -
@@ -2833,7 +3606,7 @@ ).indented() ) ).run().expect( -
@@ -2853,7 +3626,7 @@ "BwAAAAIAAQAIAAkAAQAKAAAAHQABAAEAAAAFKrcAAbEAAAABAAsAAAAGAAEA" + "AAAEAAgADAAJAAEACgAAAB4AAQAAAAAABhICswADsQAAAAEACwAAAAYAAQAA" + "AAUAAQANAAAAAgAO" - )` + ) ).run().expect(
Here, ”base64gzip“ means that the file is gzipped and then base64 @@ -2900,12 +3673,12 @@
This isn't just a convenience; lint's test infrastructure also uses -this to test some additional scenarios (for example, in a multi- module +this to test some additional scenarios (for example, in a multi-module project it will only provide the binaries, not the sources, for upstream modules.)
-@@ -2922,7 +3695,7 @@ questions.
-@@ -2947,7 +3720,7 @@
-@@ -2963,7 +3736,7 @@
You can also add in your own test modes. For example, lint adds its own -internal test mode for making sure the built-in annotation checks works +internal test mode for making sure the built-in annotation checks work with Android platform annotations in the following test mode:
@@ -3013,7 +3781,7 @@ https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/AndroidPlatformAnnotationsTestMode.kt
-@@ -3048,7 +3816,7 @@
@@ -3076,7 +3844,7 @@ .skipTestModes(TestMode.PARTIAL) .run() .expectClean() -
@@ -3119,7 +3887,7 @@ missing warnings with their line numbers to the changed source code.
-
@@ -3217,7 +3985,7 @@
something like node as? UCallExpression
.
@@ -3250,7 +4018,7 @@ val rv = IMPORT_ALIAS_1_REMOTEVIEWS(packageName, R.layout.test) val ov = other as IMPORT_ALIAS_1_REMOTEVIEWS } -
@@ -3295,7 +4063,7 @@ typealias TYPE_ALIAS_1 = String typealias TYPE_ALIAS_2 = Any typealias TYPE_ALIAS_3 = RemoteViews -
@@ -3324,7 +4092,7 @@
last argument is a literal expression such as a number or a String. You
can't just use if (call.valueArguments.lastOrNull() is
ULiteralExpression)
, because that first argument could be a
-UParenthesizedExpression
, as in `call(1, true, (“hello”)), so you'd
+UParenthesizedExpression
, as in call(1, true, ("hello"))
, so you'd
need to look inside the parentheses.
@@ -3379,7 +4147,7 @@ parentheses.
-@@ -3411,7 +4179,7 @@ to
test(n = 5, z = true, s = "test")
-
@@ -3458,7 +4226,7 @@
absence of UBlockExpression
around the child nodes.
@@ -3478,7 +4246,7 @@ tweaks.
-@@ -3509,7 +4277,7 @@ + .pattern("acquire\\s*\\(()\\s*\\)") .with("10*60*1000L /*10 minutes*/") .build(); -
@@ -3545,7 +4313,7 @@ sure the results continue to be the same.
-@@ -3565,14 +4333,80 @@ inserts a suppress directive at the nearest applicable location. It then re-runs the analysis, and makes sure that the warning no longer appears. +
++ + +When UAST comes across a method like this: + +
@JvmOverloads
+fun test(parameter: Int = 0) {
+ implementation()
+}
+ +it will “inline” these two methods in the AST, such that we see the whole +method body twice: + +
fun test() {
+ implementation()
+}
+
+fun test(parameter: Int) {
+ implementation()
+}
+ +If there were additional default parameters, there would be additional +repetitions. + +
+ +This is similar to what the compiler does, since Java doesn't have +default arguments, but the compiler will actually just generate some +trampoline code to jump to the implementation with all the parameters; +it will NOT repeat the method implementation: + +
fun test(parameter: Int) {
+ implementation()
+}
+
+// $FF: synthetic method
+fun `test$default`(var0: Int, var1: Int, var2: Any?) {
+ var var0 = var0
+ if ((var1 and 1) != 0) {
+ var0 = 0
+ }
+
+ test(var0)
+}
+ +Again, UAST will instead just repeat the method body. And this means +lint detectors may trigger repeatedly on the same code. In most cases +this will result in duplicated warnings. But it can also lead to other +problems; for example, a lint check which makes sure you don't have any +code duplication would incorrectly believe code fragments are repeated. + +
+
+Lint already looks for this situation and avoids visiting duplicated
+methods in its shared implementations (which is dispatching to most
+Detector
callbacks). However, if you manually visit a class yourself,
+you can run into this problem.
+
+
+ +This test mode simulates this situation by finding all methods where +it can safely add at least one default parameter, and marks it +@JvmOverloaded. It then makes sure the results are the same as before. +
-
@@ -3599,7 +4433,7 @@ to manually delete the TODO string first.)
-@@ -3633,7 +4467,7 @@ analysis.
-@@ -3703,7 +4537,7 @@ safe in this way.
@@ -3826,7 +4660,7 @@ val fix = fix() .url("https://developer.android.com/topic/libraries/architecture/workmanager/migrating-gcm") .build() -
@@ -3891,7 +4725,7 @@ fix().set().todo(ANDROID_URI, "text").build(), fix().set().todo(ANDROID_URI, "contentDescription") .build()) -
@@ -3918,7 +4752,7 @@ checks with quickfixes for Kotlin and Java.
-@@ -3959,7 +4793,7 @@ + if (javaClass.desiredAssertionStatus()) { assert(expensive()) } // WARN """ ) -
@@ -3971,9 +4805,9 @@ to auto-fix a suggestion right from within the code review tool.
-@@ -4010,7 +4844,7 @@ validate code reviews.
-@@ -4031,7 +4865,7 @@ that module results can be cached.
-@@ -4135,7 +4969,7 @@
@@ -4152,7 +4986,7 @@ detector is doing something it shouldn't.
-@@ -4214,7 +5048,7 @@
@@ -4231,7 +5065,7 @@ library.
-@@ -4241,7 +5075,7 @@ analysis is not written correctly, they're not.
-@@ -4345,7 +5179,7 @@ binaries (through a lint AST to PsiCompiled pretty printer.)
-
@@ -4378,8 +5212,8 @@
represents it. To report an incident you simply call
context.report(incident)
. There are several ways to create these
incidents. The easiest is to simply edit your existing call above by
-adding Incident(
(or from Java, new Incident(
) inside the
-context.report
block like this:
+putting it inside Incident(...)
(in Java, new Incident(...)
) inside
+the context.report
block like this:
context.report(Incident(
ISSUE,
@@ -4432,7 +5266,7 @@
make any changes
- Constraints
+ Constraints
@@ -4475,7 +5309,7 @@
Recording an incident with a constraint is easy; first construct the
-Incident
as before, and then report them via
+Incident
as before, and then report it via
context.report(incident, constraint)
:
String message =
@@ -4486,7 +5320,7 @@
context.report(incident, minSdkAtLeast(18));
Finally, note that you can combine constraints; there are both “and”
-and “or” operators defined for the Constraint
class. so the following
+and “or” operators defined for the Constraint
class, so the following
is valid:
val constraint = targetSdkAtLeast(23) and notLibraryProject()
@@ -4497,7 +5331,7 @@
on its own and only report incidents that meet the constraint.
- Incident LintMaps
+ Incident LintMaps
@@ -4580,7 +5414,7 @@
each time for new detector instances in the reporting task.
- Module LintMaps
+ Module LintMaps
@@ -4771,7 +5605,7 @@
* (via [Context.report]) to lint.
*/
open fun checkPartialResults(context: Context, partialResults: PartialResult) { ... }
- Optimizations
+ Optimizations
@@ -4793,7 +5627,7 @@
- Data Flow Analyzer
+ Data Flow Analyzer
@@ -4848,7 +5682,7 @@
we care about.
- Usage
+ Usage
@@ -4896,7 +5730,7 @@
}
}
-Aas you can see, the DataFlowAnalyzer
is a visitor, so when we find a
+As you can see, the DataFlowAnalyzer
is a visitor, so when we find a
call we're interested in, we construct a DataFlowAnalyzer
and
initialize it with the instance we want to track, and then we visit the
surrounding method with this visitor.
@@ -4921,7 +5755,7 @@
However, there's a lot that can go wrong, which we'll need to deal with. This is explained in the following sections
@@ -4982,7 +5816,7 @@ return super.returnsSelf(call) || call.methodName == "copy" } } -
@@ -5018,7 +5852,7 @@ if/else
-@@ -5040,7 +5874,7 @@ scenarios, and more importantly, we need to ensure that we don't offer false positive warnings in the above scenario.
-@@ -5071,7 +5905,7 @@ if (!escapes && !foundCommit) { context.report(Incident(...)) } -
@@ -5079,8 +5913,8 @@ no longer know for certain whether it gets committed is via a method call. -
fun test) {
- val transaction = getFragmentManager().beginTransaction()
+fun test() {
+ val transaction = getFragmentManager().beginTransaction()
process(transaction)
}
@@ -5106,7 +5940,7 @@
customize this by overriding ignoreArgument()
.)
- Fields
+ Fields
@@ -5141,7 +5975,7 @@
the method, and at the end you can just check its escaped
property.
- Non Local Analysis
+ Non Local Analysis
@@ -5179,7 +6013,7 @@
Complications: - storing in a field, returning, intermediate variables, self-referencing methods, scoping functions,
- Examples
+ Examples
@@ -5187,7 +6021,7 @@
built-in rules.
- Simple Example
+ Simple Example
@@ -5200,7 +6034,7 @@
Test
- Complex Example
+ Complex Example
@@ -5212,7 +6046,7 @@
Test
- TargetMethodDataFlowAnalyzer
+ TargetMethodDataFlowAnalyzer
@@ -5259,7 +6093,7 @@
- Annotations
+ Annotations
@@ -5303,7 +6137,7 @@
visitAnnotation
.
- Basics
+ Basics
@@ -5385,7 +6219,7 @@
-And the third case shows the most common scenario: a straight forward
+And the third case shows the most common scenario: a straightforward
method call to an annotated method.
@@ -5415,9 +6249,9 @@
org.jetbrains.annotations.Nullable
will both match.
- Annotation Usage Types and isApplicableAnnotationUsage
+ Annotation Usage Types and isApplicableAnnotationUsage
- Method Override
+ Method Override
@@ -5451,7 +6285,7 @@
method somewhere in the method body.
- Method Return
+ Method Return
@@ -5493,7 +6327,7 @@
@StringRes
).
- Handling Usage Types
+ Handling Usage Types
@@ -5530,7 +6364,7 @@
types by default in the super implementation.
- Usage Types Filtered By Default
+ Usage Types Filtered By Default
@@ -5568,7 +6402,7 @@
src/test/pkg/HalfFloatTest.java:50: Error: Half-float type in expression widened to int [HalfFloat]
Math.round(float1); // Error: should use Half.round
------
-
@@ -5613,7 +6447,7 @@
The fileStack.push
call on line 4 also resolves to the same method
as the call on line 2 (even though the concrete type is a FileStack
-instead of a `Stack), so like on line 2, this call is taken to be
+instead of a Stack
), so like on line 2, this call is taken to be
thread safe.
@@ -5689,7 +6523,7 @@ you are invoked for the innermost one.
-@@ -5736,9 +6570,9 @@
-@@ -5779,7 +6613,7 @@ (See the lint.xml documentation for more.)
-@@ -5848,7 +6682,7 @@
-@@ -5861,7 +6695,7 @@ user, or if not set, our original default value, in this case 80.
-@@ -5898,7 +6732,7 @@
val configuration = context.findConfiguration(context.file)
val maxCount = MAX_COUNT.getValue(configuration)
- @@ -5911,7 +6745,7 @@ string as necessary.
-@@ -5941,7 +6775,7 @@ <option name="duration" value="100.0"> ---------------------------------------- 1 errors, 0 warnings -
@@ -5958,7 +6792,7 @@ .configureOption(MAX_COUNT, 150) .run() .expectClean() -
@@ -5988,9 +6822,9 @@
-@@ -6017,7 +6851,7 @@ while still legible.
-@@ -6040,11 +6874,11 @@ quotes.
--One line error messages should not be punctuated - e.g. the error message +One line error messages should not be punctuated — e.g. the error message should be “Unused import foo”, not “Unused import foo.”
@@ -6058,7 +6892,7 @@ (?) sign.
-@@ -6078,7 +6912,7 @@ match them in order, which often would match the wrong import.
-@@ -6087,7 +6921,7 @@ “API 31“.
-@@ -6118,7 +6952,7 @@
@@ -6139,7 +6973,7 @@ ”register your receivers in the manifest“
-@@ -6301,7 +7135,7 @@
@@ -6309,7 +7143,7 @@ have asked in the past.
-@@ -6344,7 +7178,7 @@ detail in the unit testing chapter.
-@@ -6390,7 +7224,7 @@ information about this.
-visitAnnotationUsage
isn't called for annotationsvisitAnnotationUsage
isn't called for annotations
@@ -6409,7 +7243,7 @@
visitAnnotation
.
@@ -6433,7 +7267,7 @@ see the next question.
-PsiElement
and I have a UElement
?PsiElement
and I have a UElement
?
@@ -6441,11 +7275,11 @@
by calling element.sourcePsi
.
UMethod
for a PsiMethod
?UMethod
for a PsiMethod
?
-Call psiMethod.toUElementOfType<umethod>()
. Note that this may return
+Call psiMethod.toUElementOfType<UMethod>()
. Note that this may return
null if UAST cannot find valid Java or Kotlin source code for the
method.
@@ -6455,7 +7289,7 @@
toUElementOfType
type arguments.
JavaEvaluator
?JavaEvaluator
?@@ -6472,7 +7306,7 @@ of operations on the evaluator.
-
@@ -6485,7 +7319,7 @@
PsiMethod
, PsiClass
, PsiField
, PsiMember
, PsiVariable
, etc.)
@@ -6499,7 +7333,7 @@ open fun isOperator(owner: PsiModifierListOwner?): Boolean { /* ... */ open fun isInfix(owner: PsiModifierListOwner?): Boolean { /* ... */ open fun isSuspend(owner: PsiModifierListOwner?): Boolean { /* ... */ -
@@ -6508,7 +7342,7 @@ nullable.
-@@ -6518,7 +7352,7 @@
abstract fun getClassType(psiClass: PsiClass?): PsiClassType?
abstract fun getTypeClass(psiType: PsiType?): PsiClass?
- @@ -6538,7 +7372,7 @@ owner: PsiModifierListOwner, inHierarchy: Boolean ): Array<psiannotation> -
@@ -6576,7 +7410,7 @@ interfaceName: String, strict: Boolean = false ): Boolean -
@@ -6600,7 +7434,7 @@
open fun computeArgumentMapping(
call: UCallExpression,
method: PsiMethod
- ): Map<uexpression, psiparameter=""> { /* ... */
+ ): Map<UExpression, PsiParameter> { /* ... */
This returns a map from UAST expressions (each argument to a UAST call
is a UExpression
, and these are the valueArguments
property on the
@@ -6608,7 +7442,7 @@
PsiMethod
that the method calls.
@@ -6625,7 +7459,7 @@ outside of the current API level.
-
@@ -6641,18 +7475,18 @@
Issue
, set the suppressNames
property to an empty collection.
Kotlin supports overloaded operators, but these are not handled as
-calls in the AST - instead, an implicit get
or set
method from an
+calls in the AST — instead, an implicit get
or set
method from an
array access will show up as a UArrayAccessExpression
. Lint has
specific support to help handling these scenarios; see the “Implicit
Calls” section in the basics chapter.
$ git clone --branch=mirror-goog-studio-main --single-branch \
https://android.googlesource.com/platform/tools/base
Cloning into 'base'...
@@ -6669,7 +7503,7 @@
.gitignore MODULE_LICENSE_APACHE2 cli/
$ ls libs/
intellij-core/ kotlin-compiler/ lint-api/ lint-checks/ lint-gradle/ lint-model/ lint-tests/ uast/
- @@ -6679,12 +7513,23 @@ browse sources online: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/ +
++ + +The new Kotlin Analysis API offers access to detailed information about +Kotlin (types, resolution, as well as information the compiler has +figured out such as smart casts, nullability, deprecation info, and so +on). There are more details about this, as well as a number of recipes, +in the AST Analysis chapter. +
-
@@ -6699,6 +7544,27 @@
+8.6 + +
+ +
UElementHandler
now supports recently added UAST element
+ types: UPatternExpression
and UBinaryExpressionWithPattern
.
+@JvmOverloads
. See the test modes chapter
+ for more.+ 8.4
@@ -7232,7 +8098,7 @@
-@@ -7242,9 +8108,9 @@ what they are seems useful.
-@@ -7276,7 +8142,7 @@ for that check.
@@ -7330,7 +8196,7 @@
Corresponding system property: android.lint.skip.bytecode.verifier
@@ -7364,7 +8230,7 @@ server or by the rest of the development team.
-
@@ -7435,4 +8301,4 @@
Corresponding system property: android.lint.skip.bytecode.verifier