blob: e3f78a1b53be484ece3b0518572a0da3619e2aa1 [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
635#### Debug against all lint check tests
636
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 Teame31e9592021-12-09 11:27:33 -0800653## Helpful tips {#tips}
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700654
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800655### Useful classes/packages
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700656
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800657[`SdkConstants`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:common/src/main/java/com/android/SdkConstants.java) -
658contains most of the canonical names for Android core library classes, as well
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700659as XML tag names.
660
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000661### Updating bytecode and checksum in tests {#tips-bytecode}
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800662
663When updating a file that is used in a lint test, the following error may appear
664when running tests:
665
666```
667The checksum does not match for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt;
668expected 0x1af1856 but was 0x6692f601.
669Has the source file been changed without updating the binaries?
670Don't just update the checksum -- delete the binary file arguments and re-run the test first!
671java.lang.AssertionError: The checksum does not match for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt;
672expected 0x1af1856 but was 0x6692f601.
673Has the source file been changed without updating the binaries?
674Don't just update the checksum -- delete the binary file arguments and re-run the test first!
675 at org.junit.Assert.fail(Assert.java:89)
676 ...
677```
678
679Here are the steps to fix this:
680
6811. Remove the arguments in `compiled()`:
682
683 ```
684 // Before
685 compiled(
686 "libs/ktlib.jar",
687 ktSample("androidx.sample.deprecated.DeprecatedKotlinClass"),
688 0x6692f601,
689 """
690 META-INF/main.kotlin_module:
691 H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM2gxKDFAABNj30wGAAAAA==
692 """,
693 """
694 androidx/sample/deprecated/DeprecatedKotlinClass.class:
695 H4sIAAAAAAAAAJVSy27TQBQ9YydxcQNNH5SUZyivlkWSpuxAiFIEighBCiit
696 // rest of bytecode
697 """
698 )
699
700 // After
701 compiled(
702 "libs/ktlib.jar",
703 ktSample("androidx.sample.deprecated.DeprecatedKotlinClass"),
704 )
705 ```
706
7072. Set `$LINT_TEST_KOTLINC` to the location of `kotlinc` if you haven't
708 already, and add it to the test run configuration's environment variables.
709
710 Note: The location of `kotlinc` can vary; use your system's file finder to
711 determine the exact location. For gLinux, search under
712 `~/.local/share/JetBrains`. For Mac, search under `<your androidx checkout
713 root>/frameworks/support/studio`
714
715 If it's not set (or set incorrectly), this error message appears when
716 running tests:
717
718 ```
719 Couldn't find kotlinc to update test file java/androidx/sample/deprecated/DeprecatedKotlinClass.kt with.
720 Point to it with $LINT_TEST_KOTLINC
721 ```
722
7233. Run the test, which will output the new bytecode and checksum:
724
725 ```
726 Update the test source declaration for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt with this list of encodings:
727
728 Kotlin:
729 compiled(
730 "libs/ktlib.jar",
731 kotlin(
732 """
733 package java.androidx.sample.deprecated
734
735 @Deprecated(
736 // (rest of inlined sample file)
737 """
738 ).indented(),
739 0x5ba03e2d,
740 """
741 META-INF/main.kotlin_module:
742 H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM2gxKDFAABNj30wGAAAAA==
743 // rest of bytecode
744 """,
745 """
746 java/androidx/sample/deprecated/DeprecatedKotlinClass.class:
747 """
748 )
749 ```
750
751Note: the generated replacement code will inline the specified sample file (in
752our case, `ktSample("androidx.sample.deprecated.DeprecatedKotlinClass")`).
753Replace the inlined code with the sample declaration.
754
AndroidX Core Teama0d74872024-03-13 10:09:04 -0700755### Lint checks with WARNING severity (my lint check won't run!) {#tips-warnings}
756
757In AndroidX lint checks with a severity of `WARNING` are ignored by default to
758prevent noise from bundled lint checks. If your lint check has this severity,
759and you want it to run inside AndroidX, you'll need to override the severity: in
760Compose for example this happens in
761[AndroidXComposeLintIssues](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeLintIssues.kt).
762
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700763## Helpful links
764
AndroidX Core Team8a082f92021-07-01 11:46:10 -0700765[Writing Custom Lint Rules](https://googlesamples.github.io/android-custom-lint-rules/)
766
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000767[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 -0700768
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000769[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 -0700770
771[Creating Custom Link Checks (external)](https://twitter.com/alexjlockwood/status/1176675045281693696)
772
773[Android Custom Lint Rules by Tor](https://github.com/googlesamples/android-custom-lint-rules)
774
775[Public lint-dev Google Group](https://groups.google.com/forum/#!forum/lint-dev)
776
777[In-depth Lint Video Presentation by Tor](https://www.youtube.com/watch?v=p8yX5-lPS6o)
778(partially out-dated)
779([Slides](https://resources.jetbrains.com/storage/products/kotlinconf2017/slides/KotlinConf+Lint+Slides.pdf))
780
781[ADS 19 Presentation by Alan & Rahul](https://www.youtube.com/watch?v=jCmJWOkjbM0)
782
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000783[META-INF vs Manifest](https://groups.google.com/forum/#!msg/lint-dev/z3NYazgEIFQ/hbXDMYp5AwAJ)