ktlint配置及使用方法

ktlint是一个自带格式化的静态代码分析工具,可用于规范化kotlin代码风格,还可以自动格式化代码,大大节省手动格式化的时间。简单来说,ktlint是一个包含了linter和formatter的静态代码分析工具。

特性(Features)

  • 无须配置。也就是说无须讨论决定使用什么代码规范,也无须去管理特定文件。ktlint会从 kotlinlang.orgAndroid Kotlin Style Guide 中获取官方代码规范。当然 ktlint 不需要配置规则文件不意味着不允许扩展,ktlint 支持自定义.editorconfig规则集(ruleset)
  • 内置格式化工具。因此你无须手动去修复不规范的代码。
  • 自定义化输出。在检查代码格式时,可以指定任何类型的输出,例如plain (+ plain?group_by_file,默认输出格式), json, checkstyle 等类型,同时也很容易创建自己的输出格式
  • 已包含所有依赖的单个jar文件,即不需要引入其他的依赖,只需要下载单个jar文件即可。

标准规则(Standard rules)

  • 缩进用4个space(除非在.editorConfig中设置了不同的 indent_size 值(使用方法参考EditorConfig));
  • 无分号(除非用于在同一行上分隔多个语句);
  • 不使用通配符和无用的 import
  • 无连续空行;
  • } 之前没有空行;
  • 无尾部空白;
  • 不使用 Unit 返回值(即用 fun fn {} 代替 fun fn: Unit {});
  • 没有空的({}) 类主体;
  • 范围(..)运算符周围没有空格;
  • +, -*/%&&|| 等二进制运算符之前无换行符;
  • 在链式调用中, .?.?: 应该放在下一行
  • 在赋值(=)操作符处分行时,后续内容紧跟在赋值符号后面;
  • 当类/函数签名不适合单行时,每个参数必须位于单独一行;
  • 统一字符串模板($v 而不是 ${v}${p.v} 而不是 ${p.v.toString()});
  • 统一修饰剂的顺序;
  • 统一关键字,逗号,冒号,大括号,中缀运算符,注释等后面的间距;
  • 在每个文件末尾换行(默认情况下不使用,但推荐使用)(在.editorConfig中设置 insert_final_newline=true 便可使用) 。

EditorConfig

ktlint可以识别以下.editorconfig中的属性(需要在[*.{kt,kts}]下面指定):
(下面显示的值为默认值,不需要显示指定)

[*.{kt,kts}]
# possible values: number (e.g. 2), "unset" (makes ktlint ignore indentation completely)  
indent_size=4
# possible values: number (e.g. 2), "unset"
continuation_indent_size=4
# true (recommended) / false
insert_final_newline=unset
# possible values: number (e.g. 120) (package name, imports & comments are ignored), "off"
# it's automatically set to 100 on `ktlint --android ...` (per Android Kotlin Style Guide)
max_line_length=off

安装

如果不需要使用ktlint的命令行接口,可以直接跳到集成,可任选以下一种进行安装。

  1. 使用 curl (所有平台通用方法,但速度较慢,不推荐)
curl -sSLO https://github.com/shyiko/ktlint/releases/download/0.29.0/ktlint &&chmod a+x ktlint && sudo mv ktlint /usr/local/bin/
  1. 直接从发布页面下载ktlint,下载成功将ktlint文件放到git gui(即MINGW64)安装目录的/usr/local/bin目录下,如 D:\Program Files\Git\usr\local\bin,之后执行 chmod a+x ktlint,确保ktlint为可执行文件。

  2. 在MacOS或Linux上,也可以使用 brew 进行安装: brew install shyiko/ktlint/ktlint

  3. 使用 wget 代替 curl,只需用 wget -qO- 代替 curl -sL

集成

Gradle集成

// 在module下的build.gradle中添加以下代码
repositories {
	jcenter()
}

configurations {
	ktlint
}

dependencies {
	ktlint "com.github.shyiko:ktlint:0.29.0"
	// additional 3rd party ruleset(s) can be specified here
	// just add them to the classpath (e.g. ktlint 'groupId:artifactId:version') and 
    // ktlint will pick them up
}

task ktlint(type: JavaExec, group: "verification") {
	description = "Check Kotlin code style."
	classpath = configurations.ktlint
	main = "com.github.shyiko.ktlint.Main"
	args "-a", "src/**/*.kt" // -a 表示代码规范使用 Android Kotlin Style Guide,不需要可以去掉,即改为 args "src/**/*.kt"
	// to generate report in checkstyle format prepend following args:
	// "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/ktlint.xml"
    // see https://github.com/shyiko/ktlint#usage for more
}
check.dependsOn ktlint

task ktlintFormat(type: JavaExec, group: "formatting") {
	description = "Fix Kotlin code style deviations."
	classpath = configurations.ktlint
	main = "com.github.shyiko.ktlint.Main"
	args "-F", "src/**/*.kt"
}

通过gradle ktlint检查代码规范,通过gradle ktlintFormat执行代码格式化。
以上为不使用插件的方式,也可以使用 jlleitschuh/ktlint-gradlejeremymailen/kotlinter-gradle 插件执行 ktlint 的功能。不过在 Android Studio 的内部命令行窗口里,我们一般用 gradlew ktlint 执行ktlint命令。通过run gradle task 的方式来执行 ktlint 最终都会显示 BUILD FAILED,但其实任务已经执行完成了,只是在显示完所有检测结果后显示以下错误而已,无须介意:
ktlint配置及使用方法

