detekt in action

One of the disadvantage of Kotlin over Java is that there aren’t many code analysis tools around that work for Kotlin. I recently came across detekt, a static code analysis tool for Kotlin, and found it very helpful. It has all sorts of rules that cover problems in styling, code complexity, potential bugs, performance issues and more. The repository itself has very good documentation on its features, so in this post I’m just going to give a detailed walk through of how you can create and use custom rules for your Kotlin project.

Clone the repo

Custom rules depend on other parts of the detekt repo. It also contains a very nice sample custom ruleset which I’ll be walking you through in this post, so it’s nice to have the repo ready on your laptop:

git clone https://github.com/arturbosch/detekt.git

How to write a rule

Let’s take a look at the TooManyFunctions rule in detekt/detekt-sample-ruleset :

Detekt operates on the abstract syntax tree provided by the Kotlin compiler, which means you can override the visitXxx() methods to let them do your bidding. The call to super.visitXxx() would traverse the child nodes of Xxx in the AST. You can also do this by implementing your own DetektVisitor . Take the implementation of NestedBlockDepth rule, for example:

In this use case since each function needs to count its own depth, it’s much cleaner to have a Visitor hold the count rather than having a global counter like TooManyFunctions does. Notice that you can also make your rule configurable by simply adding config: Config as a parameter in its constructor.

Testing your rule

You can use either Spek or JUnit to test rules. The example tests are pretty straight-forward:

I just want to highlight two things:

If you use Spek, the superclass SubjectSpek and the subject closure need to know which rule you’re testing for.

and the closure need to know which rule you’re testing for. In both tests, subject/rule.lint(String) compiles the String you defined and tests against it. There’s also a subject/rule.lint(path: Path) function if you want to pass in the Path to your test file instead. Another extension function that you might find useful is Rule.format(String/Path) . It would format the code you pass in with the detekt formatting rules.

Using your rule

cd detekt/detekt-sample-ruleset/

gradle build

You’ll see two jars appeared in detekt-sample-ruleset/build/libs . The one we want is detekt-sample-ruleset-[version].jar . We can test the rules out on the detekt repo itself. Go to detekt/build.gradle , and at the end of the file you’ll find a detekt block like this:

detekt {

// ...

profile("main") {

input = "$project.projectDir"

filters = '.*/test/.*, .*/resources/.*, .*/build/.*'

config = "$project.projectDir/detekt-cli/src/main/resources/default-detekt-config.yml"

baseline = "$project.projectDir/reports/baseline.xml"

}

// ...

}

Add a line ruleSets = “$projectDir/detekt-sample-ruleset/build/libs/detekt-sample-ruleset-[version].jar” in the profile(“main”) block. This tells detekt it should include the rules in the sample jar. Now run:

// In detekt/

gradle detektCheck

You should see that the build failed because of something like this:

Ruleset: sample

TooManyFunctions - [Configurations.kt] at detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/Configurations.kt:1:1

That’s it!