3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

マルチモジュールプロジェクトにktlintのカスタムルールを入れる

Last updated at Posted at 2024-03-20

意外と簡単にできるktlintのカスタムルール

専用モジュールを作成して依存を追加する

// build.gradle

plugins {
    kotlin("jvm")
}

/*
ktlint-engine-core = { module = "com.pinterest.ktlint:ktlint-rule-engine-core", version.ref = "ktlint" }
ktlint-cli-ruleset-core = { module = "com.pinterest.ktlint:ktlint-cli-ruleset-core", version.ref = "ktlint" }
ktlint-test = { module = "com.pinterest.ktlint:ktlint-test", version.ref = "ktlint" }
slf4j = { module = "org.slf4j:slf4j-simple", version = "2.0.12" }
 */
dependencies {
    implementation(libs.ktlint.engine.core)
    implementation(libs.ktlint.cli.ruleset.core)
    testImplementation(libs.junit)
    testImplementation(libs.ktlint.test)
    testRuntimeOnly(libs.slf4j)
}
  • ktlint-engine-core

lintingとformattingを行う

  • ktlint-cli-ruleset-core

標準のルールセットや、カスタムルールセットを定義するためのロジックが入ってる

  • ktlint-test

UT用のパッケージ

  • slf4j

ktlint自体がこのロギングライブラリのラッパーなので依存として必要(これなしだと、slf4j not found のエラーが出る)

Ktlint uses the io.github.oshai:kotlin-logging which is a slf4j wrapper. As API consumer you can choose which logging framework you want to use and configure that framework to your exact needs. The basic API Consumer contains an example with org.slf4j:slf4j-simple as logging provider and a customized configuration which shows logging at DEBUG level for all classes except one specific class which only displays logging at WARN level.

他にも部品はいっぱいあるが、最低限必要なのはこれだけ。

カスタムルールの作成

import com.pinterest.ktlint.rule.engine.core.api.ElementType
import com.pinterest.ktlint.rule.engine.core.api.Rule
import com.pinterest.ktlint.rule.engine.core.api.RuleId
import giga.custom.ktlint.rules.CUSTOM_RULE_SET_ID
import org.jetbrains.kotlin.com.intellij.lang.ASTNode

class NoPackageDeclaration : Rule(
    ruleId = RuleId("$CUSTOM_RULE_SET_ID:no-package-declaration"),
    about = About(), // Stack Traceに載せたい情報
) {
    
    override fun beforeVisitChildNodes(
        node: ASTNode,
        autoCorrect: Boolean,
        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit,
    ) {
        val hasNotPackageDirective = node.findChildByType(ElementType.PACKAGE_DIRECTIVE)?.getChildren(null)?.isEmpty()
        if (hasNotPackageDirective == true) {
            emit(0, "File does not have a package declaration", false)
        }
    }
}

今回はパッケージ宣言が空の場合にエラーにする処理を追加した。

抽象構文木のノード。この中にktファイルの構造が入っていて、これを交差して警告を出す形。

inspecter が提供されている

Custom rule set - Ktlint

サービスプロバイダーの宣言

ktlintにカスタムルールを提供する

import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3
import com.pinterest.ktlint.rule.engine.core.api.RuleProvider
import com.pinterest.ktlint.rule.engine.core.api.RuleSetId
import packagename.NoPackageDeclaration

internal val CUSTOM_RULE_SET_ID = "custom-rule-set-id"

class CustomRuleSetProvider : RuleSetProviderV3(RuleSetId(CUSTOM_RULE_SET_ID)) {
    override fun getRuleProviders(): Set<RuleProvider> =
        setOf(
            RuleProvider { NoPackageDeclaration() },
        )
}
// META-INF/services/com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3
packagename.CustomRuleSetProvider

ここまでのモジュールをビルドすると、build/libs/{module-name}.jar バイナリができる

生成したバイナリをktlintに登録

使っているビルドツールやライブラリによってお作法が変わるが、kotlinter だと ルートレベルのbuildScriptでバイナリを追加するだけ

buildscript {
    dependencies {
        classpath(files("path/to/{module-name}.jar "))
    }
}

Test

カスタムルール専用のAsserterを使う

import com.pinterest.ktlint.rule.engine.core.api.Rule
import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule
import org.junit.Test

fun getRuleAsserter(provider: () -> Rule) = assertThatRule { provider() }

class NoPackageDeclarationTest {

    @Test
    fun test() {
        val wrappingRuleAssertThat = getRuleAsserter { NoPackageDeclaration() }
        val code =
            """
                
            class NoPackageDeclarationClassTest {}
            
            """.trimIndent()
        wrappingRuleAssertThat(code)
            .hasLintViolationWithoutAutoCorrect(1, 1, "File does not have a package declaration")
    }
}
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?