Android Custom Lint Rules Samples

repository·main·Indexed 21 days ago

https://github.com/googlesamples/android-custom-lint-rules

Sample implementation and documentation for creating, packaging, and distributing custom Android Lint rules. This repository demonstrates how to build lint checks as JARs, distribute them via AAR libraries or direct module dependencies, and implement custom detectors using UastScanner and IssueRegistry.

Tokens
3.2K
Snippets
9
Records
13
Agent score
77%

What's inside android-custom-lint-rules

  1. Distribute Lint Checks via an AAR Library

    main

    If you want to distribute your lint rules to users of your Android library, you can package the lint check .jar inside your library's .aar file.

    In your Android library's build.gradle, use the lintPublish configuration to point to your lint check project. This ensures that any project depending on your library will automatically run your custom lint checks.

    dependencies {
        lintPublish project(':checks')
    }
  2. Implement a Lint Check Jar Library

    main

    To create the actual lint check logic, create a project using the Gradle java or kotlin plugins. This project will produce a .jar file containing your custom detectors.

    Important: All dependencies for the lint check project (excluding testing dependencies) must be declared as compileOnly to avoid including the Lint API in your final artifact.

    dependencies {
        compileOnly "com.android.tools.lint:lint-api:$lintVersion"
        compileOnly "com.android.tools.lint:lint-checks:$lintVersion"
        ...
    }
  3. Run the sample project

    main

    To see the custom lint checks in action, clone the repository and run the :app:lint Gradle task. This task compiles the checks, wraps them into the library, and then runs lint on the sample app module to demonstrate detected violations.

    git clone https://github.com/googlesamples/android-custom-lint-rules.git
    cd android-custom-lint-rules
    ./gradlew :app:lint
  4. Overview of Android Lint

    main
    Android Lint is a static analysis tool used to find bugs related to correctness, performance, security, internationalization, and usability. While primarily used for Android, it can also analyze Java and Kotlin server-side code and desktop software. It is not a source code style checker; its primary purpose is identifying potential issues in the code.
  5. Access documentation for Lint users

    main

    If you are using Lint to analyze your code, refer to the following documentation categories:

    • User Guide: Comprehensive documentation for using Lint, including performance tuning, suppressing incidents, using baselines, and configuring lint.xml files.
    • Issue Documentation: A list of all available checks and detailed documentation for each specific check.
    • Gradle Integration: Information on the Gradle plugin DSL and using newer versions of Lint than the one bundled with Android Gradle Plugin (AGP).
    • Command Line: Details on Lint command line flags and environment variables/properties.
  6. Access documentation for Lint authors

    main

    If you are developing custom Lint checks, refer to the api-guide documentation, which covers:

    • Basics & Terminology: Fundamental concepts and terminology for writing checks.
    • Implementation: Guides on AST Analysis, analyzing data flow, using annotations, and adding Quick Fixes.
    • Testing: Instructions for unit testing and using different test modes.
    • Configuration: How to configure detectors with options and craft error messages.
    • Publishing: How to publish your custom Lint checks.
  7. Reference Lint Dependencies

    main

    When building custom rules, use the following dependencies based on your needs. Note that the Lint API is not a final API and may change between tool releases.

    ### Source Dependencies
    - **com.android.tools.lint:lint-api**: Contains core classes like `LintClient`, `Detector`, and `Issue`.
    - **com.android.tools.lint:lint-checks**: Contains built-in checks and utilities like `VersionChecks`.
    
    ### Test Dependencies
    - **com.android.tools.lint:lint-tests**: Utilities for writing unit tests, including the `LintDetectorTest` base class.
    - **com.android.tools.lint:lint**: Used by tools to integrate lint with the command line (reporting, terminal output, etc.). Not required for writing the rules themselves.
  8. Implement a custom Lint detector using UastScanner

    main

    To create a custom lint rule that analyzes Kotlin or Java code, implement the Detector interface and the UastScanner interface.

    1. Define applicable types: Override getApplicableUastTypes() to return a list of UAST (Unified Abstract Syntax Tree) element classes you want to inspect (e.g., ULiteralExpression::class.java).
    2. Create a handler: Override createUastHandler(context: JavaContext) to return a UElementHandler. This handler contains the logic for visiting specific nodes.
    3. Report issues: Inside the handler's visit methods, use context.report() to flag violations. You must provide the Issue definition, the node being inspected, the location of the issue, and a descriptive message.

    Note: While UElementHandler is a general-purpose mechanism, Lint provides specialized support for common tasks like visiting classes that extend a specific superclass or method call sites. Always check UastScanner implementations and context.getJavaEvaluator() for utility functions before implementing manual UAST traversal.

    class SampleCodeDetector : Detector(), UastScanner {
      override fun getApplicableUastTypes(): List<Class<out UElement?>> {
        return listOf(ULiteralExpression::class.java)
      }
    
      override fun createUastHandler(context: JavaContext): UElementHandler {
        return object : UElementHandler() {
          override fun visitLiteralExpression(node: ULiteralExpression) {
            val string = node.evaluateString() ?: return
            if (string.contains("lint")) {
              context.report(
                ISSUE,
                node,
                context.getLocation(node),
                "This code mentions `lint`: **Congratulations**",
              )
            }
          }
        }
      }
    }
  9. Implement IssueRegistry to register custom lint rules

    main

    To make your custom lint rules available to the Android Lint engine, you must implement the IssueRegistry class. This registry acts as the entry point that tells Lint which issues to check.

    Your implementation must override the following properties:

    • issues: A list of Issue objects provided by your detectors (e.g., SampleCodeDetector.ISSUE).
    • api: The current Lint API version, typically using com.android.tools.lint.detector.api.CURRENT_API.
    • minApi: The minimum Lint API version required for your rules to run (e.g., 8 for Studio 4.1 or later).
    • vendor (optional): A Vendor object containing metadata like vendorName, feedbackUrl, and contact for users to report issues.
    import com.android.tools.lint.client.api.IssueRegistry
    import com.android.tools.lint.client.api.Vendor
    import com.android.tools.lint.detector.api.CURRENT_API
    
    class SampleIssueRegistry : IssueRegistry() {
      override val issues =
        listOf(SampleCodeDetector.ISSUE, AvoidDateDetector.ISSUE, NotNullAssertionDetector.ISSUE)
    
      override val api: Int
        get() = CURRENT_API
    
      override val minApi: Int
        get() = 8
    
      override val vendor: Vendor =
        Vendor(
          vendorName = "Android Open Source Project",
          feedbackUrl = "https://github.com/googlesamples/android-custom-lint-rules/issues",
          contact = "https://github.com/googlesamples/android-custom-lint-rules",
        )
    }
  10. Define a Lint Issue

    main

    A Lint Issue defines how a problem is identified, described, and categorized in the IDE and analysis reports. It is typically defined in a companion object within your Detector class using Issue.create().

    Key parameters for Issue.create():

    • id: A unique string used for @SuppressLint warnings.
    • briefDescription: A short title shown in IDE preference dialogs and analysis results.
    • explanation: A detailed description of the issue, supporting Markdown (e.g., monospace, italic, bold).
    • category: The Category of the issue (e.g., Category.CORRECTNESS).
    • priority: An integer representing the importance.
    • severity: The Severity level (e.g., Severity.WARNING).
    • implementation: An Implementation object specifying the detector class and its Scope (e.g., Scope.JAVA_FILE_SCOPE).
    @JvmField
    val ISSUE: Issue =
      Issue.create(
        id = "SampleId",
        briefDescription = "Lint Mentions",
        explanation = "This check highlights string literals in code which mentions the word `lint`.",
        category = Category.CORRECTNESS,
        priority = 6,
        severity = Severity.WARNING,
        implementation = Implementation(SampleCodeDetector::class.java, Scope.JAVA_FILE_SCOPE),
      )