blob: 49b207076d49246e697fd296ad39aa21adea7a74 [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```
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070036import androidx.build.LibraryGroups
AndroidX Core Teame80aab72021-09-29 08:44:33 -070037import androidx.build.LibraryType
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070038import androidx.build.LibraryVersions
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070039
40plugins {
41 id("AndroidXPlugin")
42 id("kotlin")
43}
44
45dependencies {
AndroidX Core Teame80aab72021-09-29 08:44:33 -070046 compileOnly(libs.androidLintMinApi)
47 compileOnly(libs.kotlinStdlib)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070048
AndroidX Core Teame80aab72021-09-29 08:44:33 -070049 testImplementation(libs.kotlinStdlib)
50 testImplementation(libs.androidLint)
51 testImplementation(libs.androidLintTests)
52 testImplementation(libs.junit)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070053}
54
55androidx {
AndroidX Core Teame80aab72021-09-29 08:44:33 -070056 name = "MyLibrary lint checks"
57 type = LibraryType.LINT
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070058 mavenVersion = LibraryVersions.MYLIBRARY
59 mavenGroup = LibraryGroups.MYLIBRARY
AndroidX Core Team21ccf652022-04-01 14:53:07 +000060 inceptionYear = "2022"
AndroidX Core Teame80aab72021-09-29 08:44:33 -070061 description = "Lint checks for MyLibrary"
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070062}
63```
64
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070065### Issue registry
66
67Your new module will need to have a registry that contains a list of all of the
68checks to be performed on the library. There is an
AndroidX Core Team2e416b22020-12-03 22:58:07 +000069[`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 -070070class provided by the tools team. Extend this class into your own
71`IssueRegistry` class, and provide it with the issues in the module.
72
AndroidX Core Teame80aab72021-09-29 08:44:33 -070073`MyLibraryIssueRegistry.kt`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070074
75```kotlin
76class MyLibraryIssueRegistry : IssueRegistry() {
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070077 override val minApi = CURRENT_API
AndroidX Core Team21ccf652022-04-01 14:53:07 +000078 override val api = 13
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070079 override val issues get() = listOf(MyLibraryDetector.ISSUE)
AndroidX Core Team21ccf652022-04-01 14:53:07 +000080 override val vendor = Vendor(
81 feedbackUrl = "https://issuetracker.google.com/issues/new?component=######",
82 identifier = "androidx.mylibrary",
83 vendorName = "Android Open Source Project",
84 )
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070085}
86```
87
AndroidX Core Team21ccf652022-04-01 14:53:07 +000088The maximum version this lint check will will work with is defined by `api =
8913`. Typically, this should track `CURRENT_API`.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070090
AndroidX Core Team21ccf652022-04-01 14:53:07 +000091`minApi = CURRENT_API` sets the lowest version of the Lint tool that this will
92work with.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -070093
AndroidX Core Team21ccf652022-04-01 14:53:07 +000094`CURRENT_API` is defined by the Lint tool API version against which your project
95is compiled, as defined in the module's `build.gradle` file. Jetpack lint check
96modules should compile using the Lint tool API version referenced in
97[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 -070098
AndroidX Core Team21ccf652022-04-01 14:53:07 +000099We guarantee that our lint checks work with the versions referenced by `minApi`
AndroidX Core Teame80aab72021-09-29 08:44:33 -0700100and `api` by running our tests with both versions. For newer versions of Android
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000101Studio (and consequently, the Lint tool) the API variable will need to be
102updated.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700103
104The `IssueRegistry` requires a list of all of the issues to check. You must
105override the `IssueRegistry.getIssues()` method. Here, we override that method
106with a Kotlin `get()` property delegate:
107
AndroidX Core Teame80aab72021-09-29 08:44:33 -0700108[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 -0700109
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000110There are 4 primary types of lint checks:
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700111
1121. Code - Applied to source code, ex. `.java` and `.kt` files
1131. XML - Applied to XML resource files
1141. Android Manifest - Applied to `AndroidManifest.xml`
1151. Gradle - Applied to Gradle configuration files, ex. `build.gradle`
116
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000117It is also possible to apply lint checks to compiled bytecode (`.class` files)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700118or binary resource files like images, but these are less common.
119
120## PSI & UAST mapping
121
122To view the PSI structure of any file in Android Studio, use the
123[PSI Viewer](https://www.jetbrains.com/help/idea/psi-viewer.html) located in
124`Tools > View PSI Structure`. The PSI Viewer should be enabled by default on the
AndroidX Core Team408c27b2020-12-15 15:57:00 +0000125Android Studio configuration loaded by `studiow` in `androidx-main`. If it is
126not available under `Tools`, you must enable it by adding the line
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700127`idea.is.internal=true` to `idea.properties.`
128
129<table>
130 <tr>
131 <td><strong>PSI</strong>
132 </td>
133 <td><strong>UAST</strong>
134 </td>
135 </tr>
136 <tr>
137 <td>PsiAnnotation
138 </td>
139 <td>UAnnotation
140 </td>
141 </tr>
142 <tr>
143 <td>PsiAnonymousClass
144 </td>
145 <td>UAnonymousClass
146 </td>
147 </tr>
148 <tr>
149 <td>PsiArrayAccessExpression
150 </td>
151 <td>UArrayAccessExpression
152 </td>
153 </tr>
154 <tr>
155 <td>PsiBinaryExpression
156 </td>
157 <td>UArrayAccesExpression
158 </td>
159 </tr>
160 <tr>
161 <td>PsiCallExpression
162 </td>
163 <td>UCallExpression
164 </td>
165 </tr>
166 <tr>
167 <td>PsiCatchSection
168 </td>
169 <td>UCatchClause
170 </td>
171 </tr>
172 <tr>
173 <td>PsiClass
174 </td>
175 <td>UClass
176 </td>
177 </tr>
178 <tr>
179 <td>PsiClassObjectAccessExpression
180 </td>
181 <td>UClassLiteralExpression
182 </td>
183 </tr>
184 <tr>
185 <td>PsiConditionalExpression
186 </td>
187 <td>UIfExpression
188 </td>
189 </tr>
190 <tr>
191 <td>PsiDeclarationStatement
192 </td>
193 <td>UDeclarationExpression
194 </td>
195 </tr>
196 <tr>
197 <td>PsiDoWhileStatement
198 </td>
199 <td>UDoWhileExpression
200 </td>
201 </tr>
202 <tr>
203 <td>PsiElement
204 </td>
205 <td>UElement
206 </td>
207 </tr>
208 <tr>
209 <td>PsiExpression
210 </td>
211 <td>UExpression
212 </td>
213 </tr>
214 <tr>
215 <td>PsiForeachStatement
216 </td>
217 <td>UForEachExpression
218 </td>
219 </tr>
220 <tr>
221 <td>PsiIdentifier
222 </td>
223 <td>USimpleNameReferenceExpression
224 </td>
225 </tr>
226 <tr>
227 <td>PsiLiteral
228 </td>
229 <td>ULiteralExpression
230 </td>
231 </tr>
232 <tr>
233 <td>PsiLocalVariable
234 </td>
235 <td>ULocalVariable
236 </td>
237 </tr>
238 <tr>
239 <td>PsiMethod
240 </td>
241 <td>UMethod
242 </td>
243 </tr>
244 <tr>
245 <td>PsiMethodCallExpression
246 </td>
247 <td>UCallExpression
248 </td>
249 </tr>
250 <tr>
251 <td>PsiParameter
252 </td>
253 <td>UParameter
254 </td>
255 </tr>
256</table>
257
258## Code detector
259
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000260These are lint checks that will apply to source code files -- primarily Java and
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700261Kotlin, but can also be used for other similar file types. All code detectors
262that analyze Java or Kotlin files should implement the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000263[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 -0700264
265### API surface
266
267#### Calls to specific methods
268
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000269##### `getApplicableMethodNames`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700270
271This defines the list of methods where lint will call the visitMethodCall
272callback.
273
274```kotlin
275override fun getApplicableMethodNames(): List<String>? = listOf(METHOD_NAMES)
276```
277
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000278##### `visitMethodCall`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700279
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000280This defines the callback that the Lint tool will call when it encounters a call
281to an applicable method.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700282
283```kotlin
284override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {}
285```
286
287#### Calls to specific class instantiations
288
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000289##### `getApplicableConstructorTypes`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700290
291```kotlin
292override fun getApplicableConstructorTypes(): List<String>? = listOf(CLASS_NAMES)
293```
294
295##### visitConstructor
296
297```kotlin
298override fun visitConstructor(context: JavaContext, node: UCallExpression, method: PsiMethod) {}
299```
300
301#### Classes that extend given superclasses
302
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000303##### `getApplicableSuperClasses`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700304
305```kotlin
306override fun applicableSuperClasses(): List<String>? = listOf(CLASS_NAMES)
307```
308
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000309##### `visitClass`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700310
311```kotlin
312override fun visitClass(context: JavaContext, declaration: UClass) {}
313```
314
315#### Call graph support
316
317It is possible to perform analysis on the call graph of a project. However, this
318is highly resource intensive since it generates a single call graph of the
319entire project and should only be used for whole project analysis. To perform
320this analysis you must enable call graph support by overriding the
321`isCallGraphRequired` method and access the call graph with the
322`analyzeCallGraph(context: Context, callGraph: CallGraphResult)` callback
323method.
324
325For performing less resource intensive, on-the-fly analysis it is best to
326recursively analyze method bodies. However, when doing this there should be a
327depth limit on the exploration. If possible, lint should also not explore within
328files that are currently not open in studio.
329
330### Method call analysis
331
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000332#### `resolve()`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700333
334Resolves into a `UCallExpression` or `UMethod` to perform analysis requiring the
335method body or containing class.
336
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000337#### `receiverType`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700338
339Each `UCallExpression` has a `receiverType` corresponding to the `PsiType` of
340the receiver of the method call.
341
342```kotlin
343public abstract class LiveData<T> {
344 public void observe() {}
345}
346
347public abstract class MutableLiveData<T> extends LiveData<T> {}
348
349MutableLiveData<String> liveData = new MutableLiveData<>();
350liveData.observe() // receiverType = PsiType<MutableLiveData>
351```
352
353#### Kotlin named parameter mapping
354
355`JavaEvaluator`contains a helper method `computeArgumentMapping(call:
356UCallExpression, method: PsiMethod)` that creates a mapping between method call
357parameters and the corresponding resolved method arguments, accounting for
358Kotlin named parameters.
359
360```kotlin
361override fun visitMethodCall(context: JavaContext, node: UCallExpression,
362 method: PsiMethod) {
363 val argMap: Map<UExpression, PsiParameter> = context.evaluator.computArgumentMapping(node, psiMethod)
364}
365```
366
367### Testing
368
369Because the `LintDetectorTest` API does not have access to library classes and
370methods, you must implement stubs for any necessary classes and include these as
371additional files in your test cases. For example, if a lint check involves
372Fragment's `getViewLifecycleOwner` and `onViewCreated` methods, then we must
373create a stub for this:
374
375```
376java("""
377 package androidx.fragment.app;
378
379 import androidx.lifecycle.LifecycleOwner;
380
381 public class Fragment {
382 public LifecycleOwner getViewLifecycleOwner() {}
383 public void onViewCreated() {}
384 }
385""")
386```
387
388Since this class also depends on the `LifecycleOwner` class it is necessary to
389create another stub for this.
390
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000391NOTE `package-info.java` cannot be represented by a source stub and must be
392provided as bytecode. See [Updating bytecode](#tips-bytecode) for tips on using
393bytecode in lint tests.
394
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700395## XML resource detector
396
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000397These are lint checks that will apply to resource files including `anim`,
398`layout`, `values`, etc. lint checks being applied to resource files should
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700399extend
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000400[`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 -0700401The `Detector` must define the issue it is going to detect, most commonly as a
402static variable of the class.
403
404```kotlin
405companion object {
406 val ISSUE = Issue.create(
407 id = "TitleOfMyIssue",
408 briefDescription = "Short description of issue. This will be what the studio inspection menu shows",
409 explanation = """Here is where you define the reason that this lint rule exists in detail.""",
410 category = Category.CORRECTNESS,
411 severity = Severity.LEVEL,
412 implementation = Implementation(
413 MyIssueDetector::class.java, Scope.RESOURCE_FILE_SCOPE
414 ),
415 androidSpecific = true
416 ).addMoreInfo(
417 "https://linkToMoreInfo.com"
418 )
419}
420```
421
422### API surface
423
424The following methods can be overridden:
425
426```kotlin
427appliesTo(folderType: ResourceFolderType)
428getApplicableElements()
429visitElement(context: XmlContext, element: Element)
430```
431
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000432#### `appliesTo`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700433
434This determines the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000435[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 -0700436that the check will run against.
437
438```kotlin
439override fun appliesTo(folderType: ResourceFolderType): Boolean {
440 return folderType == ResourceFolderType.TYPE
441}
442```
443
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000444#### `getApplicableElements`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700445
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000446This defines the list of elements where the Lint tool will call your
447`visitElement` callback method when encountered.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700448
449```kotlin
450override fun getApplicableElements(): Collection<String>? = Collections.singleton(ELEMENT)
451```
452
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000453#### `visitElement`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700454
455This defines the behavior when an applicable element is found. Here you normally
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000456place the actions you want to take if a violation of the lint check is found.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700457
458```kotlin
459override fun visitElement(context: XmlContext, element: Element) {
AndroidX Core Teamcf946032022-02-11 15:52:08 -0800460 val lintFix = fix().replace()
461 .text(ELEMENT)
462 .with(REPLACEMENT TEXT)
463 .build()
464
465 val incident = Incident(context)
466 .fix(lintFix)
467 .issue(ISSUE)
468 .location(context.getLocation(node))
469 .message("My issue message")
470 .scope(context.getNameLocation(element))
471
472 context.report(incident)
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700473}
474```
475
476In this instance, the call to `report()` takes the definition of the issue, the
477location of the element that has the issue, the message to display on the
478element, as well as a quick fix. In this case we replace our element text with
479some other text.
480
AndroidX Core Team408c27b2020-12-15 15:57:00 +0000481[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 -0700482
483### Testing
484
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000485You need tests for two things. First, you must test that the Lint tool API
486version is properly set. That is done with a simple `ApiLintVersionTest` class.
487It asserts the API version code set earlier in the `IssueRegistry()` class. This
488test intentionally fails in the IDE because different Lint tool API versions are
489used in Studio and the command line.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700490
491Example `ApiLintVersionTest`:
492
493```kotlin
494class ApiLintVersionsTest {
495
496 @Test
497 fun versionsCheck() {
498 val registry = MyLibraryIssueRegistry()
499 assertThat(registry.api).isEqualTo(CURRENT_API)
500 assertThat(registry.minApi).isEqualTo(3)
501 }
502}
503```
504
505Next, you must test the `Detector` class. The Tools team provides a
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000506[`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 -0700507class that should be extended. Override `getDetector()` to return an instance of
508the `Detector` class:
509
510```kotlin
511override fun getDetector(): Detector = MyLibraryDetector()
512```
513
514Override `getIssues()` to return the list of Detector Issues:
515
516```kotlin
517getIssues(): MutableList<Issue> = mutableListOf(MyLibraryDetector.ISSUE)
518```
519
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000520[`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 -0700521provides a `lint()` method that returns a
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000522[`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 -0700523`TestLintTask` is a builder class for setting up lint tests. Call the `files()`
524method and provide an `.xml` test file, along with a file stub. After completing
525the set up, call `run()` which returns a
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000526[`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 -0700527`TestLintResult` provides methods for checking the outcome of the provided
528`TestLintTask`. `ExpectClean()` means the output is expected to be clean because
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000529the lint check was followed. `Expect()` takes a string literal of the expected
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700530output of the `TestLintTask` and compares the actual result to the input string.
531If a quick fix was implemented, you can check that the fix is correct by calling
532`checkFix()` and providing the expected output file stub.
533
AndroidX Core Team408c27b2020-12-15 15:57:00 +0000534[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 -0700535
536## Android manifest detector
537
538Lint checks targeting `AndroidManifest.xml` files should implement the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000539[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 -0700540and define target scope in issues as `Scope.MANIFEST`
541
542## Gradle detector
543
544Lint checks targeting Gradle configuration files should implement the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000545[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 -0700546and define target scope in issues as `Scope.GRADLE_SCOPE`
547
548### API surface
549
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000550#### `checkDslPropertyAssignment`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700551
552Analyzes each DSL property assignment, providing the property and value strings.
553
554```kotlin
555fun checkDslPropertyAssignment(
556 context: GradleContext,
557 property: String,
558 value: String,
559 parent: String,
560 parentParent: String?,
561 propertyCookie: Any,
562 valueCookie: Any,
563 statementCookie: Any
564) {}
565```
566
567The property, value, and parent string parameters provided by this callback are
568the literal values in the gradle file. Any string values in the Gradle file will
569be quote enclosed in the value parameter. Any constant values cannot be resolved
570to their values.
571
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000572The cookie parameters should be used for reporting lint check errors. To report
573an issue on the value, use `context.getLocation(statementCookie)`.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700574
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000575## Enabling lint checks for a library
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700576
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000577Once the lint module is implemented we need to enable it for the desired
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700578library. This can be done by adding a `lintPublish` rule to the `build.gradle`
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000579of the library the lint check should apply to.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700580
581```
582lintPublish(project(':mylibrary:mylibrary-lint'))
583```
584
585This adds a `lint.jar` file into the `.aar` bundle of the desired library.
586
587Then we should add a `com.android.tools.lint.client.api.IssueRegistry` file in
588`main > resources > META-INF > services`. The file should contain a single line
589that has the `IssueRegistry` class name with the full path. This class can
590contain more than one line if the module contains multiple registries.
591
592```
593androidx.mylibrary.lint.MyLibraryIssueRegistry
594```
595
596## Advanced topics:
597
598### Analyzing multiple different file types
599
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000600Sometimes it is necessary to implement multiple different scanners in a lint
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700601detector. For example, the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000602[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 +0000603lint check implements an XML and SourceCodeScanner in order to determine if
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700604resources defined in XML files are ever references in the Java/Kotlin source
605code.
606
607#### File type iteration order
608
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000609The Lint tool processes files in a predefined order:
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700610
6111. Manifests
6121. Android XML Resources (alphabetical by folder type)
6131. Java & Kotlin
6141. Bytecode
6151. Gradle
616
617### Multi-pass analysis
618
619It is often necessary to process the sources more than once. This can be done by
620using `context.driver.requestRepeat(detector, scope)`.
621
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800622## Helpful tips {#tips}
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700623
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800624### Useful classes/packages
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700625
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800626[`SdkConstants`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:common/src/main/java/com/android/SdkConstants.java) -
627contains most of the canonical names for Android core library classes, as well
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700628as XML tag names.
629
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000630### Updating bytecode and checksum in tests {#tips-bytecode}
AndroidX Core Teame31e9592021-12-09 11:27:33 -0800631
632When updating a file that is used in a lint test, the following error may appear
633when running tests:
634
635```
636The checksum does not match for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt;
637expected 0x1af1856 but was 0x6692f601.
638Has the source file been changed without updating the binaries?
639Don't just update the checksum -- delete the binary file arguments and re-run the test first!
640java.lang.AssertionError: The checksum does not match for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt;
641expected 0x1af1856 but was 0x6692f601.
642Has the source file been changed without updating the binaries?
643Don't just update the checksum -- delete the binary file arguments and re-run the test first!
644 at org.junit.Assert.fail(Assert.java:89)
645 ...
646```
647
648Here are the steps to fix this:
649
6501. Remove the arguments in `compiled()`:
651
652 ```
653 // Before
654 compiled(
655 "libs/ktlib.jar",
656 ktSample("androidx.sample.deprecated.DeprecatedKotlinClass"),
657 0x6692f601,
658 """
659 META-INF/main.kotlin_module:
660 H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM2gxKDFAABNj30wGAAAAA==
661 """,
662 """
663 androidx/sample/deprecated/DeprecatedKotlinClass.class:
664 H4sIAAAAAAAAAJVSy27TQBQ9YydxcQNNH5SUZyivlkWSpuxAiFIEighBCiit
665 // rest of bytecode
666 """
667 )
668
669 // After
670 compiled(
671 "libs/ktlib.jar",
672 ktSample("androidx.sample.deprecated.DeprecatedKotlinClass"),
673 )
674 ```
675
6762. Set `$LINT_TEST_KOTLINC` to the location of `kotlinc` if you haven't
677 already, and add it to the test run configuration's environment variables.
678
679 Note: The location of `kotlinc` can vary; use your system's file finder to
680 determine the exact location. For gLinux, search under
681 `~/.local/share/JetBrains`. For Mac, search under `<your androidx checkout
682 root>/frameworks/support/studio`
683
684 If it's not set (or set incorrectly), this error message appears when
685 running tests:
686
687 ```
688 Couldn't find kotlinc to update test file java/androidx/sample/deprecated/DeprecatedKotlinClass.kt with.
689 Point to it with $LINT_TEST_KOTLINC
690 ```
691
6923. Run the test, which will output the new bytecode and checksum:
693
694 ```
695 Update the test source declaration for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt with this list of encodings:
696
697 Kotlin:
698 compiled(
699 "libs/ktlib.jar",
700 kotlin(
701 """
702 package java.androidx.sample.deprecated
703
704 @Deprecated(
705 // (rest of inlined sample file)
706 """
707 ).indented(),
708 0x5ba03e2d,
709 """
710 META-INF/main.kotlin_module:
711 H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM2gxKDFAABNj30wGAAAAA==
712 // rest of bytecode
713 """,
714 """
715 java/androidx/sample/deprecated/DeprecatedKotlinClass.class:
716 """
717 )
718 ```
719
720Note: the generated replacement code will inline the specified sample file (in
721our case, `ktSample("androidx.sample.deprecated.DeprecatedKotlinClass")`).
722Replace the inlined code with the sample declaration.
723
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700724## Helpful links
725
AndroidX Core Team8a082f92021-07-01 11:46:10 -0700726[Writing Custom Lint Rules](https://googlesamples.github.io/android-custom-lint-rules/)
727
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000728[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 -0700729
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000730[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 -0700731
732[Creating Custom Link Checks (external)](https://twitter.com/alexjlockwood/status/1176675045281693696)
733
734[Android Custom Lint Rules by Tor](https://github.com/googlesamples/android-custom-lint-rules)
735
736[Public lint-dev Google Group](https://groups.google.com/forum/#!forum/lint-dev)
737
738[In-depth Lint Video Presentation by Tor](https://www.youtube.com/watch?v=p8yX5-lPS6o)
739(partially out-dated)
740([Slides](https://resources.jetbrains.com/storage/products/kotlinconf2017/slides/KotlinConf+Lint+Slides.pdf))
741
742[ADS 19 Presentation by Alan & Rahul](https://www.youtube.com/watch?v=jCmJWOkjbM0)
743
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000744[META-INF vs Manifest](https://groups.google.com/forum/#!msg/lint-dev/z3NYazgEIFQ/hbXDMYp5AwAJ)