ktlint
An anti-bikeshedding Kotlin linter with built-in formatter.
Getting Started

Installation

curl -sSLO https://github.com/shyiko/ktlint/releases/download/0.12.1/ktlint && chmod a+x ktlint
# you can also download ktlint manually from https://github.com/shyiko/ktlint/releases
# another option is "brew install shyiko/ktlint/ktlint"

# verify PGP signature (optional but recommended)
curl -sS https://keybase.io/shyiko/pgp_keys.asc | gpg --import
curl -sSLO https://github.com/shyiko/ktlint/releases/download/0.12.1/ktlint.asc
gpg --verify ktlint.asc

Usage

# check the style of all Kotlin files inside the current dir (recursively)
# (hidden folders will be skipped)
$ ktlint
  src/main/kotlin/Main.kt:10:10: Unused import
  
# check only certain locations (prepend ! to negate the pattern) 
$ ktlint "src/**/*.kt" "!src/**/*Test.kt"

# auto-correct style violations
# (if some errors cannot be fixed automatically they will be printed to stderr) 
$ ktlint -F "src/**/*.kt"

# print style violations grouped by file
$ ktlint --reporter=plain?group_by_file
# print style violations as usual + create report in checkstyle format 
$ ktlint --reporter=plain --reporter=checkstyle,output=ktlint-report-in-checkstyle-format.xml

# install git hook to automatically check files for style violations on commit
$ ktlint --install-git-pre-commit-hook

Integration

Add the following snippet to pom.xml (inside <build><plugins>...</plugins></build>)

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <id>ktlint</id>
            <phase>verify</phase>
            <configuration>
            <target name="ktlint">
                <java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
                    classname="com.github.shyiko.ktlint.Main" classpathref="maven.plugin.classpath">
                    <arg value="src/**/*.kt"/>
                    <!-- to generate report in checkstyle format prepend following args: -->
                    <!-- 
                    <arg value="--reporter=plain"/>
                    <arg value="--reporter=checkstyle,output=${project.build.directory}/ktlint.xml"/>
                    -->
                    <!-- see https://github.com/shyiko/ktlint#usage for more -->    
                </java>
            </target>
            </configuration>
            <goals><goal>run</goal></goals>
        </execution>
        <execution>
            <id>ktlint-format</id>
            <configuration>
            <target name="ktlint">
                <java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
                    classname="com.github.shyiko.ktlint.Main" classpathref="maven.plugin.classpath">
                    <arg value="-F"/>
                    <arg value="src/**/*.kt"/>
                </java>
            </target>
            </configuration>
            <goals><goal>run</goal></goals>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>com.github.shyiko</groupId>
            <artifactId>ktlint</artifactId>
            <version>0.12.1</version>
        </dependency>
        <!-- additional 3rd party ruleset(s) can be specified here -->
    </dependencies>
</plugin>

Usage

# check code style (it's also bound to "mvn verify")
$ mvn antrun:run@ktlint 
  src/main/kotlin/Main.kt:10:10: Unused import
  
# fix code style deviations (runs built-in formatter)
$ mvn antrun:run@ktlint-format

Integration

Add the following snippet to build.gradle

apply plugin: "java"

repositories {
    mavenCentral()
}

configurations {
    ktlint
}

dependencies {
    ktlint "com.github.shyiko:ktlint:0.12.1"
    // additional 3rd party ruleset(s) can be specified here
    // just add them to the classpath (ktlint 'groupId:artifactId:version') and 
    // ktlint will pick them up
}

task ktlint(type: JavaExec, group: "verification") {
    description = "Check Kotlin code style."
    main = "com.github.shyiko.ktlint.Main"
    classpath = configurations.ktlint
    args "src/**/*.kt"
    // to generate report in checkstyle format prepend following args:
    // "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/ktlint.xml"
    // see https://github.com/shyiko/ktlint#usage for more    
}
check.dependsOn ktlint

task ktlintFormat(type: JavaExec, group: "formatting") {
    description = "Fix Kotlin code style deviations."
    main = "com.github.shyiko.ktlint.Main"
    classpath = configurations.ktlint
    args "-F", "src/**/*.kt"
}

Usage

# check code style (it's also bound to "gradle check")
$ gradle ktlint
  src/main/kotlin/Main.kt:10:10: Unused import
  
# fix code style deviations (runs built-in formatter)
$ gradle ktlintFormat

Alternatives

There are few community-driven plugins that you might find interesting:

  • jlleitschuh/ktlint-gradle The very first ktlint gradle plugin.
  • jeremymailen/kotlinter-gradle Gradle plugin featuring incremental build support, file reports, .kt & .kts source support, etc.
  • diffplug/spotless Spotless is not really a plugin, it's a standalone project that happens to have a built-in ktlint support. In addition to linting/formatting kotlin code it allows you to keep license headers, markdown documentation, etc. in check.
