blob: 844a77cb66d4463e7bb4b6fccc1dcfed9590a2aa [file] [log] [blame] [view]
AndroidX Core Team21ccf652022-04-01 14:53:07 +00001# Adding custom lint checks
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -07002
AndroidX Core Team2e416b22020-12-03 22:58:07 +00003[TOC]
4
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -07005## Getting started
6
7Lint is a static analysis tool that checks Android project source files. Lint
AndroidX Core Teamee9c1aa2021-04-06 17:29:05 +00008checks come with Android Studio by default, but custom lint checks can be added
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -07009to specific library modules to help avoid potential bugs and encourage best code
10practices.
11
AndroidX Core Teamee9c1aa2021-04-06 17:29:05 +000012This guide is targeted to developers who would like to quickly get started with
13adding lint checks in the AndroidX development workflow. For a complete guide to
14writing and running lint checks, see the official
15[Android lint documentation](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/docs/README.md.html).
16
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070017### Create a module
18
AndroidX Core Team21ccf652022-04-01 14:53:07 +000019If this is the first lint rule for a library, you will need to create a module
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070020by doing the following:
21
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070022Include the project in the top-level `settings.gradle` file so that it shows up
23in Android Studio's list of modules:
24
25```
26includeProject(":mylibrary:mylibrary-lint", "mylibrary/mylibrary-lint")
27```
28
29Manually create a new module in `frameworks/support` (preferably in the
30directory you are making lint rules for). In the new module, add a `src` folder
31and a `build.gradle` file containing the needed dependencies.
32
AndroidX Core Teame80aab72021-09-29 08:44:33 -070033`mylibrary/mylibrary-lint/build.gradle`:
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070034
35```
AndroidX Core Teame80aab72021-09-29 08:44:33 -070036import androidx.build.LibraryType
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070037
38plugins {
39 id("AndroidXPlugin")
40 id("kotlin")
41}
42
43dependencies {
AndroidX Core Teame80aab72021-09-29 08:44:33 -070044 compileOnly(libs.androidLintMinApi)
45 compileOnly(libs.kotlinStdlib)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070046
AndroidX Core Teame80aab72021-09-29 08:44:33 -070047 testImplementation(libs.kotlinStdlib)
48 testImplementation(libs.androidLint)
49 testImplementation(libs.androidLintTests)
50 testImplementation(libs.junit)
AndroidX Core Teambb6223c2022-09-27 10:39:19 -070051 testImplementation(libs.truth)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070052}
53
54androidx {
AndroidX Core Teame80aab72021-09-29 08:44:33 -070055 name = "MyLibrary lint checks"
56 type = LibraryType.LINT
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070057 mavenGroup = LibraryGroups.MYLIBRARY
AndroidX Core Team21ccf652022-04-01 14:53:07 +000058 inceptionYear = "2022"
AndroidX Core Teame80aab72021-09-29 08:44:33 -070059 description = "Lint checks for MyLibrary"
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070060}
61```
62
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070063### Issue registry
64
65Your new module will need to have a registry that contains a list of all of the
66checks to be performed on the library. There is an
AndroidX Core Team2e416b22020-12-03 22:58:07 +000067[`IssueRegistry`](https://cs.android.com/android/platform/superproject/+/master:tools/base/lint/libs/lint-api/src/main/java/com/android/tools/lint/client/api/IssueRegistry.java;l=47)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070068class provided by the tools team. Extend this class into your own
69`IssueRegistry` class, and provide it with the issues in the module.
70
AndroidX Core Teame80aab72021-09-29 08:44:33 -070071`MyLibraryIssueRegistry.kt`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070072
73```kotlin
74class MyLibraryIssueRegistry : IssueRegistry() {
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070075 override val minApi = CURRENT_API
AndroidX Core Team21ccf652022-04-01 14:53:07 +000076 override val api = 13
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070077 override val issues get() = listOf(MyLibraryDetector.ISSUE)
AndroidX Core Team21ccf652022-04-01 14:53:07 +000078 override val vendor = Vendor(
79 feedbackUrl = "https://issuetracker.google.com/issues/new?component=######",
80 identifier = "androidx.mylibrary",
81 vendorName = "Android Open Source Project",
82 )
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070083}
84```
85
AndroidX Core Team21ccf652022-04-01 14:53:07 +000086The maximum version this lint check will will work with is defined by `api =
8713`. Typically, this should track `CURRENT_API`.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070088
AndroidX Core Team21ccf652022-04-01 14:53:07 +000089`minApi = CURRENT_API` sets the lowest version of the Lint tool that this will
90work with.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070091
AndroidX Core Team21ccf652022-04-01 14:53:07 +000092`CURRENT_API` is defined by the Lint tool API version against which your project
93is compiled, as defined in the module's `build.gradle` file. Jetpack lint check
94modules should compile using the Lint tool API version referenced in
95[libs.versions.toml](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:gradle/libs.versions.toml;l=8).
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070096
AndroidX Core Team21ccf652022-04-01 14:53:07 +000097We guarantee that our lint checks work with the versions referenced by `minApi`
AndroidX Core Teame80aab72021-09-29 08:44:33 -070098and `api` by running our tests with both versions. For newer versions of Android
AndroidX Core Team21ccf652022-04-01 14:53:07 +000099Studio (and consequently, the Lint tool) the API variable will need to be
100updated.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700101
102The `IssueRegistry` requires a list of all of the issues to check. You must
103override the `IssueRegistry.getIssues()` method. Here, we override that method
104with a Kotlin `get()` property delegate:
105
AndroidX Core Teame80aab72021-09-29 08:44:33 -0700106[Example `IssueRegistry` Implementation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700107
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000108There are 4 primary types of lint checks:
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700109
1101. Code - Applied to source code, ex. `.java` and `.kt` files
1111. XML - Applied to XML resource files
1121. Android Manifest - Applied to `AndroidManifest.xml`
1131. Gradle - Applied to Gradle configuration files, ex. `build.gradle`
114
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000115It is also possible to apply lint checks to compiled bytecode (`.class` files)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700116or binary resource files like images, but these are less common.
117
118## PSI & UAST mapping
119
120To view the PSI structure of any file in Android Studio, use the
121[PSI Viewer](https://www.jetbrains.com/help/idea/psi-viewer.html) located in
AndroidX Core Team5dcbcae2023-06-05 12:14:33 -0700122`Tools > View PSI Structure`.
123
124Note: The PSI Viewer requires enabling internal mode. Follow the directions
125[here](https://plugins.jetbrains.com/docs/intellij/enabling-internal.html) to
126add `idea.is.internal=true` to `idea.properties.`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700127
128<table>
129 <tr>
130 <td><strong>PSI</strong>
131 </td>
132 <td><strong>UAST</strong>
133 </td>
134 </tr>
135 <tr>
136 <td>PsiAnnotation
137 </td>
138 <td>UAnnotation
139 </td>
140 </tr>
141 <tr>
142 <td>PsiAnonymousClass
143 </td>
144 <td>UAnonymousClass
145 </td>
146 </tr>
147 <tr>
148 <td>PsiArrayAccessExpression
149 </td>
150 <td>UArrayAccessExpression
151 </td>
152 </tr>
153 <tr>
154 <td>PsiBinaryExpression
155 </td>
156 <td>UArrayAccesExpression
157 </td>
158 </tr>
159 <tr>
160 <td>PsiCallExpression
161 </td>
162 <td>UCallExpression
163 </td>
164 </tr>
165 <tr>
166 <td>PsiCatchSection
167 </td>
168 <td>UCatchClause
169 </td>
170 </tr>
171 <tr>
172 <td>PsiClass
173 </td>
174 <td>UClass
175 </td>
176 </tr>
177 <tr>
178 <td>PsiClassObjectAccessExpression
179 </td>
180 <td>UClassLiteralExpression
181 </td>
182 </tr>
183 <tr>
184 <td>PsiConditionalExpression
185 </td>
186 <td>UIfExpression
187 </td>
188 </tr>
189 <tr>
190 <td>PsiDeclarationStatement
191 </td>
192 <td>UDeclarationExpression
193 </td>
194 </tr>
195 <tr>
196 <td>PsiDoWhileStatement
197 </td>
198 <td>UDoWhileExpression
199 </td>
200 </tr>
201 <tr>
202 <td>PsiElement
203 </td>
204 <td>UElement
205 </td>
206 </tr>
207 <tr>
208 <td>PsiExpression
209 </td>
210 <td>UExpression
211 </td>
212 </tr>
213 <tr>
214 <td>PsiForeachStatement
215 </td>
216 <td>UForEachExpression
217 </td>
218 </tr>
219 <tr>
220 <td>PsiIdentifier
221 </td>
222 <td>USimpleNameReferenceExpression
223 </td>
224 </tr>
225 <tr>
226 <td>PsiLiteral
227 </td>
228 <td>ULiteralExpression
229 </td>
230 </tr>
231 <tr>
232 <td>PsiLocalVariable
233 </td>
234 <td>ULocalVariable
235 </td>
236 </tr>
237 <tr>
238 <td>PsiMethod
239 </td>
240 <td>UMethod
241 </td>
242 </tr>
243 <tr>
244 <td>PsiMethodCallExpression
245 </td>
246 <td>UCallExpression
247 </td>
248 </tr>
249 <tr>
250 <td>PsiParameter
251 </td>
252 <td>UParameter
253 </td>
254 </tr>
255</table>
256
257## Code detector
258
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000259These are lint checks that will apply to source code files -- primarily Java and
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700260Kotlin, but can also be used for other similar file types. All code detectors
261that analyze Java or Kotlin files should implement the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000262[SourceCodeScanner](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/SourceCodeScanner.kt).
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700263
264### API surface
265
266#### Calls to specific methods
267
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000268##### `getApplicableMethodNames`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700269
270This defines the list of methods where lint will call the visitMethodCall
271callback.
272
273```kotlin
274override fun getApplicableMethodNames(): List<String>? = listOf(METHOD_NAMES)
275```
276
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000277##### `visitMethodCall`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700278
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000279This defines the callback that the Lint tool will call when it encounters a call
280to an applicable method.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700281
282```kotlin
283override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {}
284```
285
286#### Calls to specific class instantiations
287
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000288##### `getApplicableConstructorTypes`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700289
290```kotlin
291override fun getApplicableConstructorTypes(): List<String>? = listOf(CLASS_NAMES)
292```
293
294##### visitConstructor
295
296```kotlin
297override fun visitConstructor(context: JavaContext, node: UCallExpression, method: PsiMethod) {}
298```
299
300#### Classes that extend given superclasses
301
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000302##### `getApplicableSuperClasses`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700303
304```kotlin
305override fun applicableSuperClasses(): List<String>? = listOf(CLASS_NAMES)
306```
307
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000308##### `visitClass`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700309
310```kotlin
311override fun visitClass(context: JavaContext, declaration: UClass) {}
312```
313
314#### Call graph support
315
316It is possible to perform analysis on the call graph of a project. However, this
317is highly resource intensive since it generates a single call graph of the
318entire project and should only be used for whole project analysis. To perform
319this analysis you must enable call graph support by overriding the
320`isCallGraphRequired` method and access the call graph with the
321`analyzeCallGraph(context: Context, callGraph: CallGraphResult)` callback
322method.
323
324For performing less resource intensive, on-the-fly analysis it is best to
325recursively analyze method bodies. However, when doing this there should be a
326depth limit on the exploration. If possible, lint should also not explore within
327files that are currently not open in studio.
328
329### Method call analysis
330
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000331#### `resolve()`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700332
333Resolves into a `UCallExpression` or `UMethod` to perform analysis requiring the
334method body or containing class.
335
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000336#### `receiverType`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700337
338Each `UCallExpression` has a `receiverType` corresponding to the `PsiType` of
339the receiver of the method call.
340
341```kotlin
342public abstract class LiveData<T> {
343 public void observe() {}
344}
345
346public abstract class MutableLiveData<T> extends LiveData<T> {}
347
348MutableLiveData<String> liveData = new MutableLiveData<>();
349liveData.observe() // receiverType = PsiType<MutableLiveData>
350```
351
352#### Kotlin named parameter mapping
353
354`JavaEvaluator`contains a helper method `computeArgumentMapping(call:
355UCallExpression, method: PsiMethod)` that creates a mapping between method call
356parameters and the corresponding resolved method arguments, accounting for
357Kotlin named parameters.
358
359```kotlin
360override fun visitMethodCall(context: JavaContext, node: UCallExpression,
361 method: PsiMethod) {
362 val argMap: Map<UExpression, PsiParameter> = context.evaluator.computArgumentMapping(node, psiMethod)
363}
364```
365
366### Testing
367
368Because the `LintDetectorTest` API does not have access to library classes and
369methods, you must implement stubs for any necessary classes and include these as
370additional files in your test cases. For example, if a lint check involves
371Fragment's `getViewLifecycleOwner` and `onViewCreated` methods, then we must
372create a stub for this:
373
374```
375java("""
376 package androidx.fragment.app;
377
378 import androidx.lifecycle.LifecycleOwner;
379
380 public class Fragment {
381 public LifecycleOwner getViewLifecycleOwner() {}
382 public void onViewCreated() {}
383 }
384""")
385```
386
387Since this class also depends on the `LifecycleOwner` class it is necessary to
388create another stub for this.
389
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000390NOTE `package-info.java` cannot be represented by a source stub and must be
391provided as bytecode. See [Updating bytecode](#tips-bytecode) for tips on using
392bytecode in lint tests.
393
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700394## XML resource detector
395
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000396These are lint checks that will apply to resource files including `anim`,
397`layout`, `values`, etc. lint checks being applied to resource files should
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700398extend
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000399[`ResourceXmlDetector`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/ResourceXmlDetector.java).
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700400The `Detector` must define the issue it is going to detect, most commonly as a
401static variable of the class.
402
403```kotlin
404companion object {
405 val ISSUE = Issue.create(
406 id = "TitleOfMyIssue",
407 briefDescription = "Short description of issue. This will be what the studio inspection menu shows",
408 explanation = """Here is where you define the reason that this lint rule exists in detail.""",
409 category = Category.CORRECTNESS,
410 severity = Severity.LEVEL,
411 implementation = Implementation(
412 MyIssueDetector::class.java, Scope.RESOURCE_FILE_SCOPE
413 ),
414 androidSpecific = true
415 ).addMoreInfo(
416 "https://linkToMoreInfo.com"
417 )
418}
419```
420
421### API surface
422
423The following methods can be overridden:
424
425```kotlin
426appliesTo(folderType: ResourceFolderType)
427getApplicableElements()
428visitElement(context: XmlContext, element: Element)
429```
430
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000431#### `appliesTo`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700432
433This determines the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000434[ResourceFolderType](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:layoutlib-api/src/main/java/com/android/resources/ResourceFolderType.java)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700435that the check will run against.
436
437```kotlin
438override fun appliesTo(folderType: ResourceFolderType): Boolean {
439 return folderType == ResourceFolderType.TYPE
440}
441```
442
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000443#### `getApplicableElements`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700444
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000445This defines the list of elements where the Lint tool will call your
446`visitElement` callback method when encountered.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700447
448```kotlin
449override fun getApplicableElements(): Collection<String>? = Collections.singleton(ELEMENT)
450```
451
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000452#### `visitElement`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700453
454This defines the behavior when an applicable element is found. Here you normally
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000455place the actions you want to take if a violation of the lint check is found.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700456
457```kotlin
458override fun visitElement(context: XmlContext, element: Element) {
AndroidX Core Team1dbea9e2023-11-03 10:21:51 -0700459 val fix = LintFix.create()
460 .replace()
AndroidX Core Teamcf946032022-02-11 15:52:08 -0800461 .text(ELEMENT)
AndroidX Core Team1dbea9e2023-11-03 10:21:51 -0700462 .with(REPLACEMENT_TEXT)
AndroidX Core Teamcf946032022-02-11 15:52:08 -0800463 .build()
464
AndroidX Core Team1dbea9e2023-11-03 10:21:51 -0700465 context.report(
466 issue = ISSUE,
467 location = context.getNameLocation(element),
468 message = "My issue message",
469 quickFixData = fix
470 )
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700471}
472```
473
474In this instance, the call to `report()` takes the definition of the issue, the
475location of the element that has the issue, the message to display on the
476element, as well as a quick fix. In this case we replace our element text with
477some other text.
478
AndroidX Core Team408c27b2020-12-15 15:57:00 +0000479[Example Detector Implementation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentTagDetector.kt)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700480
481### Testing
482
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000483You need tests for two things. First, you must test that the Lint tool API
484version is properly set. That is done with a simple `ApiLintVersionTest` class.
485It asserts the API version code set earlier in the `IssueRegistry()` class. This
486test intentionally fails in the IDE because different Lint tool API versions are
487used in Studio and the command line.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700488
489Example `ApiLintVersionTest`:
490
491```kotlin
492class ApiLintVersionsTest {
493
494 @Test
495 fun versionsCheck() {
AndroidX Core Teambb6223c2022-09-27 10:39:19 -0700496 LintClient.clientName = LintClient.CLIENT_UNIT_TESTS
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700497 val registry = MyLibraryIssueRegistry()
498 assertThat(registry.api).isEqualTo(CURRENT_API)
AndroidX Core Teambb6223c2022-09-27 10:39:19 -0700499 assertThat(registry.minApi).isEqualTo(10)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700500 }
501}
502```
503
504Next, you must test the `Detector` class. The Tools team provides a
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000505[`LintDetectorTest`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700506class that should be extended. Override `getDetector()` to return an instance of
507the `Detector` class:
508
509```kotlin
510override fun getDetector(): Detector = MyLibraryDetector()
511```
512
513Override `getIssues()` to return the list of Detector Issues:
514
515```kotlin
516getIssues(): MutableList<Issue> = mutableListOf(MyLibraryDetector.ISSUE)
517```
518
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000519[`LintDetectorTest`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/LintDetectorTest.java)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700520provides a `lint()` method that returns a
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000521[`TestLintTask`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestLintTask.java).
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700522`TestLintTask` is a builder class for setting up lint tests. Call the `files()`
523method and provide an `.xml` test file, along with a file stub. After completing
524the set up, call `run()` which returns a
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000525[`TestLintResult`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-tests/src/main/java/com/android/tools/lint/checks/infrastructure/TestLintResult.kt).
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700526`TestLintResult` provides methods for checking the outcome of the provided
527`TestLintTask`. `ExpectClean()` means the output is expected to be clean because
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000528the lint check was followed. `Expect()` takes a string literal of the expected
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700529output of the `TestLintTask` and compares the actual result to the input string.
530If a quick fix was implemented, you can check that the fix is correct by calling
531`checkFix()` and providing the expected output file stub.
532
AndroidX Core Team408c27b2020-12-15 15:57:00 +0000533[TestExample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment-lint/src/test/java/androidx/fragment/lint/FragmentTagDetectorTest.kt)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700534
535## Android manifest detector
536
537Lint checks targeting `AndroidManifest.xml` files should implement the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000538[XmlScanner](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/XmlScanner.kt)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700539and define target scope in issues as `Scope.MANIFEST`
540
541## Gradle detector
542
543Lint checks targeting Gradle configuration files should implement the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000544[GradleScanner](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/GradleScanner.kt)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700545and define target scope in issues as `Scope.GRADLE_SCOPE`
546
547### API surface
548
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000549#### `checkDslPropertyAssignment`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700550
551Analyzes each DSL property assignment, providing the property and value strings.
552
553```kotlin
554fun checkDslPropertyAssignment(
555 context: GradleContext,
556 property: String,
557 value: String,
558 parent: String,
559 parentParent: String?,
560 propertyCookie: Any,
561 valueCookie: Any,
562 statementCookie: Any
563) {}
564```
565
566The property, value, and parent string parameters provided by this callback are
567the literal values in the gradle file. Any string values in the Gradle file will
568be quote enclosed in the value parameter. Any constant values cannot be resolved
569to their values.
570
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000571The cookie parameters should be used for reporting lint check errors. To report
572an issue on the value, use `context.getLocation(statementCookie)`.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700573
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000574## Enabling lint checks for a library
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700575
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000576Once the lint module is implemented we need to enable it for the desired
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700577library. This can be done by adding a `lintPublish` rule to the `build.gradle`
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000578of the library the lint check should apply to.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700579
580```
581lintPublish(project(':mylibrary:mylibrary-lint'))
582```
583
584This adds a `lint.jar` file into the `.aar` bundle of the desired library.
585
586Then we should add a `com.android.tools.lint.client.api.IssueRegistry` file in
AndroidX Core Teambb6223c2022-09-27 10:39:19 -0700587`mylibrary > mylibrary-lint > main > resources > META-INF > services`. The file
588should contain a single line that has the `IssueRegistry` class name with the
589full path. This class can contain more than one line if the module contains
590multiple registries.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700591
592```
593androidx.mylibrary.lint.MyLibraryIssueRegistry
594```
595
AndroidX Core Teama0d74872024-03-13 10:09:04 -0700596Note that `lintPublish` only publishes the lint module, it doesn't include it
597when running lint on the module that `lintPublish` is attached to. In order to
598also run these lint checks as part of the module that is publishing them, you
599can add `lintChecks` in the same way.
600
601```
602lintChecks(project(':mylibrary:mylibrary-lint'))
603```
604
AndroidX Core Teambec44682022-09-27 13:00:25 -0700605## Advanced topics
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700606
607### Analyzing multiple different file types
608
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000609Sometimes it is necessary to implement multiple different scanners in a lint
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700610detector. For example, the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000611[Unused Resource](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/UnusedResourceDetector.java)
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000612lint check implements an XML and SourceCodeScanner in order to determine if
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700613resources defined in XML files are ever references in the Java/Kotlin source
614code.
615
616#### File type iteration order
617
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000618The Lint tool processes files in a predefined order:
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700619
6201. Manifests
6211. Android XML Resources (alphabetical by folder type)
6221. Java & Kotlin
6231. Bytecode
6241. Gradle
625
626### Multi-pass analysis
627
628It is often necessary to process the sources more than once. This can be done by
629using `context.driver.requestRepeat(detector, scope)`.
630
AndroidX Core Teama5c159d2024-03-28 12:20:16 -0700631### Debugging custom lint checks
632
633Using Android Studio, there are a few ways to debug custom lint checks:
634
AndroidX Core Teamd32da5d2024-05-15 12:46:37 -0700635#### Debug against lint running from the command line
AndroidX Core Teama5c159d2024-03-28 12:20:16 -0700636
6371. Set breakpoint(s) in the desired lint detector sources
6381. Click the `Gradle` icon on the right menu bar
6391. Run the `lintDebug` Gradle task and then hit the `Stop` icon in the top menu
640 bar. This creates a Run configuration.
6411. Click the `Debug` icon in the top menu bar for the newly-selected Run
642 configuration
6431. Breakpoint will get hit
644
645#### Debug against a single lint check test
646
6471. Set breakpoint(s) in the desired lint detector sources
6481. Open a lint check test, such as
649 [`AnnotationRetentionDetectorTest`](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:annotation/annotation-experimental-lint/src/test/kotlin/androidx/annotation/experimental/lint/AnnotationRetentionDetectorTest.kt)
6501. Right-click on a test method and select `Debug`
6511. Breakpoint will get hit
652
AndroidX Core Teamd32da5d2024-05-15 12:46:37 -0700653#### Debug against lint running inside Android Studio
654
655The UAST environment can be different when a lint check is running on the fly
656inside Android Studio, instead of the command line (for example b/191508358). To
657debug issues with a lint check that only occur inside the IDE, you can debug the
658lint check when it runs inside Studio.
659
6601. Set breakpoint(s) in the desired lint detector sources (make sure that the
661 sources you have match the sources being used in the library version you
662 want to test against)
6632. Download a separate Studio instance (such as latest canary), and open it.
6643. With the new Studio instance, go to Help -> Edit Custom VM Options - this
665 will open up studio.vmoptions. Add the following lines:
666
667```
668-Xdebug
669-Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y
670```
671
6721. Then close Android Studio. This will allow attaching a debugger next time
673 this Studio instance is opened.
6742. In the original (AndroidX) Studio instance, create a
675 [remote JVM debug configuration](https://www.jetbrains.com/help/idea/tutorial-remote-debug.html#create-run-configurations).
676 The default configuration should be correct, double check that the port
677 specified is the same as the address you specified in studio.vmoptions in
678 the previous step.
6793. Launch the separate Studio instance: it should wait for a debugger to be
680 attached
6814. In the AndroidX Studio instance, press debug with the remote configuration -
682 this should attach the debugger, and the separate Studio instance should
683 continue to open normally
6845. In the separate Studio instance, open / create a sample project that uses
685 the library with the lint check you want to debug, and add some code that
686 should trigger / not trigger an error accordingly.
6876. Breakpoint will get hit
688
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800689## Helpful tips {#tips}
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700690
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800691### Useful classes/packages
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700692
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800693[`SdkConstants`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:common/src/main/java/com/android/SdkConstants.java) -
694contains most of the canonical names for Android core library classes, as well
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700695as XML tag names.
696
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000697### Updating bytecode and checksum in tests {#tips-bytecode}
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800698
699When updating a file that is used in a lint test, the following error may appear
700when running tests:
701
702```
703The checksum does not match for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt;
704expected 0x1af1856 but was 0x6692f601.
705Has the source file been changed without updating the binaries?
706Don't just update the checksum -- delete the binary file arguments and re-run the test first!
707java.lang.AssertionError: The checksum does not match for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt;
708expected 0x1af1856 but was 0x6692f601.
709Has the source file been changed without updating the binaries?
710Don't just update the checksum -- delete the binary file arguments and re-run the test first!
711 at org.junit.Assert.fail(Assert.java:89)
712 ...
713```
714
715Here are the steps to fix this:
716
7171. Remove the arguments in `compiled()`:
718
719 ```
720 // Before
721 compiled(
722 "libs/ktlib.jar",
723 ktSample("androidx.sample.deprecated.DeprecatedKotlinClass"),
724 0x6692f601,
725 """
726 META-INF/main.kotlin_module:
727 H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM2gxKDFAABNj30wGAAAAA==
728 """,
729 """
730 androidx/sample/deprecated/DeprecatedKotlinClass.class:
731 H4sIAAAAAAAAAJVSy27TQBQ9YydxcQNNH5SUZyivlkWSpuxAiFIEighBCiit
732 // rest of bytecode
733 """
734 )
735
736 // After
737 compiled(
738 "libs/ktlib.jar",
739 ktSample("androidx.sample.deprecated.DeprecatedKotlinClass"),
740 )
741 ```
742
7432. Set `$LINT_TEST_KOTLINC` to the location of `kotlinc` if you haven't
744 already, and add it to the test run configuration's environment variables.
745
746 Note: The location of `kotlinc` can vary; use your system's file finder to
747 determine the exact location. For gLinux, search under
748 `~/.local/share/JetBrains`. For Mac, search under `<your androidx checkout
749 root>/frameworks/support/studio`
750
751 If it's not set (or set incorrectly), this error message appears when
752 running tests:
753
754 ```
755 Couldn't find kotlinc to update test file java/androidx/sample/deprecated/DeprecatedKotlinClass.kt with.
756 Point to it with $LINT_TEST_KOTLINC
757 ```
758
7593. Run the test, which will output the new bytecode and checksum:
760
761 ```
762 Update the test source declaration for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt with this list of encodings:
763
764 Kotlin:
765 compiled(
766 "libs/ktlib.jar",
767 kotlin(
768 """
769 package java.androidx.sample.deprecated
770
771 @Deprecated(
772 // (rest of inlined sample file)
773 """
774 ).indented(),
775 0x5ba03e2d,
776 """
777 META-INF/main.kotlin_module:
778 H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM2gxKDFAABNj30wGAAAAA==
779 // rest of bytecode
780 """,
781 """
782 java/androidx/sample/deprecated/DeprecatedKotlinClass.class:
783 """
784 )
785 ```
786
787Note: the generated replacement code will inline the specified sample file (in
788our case, `ktSample("androidx.sample.deprecated.DeprecatedKotlinClass")`).
789Replace the inlined code with the sample declaration.
790
AndroidX Core Teama0d74872024-03-13 10:09:04 -0700791### Lint checks with WARNING severity (my lint check won't run!) {#tips-warnings}
792
793In AndroidX lint checks with a severity of `WARNING` are ignored by default to
794prevent noise from bundled lint checks. If your lint check has this severity,
795and you want it to run inside AndroidX, you'll need to override the severity: in
796Compose for example this happens in
797[AndroidXComposeLintIssues](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeLintIssues.kt).
798
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700799## Helpful links
800
AndroidX Core Team8a082f92021-07-01 11:46:10 -0700801[Writing Custom Lint Rules](https://googlesamples.github.io/android-custom-lint-rules/)
802
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000803[Studio Lint Rules](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700804
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000805[Lint Detectors and Scanners Source Code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:lint/libs/lint-api/src/main/java/com/android/tools/lint/detector/api/)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700806
807[Creating Custom Link Checks (external)](https://twitter.com/alexjlockwood/status/1176675045281693696)
808
809[Android Custom Lint Rules by Tor](https://github.com/googlesamples/android-custom-lint-rules)
810
811[Public lint-dev Google Group](https://groups.google.com/forum/#!forum/lint-dev)
812
813[In-depth Lint Video Presentation by Tor](https://www.youtube.com/watch?v=p8yX5-lPS6o)
814(partially out-dated)
815([Slides](https://resources.jetbrains.com/storage/products/kotlinconf2017/slides/KotlinConf+Lint+Slides.pdf))
816
817[ADS 19 Presentation by Alan & Rahul](https://www.youtube.com/watch?v=jCmJWOkjbM0)
818
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000819[META-INF vs Manifest](https://groups.google.com/forum/#!msg/lint-dev/z3NYazgEIFQ/hbXDMYp5AwAJ)