「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,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