레이블이 ktlint인 게시물을 표시합니다. 모든 게시물 표시
레이블이 ktlint인 게시물을 표시합니다. 모든 게시물 표시

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