Slide 1

Slide 1 text

Building a Better Codebase with Lint Subhrajyoti Sen @iamsubhrajyoti

Slide 2

Slide 2 text

What is Lint?

Slide 3

Slide 3 text

What is Lint? ● Static analysis tool

Slide 4

Slide 4 text

What is Lint? ● Static analysis tool ● Open Source

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

What does it look like?

Slide 10

Slide 10 text

Internationalization

Slide 11

Slide 11 text

Security

Slide 12

Slide 12 text

Performance

Slide 13

Slide 13 text

Accessibility

Slide 14

Slide 14 text

Why Consider Lint

Slide 15

Slide 15 text

Why Consider Lint ● Opinionated codebase

Slide 16

Slide 16 text

Why Consider Lint ● Opinionated codebase ● Simpler code reviews

Slide 17

Slide 17 text

Why Consider Lint ● Opinionated codebase ● Simpler code reviews ● Adherence to style guide

Slide 18

Slide 18 text

Setup

Slide 19

Slide 19 text

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' }

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Setup jar { manifest { attributes 'Lint-Registry-V2': 'me.subhrajyoti.lint.Registry' } }

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

The Basic Elements Severity How severe is the issue ● INFORMATIONAL ● WARNING ● ERROR ● FATAL

Slide 25

Slide 25 text

The Basic Elements Category The category the issue falls in, from a pre-defined list of categories ● SECURITY ● PERFORMANCE ● I18N ● A11Y

Slide 26

Slide 26 text

A Use case - TextView

Slide 27

Slide 27 text

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 ) )

Slide 28

Slide 28 text

A Custom Rule Structure class MissingStyleAttributeDetector : ResourceXmlDetector() { }

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

A Custom Rule Structure class MissingStyleAttributeDetector : ResourceXmlDetector() { @Nullable override fun getApplicableElements(): Collection? { return listOf(SdkConstants.TEXT_VIEW) } }

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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" ) } } }

Slide 33

Slide 33 text

A Custom Rule Structure class Registry : IssueRegistry() { @get:NotNull override val issues: List get() = listOf(MissingStyleAttributeDetector.ISSUE) }

Slide 34

Slide 34 text

Remember! class MissingStyleAttributeDetector : ResourceXmlDetector() { @Nullable override fun getApplicableElements(): Collection? { return listOf(SdkConstants.TEXT_VIEW) } }

Slide 35

Slide 35 text

A small Bug

Slide 36

Slide 36 text

A Use case - Snackbar

Slide 37

Slide 37 text

A Custom Rule Structure val ISSUE = Issue.create( "CustomSnackbarIssue", DESCRIPTION, EXPLANATION, Category.CORRECTNESS, 5, Severity.ERROR, Implementation( MaterialSnackbarUsageDetector::class.java, Scope.JAVA_FILE_SCOPE ) )

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

A Custom Rule Structure class MaterialSnackbarUsageDetector : Detector(), SourceCodeScanner { override fun getApplicableMethodNames() = listOf("make") }

Slide 40

Slide 40 text

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 } } }

Slide 41

Slide 41 text

Testing a rule

Slide 42

Slide 42 text

Testing a rule ● JUnit tests

Slide 43

Slide 43 text

Testing a rule ● JUnit tests ● lint-tests library

Slide 44

Slide 44 text

Testing a rule ● JUnit tests ● lint-tests library ● IDE syntax checker and code highlight

Slide 45

Slide 45 text

IDE syntax check

Slide 46

Slide 46 text

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" }

Slide 47

Slide 47 text

A successful case

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

@Test fun `check no lint error`() { lint() .files( xml( "res/layout/layout.xml", """ """ ) ) }

Slide 51

Slide 51 text

@Test fun `check no lint error`() { lint() .files( xml( "res/layout/layout.xml", """ """ ) ) .run() }

Slide 52

Slide 52 text

@Test fun `check no lint error`() { lint() .files( xml( "res/layout/layout.xml", """ """ ) ) .run() .expectClean() }

Slide 53

Slide 53 text

@Test fun `check no lint error`() { lint() .files( xml( "res/layout/layout.xml", """ """ ) ) .run() .expectClean() }

Slide 54

Slide 54 text

An error case

Slide 55

Slide 55 text

@Test fun `check exactly 1 lint error`() { lint() .files( xml( "res/layout/layout.xml", """ """ ) ) .run() }

Slide 56

Slide 56 text

@Test fun `check exactly 1 lint error`() { lint() .files( xml( "res/layout/layout.xml", """ """ ) ) .run() .expectErrorCount(1) }

Slide 57

Slide 57 text

@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]

Slide 58

Slide 58 text

Integration

Slide 59

Slide 59 text

Integration ● Incremental fixes

Slide 60

Slide 60 text

Integration ● Incremental fixes ● Use a baseline

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Baseline

Slide 63

Slide 63 text

Baseline

Slide 64

Slide 64 text

Baseline

Slide 65

Slide 65 text

Performance

Slide 66

Slide 66 text

Performance ● Single-pass: ○ Missing attribute ○ Fast ○ Enable in IDE

Slide 67

Slide 67 text

Performance ● Single-pass: ○ Missing attribute ○ Fast ○ Enable in IDE ● Multiple-pass : ○ Unused resources ○ Slow ○ Disable in IDE

Slide 68

Slide 68 text

Kotlin Considerations ● No explicit return statements

Slide 69

Slide 69 text

Kotlin Considerations ● No explicit return statements ● No explicit type declarations

Slide 70

Slide 70 text

Kotlin Considerations ● No explicit return statements ● No explicit type declarations ● Write one test case for Kotlin

Slide 71

Slide 71 text

References ● https://groups.google.com/g/lint-dev ● https://github.com/SubhrajyotiSen/LintTalk/ ● https://www.youtube.com/watch?v=p8yX5-lPS6o

Slide 72

Slide 72 text

Thank You @iamsubhrajyoti