blob: e125cf37c08998661a4b06a7562bf3323e473459 [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
122`Tools > View PSI Structure`. The PSI Viewer should be enabled by default on the
AndroidX Core Team408c27b2020-12-15 15:57:00 +0000123Android Studio configuration loaded by `studiow` in `androidx-main`. If it is
124not available under `Tools`, you must enable it by adding the line
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700125`idea.is.internal=true` to `idea.properties.`
126
127<table>
128 <tr>
129 <td><strong>PSI</strong>
130 </td>
131 <td><strong>UAST</strong>
132 </td>
133 </tr>
134 <tr>
135 <td>PsiAnnotation
136 </td>
137 <td>UAnnotation
138 </td>
139 </tr>
140 <tr>
141 <td>PsiAnonymousClass
142 </td>
143 <td>UAnonymousClass
144 </td>
145 </tr>
146 <tr>
147 <td>PsiArrayAccessExpression
148 </td>
149 <td>UArrayAccessExpression
150 </td>
151 </tr>
152 <tr>
153 <td>PsiBinaryExpression
154 </td>
155 <td>UArrayAccesExpression
156 </td>
157 </tr>
158 <tr>
159 <td>PsiCallExpression
160 </td>
161 <td>UCallExpression
162 </td>
163 </tr>
164 <tr>
165 <td>PsiCatchSection
166 </td>
167 <td>UCatchClause
168 </td>
169 </tr>
170 <tr>
171 <td>PsiClass
172 </td>
173 <td>UClass
174 </td>
175 </tr>
176 <tr>
177 <td>PsiClassObjectAccessExpression
178 </td>
179 <td>UClassLiteralExpression
180 </td>
181 </tr>
182 <tr>
183 <td>PsiConditionalExpression
184 </td>
185 <td>UIfExpression
186 </td>
187 </tr>
188 <tr>
189 <td>PsiDeclarationStatement
190 </td>
191 <td>UDeclarationExpression
192 </td>
193 </tr>
194 <tr>
195 <td>PsiDoWhileStatement
196 </td>
197 <td>UDoWhileExpression
198 </td>
199 </tr>
200 <tr>
201 <td>PsiElement
202 </td>
203 <td>UElement
204 </td>
205 </tr>
206 <tr>
207 <td>PsiExpression
208 </td>
209 <td>UExpression
210 </td>
211 </tr>
212 <tr>
213 <td>PsiForeachStatement
214 </td>
215 <td>UForEachExpression
216 </td>
217 </tr>
218 <tr>
219 <td>PsiIdentifier
220 </td>
221 <td>USimpleNameReferenceExpression
222 </td>
223 </tr>
224 <tr>
225 <td>PsiLiteral
226 </td>
227 <td>ULiteralExpression
228 </td>
229 </tr>
230 <tr>
231 <td>PsiLocalVariable
232 </td>
233 <td>ULocalVariable
234 </td>
235 </tr>
236 <tr>
237 <td>PsiMethod
238 </td>
239 <td>UMethod
240 </td>
241 </tr>
242 <tr>
243 <td>PsiMethodCallExpression
244 </td>
245 <td>UCallExpression
246 </td>
247 </tr>
248 <tr>
249 <td>PsiParameter
250 </td>
251 <td>UParameter
252 </td>
253 </tr>
254</table>
255
256## Code detector
257
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000258These are lint checks that will apply to source code files -- primarily Java and
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700259Kotlin, but can also be used for other similar file types. All code detectors
260that analyze Java or Kotlin files should implement the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000261[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 -0700262
263### API surface
264
265#### Calls to specific methods
266
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000267##### `getApplicableMethodNames`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700268
269This defines the list of methods where lint will call the visitMethodCall
270callback.
271
272```kotlin
273override fun getApplicableMethodNames(): List<String>? = listOf(METHOD_NAMES)
274```
275
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000276##### `visitMethodCall`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700277
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000278This defines the callback that the Lint tool will call when it encounters a call
279to an applicable method.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700280
281```kotlin
282override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {}
283```
284
285#### Calls to specific class instantiations
286
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000287##### `getApplicableConstructorTypes`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700288
289```kotlin
290override fun getApplicableConstructorTypes(): List<String>? = listOf(CLASS_NAMES)
291```
292
293##### visitConstructor
294
295```kotlin
296override fun visitConstructor(context: JavaContext, node: UCallExpression, method: PsiMethod) {}
297```
298
299#### Classes that extend given superclasses
300
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000301##### `getApplicableSuperClasses`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700302
303```kotlin
304override fun applicableSuperClasses(): List<String>? = listOf(CLASS_NAMES)
305```
306
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000307##### `visitClass`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700308
309```kotlin
310override fun visitClass(context: JavaContext, declaration: UClass) {}
311```
312
313#### Call graph support
314
315It is possible to perform analysis on the call graph of a project. However, this
316is highly resource intensive since it generates a single call graph of the
317entire project and should only be used for whole project analysis. To perform
318this analysis you must enable call graph support by overriding the
319`isCallGraphRequired` method and access the call graph with the
320`analyzeCallGraph(context: Context, callGraph: CallGraphResult)` callback
321method.
322
323For performing less resource intensive, on-the-fly analysis it is best to
324recursively analyze method bodies. However, when doing this there should be a
325depth limit on the exploration. If possible, lint should also not explore within
326files that are currently not open in studio.
327
328### Method call analysis
329
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000330#### `resolve()`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700331
332Resolves into a `UCallExpression` or `UMethod` to perform analysis requiring the
333method body or containing class.
334
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000335#### `receiverType`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700336
337Each `UCallExpression` has a `receiverType` corresponding to the `PsiType` of
338the receiver of the method call.
339
340```kotlin
341public abstract class LiveData<T> {
342 public void observe() {}
343}
344
345public abstract class MutableLiveData<T> extends LiveData<T> {}
346
347MutableLiveData<String> liveData = new MutableLiveData<>();
348liveData.observe() // receiverType = PsiType<MutableLiveData>
349```
350
351#### Kotlin named parameter mapping
352
353`JavaEvaluator`contains a helper method `computeArgumentMapping(call:
354UCallExpression, method: PsiMethod)` that creates a mapping between method call
355parameters and the corresponding resolved method arguments, accounting for
356Kotlin named parameters.
357
358```kotlin
359override fun visitMethodCall(context: JavaContext, node: UCallExpression,
360 method: PsiMethod) {
361 val argMap: Map<UExpression, PsiParameter> = context.evaluator.computArgumentMapping(node, psiMethod)
362}
363```
364
365### Testing
366
367Because the `LintDetectorTest` API does not have access to library classes and
368methods, you must implement stubs for any necessary classes and include these as
369additional files in your test cases. For example, if a lint check involves
370Fragment's `getViewLifecycleOwner` and `onViewCreated` methods, then we must
371create a stub for this:
372
373```
374java("""
375 package androidx.fragment.app;
376
377 import androidx.lifecycle.LifecycleOwner;
378
379 public class Fragment {
380 public LifecycleOwner getViewLifecycleOwner() {}
381 public void onViewCreated() {}
382 }
383""")
384```
385
386Since this class also depends on the `LifecycleOwner` class it is necessary to
387create another stub for this.
388
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000389NOTE `package-info.java` cannot be represented by a source stub and must be
390provided as bytecode. See [Updating bytecode](#tips-bytecode) for tips on using
391bytecode in lint tests.
392
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700393## XML resource detector
394
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000395These are lint checks that will apply to resource files including `anim`,
396`layout`, `values`, etc. lint checks being applied to resource files should
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700397extend
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000398[`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 -0700399The `Detector` must define the issue it is going to detect, most commonly as a
400static variable of the class.
401
402```kotlin
403companion object {
404 val ISSUE = Issue.create(
405 id = "TitleOfMyIssue",
406 briefDescription = "Short description of issue. This will be what the studio inspection menu shows",
407 explanation = """Here is where you define the reason that this lint rule exists in detail.""",
408 category = Category.CORRECTNESS,
409 severity = Severity.LEVEL,
410 implementation = Implementation(
411 MyIssueDetector::class.java, Scope.RESOURCE_FILE_SCOPE
412 ),
413 androidSpecific = true
414 ).addMoreInfo(
415 "https://linkToMoreInfo.com"
416 )
417}
418```
419
420### API surface
421
422The following methods can be overridden:
423
424```kotlin
425appliesTo(folderType: ResourceFolderType)
426getApplicableElements()
427visitElement(context: XmlContext, element: Element)
428```
429
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000430#### `appliesTo`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700431
432This determines the
AndroidX Core Team2e416b22020-12-03 22:58:07 +0000433[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 -0700434that the check will run against.
435
436```kotlin
437override fun appliesTo(folderType: ResourceFolderType): Boolean {
438 return folderType == ResourceFolderType.TYPE
439}
440```
441
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000442#### `getApplicableElements`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700443
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000444This defines the list of elements where the Lint tool will call your
445`visitElement` callback method when encountered.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700446
447```kotlin
448override fun getApplicableElements(): Collection<String>? = Collections.singleton(ELEMENT)
449```
450
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000451#### `visitElement`
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700452
453This defines the behavior when an applicable element is found. Here you normally
AndroidX Core Team21ccf652022-04-01 14:53:07 +0000454place the actions you want to take if a violation of the lint check is found.
Jeremy Woodsfeffecaf2020-10-15 12:08:38 -0700455
456```kotlin
457override fun visitElement(context: XmlContext, element: Element) {
AndroidX Core Teamcf946032022-02-11 15:52:08 -0800458 val lintFix = fix().replace()
459 .text(ELEMENT)
460 .with(REPLACEMENT TEXT)
461 .build()
462
463 val incident = Incident(context)
464 .fix(lintFix)
465 .issue(ISSUE)
466 .location(context.getLocation(node))
467 .message("My issue message")
468 .scope(context.getNameLocation(element))
469
470 context.report(incident)
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
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)