其他平台的集成方式请查阅 ktlint

用法(Usage)

# 递归检查当前目录下所有kotlin文件的代码规范(隐藏的文件夹则会被跳过),--color作用为用不同颜色区分输出的结果
$ ktlint --color
# 结果如下:
src/main/kotlin/Main.kt:10:10: Unused import
# Andorid常用命令:
$ ktlint -a --color
  
# 检查指定文件 (用 ! 指定不检查指定文件) 
$ ktlint "src/**/*.kt" "!src/**/*Test.kt"

# 自动格式化代码(会打印无法自动修复的错误) 
$ ktlint -F 
# or 
$ ktlint -F "src/**/*.kt"

# 按照分组打印不规范代码,group_by_file表示按照文件分组,即同个文件的问题会打印在一起
$ ktlint --reporter=plain?group_by_file
# 依照指定输出格式打印不规范代码,下面命令表示在控制台按照plain输出,同时以checkstyle格式输出到ktlint-report-in-checkstyle-format.xml
$ ktlint --reporter=plain --reporter=checkstyle,output=ktlint-report-in-checkstyle-format.xml

# 安装git hook方便在commit前可以自动检查代码,在对应项目目录下执行以下命令
$ ktlint --install-git-pre-commit-hook
# 如果想要改为在push前检查则使用
$ ktlint --install-git-pre-push-hook

在window上需要使用 java -jar ktlint ....,
可以使用 ktlint --help 查看更多用法

创建规则集(Creating a ruleset)

这里的规则集指的是包含了一个聚集了一个或多个规则的规则集的jar文件。
ktlint通过ServiceLoader去发现classpath上所有可达的"RuleSet",

创建规则步骤:

  • 添加依赖 com.github.shyiko.ktlint:ktlint-core:0.29.0
  • 创建子类继承 Rule,并重写 visit 方法,实现自定义规则
  • 创建子类继承 RuleSetProvider,重写 get 方法,返回自定义的 Rule 类
  • src/main 目录下创建 resources/META-INF/services/com.github.shyiko.ktlint.core.RuleSetProvider,并在该文件里输入自已的 RuleSetProvider 全名,如 com.fgj.expamle.CustomRuleSetProvider
  • 将上述文件打包成 jar 文件,在运行 ktlint 命令时借助 -R 参数输入该jar文件的路径,如 ktlint "**/ForTest.kt" -R pojectName/build/libs/customRule.jar --relative; 若是使用 gradle 执行 ktlint,则可以将自定义的规则文件放到新的 module 里,然后在主 module 下的 build.gralde 中引入自定义规则的 module ,如
dependencies {
  ktlint project(":custom-ktlint-rules")
}

See also Writing your first ktlint rule by Niklas Baudy.

class NoInternalImportRule  : Rule("no-internal-import") {
    override fun visit(
            node: ASTNode,
            autoCorrect: Boolean,
            emit: (offset: Int, errorMessage: String, canBeAutoCorrected:
            Boolean) -> Unit
    ) {
        if (node.elementType == KtStubElementTypes.IMPORT_DIRECTIVE) {
            val importDirective = node.psi as KtImportDirective
            val path = importDirective.importPath?.pathStr
            if (path != null && path.contains("internal")) {
                emit(node.startOffset, "Importing from an internal package",
                        false)
            }
        }
    }
}

class CustomRuleSetProvider : RuleSetProvider {
  override fun get()
      = RuleSet("custom-ktlint-rules", NoInternalImportRule())
}

visit 方法中的 ASTNode 参数是 Abstract Syntax Tree的缩写。任何编程语言的代码都可以呈现为 ASTNode 的集合,且它们都是树的结构。

禁用规则(Disable rule)

ktlint 虽然可以大大节省我们检查代码规范和格式化的时间,但有时某些代码我们并不希望被 ktlint 的规则检测到,或者说我们并不希望被修改为 ktlint 的代码风格,此时我们便可以对这部分代码屏蔽掉对应规则,例如,在 kotlin 中,是极不提倡使用通配符的,所以 ktlint 也是认为使用通配符 import 是不规范的,但我们的IDE却不这么认为,总会自动帮我们把同一个包的多个 import 合并为 import package.*,此时我们并可以屏蔽这部分代码,具体如下:

import package.* // ktlint-disable no-wildcard-imports

或者使用一下格式屏蔽代码块

/* ktlint-disable no-wildcard-imports */
import package.a.*
import package.b.*
/* ktlint-enable no-wildcard-imports */

若是想禁用所有规则,则可以:

import package.* // ktlint-disable

不过这样依然需要为每个文件都添加这些注释,也是个耗时费力的差。遗憾的是,ktlint 目前并不支持全局禁用。

自定义输出格式(Creating a reporter)

自定义输出格式需要以下几个步骤:

  • 实现 Reporter 类,在类中定义自己的格式
  • 创建自定义的 ReporterProvider
  • 通过 META-INF/services/com.github.shyiko.ktlint.core.ReporterProvider 进行注册
  • 将它们打包为 jar 包
  • 通过 ktlint --reporter=name,artifact=groupId:artifactId:versionktlint --reporter=name,artifact=/path/to/custom-ktlint-reporter.jar 加载自定义(或第三方)的输出格式jar文件

具体使用可参考ktlint-reporter-plainmcassiano/ktlint-html-reporter

参考