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

Enforcing code conventions with Lint

Enforcing code conventions with Lint

LINE DevDay 2020

November 26, 2020
Tweet

More Decks by LINE DevDay 2020

Other Decks in Technology

Transcript

  1. Big teams Big teams bring challenges: › Onboarding of new

    members. › Code consistency. › Code conventions.
  2. Big teams Big teams bring challenges: › Onboarding of new

    members. › Code consistency. › Code conventions. › Internal APIs.
  3. Code conventions fun getInt()k= (0..10).map { it.also { "Some side

    effect" } }.also { "Some other side effect" }.apply { size }.let { it.last() }.takeIf { it > 10 }
  4. Code conventions fun getInt(): Int?k= (0..10).map { it.also { "Some

    side effect" } }.also { "Some other side effect" }.apply { size }.let { it.last() }.takeIf { it > 10 }
  5. Why Lint? › Compatible with Java/Kotlin (UAST). › Lint client

    is implemented by Android Studio. › Quick fixes in Android Studio.
  6. Custom Lint checks In order to create a custom Lint

    check we have to roughly follow these steps:
  7. Custom Lint checks In order to create a custom Lint

    check we have to roughly follow these steps: › Create a Lint Issue.
  8. Custom Lint checks In order to create a custom Lint

    check we have to roughly follow these steps: › Create a Lint Issue. › Register the Issue so lint can track it.
  9. Custom Lint checks In order to create a custom Lint

    check we have to roughly follow these steps: › Create a Lint Issue. › Register the Issue so lint can track it. › Create a detector for the Issue.
  10. Custom Lint checks In order to create a custom Lint

    check we have to roughly follow these steps: › Create a Lint Issue. › Register the Issue so lint can track it. › Create a detector for the Issue. › Report the Issue.
  11. Custom Lint checks In order to create a custom Lint

    check we have to roughly follow these steps: › Create a Lint Issue. › Register the Issue so lint can track it. › Create a detector for the Issue. › Report the Issue. › [Optional] Provide a fix.
  12. Create an issue class MyDetector : Detector(), SourceCodeScanner { companion

    object { @JvmField val ISSUE: Issue = Issue.create( “InferredType", “MyDetector short description", "My detector **very** *long* `description`.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE) )k }k …k }k
  13. Create an issue class MyDetector : Detector(), SourceCodeScanner { companion

    object { @JvmField val ISSUE: Issue = Issue.create( “InferredType”, “MyDetector short description", "My detector **very** *long* `description`.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE) )k }k …k }k
  14. Create an issue class MyDetector : Detector(), SourceCodeScanner { companion

    object { @JvmField val ISSUE: Issue = Issue.create( “InferredType”, “MyDetector short description", "My detector **very** *long* `description`.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE) )k }k …k }k
  15. Create an issue class MyDetector : Detector(), SourceCodeScanner { companion

    object { @JvmField val ISSUE: Issue = Issue.create( “InferredType”, “MyDetector short description", "My detector **very** *long* `description`.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE) )k }k …k }k
  16. Create an issue class MyDetector : Detector(), SourceCodeScanner { companion

    object { @JvmField val ISSUE: Issue = Issue.create( “InferredType”, “MyDetector short description", "My detector **very** *long* `description`.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE) )k }k …k }k
  17. Create an issue class MyDetector : Detector(), SourceCodeScanner { companion

    object { @JvmField val ISSUE: Issue = Issue.create( “InferredType”, “MyDetector short description", "My detector **very** *long* `description`.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE) )k }k …k }k
  18. Create an issue class MyDetector : Detector(), SourceCodeScanner { companion

    object { @JvmField val ISSUE: Issue = Issue.create( “InferredType”, “MyDetector short description", "My detector **very** *long* `description`.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE) )k }k …k }k
  19. Create an issue class MyDetector : Detector(), SourceCodeScanner { companion

    object { @JvmField val ISSUE: Issue = Issue.create( “InferredType”, “MyDetector short description", "My detector **very** *long* `description`.", Category.CORRECTNESS, 6, Severity.WARNING, Implementation(MyDetector::class.java, Scope.JAVA_FILE_SCOPE) )k }k …k }k
  20. Register the issue Now we have to add the issue

    to the lint issue registry: class MyIssueRegistry : IssueRegistry() {k override val issues: List<Issue> = listOf(MyDetector.ISSUE) override val api: Int = CURRENT_API override val minApi: Int = MIN_API companion object {k const val MIN_API = 2 // Corresponds to android gradle plugin 3.2+ }k }k
  21. Register the issue Now we have to add the issue

    to the lint issue registry: @AutoService(IssueRegistry::class) class MyIssueRegistry : IssueRegistry() {k override val issues: List<Issue> = listOf(MyDetector.ISSUE) override val api: Int = CURRENT_API override val minApi: Int = MIN_API companion object {k const val MIN_API = 2 // Corresponds to android gradle plugin 3.2+ }k }k
  22. Custom Lint checks Lint has a number of Detector specializations

    which makes a lot easier to write checks:
  23. Custom Lint checks Lint has a number of Detector specializations

    which makes a lot easier to write checks: › XmlScanner: lets you visit the XML file with DOM.
  24. Custom Lint checks Lint has a number of Detector specializations

    which makes a lot easier to write checks: › XmlScanner: lets you visit the XML file with DOM. › SourceCodeScanner: useful for Kotlin/Java analysis.
  25. Custom Lint checks Lint has a number of Detector specializations

    which makes a lot easier to write checks: › XmlScanner: lets you visit the XML file with DOM. › SourceCodeScanner: useful for Kotlin/Java analysis. › ClassScanner, BinaryResourceScanner, ResourceFolderScanner…
  26. SourceCodeScanner fun getInt()k= (0..10).map { it.also { "test" } }.also

    { “Test2" }.apply { size }.let { it.last() }.takeIf { it > 10 }
  27. SourceCodeScanner fun getInt(): Int?k= (0..10).map { it.also { "test" }

    }.also { “Test2" }.apply { size }.let { it.last() }.takeIf { it > 10 }
  28. SourceCodeScanner class MyDetector : Detector(), SourceCodeScanner { ... override fun

    getApplicableUastTypes(): MutableList<Class<out UElement>> = mutableListOf(UMethod::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = InferredTypeHandler(context) class InferredTypeHandler(private val context: JavaContext) : UElementHandler() { ... override fun visitMethod(node: UMethod) { if (!isKotlin(node)) return if (isInferredMethod(node)){ report(context) } k } k ... } k
  29. SourceCodeScanner class MyDetector : Detector(), SourceCodeScanner { ... override fun

    getApplicableUastTypes(): MutableList<Class<out UElement>> = mutableListOf(UMethod::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = InferredTypeHandler(context) class InferredTypeHandler(private val context: JavaContext) : UElementHandler() { ... override fun visitMethod(node: UMethod) { if (!isKotlin(node)) return if (isInferredMethod(node)){ report(context) } k } k ... } k
  30. SourceCodeScanner class MyDetector : Detector(), SourceCodeScanner { ... override fun

    getApplicableUastTypes(): MutableList<Class<out UElement>> = mutableListOf(UMethod::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = InferredTypeHandler(context) class InferredTypeHandler(private val context: JavaContext) : UElementHandler() { ... override fun visitMethod(node: UMethod) { if (!isKotlin(node)) return if (isInferredMethod(node)){ report(context) } k } k … } k
  31. SourceCodeScanner class MyDetector : Detector(), SourceCodeScanner { ... override fun

    getApplicableUastTypes(): MutableList<Class<out UElement>> = mutableListOf(UMethod::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = InferredTypeHandler(context) class InferredTypeHandler(private val context: JavaContext) : UElementHandler() { ... override fun visitMethod(node: UMethod) { if (!isKotlin(node)) return if (isInferredMethod(node)){ report(context) } k } k … } k
  32. SourceCodeScanner private fun isInferredMethod(node: UMethod): Boolean { val function =

    node.sourcePsi as? KtNamedFunction ?: return false val isFunction = function.funKeyword != null val hasColon = function.colon != null val isVoid = returnType == PsiType.VOID || returnType.presentableText == “Unit" return isFunction && !hasColon && !isVoid }
  33. SourceCodeScanner private fun isInferredMethod(node: UMethod): Boolean { val function =

    node.sourcePsi as? KtNamedFunction ?: return false val isFunction = function.funKeyword != null val hasColon = function.colon != null val isVoid = returnType == PsiType.VOID || returnType.presentableText == “Unit" return isFunction && !hasColon && !isVoid }
  34. SourceCodeScanner class MyDetector : Detector(), SourceCodeScanner { ... override fun

    getApplicableUastTypes(): MutableList<Class<out UElement>> = mutableListOf(UMethod::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = InferredTypeHandler(context) class InferredTypeHandler(private val context: JavaContext) : UElementHandler() { ... override fun visitMethod(node: UMethod) { if (!isKotlin(node)) return if (isInferredMethod(node)){ report(context) } k } k ... } k
  35. SourceCodeScanner class MyDetector : Detector(), SourceCodeScanner { ... override fun

    getApplicableUastTypes(): MutableList<Class<out UElement>> = mutableListOf(UMethod::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = InferredTypeHandler(context) class InferredTypeHandler(private val context: JavaContext) : UElementHandler() { ... override fun visitMethod(node: UMethod) { if (!isKotlin(node)) return if (isInferredMethod(node)){ report(context) } k } k ... } k
  36. SourceCodeScanner class MyDetector : Detector(), SourceCodeScanner { ... override fun

    getApplicableUastTypes(): MutableList<Class<out UElement>> = mutableListOf(UMethod::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = InferredTypeHandler(context) class InferredTypeHandler(private val context: JavaContext) : UElementHandler() { ... override fun visitMethod(node: UMethod) { if (!isKotlin(node)) return if (isInferredMethod(node)){ report(context) } k } k ... } k
  37. SourceCodeScanner class MyDetector : Detector(), SourceCodeScanner { ... override fun

    getApplicableUastTypes(): MutableList<Class<out UElement>> = mutableListOf(UMethod::class.java) override fun createUastHandler(context: JavaContext): UElementHandler = InferredTypeHandler(context) class InferredTypeHandler(private val context: JavaContext) : UElementHandler() { ... override fun visitMethod(node: UMethod) { if (!isKotlin(node)) return if (isInferredMethod(node)){ report(context) } k } k ... } k
  38. Examples of checks Some examples of checks we are currently

    using: › BaseFragment within a package. › Internal APIs to recommend to use a custom subscriber. › Nesting in lambdas. › Nesting in custom LiveData. › Top level class comments. › Internal deprecations.