「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!」html
前面3篇文章,咱们介绍了静态代码扫描在团队的重要性以及在实际团队实践中如何使用Gitlab CI/CD配合静态代码扫描实现让团队成员低感知地遵照代码规范。而在以前咱们的实践中仅仅是使用了 ktlint 实现了 Kotlind的官方代码风格规范 检查,但在实际开发过程当中,咱们还会有更多团队中的代码规范,如日志打印方法的统1、每一个activity文件必需要有注释等。
所以,做为Android静态代码扫描实践的收官文章,我将带着你们如何使用 ktlint 写出自定义规则。前端
虽然官方也有简单的文档教咱们如何自定义ktlint规则,可是我以为先搞懂执行 ./gradlew ktlint
后如何加载规则,更有助于咱们自定义ktlint规则。node
首先先找到定义 ktlint
这个gradle任务的地方,在项目根目录下的app目录下的build.gradle里面git
...
configurations {
ktlint
}
...
task ktlint(type: JavaExec, group: "verification") {
description = "Check Kotlin code style."
classpath = configurations.ktlint
main = "com.pinterest.ktlint.Main"
args "-a", "src/**/*.kt", "--reporter=html,output=${buildDir}/ktlint.html"
}
...
dependencies {
...
ktlint("com.pinterest:ktlint:0.41.0") {
attributes {
attribute(Bundling.BUNDLING_ATTRIBUTE, getObjects().named(Bundling, Bundling.EXTERNAL))
}
}
...
}
复制代码
其中定义了一个 name 为 ktlint 的 gradle 任务,类型为 JavaExec,执行后将会在子进程中执行 Java 应用程序(Jar),classpath 定义了要执行的 Jar 的路径,而 configurations.ktlint
是一个定义好的名为 ktlint 的引用集合,在这里面仅引用了 "com.pinterest:ktlint:0.41.0"
,后续你能够添加本身的Jar。 main
表示要执行的 main 方法为 com.pinterest.ktlint.Main
.程序员
所以咱们能够直接在 ktlint 的源码看到 com.pinterest.ktlint.Main方法github
咱们执行 ./gradlew ktlint
的时候并无带子命令,所以直接进入下一步 ktlintCommand.run()
方法。编程
其中 failOnOldRulesetProviderUsage()
是判断使用的 Jar 是否有继承老的规则方法,若是有,直接报错。然后续就是咱们要找的加载规则的方法。后端
val ruleSetProviders = rulesets.loadRulesets(experimental, debug, disabledRules)
复制代码
再进入看看loadRulesets方法api
能够看到加载了 Jar 里全部实现 RuleSetProvider
抽象类的类,固然还有一些过滤条件,而 RuleSetProvider
抽象类 get
方法返回了一系列实现 com.pinterest.ktlint.core.Rule
抽象类的规则类, 对后面的步骤还感兴趣的,你们能够去看ktlint的源码,这里咱们只须要了解加载规则的流程。粗略总结以下图:markdown
所以咱们自定义规则就是自定义一个类实现 RuleSetProvider
抽象类,在这个类中返回自定义的规则集合,而后导出成 Jar , 而后在项目根目录下的app目录下的build.gradle里面经过 ktlint
引用你的 Jar。
上面简单介绍了 ktlin 如何加载自定义规则,了解后明白咱们须要自定义一个类实现 RuleSetProvider
抽象类,在这个类中返回自定义的规则集合,而规则是一个实现 com.pinterest.ktlint.core.Rule
抽象类,在这个实现了规则的类中的 visit
抽象方法,在这个方法里面咱们要完成识别不符合规范的代码块并输出警告提醒文本的功能,而该抽象方法的 ASTNode
参数就是咱们识别代码块的关键。
ASTNode
是 JetBrains 对于旗下 IDE 的抽象语法树(Abstract Syntax Tree,AST)的实现 -- PSI(程序结构接口)其中的一个类。以树状的形式表现编程语言,将咱们程序员所编写的源代码语法结构进行抽象表示。能够理解为 PSI 将程序员编写的代码转换为方便进行代码语法分析的树状结构代码。
而咱们能够使用 PsiViewer插件 来直观的查看经过 PSI 生成的树状结构,下面两张图能够直观的看出该插件的使用以及树状结构的展现:
「Talk is cheap. Show me the code」. 所以这里我用一个自定义的 ktlint 规则 -- 不可直接继承 Activity() , 必须继承 BaseActivity 的实现当示例,但愿你们能从中了解如何实现自定义规则,示例代码Github.为方便调试,下面示例是在一个可用的 Android 项目下进行,这样方便咱们调试,完成开发后,能够迁移到一个独立的 kotlin 项目,方便分发使用,如示例代码Github.
build.gradle
文件修改以下,其中依赖的 kotlin 版本号要与项目根目录的 build.gradle
文件的版本一致:plugins {
id 'kotlin'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.5.20"
compileOnly "com.pinterest.ktlint:ktlint-core:0.41.0"
}
复制代码
RuleSetProvider
的类.在新建模块下的 src->main
下面新建文件夹 resources/META-INF/services
,而且在该目录下新建 com.pinterest.ktlint.core.RuleSetProvider
文件,在文件中添加com.tc.custom_rules.CustomRuleSetProvider
复制代码
这时候咱们的文件目录如图:
ExtendBaseRule
类实现 Rule
抽象类,其中 id 是方便咱们查找过滤该规则的, ASTNode 的相关能够参考 IDEA 程序结构接口 (PSI) 官方参考文档 ,结合 PsiViewer插件 咱们能够清楚如何筛选不符合规则的 kotlin 文件package com.tc.custom_rules
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType
import com.pinterest.ktlint.core.ast.children
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
class ExtendBaseRule : Rule("kclass-extend-base-rules") {
override fun visit( node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) {
if (node.elementType == KtStubElementTypes.CLASS) {
println("使用调试打印日志:${node.text}")
//修饰符为 class 的ASTNode
var isExtendActivity = false
//判断该class是否继承了Activity
for (childNode in node.children()) {
if (childNode.elementType == KtStubElementTypes.SUPER_TYPE_LIST) {
//psi中继承与实现的类
for (minChild in childNode.children()) {
if (minChild.elementType == KtStubElementTypes.SUPER_TYPE_CALL_ENTRY) {
//psi中继承的类,判断继承的ASTNode的文本
if (minChild.text == "Activity()") {
isExtendActivity = true
}
break
}
}
}
}
if (isExtendActivity) {
//该class是继承了Activity,再判断是否是BaseActivity
for (childNode in node.children()) {
if (childNode.elementType == ElementType.IDENTIFIER) {
//第一个标识符,是类名
if (isExtendActivity && childNode.text != "BaseActivity") {
//该class是继承了Activity,也不是BaseActivity,所以输出错误
emit(
childNode.startOffset,
"Activity请继承BaseActivity!",
false
)
break
}
break
}
}
}
}
}
}
复制代码
CustomRuleSetProvider
类实现 RuleSetProvider
,返回上面定义的规则package com.tc.custom_rules
import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider
class CustomRuleSetProvider : RuleSetProvider {
override fun get(): RuleSet = RuleSet(
"custom-rule-set",
ExtendBaseRule()
)
}
复制代码
app
模块的 build.gradle
依赖模块...
dependencies {
...
ktlint project(':custom_rules')
}
复制代码
./gradlew ktlint
,能够看到咱们自定义的规则已经产生做用实际实践中,咱们并不可能每次有新项目配置规则的时候都添加一个自定义规则模块,所以咱们须要把自定义规则模块导出成 jar ,方便 Android 项目引用。
你能够在刚才的自定义规则模块基础上执行
./gradlew :custom_rules:build
复制代码
或者把刚才的自定义模块独立成一个 kotlin 项目,执行
./gradlew build
复制代码
能够在 build->lib
中看到构建出的 jar , 以后就能够发布到 Maven 仓库了。
讲解了如何实现自定义规则,基于 ktlint 和 Gitlab CI/CD 的团队静态代码规范实践这个系列基本上也完结了。
若是个人文章对你有帮助或启发,辛苦大佬们点个赞👍🏻,支持我一下。
若是有错漏,欢迎大佬们指正,也欢迎你们一块儿讨论,感谢。
Gradle 参考文档
Writing your first ktlint rule -- Niklas Baudy
IDEA 程序结构接口 (PSI) 官方参考文档
自定义规则示例代码Github