**Lint Development Guidelines** (This folder should also contain architectural information documents.) # Setup To develop lint, use a recent IntelliJ, check out the Android tools repository and then open up the folder `tools/` as a Gradle project. # Code * All new files should be written in Kotlin. * Code should be formatted using “cd lint && gradle formatLint”, which will format both the Java and Kotlin files according to the configured style. * All lint checks must have tests checking both for false positives and false negatives. * Make sure that lint checks have the correct platform applied (e.g. androidSpecific=true in the issue registration for any Android checks.) # Debugging lint under Gradle To be able to attach to and debug lint when invoked from within the Android Gradle plugin, add this to the `gradle.properties` file in the target project before invoking gradle with the debug flags: ``` android.experimental.runLintInProcess=true ``` # Debugging lint in the IDE By default the IDE cancels certain operations if they take too long, including Lint. This makes debugging tricky. To disable this behavior, start the IDE with ``` -Didea.ProcessCanceledException=disabled ```` Alternatively, you can use the internal action "Disable ProcessCanceledException". Note, to use internal actions you must first go to `Help > Edit Custom Properties` and add ``` idea.is.internal=true ``` # Updating Baselines If you've added a new lint check which has a number of existing violations in the Studio tree, you can run the following target to update the baselines: ``` bazel run --test_env=UPDATE_LINT_BASELINE=1 \ $(bazel query --output=label 'kind(.*lint_test, //tools/...)') ``` # Registering Inspections When lint runs in the IDE, all lint checks are wrapped as IntelliJ inspections, such that they show up in the Inspections options list etc. Lazily discovered lint checks (3rd party lint checks) will show up as soon as lint has run to discover them, but for built-in checks we should register them in advance such that they always show up. To do this, open the `tools/adt/idea` project and run the `LintInspectionRegistrationTest` test. To have your sources updated in place, either set `$ADT_SOURCE_TREE` or edit the test first to point the `sourceTree` var to point to your checkout. # Deprecating and Removing APIs If we have to remove methods (such as `UastContext`, which is slated for removal from UAST in the next version of IntelliJ), just deprecating doesn't seem to be enough; a lot of existing lint checks will use APIs if the code continues to compile and run. We are guilty of this ourselves; lint itself is still referencing a lot of deprecated APIs. However, if we just delete APIs, that will break existing lint checks in the field. Therefore, to deprecate APIs, we should be using Kotlin's special support for marking deprecated methods hidden (the `DeprecationLevel` attribute in a `Deprecated` annotation). This will keep the method in the runtime jar file, and older lint checks will be able to invoke it -- but it's removed from compilation, therefore forcing lint checks to update before we actually remove the API for good. (This support is one of the reasons why we want all new APIs in lint to be implemented in Kotlin.) # Issue Granularity We regularly get requests for a new lint check to highlight or discourage one specific method. Here's some discussion from the most recent time this came up, which might provide some insights into how we think about this: Generally, if an issue is “just” data, we shouldn't give it its own issue id. Each issue id is configured separately (in lint.xml files), shows up independently in the inspections UI in Studio, and it's a global namespace. Most of the API-specific lint checks are doing something more subtle; for example, the ToastDetector makes sure that if you call `makeText()`, you end up calling `show` on the resulting object, even if indirectly. Similarly, `AlarmDetector` is specific to the `setRepeating()` method on `AlarmDetector`, but it does data analysis to figure out if the parameter passed as the duration is not long enough. (There might be a few examples where we do have a lint check dedicated to a specific API but if we do, we regret those...) For simple discouraged APIs, we have several approaches: 1. (Best) Create a new annotation, for example @Discouraged, which we put in the support library and we then annotate all the platform calls we want to discourage users from. We then have a detector which looks for this annotation and flags any uses of it. There are many detectors like this in lint already, enforcing a wide range of annotations. The discourage-message could be part of the annotation metadata. 2. Like (1), but instead of annotations, with bundled data. For example, lint's API check works this way; it ships with a database of API “since” info that it uses for enforcement. There are a bunch of lint checks like this -- see `ApiLookup`, `PrivateApiLookup`, `PluralsDatabase`, etc. 3. Like (2), but the data is bundled directly as code/lists/arrays within the check: Create a shared detector which has a hardcoded list of APIs that we're enforcing. For example, for lint checks that run against Studio's own codebase, we have this detector which has a denylist of certain APIS we don't want used: https://source.corp.google.com/android/tools/base/lint/studio-checks/src/main/java/com/android/tools/lint/checks/studio/ForbiddenStudioCallDetector.kt In lint itself, there is a similar check, dedicated to deprecations: https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/DeprecationDetector.kt;l=1?q=DeprecationDetector.kt The DeprecationDetector is used to flag APIs that are discouraged. It was initially written to let us deprecate things in XML files (since java and Kotlin have their own existing facility for providing warning messages for discouraged APIs). However, since then we've actually added Java/Kotlin specific calls, where we really want to not just say “deprecated” but to customize the specific error message -- see the Firebase/JobScheduling messages. And note that as of Arctic Fox you can actually vary the severity on a per incident basis too, so you can pick warning or error as default based on the specific check. # Informational Checks Lint checks should pinpoint a problem. They are not intended to only be educational or to just "bring awareness" around recent changes to an API. Especially if the lint check cannot check whether the guidance applies, for example because it depends on policy approval which is not available for inspection in the code. A check which unconditionally flags code in order to educate users is often considered by users to be "spammy". We've had some checks like that in the past, and they were not popular, and the issue id soon ended up in default ignore rules for lint. For an example, see [this StackOverflow thread](https://stackoverflow.com/questions/34173545/missing-support-for-fireb ase-app-indexing-android-lint).