doilux’s tech blog

ITに関する備忘録。 DDP : http://doiluxng.hatenablog.com/entry/2018/01/01/195409

Kotlintを試す

導入手順は公式に書いてある通り。gradleでしたら警告してくれます。

./gradlew ktlint                                                  
:ktlint
/Users/****/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/OrderService.kt:3:24: Unnecessary block ("{}")
/Users/****/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/OrderService.kt:4:1: Unexpected blank line(s) before "}"
/Users/****/Develop/git/ddd-sample-kotlin/src/test/kotlin/work/doilux/dddsamplekotlin/DddSampleKotlinApplicationTests.kt:12:1: Unexpected indentation (1) (it should be 4)
/Users/****/Develop/git/ddd-sample-kotlin/src/test/kotlin/work/doilux/dddsamplekotlin/DddSampleKotlinApplicationTests.kt:15:1: Unexpected blank line(s) before "}"
:ktlint FAILED

指摘事項を直して再実行するとエラーが消えました。

./gradlew ktlint                                                  
:ktlint
BUILD SUCCESSFUL

カスタムルールを作る

「性別を表すクラスであの単語を使うな」というルールを作ってみる。

package work.doilux.dddsamplekotlin.ftth.order

import java.time.LocalDate

data class Customer(
        val firstName: FirstName,
        val lastName: LastName,
        val birthDay: BirthDay,
        val sex: Sex
)

data class FirstName(val value: String)
data class LastName(val value: String)
data class BirthDay(val value: LocalDate)
enum class Sex {
    MALE, FEMALE, ETC
}

テンプレートをゲットする

  1. ktlintをcloneする
  2. ktlint-ruleset-templateをIDEで開く

ktlintコマンドを使ってパースする

必須ではないけど、実装とデバッグがしやすいため。手順は公式に書いてるので割愛。

Ruleを書く

テンプレートにあるNoVarRule.ktを参考に実装していけば良さそう。Ruleインターフェースを実装して、visitメソッドをオーバーライドすればいいようです。引数はこんな感じ。

key value
node AST node
前述のステップでパースした結果の1行だと思う
autoCorrect indicates whether rule should attempt auto-correction
ktlintには自動的に修正する機能があるっぽい。その機能を使うかどうか(今回は対応しない)
emit a way for rule to notify about a violation (lint error)
違反があった場合の通知メソッド

NoVarRuleを軽く解説しておくと。

override fun visit(
    node: ASTNode,
    autoCorrect: Boolean,
    emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
    if (node is LeafPsiElement && node.textMatches("var") &&
            getNonStrictParentOfType(node, KtStringTemplateEntry::class.java) == null) {
        emit(node.startOffset, "Unexpected var, use val instead", false)
    }
}

node is LeafPsiElementは意味そのままで、「リーフエレメントのとき」、node.textMatches("var")もそのまま「"var"と一致するとき」という条件式。 getNonStrictParentOfTypeKtStringTemplateEntry型の親ノードを取得しています。ここでは== nullという条件なので、取得できないときに真という条件。これが意味するのは、例えばvar str = "var"のとき、文字リテラルの"var"はチェック対象外ってことです。

ということで実装しました。

class NoSexRule : Rule("no-sex") {

    override fun visit(
            node: ASTNode,
            autoCorrect: Boolean,
            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
    ) {
        if (node is LeafPsiElement &&
                (node.textMatches("sex") || node.textMatches("Sex") || node.textMatches("SEX")) &&
                getNonStrictParentOfType(node, KtStringTemplateEntry::class.java) == null) {
            emit(node.startOffset, "Unexpected word, use gender instead", false)
        }
    }
}

テストもあります(ここでは割愛) 実装終わったらRuleSetProviderに追加します。

class CustomRuleSetProvider : RuleSetProvider {

    override fun get(): RuleSet = RuleSet("custom", NoVarRule(), NoSexRule())
}

kotlinコマンドで試す

まずはjarに固めます。

./gradlew clean jar

公式にある通り、-Rでjarファイルを指定したらちゃんと検出してくれました。

kotlint -R /Users/xxxx/Develop/git/ddd-sample-kotlin/ktlint/rules/ktlint-ruleset-template.jar src/**/*kt
/Users/xxxx/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/Customer.kt:9:13: Unexpected word, use gender instead
/Users/xxxx/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/Customer.kt:9:18: Unexpected word, use gender instead
/Users/xxxx/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/Customer.kt:15:12: Unexpected word, use gender instead

gradleで試す

前述のjarファイルを$PROJECT_ROOT/ktlint/roles配下に配置して、build.gradleを以下のようにすると、gradleのタスクからチェックできました。

task ktlint(type: JavaExec, group: "verification") {
    description = "Check Kotlin code style."
    classpath = configurations.ktlint
    main = "com.github.shyiko.ktlint.Main"
    args "-R", "ktlint/rules/ktlint-ruleset-template.jar", "src/**/*.kt"
}
./gradlew ktlint                                                                   
:ktlint
/Users/xxxx/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/Customer.kt:9:13: Unexpected word, use gender instead
/Users/xxxx/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/Customer.kt:9:18: Unexpected word, use gender instead
/Users/xxxx/Develop/git/ddd-sample-kotlin/src/main/kotlin/work/doilux/dddsamplekotlin/ftth/order/Customer.kt:15:12: Unexpected word, use gender instead
:ktlint FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':ktlint'.
> Process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/java'' finished with non-zero exit value 1

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 4.349 secs