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

Lint for Kotlin @R.kt#3

TakuSemba
March 15, 2018

Lint for Kotlin @R.kt#3

TakuSemba

March 15, 2018
Tweet

More Decks by TakuSemba

Other Decks in Programming

Transcript

  1. Lint For Kotlin
    TakuSemba
    CyberAgent.Inc

    View Slide

  2. @takusemba
    https://github.com/TakuSemba

    View Slide

  3. https://android.github.io/kotlin-guides/style.html

    View Slide

  4. ktlint
    ɾStatic code analysis tool for kotlin
    ɾBased on official code style
    ɾBuilt-in formatter
    ɾCustomizable output

    View Slide

  5. ktlint with gradle
    configurations {
    ktlint
    }
    dependencies {
    ktlint "com.github.shyiko:ktlint:$KTLINT_VERSION"
    }
    check.dependsOn ktlint

    View Slide

  6. ktlint with gradle
    task ktlint(type: JavaExec, group: "kotlin verification") {
    description = 'Check Kotlin code style.'
    args 'src/**/*.kt'
    main = 'com.github.shyiko.ktlint.Main'
    classpath = configurations.ktlint + sourceSets.main.output
    }
    $ ./gradlew tasks

    View Slide

  7. ktlint with gradle
    task ktFormat(type: JavaExec, group: "kotlin Formatting") {
    description = 'Fix Kotlin code style deviations.'
    args '-F', 'src/**/*.kt'
    main = 'com.github.shyiko.ktlint.Main'
    classpath = configurations.ktlint + sourceSets.main.output
    }
    $ ./gradlew tasks

    View Slide

  8. ktlint with CLI
    $ brew install shyiko/ktlint/ktlint
    $ ktlint "src/**/*.kt"

    View Slide

  9. Outputs
    task ktlint(type: JavaExec, dependsOn: classes) {
    description = 'Check Kotlin code style.'
    args 'src/**/*.kt', ‘--reporter=plain'
    main = 'com.github.shyiko.ktlint.Main'
    classpath = configurations.ktlint + sourceSets.main.output
    }

    View Slide

  10. Outputs
    task ktlint(type: JavaExec, dependsOn: classes) {
    description = 'Check Kotlin code style.'
    args 'src/**/*.kt', ‘--reporter=plain'
    main = 'com.github.shyiko.ktlint.Main'
    classpath = configurations.ktlint + sourceSets.main.output
    }
    '--reporter=plain'

    View Slide

  11. Outputs
    --reporter=plain
    takusemba/ktlint-sample/app/…/Hoge.kt:44:1: Unexpected blank
    line(s) before "}"
    takusemba/ktlint-sample/app/…/Fuga.kt:108:1: Needless blank
    line(s)
    takusemba/ktlint-sample/app/…/Foo.kt:25:7: Line break before
    assignment is not allowed
    takusemba/ktlint-sample/app/…/Bar.kt:47:1: Unexpected blank
    line(s) before “}"

    View Slide

  12. Outputs
    --reporter=checkstyle



    line="25"
    column="7"
    severity="error"
    message="Line break before assignment is not allowed"
    source="no-line-break-before-assignment" />



    View Slide

  13. Outputs
    --reporter=custom-ktlint-rule
    class CustomReporter(private val out: PrintStream) : Reporter {
    private val errors = ArrayList()
    override fun onLintError(
    file: String,
    err: LintError,
    corrected: Boolean) {
    errors.add(err)
    }
    override fun afterAll() {
    out.println("hello custom reporter.")
    out.println("found ${errors.size} errors.")
    }
    }

    View Slide

  14. Outputs
    --reporter=custom-ktlint-rule
    class CustomReporter(private val out: PrintStream) : Reporter {
    private val errors = ArrayList()
    override fun onLintError(
    file: String,
    err: LintError,
    corrected: Boolean) {
    errors.add(err)
    }
    override fun afterAll() {
    out.println("hello custom reporter.")
    out.println("found ${errors.size} errors.")
    }
    }
    override fun afterAll() {
    out.println("hello custom reporter.")
    out.println("found ${errors.size} errors.")
    }

    View Slide

  15. Outputs
    class CustomReporterProvider : ReporterProvider {
    override val id: String = “custom-ktlint-reporter"
    override fun get(
    out: PrintStream,
    opt: Map
    ): Reporter = CustomReporter(out)
    }
    --reporter=custom-ktlint-rule

    View Slide

  16. Outputs
    class CustomReporterProvider : ReporterProvider {
    override val id: String = “custom-ktlint-reporter"
    override fun get(
    out: PrintStream,
    opt: Map
    ): Reporter = CustomReporter(out)
    }
    --reporter=custom-ktlint-rule
    ): Reporter = CustomReporter(out)

    View Slide

  17. Outputs
    --reporter=custom-ktlint-rule

    View Slide

  18. Disable rules
    import package.* // ktlint-disable no-wildcard-imports
    import package.* // ktlint-disable
    disable specific rule
    disable the whole rule

    View Slide

  19. Disable rules

    View Slide

  20. Disable rules
    [*.{kt,kts}]
    indent_size=2
    continuation_indent_size=4
    insert_final_newline=unset
    max_line_length=off
    .editorconfig

    View Slide

  21. Add rules
    class CustomRule : Rule("custom-rule") {
    override fun visit(
    node: ASTNode,
    autoCorrect: Boolean,
    emit: (offset: Int, errorMessage: String, canBeAutoCorrected:
    Boolean) -> Unit
    ) {
    if (isLintError) {
    emit(node.startOffset, “LINT ERROR FOUND!”, false)
    }
    }
    }

    View Slide

  22. Add rules
    class CustomRule : Rule("custom-rule") {
    override fun visit(
    node: ASTNode,
    autoCorrect: Boolean,
    emit: (offset: Int, errorMessage: String, canBeAutoCorrected:
    Boolean) -> Unit
    ) {
    if (isLintError) {
    emit(node.startOffset, “LINT ERROR FOUND!”, false)
    }
    }
    }
    node: ASTNode,

    View Slide

  23. Abstract Syntax Tree

    View Slide

  24. AST
    fun add(a: Int, b: Int): Int {
    return a + b
    }

    View Slide

  25. node
    node node node
    node node node
    node
    node node
    AST

    View Slide

  26. AST
    fun add(a: Int, b: Int): Int {
    return a + b
    }

    View Slide

  27. AST
    fun add(a: Int, b: Int): Int {
    return a + b
    }
    KtNamedFunction

    View Slide

  28. AST
    { return a + b }
    KtKeywordToken.fun
    fun
    Int
    :
    (a: Int, b: Int)
    add KtToken.IDENTIFIER
    KtTypeReference
    KtSingleValueToken.COLON
    KtParameterList
    KtBlockExpression

    View Slide

  29. AST
    { return a + b }
    KtKeywordToken.fun
    fun
    Int
    :
    (a: Int, b: Int)
    add KtToken.IDENTIFIER
    KtTypeReference
    KtSingleValueToken.COLON
    KtParameterList
    KtBlockExpression

    View Slide

  30. AST
    KtSingleValueToken.LBRACE
    KtReturnExpression
    KtSingleValueToken.RBRACE
    }
    {
    return a + b

    View Slide

  31. AST
    KtSingleValueToken.LBRACE
    KtReturnExpression
    KtSingleValueToken.RBRACE
    }
    {
    return a + b

    View Slide

  32. AST
    KtKeywordToken.return
    KtToken.IDENTIFIER
    KtSingleValueToken.PLUS
    a
    return
    +
    b KtToken.IDENTIFIER

    View Slide

  33. node
    node node node
    node node node
    node
    node node
    AST

    View Slide

  34. Add rules
    class CustomRule : Rule("custom-rule") {
    override fun visit(
    node: ASTNode,
    autoCorrect: Boolean,
    emit: (offset: Int, errorMessage: String, canBeAutoCorrected:
    Boolean) -> Unit
    ) {
    if (isLintError) {
    emit(node.startOffset, “LINT ERROR FOUND!”, false)
    }
    }
    }

    View Slide

  35. Add rules
    class CustomRule : Rule("custom-rule") {
    override fun visit(
    node: ASTNode,
    autoCorrect: Boolean,
    emit: (offset: Int, errorMessage: String, canBeAutoCorrected:
    Boolean) -> Unit
    ) {
    if (isLintError) {
    emit(node.startOffset, “LINT ERROR FOUND!”, false)
    }
    }
    }
    node: ASTNode,

    View Slide

  36. Add rules
    fun add(a: Int, b: Int): Int = a + b
    // OK
    fun add(a: Int, b: Int) = a + b
    // NG

    View Slide

  37. fun add(a: Int, b: Int) = a + b
    // NG

    View Slide

  38. Add rules
    fun add(a: Int, b: Int) = a + b
    // NG
    if (node.elementType == KtStubElementTypes.FUNCTION
    && !node.text.contains('\n')) {
    val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return
    if (child.treeNext.elementType == KtTokens.WHITE_SPACE
    && child.treeNext.treeNext.elementType == KtTokens.EQ) {
    emit(node.startOffset, "need return type!!!", false)
    }
    }

    View Slide

  39. Add rules
    fun add(a: Int, b: Int) = a + b
    // NG
    if (node.elementType == KtStubElementTypes.FUNCTION
    && !node.text.contains('\n')) {
    val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return
    if (child.treeNext.elementType == KtTokens.WHITE_SPACE
    && child.treeNext.treeNext.elementType == KtTokens.EQ) {
    emit(node.startOffset, "need return type!!!", false)
    }
    }
    if (node.elementType == KtStubElementTypes.FUNCTION
    && !node.text.contains('\n')) {
    }

    View Slide

  40. Add rules
    fun add(a: Int, b: Int) = a + b
    // NG
    (a: Int, b: Int)
    if (node.elementType == KtStubElementTypes.FUNCTION
    && !node.text.contains('\n')) {
    val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return
    if (child.treeNext.elementType == KtTokens.WHITE_SPACE
    && child.treeNext.treeNext.elementType == KtTokens.EQ) {
    emit(node.startOffset, "need return type!!!”, false)
    }
    }
    val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return

    View Slide

  41. Add rules
    fun add(a: Int, b: Int) = a + b
    // NG
    if (node.elementType == KtStubElementTypes.FUNCTION
    && !node.text.contains('\n')) {
    val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return
    if (child.treeNext.elementType == KtTokens.WHITE_SPACE
    && child.treeNext.treeNext.elementType == KtTokens.EQ) {
    emit(node.startOffset, "need return type!!!", false)
    }
    }
    if (child.treeNext.elementType == KtTokens.WHITE_SPACE
    && child.treeNext.treeNext.elementType == KtTokens.EQ) {
    =

    View Slide

  42. Add rules
    fun add(a: Int, b: Int) = a + b
    // NG
    if (node.elementType == KtStubElementTypes.FUNCTION
    && !node.text.contains('\n')) {
    val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return
    if (child.treeNext.elementType == KtTokens.WHITE_SPACE
    && child.treeNext.treeNext.elementType == KtTokens.EQ) {
    emit(node.startOffset, "need return type!!!", false)
    }
    }
    fun add(a: Int, b: Int) = a + b
    emit(node.startOffset, "need return type!!!", false)

    View Slide

  43. Add rules
    class ExpressionFunctionRule : Rule("one-line-function") {
    override fun visit(
    node: ASTNode,
    autoCorrect: Boolean,
    emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
    if (node.elementType == KtStubElementTypes.FUNCTION
    && !node.text.contains('\n')) {
    val child = node.findChildByType(KtNodeTypes.VALUE_PARAMETER_LIST) ?: return
    if (child.treeNext.elementType == KtTokens.WHITE_SPACE
    && child.treeNext.treeNext.elementType == KtTokens.EQ) {
    emit(node.startOffset, "need return type!!!", false)
    }
    }
    }
    }

    View Slide

  44. Add rules
    class CustomRuleSetProvider : RuleSetProvider {
    override fun get(): RuleSet = RuleSet(
    “custom-rule",
    ExpressionFunctionRule()
    )
    }

    View Slide

  45. Add rules
    class CustomRuleSetProvider : RuleSetProvider {
    override fun get(): RuleSet = RuleSet(
    “custom-rule",
    ExpressionFunctionRule()
    )
    }
    ExpressionFunctionRule()

    View Slide

  46. Add rules

    View Slide

  47. Add rules
    $ ktlint "**/Hoge.kt" -R ktrule/build/libs/ktrule.jar --relative
    $ ./gradlew jar

    View Slide

  48. Add rules
    val rule = ExpressionFunctionRule()
    assertThat(rule.lint(
    """
    fun add(a: Int, b: Int): Int = a + b
    """
    .trimIndent())
    ).isEqualTo(emptyList())
    assertThat(rule.lint(
    """
    fun add(a: Int, b: Int) = a + b
    """
    .trimIndent())
    ).isEqualTo(listOf(LintError(1, 1, "one-line-function", "need
    return type!!!")))

    View Slide

  49. Add rules
    val rule = ExpressionFunctionRule()
    assertThat(rule.lint(
    """
    fun add(a: Int, b: Int): Int = a + b
    """
    .trimIndent())
    ).isEqualTo(emptyList())
    assertThat(rule.lint(
    """
    fun add(a: Int, b: Int) = a + b
    """
    .trimIndent())
    ).isEqualTo(listOf(LintError(1, 1, "one-line-function", "need
    return type!!!")))
    val rule = ExpressionFunctionRule()
    assertThat(rule.lint(
    """
    fun add(a: Int, b: Int): Int = a + b
    """
    .trimIndent())
    ).isEqualTo(emptyList())

    View Slide

  50. Add rules
    val rule = ExpressionFunctionRule()
    assertThat(rule.lint(
    """
    fun add(a: Int, b: Int): Int = a + b
    """
    .trimIndent())
    ).isEqualTo(emptyList())
    assertThat(rule.lint(
    """
    fun add(a: Int, b: Int) = a + b
    """
    .trimIndent())
    ).isEqualTo(listOf(LintError(1, 1, "one-line-function", "need
    return type!!!")))
    assertThat(rule.lint(
    """
    fun add(a: Int, b: Int) = a + b
    """
    .trimIndent())
    ).isEqualTo(listOf(LintError(1, 1, "one-line-function", "need
    return type!!!")))

    View Slide

  51. Add rules

    View Slide

  52. with Danger

    View Slide

  53. with Danger
    BQQTSDNBJOKBWBDPNUBLVTFNCB)PHFLU
    Danger
    # Dangerfile
    checkstyle_format.base_path = Dir.pwd
    checkstyle_format.report ‘path/to/result.xml’

    View Slide

  54. Lint For Kotlin
    https://github.com/takusemba
    https://twitter.com/takusemba

    View Slide