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

Building a Better Codebase with Lint - Droidcon...

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. What is Lint? • Static analysis tool • Open Source

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

    • Works on .java, .kt, .gradle and a lot more • Can be used outside Android as well
  3. 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
  4. 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
  5. 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
  6. Issue Registry class Registry : IssueRegistry() { override val api:

    Int = CURRENT_API @get:NotNull override val issues: List<Issue> get() = listOf() }
  7. 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
  8. The Basic Elements Severity How severe is the issue •

    INFORMATIONAL • WARNING • ERROR • FATAL
  9. The Basic Elements Category The category the issue falls in,

    from a pre-defined list of categories • SECURITY • PERFORMANCE • L10N • A11Y
  10. 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 ) )
  11. Custom Rule Structure class V1StyleUsageDetector : ResourceXmlDetector() { override fun

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

    getApplicableElements(): Collection<String> { return listOf(SdkConstants.TEXT_VIEW) } }
  13. Custom Rule Structure class V1StyleUsageDetector : ResourceXmlDetector() { //.. override

    fun appliesTo(folderType: ResourceFolderType): Boolean { return folderType == ResourceFolderType.LAYOUT } }
  14. 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") } } }
  15. Issue Registry class Registry : IssueRegistry() { override val api:

    Int = CURRENT_API @get:NotNull override val issues: List<Issue> get() = listOf(V1StyleUsageDetector.ISSUE) }
  16. 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(), )
  17. 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()
  18. Testing @RunWith(JUnit4 class) class V1StyleUsageDetectorTest : LintDetectorTest() { override fun

    getIssues(): MutableList<Issue> = mutableListOf(V1StyleUsageDetector.ISSUE) override fun getDetector(): Detector = V1StyleUsageDetector() }
  19. @RunWith(JUnit4 class) class V1StyleUsageDetectorTest : LintDetectorTest() { @Test fun `check

    no lint error`() { lint() .files( xml( "res/layout/layout.xml", """ <TextView android:layout_width="match_parent" 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() }
  20. @Test fun `check exactly 1 lint error`() { lint() .files(

    xml( "res/layout/layout.xml", """ <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@ id/textSpacerNoButtons" style="@style/V1.TextAppearance.AppCompat" xmlns:android="http: schemas.android.com/apk/res/android" """ ).indented() ) }
  21. @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] <TextView android:layout_width="match_parent" ^ 1 errors, 0 warnings """ ) }
  22. lint-baseline.xml <issues format="5" by="lint 3.6.3" client="gradle" variant="debug" version="3.6.3"> <issue id="MissingStyleAttribute"

    message="Add style to TextView in order to provide consistent design" errorLine1=" &lt;TextView" errorLine2=" ^"> <location file="src/main/res/layout/activity_main.xml" line="10" column="5"/> </issue> </issues>
  23. Performance • Single pass • Missing attribute • Fast •

    Enable in IDE • Multiple pass • Unused resources • Slow • Disable in IDE
  24. Kotlin Considerations? • No explicit return statements • No explicit

    type declarations • Write one test case for Kotlin usage
  25. 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/