Linting Kotlin Projects
This page will discuss common topics around enforcing the code quality of Kotlin codebases using the Mill build tool
Linting with Detekt
package build
import mill._
import kotlinlib.KotlinModule
import kotlinlib.detekt.DetektModule
object `package` extends RootModule with KotlinModule with DetektModule {
def kotlinVersion = "1.9.24"
}
This example shows how to use the Detekt
static code analyzer for linting a KotlinModule
, by mixing in the trait
DetektModule
and calling the detekt
task:
package example
import java.util.Random
fun main() {
if (Random.nextBoolean()) {
if (Random.nextBoolean()) {
if (Random.nextBoolean()) {
if (Random.nextBoolean()) {
if (Random.nextBoolean()) {
if (Random.nextBoolean()) {
println("Hello World")
}
}
}
}
}
}
}
> ./mill detekt
error: ...Foo.kt:5:5: Function main is nested too deeply. [NestedBlockDepth]
> ./mill detekt --check false
...Foo.kt:5:5: Function main is nested too deeply. [NestedBlockDepth]
Linting with KtLint
package build
import mill._
import mill.util.Jvm
import mill.api.Loose
import kotlinlib.KotlinModule
import kotlinlib.ktlint.KtlintModule
object `package` extends RootModule with KotlinModule with KtlintModule {
def kotlinVersion = "1.9.24"
def ktlintConfig = Some(PathRef(T.workspace / ".editorconfig"))
}
This example shows how to use the KtLint
linter on a KotlinModule
, by mixing in the trait KtlintModule
and calling the
ktlint
task. ktlint
also supports autoformatting to automatically resolve
code formatting violations, via the --format
flag shown below:
> ./mill ktlint --check true # run ktlint to produce a report, defaults to warning without error
error: ...src/example/FooWrong.kt:6:28: Missing newline before ")" (standard:parameter-list-wrapping)...
...src/example/FooWrong.kt:6:28: Newline expected before closing parenthesis (standard:function-signature)...
...src/example/FooWrong.kt:6:28: Missing trailing comma before ")" (standard:trailing-comma-on-declaration-site)...
> ./mill ktlint --format true
> ./mill ktlint # after fixing the violations, ktlint no longer errors
> ./mill mill.kotlinlib.ktlint.KtlintModule/ # alternatively, use external module to check/format
Autoformatting with KtFmt
package build
import mill._
import mill.util.Jvm
import mill.api.Loose
import kotlinlib.KotlinModule
import kotlinlib.ktfmt.KtfmtModule
object `package` extends RootModule with KotlinModule with KtfmtModule {
def kotlinVersion = "1.9.24"
}
This example demonstrates how to use the KtFmt
autoformatter from Facebook both to enforce and apply formatting to your KotlinModule
source files. You can configure a non-default version of KtFmt by overriding def ktfmtVersion
> ./mill ktfmt --format=false # run ktfmt to produce a list of files which should be formatter
error: ...src/example/FooWrong.kt...
> ./mill ktfmt # running without arguments will format all files
Done formatting ...src/example/FooWrong.kt
> ./mill ktfmt # after fixing the violations, ktfmt no longer prints any file
> ./mill mill.kotlinlib.ktfmt.KtfmtModule/ __.sources # alternatively, use external module to check/format
Code Coverage with Kover
package build
import mill._, kotlinlib._
import kotlinlib.kover.KoverModule
object `package` extends RootModule with KotlinModule with KoverModule {
trait KotestTests extends TestModule.Junit5 {
override def forkArgs: T[Seq[String]] = Task {
super.forkArgs() ++ Seq("-Dkotest.framework.classpath.scanning.autoscan.disable=true")
}
override def ivyDeps = super.ivyDeps() ++ Agg(
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1"
)
}
def kotlinVersion = "1.9.24"
override def koverVersion = "0.8.3"
object test extends KotlinTests with KotestTests with KoverTests
object bar extends KotlinModule with KoverModule {
def kotlinVersion = "1.9.24"
object test extends KotlinTests with KotestTests with KoverTests
}
}
This is a basic Mill build for a single KotlinModule
, enhanced with
Kover module. The root module extends the KoverModule
and
specifies the version of Kover version to use here: 0.8.3
. This
version can be changed if there is a newer one. Now you can call the
Kover tasks to produce coverage reports.
The sub test module extends KoverTests
to transform the
execution of the various testXXX tasks to use Kover and produce
coverage data.
This lets us perform the coverage operations but before that you
must first run the test.
./mill test
then ./mill show kover.htmlReport
and get your
coverage in HTML format.
Also reports for all modules can be collected in a single place by
running ./mill show mill.kotlinlib.kover.Kover/htmlReportAll
.
> ./mill test # Run the tests and produce the coverage data
...
... foo.FooTests kotlin - success started
> ./mill resolve kover._ # List what tasks are available to run from kover
...
kover.htmlReport
...
kover.xmlReport
...
> ./mill show kover.htmlReport
...
...out/kover/htmlReport.dest/kover-report...
> cat out/kover/htmlReport.dest/kover-report/index.html
...
...Kover HTML Report: Overall Coverage Summary...
> ./mill show mill.kotlinlib.kover.Kover/htmlReportAll # collect reports from all modules
...
...out/mill/kotlinlib/kover/Kover/htmlReportAll.dest/kover-report...
> cat out/mill/kotlinlib/kover/Kover/htmlReportAll.dest/kover-report/index.html
...
...Kover HTML Report: Overall Coverage Summary...