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

Static Analysis: enforcing code conventions

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Static Analysis: enforcing code conventions

It’s well known that as a team grows, the communication overhead increases geometrically until it takes up most of the time of the members. Static analysis can help us reduce the time cost, streamline the code review process, as well as to keep big teams synchronized.

Avatar for dtvc87

dtvc87

July 23, 2019

Other Decks in Programming

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

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

    } }.also { “Test2" }.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. UAST package com.test class Test { fun test() = 1

    }k println(context.uastFile?.asRecursiveLogString())
  7. UAST package com.test class Test { fun test() = 1

    }k println(context.uastFile?.asRecursiveLogString()) UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  8. UAST package com.test class Test { fun test() = 1

    }k UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  9. UAST package com.test class Test { fun test() = 1

    }k UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  10. UAST package com.test class Test { fun test() = 1

    }k UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  11. UAST package com.test class Test { fun test() = 1

    }k UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  12. UAST package com.test class Test { fun test() = 1

    }k UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  13. UAST package com.test class Test { fun test() = 1

    } UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  14. UAST package com.test; final class Test { int test() {

    return 1; }k }k UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  15. UAST package com.test; final class Test { int test() {

    return 1; }k }k UFile (package = com.test) UClass (name = Test) UMethod (name = test) UBlockExpression UReturnExpression ULiteralExpression (value = 1)
  16. Lint Issue First we have to create an issue explaining

    what’s the problem and how to fix it: class MyDetector : Detector(), SourceCodeScanner { companion object { @JvmField val ISSUE: Issue = Issue.create( “MyDetectorID", “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. Lint Issue First we have to create an issue explaining

    what’s the problem and how to fix it: class MyDetector : Detector(), SourceCodeScanner { companion object { @JvmField val ISSUE: Issue = Issue.create( “MyDetectorID", “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. Lint Issue First we have to create an issue explaining

    what’s the problem and how to fix it: class MyDetector : Detector(), SourceCodeScanner { companion object { @JvmField val ISSUE: Issue = Issue.create( “MyDetectorID", “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. Lint Issue First we have to create an issue explaining

    what’s the problem and how to fix it: class MyDetector : Detector(), SourceCodeScanner { companion object { @JvmField val ISSUE: Issue = Issue.create( “MyDetectorID", “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. Lint Issue First we have to create an issue explaining

    what’s the problem and how to fix it: class MyDetector : Detector(), SourceCodeScanner { companion object { @JvmField val ISSUE: Issue = Issue.create( “MyDetectorID", “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
  21. Lint Issue First we have to create an issue explaining

    what’s the problem and how to fix it: class MyDetector : Detector(), SourceCodeScanner { companion object { @JvmField val ISSUE: Issue = Issue.create( “MyDetectorID", “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
  22. Lint Registry 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
  23. Lint Registry 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
  24. Detectors A Detector is responsible for detecting occurrences of an

    Issue in the source code, multiple Issues can use the same Detector.
  25. Specialized Detectors 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, ResorceFolderScanner…
  26. Specialized Detectors 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, ResorceFolderScanner…
  27. Specialized Detectors 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, ResorceFolderScanner…
  28. Specialized Detectors 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, ResorceFolderScanner…
  29. SourceCodeScanner Has many useful callback functions, to name a few:

    fun applicableSuperClasses(): List<String>? fun getApplicableMethodNames(): List<String>? fun getApplicableUastTypes(): List<Class<out UElement>>?
  30. SourceCodeScanner Has many useful callback functions, to name a few:

    fun applicableSuperClasses(): List<String>? fun getApplicableMethodNames(): List<String>? fun getApplicableUastTypes(): List<Class<out UElement>>?
  31. SourceCodeScanner Has many useful callback functions, to name a few:

    fun applicableSuperClasses(): List<String>? fun getApplicableMethodNames(): List<String>? fun getApplicableUastTypes(): List<Class<out UElement>>?
  32. SourceCodeScanner Has many useful callback functions, to name a few:

    fun applicableSuperClasses(): List<String>? fun getApplicableMethodNames(): List<String>? fun getApplicableUastTypes(): List<Class<out UElement>>?
  33. SourceCodeScanner fun getInt()k= (0..10).map { it.also { "test" } }.also

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

    }.also { “Test2" }.apply { size }.let { it.last() }.takeIf { it > 10 }
  35. 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 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 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. 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 = node.returnType?.presentableText == "void" return isFunction && !hasColon && !isVoid }
  39. PSI

  40. Program Structure Interface Used in IntelliJ to model Java code,

    XML, etc Many UElements also implement PSI interfaces: interface UClass : UDeclaration, PsiClass interface UMethod : UDeclaration, PsiMethod interface UField : UVariable, PsiField interface UParameter : UVariable, PsiParameter
  41. 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
  42. JavaContext Lint provides a JavaContext with information about the project:

    • Information about the given file in which lint is running. • Report errors to the given client (Android Studio, Gradle)
  43. JavaContext Lint provides a JavaContext with information about the project:

    • Information about the given file in which lint is running. • Report errors to the given client (Android Studio, Gradle)
  44. JavaContext Lint provides a JavaContext with information about the project:

    • Information about the given file in which lint is running. • Report errors to the given client (Android Studio, Gradle)
  45. Quick Fixes Provide a fix when the user presses cmd+enter

    in the IDE: LintFix.create().replace() .name("Add inferred type") .range(getLocation(node)) .with(fixString) .reformat(true) .build()
  46. 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 lambas. • Nesting in custom LiveData. • Top level class comments
  47. 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 lambas. • Nesting in custom LiveData. • Top level class comments.
  48. 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 lambas. • Nesting in custom LiveData. • Top level class comments.
  49. 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.
  50. 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.
  51. 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.