Rules
Do.
// recommended
// see https://github.com/shyiko/ktlint/issues/26#issuecomment-275810153                    
data class C(
    val a: Any, 
    val b: Any = 0,
    val c: Any
) {}

// even though there are 13 spaces to the left from "val b" and "val c", 
// this is still OK
data class C(val a: Any, 
             val b: Any = 0,
             val c: Any) {

}
Don't.
fun f(val a: Any,
        val b: Any = 0,
      val c: Any) {}

* Starting from 0.8.0 value of indent_size specified under [*{kt,kts}] section in .editorconfig takes precedence (if any). Official recommendation is to use 4 spaces, though. (see #43 for details)

Do.
val v = ""
println(v)

// semicolons used to separate multiple statements on the same line are OK
// try avoid though
fn({ v -> println(v); v * 10 })
Don't.
val v = "";
println(v);
Do.
import io.vertx.core.Vertx
import com.google.Guice
import com.google.Injector
Don't.
import io.vertx.core.*
import com.google.inject.*
import pkg.UnusedClass
Do.
class A {}
class B {}
Don't.
// this in not a Python and you are not in PEP 8 realm
class A {}


class B {}
Do.
class A {}
class B {}
Don't.
class A {}⋅⋅⋅
class B {}⋅

* If you don't use EditorConfig it's a good time to start.

Do.
fun f() {}
Don't.
fun f(): Unit {}
Do.
class A
interface B
Don't.
class A {}
interface B {}
Do.
val a = "class = ${String::class}"
val b = "not $a"
Don't.
val a = "class = ${String::class.toString()}"
val b = "not ${a}"
Do.
internal abstract class A {
    protected open val v = ""
    internal open suspend fun f(v: Any): Any = ""
    public lateinit var lv: String
    abstract tailrec fun findFixPoint(x: Double = 1.0): Double
}

class B : A() {
    public override val v = ""
    suspend override fun f(v: Any): Any = ""
    tailrec override fun findFixPoint(x: Double): Double
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
}
Don't.
abstract internal class A {
    open protected val v = ""
    open suspend internal fun f(v: Any): Any = ""
    lateinit public var lv: String
    tailrec abstract fun findFixPoint(x: Double = 1.0): Double
}

class B : A() {
    override public val v = ""
    override suspend fun f(v: Any): Any = ""
    override tailrec fun findFixPoint(x: Double): Double
        = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
}
Do.
val short = ""
val long = ""

val v = a - b * c

class A : B, C {}

if (true) {}

@file:JvmName("Main")
class A : B
call(object : C() {})
fun fn(@field:F a: Any, b: Any, c: Any): Any
val v: String = str()

if (ok) { /* .. */ }
fn({ v -> f(v) * g(v) })
emptyList().find { true }!!.hashCode()
find { it.default ?: false }?.phone
Don't.
// multiple spaces after "val long" for vertical alignment
val short = ""
val long  = ""

// no spacing around operators
val v=a-b*c

// no space after the comma
class A : B,C {}

// no spacing after keyword ("if" in this case)
if(true) {}

// incorrect spacing around ":"
@file: JvmName("Main")
class A:B
call(object: C() {})
fun fn(@field: F a:Any, b:Any, c:Any):Any
val v:String = str()

// missing spacing around "{" and before "}"
if (true){/* .. */}
// missing spacing after "{" and before "}"
fn({v -> f(v) * g(v)}!!)
// unnecessary space after "}"
emptyList().find { true } !!.hashCode()
find { it.default ?: false } ?.phone
FAQ

fiber_manual_recordWhy should I use ktlint?

Simplicity.

Spending time on configuration (& maintenance down the road) of hundred-line long style config file(s) is counter-productive. Instead of wasting your energy on something that has no business value - focus on what really matters (not debating whether to use tabs or spaces).

By using ktlint you put the importance of code clarity and community conventions over personal preferences. This makes things easier for people reading your code as well as frees you from having to document & explain what style potential contributor(s) have to follow.

ktlint is a single binary with both linter & formatter included. All you need is to drop it in (no need to get overwhelmed while choosing among dozens of code style options).

fiber_manual_recordCan I have my own rules on top of ktlint?

Absolutely, "no configuration" doesn't mean "no extensibility". You can add your own ruleset(s) to discover potential bugs, check for anti-patterns, etc.

See Creating A Ruleset on GitHub.

fiber_manual_recordHow do I suppress an error?

This is meant primarily as an escape latch for the rare cases when ktlint is not able to produce the correct result (please report any such instances using GitHub Issues).

To disable a specific rule you'll need to turn on the verbose mode (ktlint --verbose ...). At the end of each line you'll see an error code. Use it as an argument for ktlint-disable directive (shown below).

import package.* // ktlint-disable no-wildcard-imports

/* ktlint-disable no-wildcard-imports */
import package.a.*
import package.b.*
/* ktlint-enable no-wildcard-imports */

To disable all checks:

import package.* // ktlint-disable