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

Building a Better Codebase with Lint

Building a Better Codebase with Lint

Subhrajyoti Sen

May 23, 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 The 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