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

Building a Better Codebase with Lint - Kotlin Mumbai

Building a Better Codebase with Lint - Kotlin Mumbai

Lint is a powerful static analysis tool that can be used to enforce coding styles and also find common bugs across a codebase. Google provides a bunch of lint checks by default for Android. On popular demand, the Lint API was extended to support non-Android Kotlin projects too.

In this talk, Subhrajyoti discusses some examples of custom lint checks we use on the Tickertape app to enforce out design guidelines. He will go through the source code of some Android lint checks and then show how to write a custom lint rule. This talk also demonstrates how to write test cases for the rules. By the end of the talk, you will be able to understand how lint works and how you can add custom lint rules to your projects.

Subhrajyoti Sen

August 22, 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. Setup class Registry : IssueRegistry() { override val api: Int

    = CURRENT_API @get:NotNull override val issues: List<Issue> get() = listOf() }
  6. The Basic Elements Scope The kind of files the lint

    rule will apply to • JAVA_FILE_SCOPE (also applied to Kotlin) • GRADLE_SCOPE • MANIFEST_SCOPE • PROGUARD_SCOPE • RESOURCE_FILE_SCOPE
  7. The Basic Elements Severity How severe is the issue •

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

    from a pre-defined list of categories • SECURITY • PERFORMANCE • I18N • A11Y
  9. A Custom Rule Structure val ISSUE: Issue = Issue.create( "MissingStyleAttribute",

    "style attribute is missing", "We should use style to style a TextView " + "in order to provide consistent design", Category.CORRECTNESS, 5, Severity.ERROR, Implementation( MissingStyleAttributeDetector::class.java, Scope.RESOURCE_FILE_SCOPE ) )
  10. A Custom Rule Structure class MissingStyleAttributeDetector : ResourceXmlDetector() { @Nullable

    override fun getApplicableElements(): Collection<String>? { return listOf(“TextView”) } }
  11. A Custom Rule Structure class MissingStyleAttributeDetector : ResourceXmlDetector() { @Nullable

    override fun getApplicableElements(): Collection<String>? { return listOf(SdkConstants.TEXT_VIEW) } }
  12. A Custom Rule Structure class MissingStyleAttributeDetector : ResourceXmlDetector() { override

    fun appliesTo(folderType: ResourceFolderType): Boolean { return folderType == ResourceFolderType.LAYOUT } }
  13. class MissingStyleAttributeDetector : ResourceXmlDetector() { override fun visitElement(context: XmlContext, element:

    Element) { if (!element.hasAttribute(SdkConstants.ATTR_STYLE)) { context.report( ISSUE, element, context.getLocation(element), "Add style to TextView" ) } } }
  14. A Custom Rule Structure class Registry : IssueRegistry() { @get:NotNull

    override val issues: List<Issue> get() = listOf(MissingStyleAttributeDetector.ISSUE) }
  15. A Custom Rule Structure val ISSUE = Issue.create( "CustomSnackbarIssue", DESCRIPTION,

    EXPLANATION, Category.CORRECTNESS, 5, Severity.ERROR, Implementation( MaterialSnackbarUsageDetector::class.java, Scope.JAVA_FILE_SCOPE ) )
  16. A Custom Rule Structure class MaterialSnackbarUsageDetector : Detector(), SourceCodeScanner {

    override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) { if (context.evaluator.isMemberInClass( method, "com.google.android.material.snackbar.Snackbar" )) { // report usage } } }
  17. Testing a rule • JUnit tests • lint-tests library •

    IDE syntax checker and code highlight
  18. @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/TextAppearance.AppCompat" xmlns:android="http://schemas.android.com/apk/res/android" /> """ ) ) }
  19. @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/TextAppearance.AppCompat" xmlns:android="http://schemas.android.com/apk/res/android" /> """ ) ) .run() }
  20. @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/TextAppearance.AppCompat" xmlns:android="http://schemas.android.com/apk/res/android" /> """ ) ) .run() .expectClean() }
  21. @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/TextAppearance.AppCompat" xmlns:android="http://schemas.android.com/apk/res/android" /> """ ) ) .run() .expectClean() }
  22. @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" xmlns:android="http://schemas.android.com/apk/res/android" /> """ ) ) .run() }
  23. @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" xmlns:android="http://schemas.android.com/apk/res/android" /> """ ) ) .run() .expectErrorCount(1) }
  24. @Test fun `check exactly 1 lint error`() { lint() //

    .run() .expectErrorCount(1) .expect( """ res/layout/layout.xml:2: Error: Add style to TextView in order to provide consistent design [MissingStyleAttribute] <TextView android:layout_width="match_parent" ^ 1 errors, 0 warnings """ ) }
  25. Performance • Single-pass: ◦ Missing attribute ◦ Fast ◦ Enable

    in IDE • Multiple-pass : ◦ Unused resources ◦ Slow ◦ Disable in IDE
  26. Kotlin Considerations • No explicit return statements • No explicit

    type declarations • Write one test case for Kotlin