Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building a Better Codebase with Lint - Droidcon APAC

Building a Better Codebase with Lint - Droidcon APAC

Subhrajyoti Sen

December 14, 2020
Tweet

More Decks by Subhrajyoti Sen

Other Decks in Technology

Transcript

  1. KeepTruckin
    Subhrajyoti Sen Droidcon APAC 2020
    Building a Better Codebase
    with Lint

    View Slide

  2. What is Lint?
    < / >

    View Slide

  3. What is Lint?
    • Static analysis tool

    View Slide

  4. What is Lint?
    • Static analysis tool
    • Open Source

    View Slide

  5. What is Lint?
    • Static analysis tool
    • Open Source
    • Works on .java, .kt, .gradle and a lot more

    View Slide

  6. What is Lint?
    • Static analysis tool
    • Open Source
    • Works on .java, .kt, .gradle and a lot more
    • Can be used outside Android as well

    View Slide

  7. What is Lint?
    • Static analysis tool
    • Open Source
    • Works on .java, .kt, .gradle and a lot more
    • Can be used outside Android as well
    • Can be extended to write custom rules

    View Slide

  8. What is Lint?
    • Static analysis tool
    • Open Source
    • Works on .java, .kt, .gradle and a lot more
    • Can be used outside Android as well
    • Can be extended to write custom rules
    • Can be run from both Android Studio and CLI

    View Slide

  9. What does Lint look like?
    < / >

    View Slide

  10. Internationalization

    View Slide

  11. Security

    View Slide

  12. Performance

    View Slide

  13. Accessibility

    View Slide

  14. Why consider Lint?
    < / >

    View Slide

  15. Why consider Lint?
    • Opinionated codebase

    View Slide

  16. Why consider Lint?
    • Opinionated codebase
    • Simpler code reviews

    View Slide

  17. Why consider Lint?
    • Opinionated codebase
    • Simpler code reviews
    • Adherence to style guide

    View Slide

  18. Setup
    < / >

    View Slide

  19. Create a new module

    View Slide

  20. Dependencies
    dependencies {
    compileOnly "org.jetbrains.kotlin:kotlin stdlib:$kotlin_version"
    compileOnly 'com.android.tools.lint:lint api:27.1.1'
    compileOnly 'com.android.tools.lint:lint checks:27.1.1'
    testImplementation "com.android.tools.lint:lint tests:27.1.1"
    }
    lint/build.gradle

    View Slide

  21. Issue Registry
    class Registry : IssueRegistry() {
    override val api: Int = CURRENT_API
    @get:NotNull
    override val issues: List
    get() = listOf()
    }

    View Slide

  22. Register Issue Registry
    jar {
    manifest {
    attributes 'Lint-Registry-V2': 'me.subhrajyoti.lint.Registry'
    }
    }
    lint/build.gradle

    View Slide

  23. Add dependency on lint module
    dependencies {
    lintChecks project(':lint')
    }
    app/build.gradle

    View Slide

  24. The Basic Elements
    Scope
    The kind of files the lint rule applies to
    • JAVA_FILE_SCOPE
    • GRADLE_SCOPE
    • MANIFEST_SCOPE
    • PROGUARD_SCOPE
    • RESOURCE_FILE_SCOPE

    View Slide

  25. The Basic Elements
    Severity
    How severe is the issue
    • INFORMATIONAL
    • WARNING
    • ERROR
    • FATAL

    View Slide

  26. The Basic Elements
    Category
    The category the issue falls in, from a pre-defined list of categories
    • SECURITY
    • PERFORMANCE
    • L10N
    • A11Y

    View Slide

  27. Use case - Style Migration
    V1.TextView.Header1 V2.TextView.Header1

    View Slide

  28. Custom Rule Structure
    val ISSUE: Issue = Issue.create(
    "V1StyleUsageDetector",
    "Replace V1 styles with V2",
    "V1 styles have been deprecated and " +
    "you should use V2 styles for all TextView going forward",
    Category.CORRECTNESS,
    5,
    Severity.ERROR,
    Implementation(
    V1StyleUsageDetector::class.java,
    Scope.RESOURCE_FILE_SCOPE
    )
    )

    View Slide

  29. Custom Rule Structure
    class V1StyleUsageDetector : ResourceXmlDetector() {
    }

    View Slide

  30. Custom Rule Structure
    class V1StyleUsageDetector : ResourceXmlDetector() {
    override fun getApplicableElements(): Collection {
    return listOf("TextView")
    }
    }

    View Slide

  31. Custom Rule Structure
    class V1StyleUsageDetector : ResourceXmlDetector() {
    override fun getApplicableElements(): Collection {
    return listOf(SdkConstants.TEXT_VIEW)
    }
    }

    View Slide

  32. Custom Rule Structure
    class V1StyleUsageDetector : ResourceXmlDetector() {
    //..
    override fun appliesTo(folderType: ResourceFolderType): Boolean {
    return folderType == ResourceFolderType.LAYOUT
    }
    }

    View Slide

  33. Custom Rule Structure
    class V1StyleUsageDetector : ResourceXmlDetector() {
    //..
    override fun visitElement(context: XmlContext, element: Element) {
    if (element.hasAttribute(SdkConstants.ATTR_STYLE)) {
    val attributeValue = element.getAttribute(SdkConstants.ATTR_STYLE)
    if (attributeValue.startsWith("@style/V1")) {
    context.report(
    ISSUE,
    element,
    context.getLocation(element),
    "Replace V1 style with V2")
    }
    }
    }

    View Slide

  34. Issue Registry
    class Registry : IssueRegistry() {
    override val api: Int = CURRENT_API
    @get:NotNull
    override val issues: List
    get() = listOf(V1StyleUsageDetector.ISSUE)
    }

    View Slide

  35. Lint x
    val newValue = element.getAttribute(SdkConstants.ATTR_STYLE).replace("V1", "V2")
    val compositeFix = LintFix.create()
    .name("Replace V1 with V2")
    .composite(
    LintFix.create()
    .unset(null, SdkConstants.ATTR_STYLE)
    .build(),
    LintFix.create()
    .set(null, SdkConstants.ATTR_STYLE, newValue)
    .build(),
    )

    View Slide

  36. Lint x
    context.report(
    ISSUE,
    element,
    context.getLocation(element),
    "Replace V1 style with V2",
    compositeFix
    )

    View Slide

  37. Auto x
    val newValue = element.getAttribute(SdkConstants.ATTR_STYLE).replace("V1", "V2")
    val compositeFix = LintFix.create()
    .name("Replace V1 with V2")
    .composite(
    LintFix.create()
    .unset(null, SdkConstants.ATTR_STYLE)
    .build(),
    LintFix.create()
    .set(null, SdkConstants.ATTR_STYLE, newValue)
    .build(),
    )
    .autofix()

    View Slide

  38. Lint x
    ./gradlew app:lintFix

    View Slide

  39. Testing

    View Slide

  40. Testing
    @RunWith(JUnit4 class)
    class V1StyleUsageDetectorTest : LintDetectorTest() {
    override fun getIssues(): MutableList =
    mutableListOf(V1StyleUsageDetector.ISSUE)
    override fun getDetector(): Detector = V1StyleUsageDetector()
    }

    View Slide

  41. @RunWith(JUnit4 class)
    class V1StyleUsageDetectorTest : LintDetectorTest() {
    @Test
    fun `check no lint error`() {
    lint()
    .files(
    xml(
    "res/layout/layout.xml",
    """
    android:layout_height="match_parent"
    android:id="@ id/textSpacerNoButtons"
    style="@style/V2.TextAppearance.AppCompat"
    xmlns:android="http: schemas.android.com/apk/res/android"
    """
    )
    )
    .run()
    .expectClean()
    }

    View Slide

  42. @Test
    fun `check exactly 1 lint error`() {
    lint()
    .files(
    xml(
    "res/layout/layout.xml",
    """
    android:layout_height="match_parent"
    android:id="@ id/textSpacerNoButtons"
    style="@style/V1.TextAppearance.AppCompat"
    xmlns:android="http: schemas.android.com/apk/res/android"
    """
    ).indented()
    )
    }

    View Slide

  43. @Test
    fun `check exactly 1 lint error`() {
    lint()
    .files(
    xml(
    ).indented()
    )
    .run()
    .expectErrorCount(1)
    .expect(
    """
    res/layout/layout.xml:1 Error: Replace V1 style with V2 [V1StyleUsageDetector]
    ^
    1 errors, 0 warnings
    """
    )
    }

    View Slide

  44. Integration

    View Slide

  45. Integration
    • Incremental fixes

    View Slide

  46. Integration
    • Incremental fixes
    • Using a baseline

    View Slide

  47. Baseline
    android {
    lintOptions {
    baseline file("lint baseline.xml")
    }
    }

    View Slide

  48. Baseline - Pass 1

    View Slide

  49. Baseline - Pass 2

    View Slide

  50. lint-baseline.xml

    id="MissingStyleAttribute"
    message="Add style to TextView in order to provide consistent design"
    errorLine1=" <TextView"
    errorLine2=" ^">
    file="src/main/res/layout/activity_main.xml"
    line="10"
    column="5"/>


    View Slide

  51. Performance

    View Slide

  52. Performance
    • Single pass
    • Missing attribute
    • Fast
    • Enable in IDE

    View Slide

  53. Performance
    • Single pass
    • Missing attribute
    • Fast
    • Enable in IDE
    • Multiple pass
    • Unused resources
    • Slow
    • Disable in IDE

    View Slide

  54. Kotlin Considerations?

    View Slide

  55. Kotlin Considerations?
    • No explicit return statements

    View Slide

  56. Kotlin Considerations?
    • No explicit return statements
    • No explicit type declarations

    View Slide

  57. Kotlin Considerations?
    • No explicit return statements
    • No explicit type declarations
    • Write one test case for Kotlin usage

    View Slide

  58. References
    • https://groups.google.com/g/lint-dev
    • https://github.com/SubhrajyotiSen/LintTalk
    • https://github.com/vanniktech/lint-rules
    • https://www.youtube.com/watch?v=p8yX5-lPS6o
    • 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/

    View Slide

  59. @iamsubhrajyoti

    View Slide