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

Building a Better Codebase with Lint

Building a Better Codebase with Lint

53b72671be580e70c9795c7eaf35ac12?s=128

Subhrajyoti Sen

May 23, 2020
Tweet

Transcript

  1. Building a Better Codebase with Lint Subhrajyoti Sen @iamsubhrajyoti

  2. What is Lint?

  3. What is Lint? • Static analysis tool

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

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

    • Works on .java, .kt, .gradle and a lot more
  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
  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
  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
  9. What does it look like?

  10. Internationalization

  11. Security

  12. Performance

  13. Accessibility

  14. Why Consider Lint

  15. Why Consider Lint • Opinionated codebase

  16. Why Consider Lint • Opinionated codebase • Simpler code reviews

  17. Why Consider Lint • Opinionated codebase • Simpler code reviews

    • Adherence to style guide
  18. Setup

  19. Setup dependencies { compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compileOnly 'com.android.tools.lint:lint-api:26.6.3' compileOnly 'com.android.tools.lint:lint-checks:26.6.3' }

  20. Setup class Registry : IssueRegistry() { override val api: Int

    = CURRENT_API @get:NotNull override val issues: List<Issue> get() = listOf() }
  21. Setup jar { manifest { attributes 'Lint-Registry-V2': 'me.subhrajyoti.lint.Registry' } }

  22. Setup dependencies { lintChecks project(':lint') }

  23. 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
  24. The Basic Elements Severity The severe is the issue •

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

    from a pre-defined list of categories • SECURITY • PERFORMANCE • I18N • A11Y
  26. A Use case - TextView

  27. 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 ) )
  28. A Custom Rule Structure class MissingStyleAttributeDetector : ResourceXmlDetector() { }

  29. A Custom Rule Structure class MissingStyleAttributeDetector : ResourceXmlDetector() { @Nullable

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

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

    fun appliesTo(folderType: ResourceFolderType): Boolean { return folderType == ResourceFolderType.LAYOUT } }
  32. 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" ) } } }
  33. A Custom Rule Structure class Registry : IssueRegistry() { @get:NotNull

    override val issues: List<Issue> get() = listOf(MissingStyleAttributeDetector.ISSUE) }
  34. Remember! class MissingStyleAttributeDetector : ResourceXmlDetector() { @Nullable override fun getApplicableElements():

    Collection<String>? { return listOf(SdkConstants.TEXT_VIEW) } }
  35. A small Bug

  36. A Use case - Snackbar

  37. A Custom Rule Structure val ISSUE = Issue.create( "CustomSnackbarIssue", DESCRIPTION,

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

    }
  39. A Custom Rule Structure class MaterialSnackbarUsageDetector : Detector(), SourceCodeScanner {

    override fun getApplicableMethodNames() = listOf("make") }
  40. 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 } } }
  41. Testing a rule

  42. Testing a rule • JUnit tests

  43. Testing a rule • JUnit tests • lint-tests library

  44. Testing a rule • JUnit tests • lint-tests library •

    IDE syntax checker and code highlight
  45. IDE syntax check

  46. lint-test dependency dependencies { compileOnly 'com.android.tools.lint:lint-api:26.6.3' compileOnly 'com.android.tools.lint:lint-checks:26.6.3' testImplementation "com.android.tools.lint:lint-tests:26.6.3"

    }
  47. A successful case

  48. @Test fun `check no lint error`() { lint() }

  49. @Test fun `check no lint error`() { lint() .files() }

  50. @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" /> """ ) ) }
  51. @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() }
  52. @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() }
  53. @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() }
  54. An error case

  55. @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() }
  56. @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) }
  57. @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 """ ) }
  58. Integration

  59. Integration • Incremental fixes

  60. Integration • Incremental fixes • Use a baseline

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

  62. Baseline

  63. Baseline

  64. Baseline

  65. Performance

  66. Performance • Single-pass: ◦ Missing attribute ◦ Fast ◦ Enable

    in IDE
  67. Performance • Single-pass: ◦ Missing attribute ◦ Fast ◦ Enable

    in IDE • Multiple-pass : ◦ Unused resources ◦ Slow ◦ Disable in IDE
  68. Thank You @iamsubhrajyoti