1# Adding custom lint checks
2
3[TOC]
4
5## Getting started
6
7Lint is a static analysis tool that checks Android project source files. Lint
8checks come with Android Studio by default, but custom lint checks can be added
9to specific library modules to help avoid potential bugs and encourage best code
10practices.
11
12This 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://googlesamples.github.io/android-custom-lint-rules/).
16
17### Create a module
18
19If this is the first lint rule for a library, you will need to create a module
20by doing the following:
21
22Include 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
33`mylibrary/mylibrary-lint/build.gradle`:
34
35```
36import androidx.build.LibraryType
37
38plugins {
39    id("AndroidXPlugin")
40    id("kotlin")
41}
42
43dependencies {
44    compileOnly(libs.androidLintMinApi)
45    compileOnly(libs.kotlinStdlib)
46
47    testImplementation(libs.kotlinStdlib)
48    testImplementation(libs.androidLint)
49    testImplementation(libs.androidLintTests)
50    testImplementation(libs.junit)
51    testImplementation(libs.truth)
52}
53
54androidx {
55    name = "MyLibrary lint checks"
56    type = LibraryType.LINT
57    mavenGroup = LibraryGroups.MYLIBRARY
58    inceptionYear = "2022"
59    description = "Lint checks for MyLibrary"
60}
61```
62
63### 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
67[`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)
68class provided by the tools team. Extend this class into your own
69`IssueRegistry` class, and provide it with the issues in the module.
70
71`MyLibraryIssueRegistry.kt`
72
73```kotlin
74class MyLibraryIssueRegistry : IssueRegistry() {
75    override val minApi = CURRENT_API
76    override val api = 13
77    override val issues get() = listOf(MyLibraryDetector.ISSUE)
78    override val vendor = Vendor(
79        feedbackUrl = "https://issuetracker.google.com/issues/new?component=######",
80        identifier = "androidx.mylibrary",
81        vendorName = "Android Open Source Project",
82    )
83}
84```
85
86The maximum version this lint check will will work with is defined by `api =
8713`. Typically, this should track `CURRENT_API`.
88
89`minApi = CURRENT_API` sets the lowest version of the Lint tool that this will
90work with.
91
92`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).
96
97We guarantee that our lint checks work with the versions referenced by `minApi`
98and `api` by running our tests with both versions. For newer versions of Android
99Studio (and consequently, the Lint tool) the API variable will need to be
100updated.
101
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
106[Example `IssueRegistry` Implementation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentIssueRegistry.kt)
107
108There are 4 primary types of lint checks:
109
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
115It is also possible to apply lint checks to compiled bytecode (`.class` files)
116or 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`.
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.`
127
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
259These are lint checks that will apply to source code files -- primarily Java and
260Kotlin, but can also be used for other similar file types. All code detectors
261that analyze Java or Kotlin files should implement the
262[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).
263
264### API surface
265
266#### Calls to specific methods
267
268##### `getApplicableMethodNames`
269
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
277##### `visitMethodCall`
278
279This defines the callback that the Lint tool will call when it encounters a call
280to an applicable method.
281
282```kotlin
283override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {}
284```
285
286#### Calls to specific class instantiations
287
288##### `getApplicableConstructorTypes`
289
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
302##### `getApplicableSuperClasses`
303
304```kotlin
305override fun applicableSuperClasses(): List<String>? = listOf(CLASS_NAMES)
306```
307
308##### `visitClass`
309
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
331#### `resolve()`
332
333Resolves into a `UCallExpression` or `UMethod` to perform analysis requiring the
334method body or containing class.
335
336#### `receiverType`
337
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
390NOTE `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
394## XML resource detector
395
396These are lint checks that will apply to resource files including `anim`,
397`layout`, `values`, etc. lint checks being applied to resource files should
398extend
399[`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).
400The `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
431#### `appliesTo`
432
433This determines the
434[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)
435that the check will run against.
436
437```kotlin
438override fun appliesTo(folderType: ResourceFolderType): Boolean {
439    return folderType == ResourceFolderType.TYPE
440}
441```
442
443#### `getApplicableElements`
444
445This defines the list of elements where the Lint tool will call your
446`visitElement` callback method when encountered.
447
448```kotlin
449override fun getApplicableElements(): Collection<String>? = Collections.singleton(ELEMENT)
450```
451
452#### `visitElement`
453
454This defines the behavior when an applicable element is found. Here you normally
455place the actions you want to take if a violation of the lint check is found.
456
457```kotlin
458override fun visitElement(context: XmlContext, element: Element) {
459    val fix = LintFix.create()
460        .replace()
461        .text(ELEMENT)
462        .with(REPLACEMENT_TEXT)
463        .build()
464
465    context.report(
466        issue = ISSUE,
467        location = context.getNameLocation(element),
468        message = "My issue message",
469        quickFixData = fix
470    )
471}
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
479[Example Detector Implementation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment-lint/src/main/java/androidx/fragment/lint/FragmentTagDetector.kt)
480
481### Testing
482
483You 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.
488
489Example `ApiLintVersionTest`:
490
491```kotlin
492class ApiLintVersionsTest {
493
494    @Test
495    fun versionsCheck() {
496        LintClient.clientName = LintClient.CLIENT_UNIT_TESTS
497        val registry = MyLibraryIssueRegistry()
498        assertThat(registry.api).isEqualTo(CURRENT_API)
499        assertThat(registry.minApi).isEqualTo(10)
500    }
501}
502```
503
504Next, you must test the `Detector` class. The Tools team provides a
505[`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)
506class 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
519[`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)
520provides a `lint()` method that returns a
521[`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).
522`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
525[`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).
526`TestLintResult` provides methods for checking the outcome of the provided
527`TestLintTask`. `ExpectClean()` means the output is expected to be clean because
528the lint check was followed. `Expect()` takes a string literal of the expected
529output 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
533[TestExample](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:fragment/fragment-lint/src/test/java/androidx/fragment/lint/FragmentTagDetectorTest.kt)
534
535## Android manifest detector
536
537Lint checks targeting `AndroidManifest.xml` files should implement the
538[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)
539and define target scope in issues as `Scope.MANIFEST`
540
541## Gradle detector
542
543Lint checks targeting Gradle configuration files should implement the
544[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)
545and define target scope in issues as `Scope.GRADLE_SCOPE`
546
547### API surface
548
549#### `checkDslPropertyAssignment`
550
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
571The cookie parameters should be used for reporting lint check errors. To report
572an issue on the value, use `context.getLocation(statementCookie)`.
573
574## Enabling lint checks for a library
575
576Once the lint module is implemented we need to enable it for the desired
577library. This can be done by adding a `lintPublish` rule to the `build.gradle`
578of the library the lint check should apply to.
579
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
587`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.
591
592```
593androidx.mylibrary.lint.MyLibraryIssueRegistry
594```
595
596Note 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
605## Advanced topics
606
607### Analyzing multiple different file types
608
609Sometimes it is necessary to implement multiple different scanners in a lint
610detector. For example, the
611[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)
612lint check implements an XML and SourceCodeScanner in order to determine if
613resources defined in XML files are ever references in the Java/Kotlin source
614code.
615
616#### File type iteration order
617
618The Lint tool processes files in a predefined order:
619
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
631### Debugging custom lint checks
632
633Using Android Studio, there are a few ways to debug custom lint checks:
634
635#### Debug against lint running from the command line
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
653#### Debug against lint running inside Android Studio
654
655The UAST environment can be different when a lint check is running on the fly
656inside Android Studio, instead of the command line (for example b/191508358). To
657debug issues with a lint check that only occur inside the IDE, you can debug the
658lint check when it runs inside Studio.
659
6601.  Set breakpoint(s) in the desired lint detector sources (make sure that the
661    sources you have match the sources being used in the library version you
662    want to test against)
6632.  Download a separate Studio instance (such as latest canary), and open it.
6643.  With the new Studio instance, go to Help -> Edit Custom VM Options - this
665    will open up studio.vmoptions. Add the following lines:
666
667```
668-Xdebug
669-Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y
670```
671
6721.  Then close Android Studio. This will allow attaching a debugger next time
673    this Studio instance is opened.
6742.  In the original (AndroidX) Studio instance, create a
675    [remote JVM debug configuration](https://www.jetbrains.com/help/idea/tutorial-remote-debug.html#create-run-configurations).
676    The default configuration should be correct, double check that the port
677    specified is the same as the address you specified in studio.vmoptions in
678    the previous step.
6793.  Launch the separate Studio instance: it should wait for a debugger to be
680    attached
6814.  In the AndroidX Studio instance, press debug with the remote configuration -
682    this should attach the debugger, and the separate Studio instance should
683    continue to open normally
6845.  In the separate Studio instance, open / create a sample project that uses
685    the library with the lint check you want to debug, and add some code that
686    should trigger / not trigger an error accordingly.
6876.  Breakpoint will get hit
688
689## Helpful tips {#tips}
690
691### Useful classes/packages
692
693[`SdkConstants`](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-master-dev:common/src/main/java/com/android/SdkConstants.java) -
694contains most of the canonical names for Android core library classes, as well
695as XML tag names.
696
697### Updating bytecode and checksum in tests {#tips-bytecode}
698
699When updating a file that is used in a lint test, the following error may appear
700when running tests:
701
702```
703The checksum does not match for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt;
704expected 0x1af1856 but was 0x6692f601.
705Has the source file been changed without updating the binaries?
706Don't just update the checksum -- delete the binary file arguments and re-run the test first!
707java.lang.AssertionError: The checksum does not match for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt;
708expected 0x1af1856 but was 0x6692f601.
709Has the source file been changed without updating the binaries?
710Don't just update the checksum -- delete the binary file arguments and re-run the test first!
711    at org.junit.Assert.fail(Assert.java:89)
712  ...
713```
714
715To fix this, you will need access to `kotlinc` (Kotlin Compiler) to generate a
716bytecode replacement. While Studio does include `kotlinc`, it is not available
717to the terminal and the permissions can not be changed to allow you to use it.
718That means you need your own `kotlinc` available in the terminal.
719
720For instructions on how install the compiler, please read
721[this Kotlin page](https://kotlinlang.org/docs/command-line.html#install-the-compiler).
722
723Important Note: It's best practice to match the compiler version you install to
724the kotlin version in your project to avoid issues.
725
726Otherwise, below are the steps to generate the bytecode:
727
7281.  Remove the arguments in `compiled()`:
729
730    ```
731    // Before
732    compiled(
733      "libs/ktlib.jar",
734      ktSample("androidx.sample.deprecated.DeprecatedKotlinClass"),
735      0x6692f601,
736      """
737      META-INF/main.kotlin_module:
738      H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM2gxKDFAABNj30wGAAAAA==
739      """,
740      """
741      androidx/sample/deprecated/DeprecatedKotlinClass.class:
742      H4sIAAAAAAAAAJVSy27TQBQ9YydxcQNNH5SUZyivlkWSpuxAiFIEighBCiit
743        // rest of bytecode
744      """
745    )
746
747    // After
748    compiled(
749      "libs/ktlib.jar",
750      ktSample("androidx.sample.deprecated.DeprecatedKotlinClass"),
751    )
752    ```
753
7542.  Set `$LINT_TEST_KOTLINC` to the location of `kotlinc` if you haven't
755    already, and add it to the test run configuration's environment variables.
756
757    Note: The location of `kotlinc` can vary; use your system's file finder to
758    determine the exact location. For gLinux, search under
759    `~/.local/share/JetBrains`. For Mac, search under `<your androidx checkout
760    root>/frameworks/support/studio`
761
762    If it's not set (or set incorrectly), this error message appears when
763    running tests:
764
765    ```
766    Couldn't find kotlinc to update test file java/androidx/sample/deprecated/DeprecatedKotlinClass.kt with.
767    Point to it with $LINT_TEST_KOTLINC
768    ```
769
7703.  Run the test, which will output the new bytecode and checksum:
771
772    ```
773    Update the test source declaration for java/androidx/sample/deprecated/DeprecatedKotlinClass.kt with this list of encodings:
774
775    Kotlin:
776                  compiled(
777                      "libs/ktlib.jar",
778                      kotlin(
779                          """
780                          package java.androidx.sample.deprecated
781
782                          @Deprecated(
783                              // (rest of inlined sample file)
784                          """
785                      ).indented(),
786                      0x5ba03e2d,
787                  """
788                  META-INF/main.kotlin_module:
789                  H4sIAAAAAAAAAGNgYGBmYGBgBGJWKM2gxKDFAABNj30wGAAAAA==
790                    // rest of bytecode
791                  """,
792                  """
793                  java/androidx/sample/deprecated/DeprecatedKotlinClass.class:
794                  """
795                  )
796    ```
797
798Note: the generated replacement code will inline the specified sample file (in
799our case, `ktSample("androidx.sample.deprecated.DeprecatedKotlinClass")`).
800Replace the inlined code with the sample declaration.
801
802### Lint checks with WARNING severity (my lint check won't run!) {#tips-warnings}
803
804In AndroidX lint checks with a severity of `WARNING` are ignored by default to
805prevent noise from bundled lint checks. If your lint check has this severity,
806and you want it to run inside AndroidX, you'll need to override the severity: in
807Compose for example this happens in
808[AndroidXComposeLintIssues](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:buildSrc/private/src/main/kotlin/androidx/build/AndroidXComposeLintIssues.kt).
809
810## Helpful links
811
812[Writing Custom Lint Rules](https://googlesamples.github.io/android-custom-lint-rules/)
813
814[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/)
815
816[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/)
817
818[Creating Custom Link Checks (external)](https://twitter.com/alexjlockwood/status/1176675045281693696)
819
820[Android Custom Lint Rules by Tor](https://github.com/googlesamples/android-custom-lint-rules)
821
822[Public lint-dev Google Group](https://groups.google.com/forum/#!forum/lint-dev)
823
824[In-depth Lint Video Presentation by Tor](https://www.youtube.com/watch?v=p8yX5-lPS6o)
825(partially out-dated)
826([Slides](https://resources.jetbrains.com/storage/products/kotlinconf2017/slides/KotlinConf+Lint+Slides.pdf))
827
828[ADS 19 Presentation by Alan & Rahul](https://www.youtube.com/watch?v=jCmJWOkjbM0)
829
830[META-INF vs Manifest](https://groups.google.com/forum/#!msg/lint-dev/z3NYazgEIFQ/hbXDMYp5AwAJ)
831