2022년 9월 16일 금요일

Kotlin Ktlint

Kotlin Ktlint

Ktlint

에디터상에서 어시스턴스로 부터 lint 오류를 발견하기 위해선 ktlint 플러그인이 필요하다.
https://plugins.jetbrains.com/plugin/15057-ktlint-unofficial-

gradle에서 ktlint를 실행하기 위해서는 jlleitschuh-ktlint-gradle 플러그인을 이용하여 lint용 task를 생성/활용한다.

프로젝트의 build.gradle.kts 파일의 
repositories에 gradlePluginPortal() 추가하고
dependencies에 classpath("org.jlleitschuh.gradle:ktlint-gradle:10.1.0")
추가한다.
ktlint를 하고싶은 프로젝트에서 Plugins에
id("org.jlleitschuh.gradle.ktlint")
를 추가한다.
이제 Gradle의 Task를 보면 formatting/ktlintFormat과 verificaiton/kilintCheck가 생성되어 있는것을 볼수 있다. 
오류의 결과는 build/reports/ktlint/ktlintKotlinScriptCheck 폴더에서 볼수 있다.

pinterest의 ktlint라이브러리를 이용하는 방법도 있다. ktlint태스크를 추가하고자하는 모듈( 또는 프로젝트) 에 의존성을 추가하고 규칙을 입력한다.
먼저 프로젝트의 루트폴더(setting.gradle.kts가 있는 같은 레벨)에 .editorconfig 파일을 만들자.

root = true  
  
[*]  
charset=utf-8  
end_of_line=lf  
indent_style=space  
indent_size=4  
insert_final_newline=true  
disabled_rules=no-wildcard-imports,import-ordering,comment-spacing  
  
[*.{kt,kts}]  
insert_final_newline=false
val ktlint by configurations.creating
dependencies {  
  ktlint("com.pinterest:ktlint:0.42.1")  
}
  
val ktlintCheck by tasks.creating(JavaExec::class) {  
  var inputFiles: FileCollection = files()  
  
  gradle.afterProject {  
    inputFiles += files(  
            "${this.projectDir}/src/main/java"  
    )  
    inputs.files(inputFiles)  
  }  
  outputs.dir("./build/ktlint/")  
  classpath = ktlint  
  main = "com.pinterest.ktlint.Main"  
  isIgnoreExitValue = true  // Build시에 lint가 Failed 가 있다면 Build도 Failed로 결과를 내도록 한다. true라면 ktlint의 오류는 무시한다.
  args = listOf(  
        "--reporter=plain",  
        "--reporter=checkstyle,output=$buildDir/reports/ktlint-results.xml",  
        "src/**/*.kt"  
  )  
}
Task의 ktlintCheck를 실행하면 kotlin 소스 파일들의 lint 체크를 한다.

멀티플랫폼이라면, 각 플랫폼에서 ktlintCheck Task를 각각 생성한뒤에
프로젝트의 build.gradle.kts 에서 일괄적으로 실행하는 방법도 있다.

allprojects {...} 
val sharedProjects = listOf(  
    project(":androidApp:ktlintCheck"),  
    project(":shared")  
)  
tasks.register("ktlintCheckRootAll") {  
  group = LifecycleBasePlugin.VERIFICATION_GROUP  
  dependsOn((sharedProjects).map { "${it.path}:ktlintCheck" })  
}

jlleitschuh-ktlint-gradle 플러그인과 pinterest의 ktlint라이브러리를 이용하여, 작업중인 프로젝트에 커스터마이징된 별도의 룰을 정할수 있다.
룰을 정의하는 쪽은 별도의 라이브러리 형태로 정의한다.

tools/myktlint 모듈에서 다음과 같이 정의한다.
package me.ktlint  
 
import com.pinterest.ktlint.core.RuleSet  
import com.pinterest.ktlint.core.RuleSetProvider  
 
class MyCustomRuleSetProvider : RuleSetProvider {  
   override fun get(): RuleSet =  
       RuleSet(  
           "custom",  
           MyOwnRule()  
       )  
}
// MyOwnRule 클래스파일에는 다음과 같이 정의한다.
package me.ktlint  
 
import com.pinterest.ktlint.core.Rule  
import org.jetbrains.kotlin.com.intellij.lang.ASTNode  
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement  
import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil.getNonStrictParentOfType  
import org.jetbrains.kotlin.psi.KtStringTemplateEntry  
 
class MyOwnRule : Rule("no-var"){  
   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, "No No No var", false)  
       }  
       // if (node.elementType == KtStubElementTypes.CLASS)  이런식도 된다.  
 }  
}

자이제 룰을 사용하고자 하는 프로젝트(모듈) 에서는 다음과 같이 task를 생성한다.

import org.jlleitschuh.gradle.ktlint.reporter.ReporterType

plugins {
    application
    id("org.jlleitschuh.gradle.ktlint")
    kotlin("jvm")
}

application {
    mainClass.set("org.jlleitschuh.gradle.ktlint.sample.kotlin.MainKt")
}

dependencies {
    ktlintRuleset(projects.samples.kotlinRulesetsCreating)
}

ktlint {
    verbose.set(true)
    outputToConsole.set(true)
    reporters {
        reporter(ReporterType.CHECKSTYLE)
        reporter(ReporterType.JSON)
    }
}

실사용예

모듈(android,shared 등 gradle.build.kts)

ktlint {
    version.set("0.39.0")
    outputToConsole.set(true)  
    // ignoreFailures.set(true) 빌드시 lint에러가 나도 일단 통과!
    outputColorName.set("RED")
    reporters {
        reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
    }

    filter {
        exclude { tree ->
            listOf("/build/generated/").any {
                tree.file.path.contains(it)
            }
        }
    }
}

dependencies {
    add("ktlintRuleset", project(":tools:ktlint"))
}

tasks.named("check") {
    dependsOn("ktlintCheck")
}

검사하고자하는 프로젝트(모듈)에서 var를 사용한다면
/shared/src/commonMain/kotlin/me/shared/Greeting.kt:5:5 No No No var (cannot be auto-corrected)
에러가 나타난다.


githook에서 사용한다면.git/hooks/pre-commit파일을 생성 또는 기존 파일에 아래 내요을 추가하면 commit  할때 마다 ktlint가 작동해서 ktlint가 성공하지 못할때는 커밋이 되지않도록 한다.


git diff --name-only --cached --relative | grep '\.kt[s"]\?$' | xargs ktlint --relative .

if [ $? -ne 0 ]; then exit 1; fi


0 comments:

댓글 쓰기