“This article has participated in the good article call order activity, click to see: back end, big front end double track submission, 20,000 yuan prize pool for you to challenge!”
In the previous three articles, we have described the importance of static code scanning in teams and how to use Gitlab CI/CD in conjunction with static code scanning implementations to get team members to follow code specifications with low awareness in a real team practice. In practice, we only used ktLint to check Kotlind’s official code style specifications, but in practice, we will have more team code specifications, such as uniform log printing methods, each activity file must have comments, and so on. So, as a concluding article on Android static code scanning practices, I’ll show you how to write custom rules using KtLint.
Ktlint Loads the process of rules
While there are simple official documents that teach you how to customize ktLint rules, I find it helpful to know how to load the rules after executing./gradlew ktlint.
First, find where the ktLint gradle task is defined. It’s in build.gradle in the app directory at the root of the project
.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))
}
}
...
}
Copy the code
One defines the name for ktlint gradle task, type of JavaExec, execution will execute the Java application in the child process (Jar), classpath defines the path to execute the Jar, And configurations. The ktlint is a defined called ktlint reference set, in which only refers to the “com. Pinterest: ktlint: 0.41.0”, later you can add your own jars. Main said to execute the main method for com. Pinterest. Ktlint. Main.
So we can see directly in ktlint source com. Pinterest. Ktlint. The Main method
We run./gradlew ktlint without the laced command, so we go straight to the next ktlintcommand-run () method.
Which failOnOldRulesetProviderUsage () is to determine whether a use the Jar have inherited the old rules method, if you have, error directly. The next step is how to load the rules.
val ruleSetProviders = rulesets.loadRulesets(experimental, debug, disabledRules)
Copy the code
Enter the loadRulesets method again
You can see that all the classes in the Jar that implement the RuleSetProvider abstract class are loaded, along with some filters, And RuleSetProvider abstract class the get method returns a series of com. Pinterest. Ktlint. Core. The Rule of the abstract class rules, on the back of the steps is interested, you can go to see the ktlint source, Here we just need to understand the process of loading the rules. A rough summary is as follows:
So our custom rule is to create a custom class that implements the RuleSetProvider abstract class, export it as a Jar, and reference your Jar with ktLint in the app directory build.gradle at the project root.
Program Structure Interface (PSI)
We need to define a class that implements the RuleSetProvider abstract class that returns our own set of rules. And rules is an implementation of com. Pinterest. Ktlint. Core. The abstract class Rule, the rules of the implements of visit abstract methods in class, in this way, we are going to complete identification is not in conformity with the specification of a code block and output the function of warning that text, The ASTNode parameter to this abstract method is the key to identifying the code block.
ASTNode is one of the classes in the PSI (Program Structure Interface), JetBrains’ implementation of the IDE’s Abstract Syntax Tree (AST). To express the programming language in the form of a tree, we programmers write source code syntax structure for abstract representation. PSI translates code written by programmers into a tree structure that facilitates code parsing.
We can use the PsiViewer plug-in to visually view the tree structure generated by PSI. The following two diagrams can visually see the use of the plug-in and the display of the tree structure:
Implement your first custom KtLint rule
Talk is cheap. Show me the code. So I’m using a custom ktLint rule — you can’t directly inherit Activity(), you must inherit BaseActivity implementations — as an example. Hopefully you’ll get an idea of how to implement a custom ktLint rule. For debugging purposes, the following example is done under a available Android project, which makes it easier for us to debug and, when completed, migrate to a separate Kotlin project for distribution, such as the sample code on Github.
Create a custom KtLint rule module
- Create a separate module in the project root folder at the same folder level as the app module. Here I’ll name the module custom_rules;
- Will create the module under
build.gradle
The file is modified as follows, where the dependent kotlin version number is the same as the project root directorybuild.gradle
The file version is consistent:
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"
}
Copy the code
- Tell ktLint to find our implementation
RuleSetProvider
Under the new modulesrc->main
Let’s create a new folderresources/META-INF/services
And create a new one in the directorycom.pinterest.ktlint.core.RuleSetProvider
File, add to the file
com.tc.custom_rules.CustomRuleSetProvider
Copy the code
At this time, our file directory is as follows:
Create a new rule class to implement rules
- new
ExtendBaseRule
Class implementsRule
Abstract class, where id is convenient for us to find the filter rule, ASTNode can refer to the relatedIDEA Program Structure Interface (PSI) official reference documentAnd combined with thePsiViewer plug-inCan we figure out how to filter nonconforming Kotlin files
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("Using debug to print logs:${node.text}")
// ASTNode with class modifier
var isExtendActivity = false
// Determine whether the class inherits the Activity
for (childNode in node.children()) {
if (childNode.elementType == KtStubElementTypes.SUPER_TYPE_LIST) {
// Psi inheriting and implementing classes
for (minChild in childNode.children()) {
if (minChild.elementType == KtStubElementTypes.SUPER_TYPE_CALL_ENTRY) {
// Psi class to determine the text of the inherited ASTNode
if (minChild.text == "Activity()") {
isExtendActivity = true
}
break}}}}if (isExtendActivity) {
// If the class inherits the Activity, determine if it is BaseActivity
for (childNode in node.children()) {
if (childNode.elementType == ElementType.IDENTIFIER) {
// The first identifier is the class name
if(isExtendActivity && childNode.text ! ="BaseActivity") {
// The class is inherited from the Activity and is not BaseActivity, so the output is incorrect
emit(
childNode.startOffset,
"Activity inherits BaseActivity!".false
)
break
}
break
}
}
}
}
}
}
Copy the code
CustomRuleSetProvider
Class implementsRuleSetProvider
Returns the rule defined above
package com.tc.custom_rules
import com.pinterest.ktlint.core.RuleSet
import com.pinterest.ktlint.core.RuleSetProvider
class CustomRuleSetProvider : RuleSetProvider {
override fun get(a): RuleSet = RuleSet(
"custom-rule-set",
ExtendBaseRule()
)
}
Copy the code
Used with ktLint
- in
app
The modulebuild.gradle
Rely on the module
. dependencies { ... ktlint project(':custom_rules') }Copy the code
- And then terminal execution
./gradlew ktlint
You can see that our custom rules have taken effect
Export the JAR for custom rules
In practice, it is not possible to add a custom rule module every time there is a new project configuration rule, so we need to export the custom rule module as a JAR for the Android project to reference.
You can do this on top of the custom rule module you just created
./gradlew :custom_rules:build
Copy the code
Or you can just execute the custom module as a separate Kotlin project
./gradlew build
Copy the code
You can see the built JAR in build->lib, after which you can publish it to the Maven repository.
conclusion
Having shown you how to implement custom rules, this series is almost complete with team static code specification practices based on KtLint and Gitlab CI/CD. If my article is helpful or inspiring to you, please give me a thumbs-up 👍🏻 and support me. If there is a mistake, welcome to the leaders to correct, but also welcome to discuss, thank you.
Reference documentation
Gradle Reference Document Writing your First KtLint rule — Niklas Baudy IDEA Program Structure Interface (PSI) official reference document Custom rule example code Github