ktlint adalah linter dan formatter yang menerapkan Kotlin Coding Conventions resmi dari JetBrains. Berbeda dengan detekt yang fokus pada code smell, ktlint fokus pada style: indentasi, semicolon, panjang baris, wildcard import, dan sebagainya. ktlint tidak memerlukan konfigurasi — aturan default sudah mengikuti konvensi resmi. Pada chapter ini kita akan membangun mesin ktlint sederhana yang mendemonstrasikan ktlintCheck dan ktlintFormat.
Konvensi yang Diterapkan ktlint #
Beberapa aturan utama ktlint:
| Aturan | Deskripsi | Auto-fix |
|---|---|---|
| no-semicolons | Hapus semicolon di akhir baris | Ya |
| indent | 4 spasi, bukan tab | Ya |
| max-line-length | Default 100 (dapat diubah via .editorconfig) | Tidak |
| no-wildcard-imports | Larang import x.* | Tidak |
| colon-spacing | Spasi setelah : pada return type | Ya |
| trailing-comma | Trailing comma pada argumen multi-baris | Ya |
Demo ktlintCheck dan ktlintFormat #
Berikut implementasi ktlint sederhana yang mendukung dua mode: check (lapor pelanggaran) dan format (terapkan auto-fix).
// Demo ktlint: linter + formatter untuk Kotlin Coding Conventions.
// Simulasi aturan formatting (.editorconfig) tanpa dependensi eksternal.
data class PelanggaranKtlint(
val rule: String,
val baris: Int,
val kolom: Int,
val pesan: String,
val autoFixable: Boolean
)
class LaporanKtlint {
private val temuan = mutableListOf<PelanggaranKtlint>()
fun tambah(t: PelanggaranKtlint) = temuan.add(t)
fun autofixable() = temuan.count { it.autoFixable }
fun manual() = temuan.count { !it.autoFixable }
fun total() = temuan.size
fun cetak() {
println("=== ktlint report ===")
println("Total: ${total()} (${autofixable()} auto-fixable, ${manual()} manual)")
println()
temuan.forEach {
val tag = if (it.autoFixable) "[AUTOFIX]" else "[MANUAL]"
println(" $tag ${it.rule} di baris ${it.baris}:${it.kolom} -> ${it.pesan}")
}
}
}
class KtlintChecker {
fun periksa(sumber: String): LaporanKtlint {
val laporan = LaporanKtlint()
sumber.lines().forEachIndexed { i, baris ->
val no = i + 1
// Aturan: no-semicolons (semicolon di akhir baris)
if (baris.trimEnd().endsWith(";")) {
laporan.tambah(PelanggaranKtlint("no-semicolons", no, baris.length,
"Hapus semicolon di akhir baris", autoFixable = true))
}
// Aturan: indent 4 spasi (bukan tab)
if (baris.startsWith("\t")) {
laporan.tambah(PelanggaranKtlint("indent", no, 1,
"Gunakan 4 spasi, bukan tab", autoFixable = true))
}
// Aturan: max-line-length (default 100/120)
if (baris.length > 100) {
laporan.tambah(PelanggaranKtlint("max-line-length", no, 100,
"Baris lebih dari 100 karakter (${baris.length})", autoFixable = false))
}
// Aturan: no-wildcard-imports
if (Regex("""^import\s+\w+(\.\w+)*\.\*""").containsMatchIn(baris)) {
laporan.tambah(PelanggaranKtlint("no-wildcard-imports", no, 1,
"Hindari wildcard import", autoFixable = false))
}
// Aturan: spacing-around-colon untuk deklarasi tipe
val m = Regex("""fun\s+\w+\s*\([^)]*\)\s*:(?!\s)""").find(baris)
if (m != null) {
laporan.tambah(PelanggaranKtlint("colon-spacing", no, m.range.first + 1,
"Tambahkan spasi setelah titik dua return type", autoFixable = true))
}
}
return laporan
}
fun format(sumber: String): String {
// ktlintFormat: terapkan auto-fix saja
return sumber.lines().joinToString("\n") { baris ->
var hasil = baris
hasil = hasil.replace("\t", " ".repeat(1)) // tab -> 4 spasi (sederhana)
hasil = hasil.trimEnd().removeSuffix(";")
hasil
}
}
}
fun main() {
println("=== Demo ktlint (Check) ===")
val kodeKotor = """
import kotlin.collections.*
fun hitung(a:Int, b: Int):Int {
val total = a + b;
return total;
}
fun namaSangatPanjangYangMelebihiBatasStandarYaituSeratusKarakterDenganTambahanKataLagi(x: Int): Int = x
""".trimIndent()
val checker = KtlintChecker()
val laporan = checker.periksa(kodeKotor)
laporan.cetak()
println()
println("=== Setelah ktlintFormat (autofix) ===")
val bersih = checker.format(kodeKotor)
println(bersih)
println()
println("=== Re-check setelah format ===")
val laporan2 = checker.periksa(bersih)
laporan2.cetak()
}Output yang Dihasilkan:
=== Demo ktlint (Check) ===
=== ktlint report ===
Total: 7 (5 auto-fixable, 2 manual)
[MANUAL] no-wildcard-imports di baris 1:1 -> Hindari wildcard import
[AUTOFIX] colon-spacing di baris 3:1 -> Tambahkan spasi setelah titik dua return type
[AUTOFIX] no-semicolons di baris 4:19 -> Hapus semicolon di akhir baris
[AUTOFIX] indent di baris 4:1 -> Gunakan 4 spasi, bukan tab
[AUTOFIX] no-semicolons di baris 5:14 -> Hapus semicolon di akhir baris
[AUTOFIX] indent di baris 5:1 -> Gunakan 4 spasi, bukan tab
[MANUAL] max-line-length di baris 8:100 -> Baris lebih dari 100 karakter (104)
=== Setelah ktlintFormat (autofix) ===
import kotlin.collections.*
fun hitung(a:Int, b: Int):Int {
val total = a + b
return total
}
fun namaSangatPanjangYangMelebihiBatasStandarYaituSeratusKarakterDenganTambahanKataLagi(x: Int): Int = x
=== Re-check setelah format ===
=== ktlint report ===
Total: 3 (1 auto-fixable, 2 manual)
[MANUAL] no-wildcard-imports di baris 1:1 -> Hindari wildcard import
[AUTOFIX] colon-spacing di baris 3:1 -> Tambahkan spasi setelah titik dua return type
[MANUAL] max-line-length di baris 8:100 -> Baris lebih dari 100 karakter (104)Bedah Output #
Output demo menunjukkan dua siklus check-format-check:
- Check pertama: 7 pelanggaran (5 auto-fixable, 2 manual). Auto-fixable mencakup semicolon dan tab; manual mencakup wildcard import dan baris terlalu panjang.
- Format: auto-fix diterapkan. Tab diubah menjadi 4 spasi; semicolon dihapus.
- Check kedua: tersisa 3 pelanggaran yang manual (wildcard import dan baris panjang).
Pelanggaran manual harus diperbaiki dengan refactor: hapus wildcard import dan pecah baris panjang menjadi beberapa baris.
Integrasi Gradle #
Plugin org.jlleitschuh.gradle.ktlint menyediakan task ktlintCheck dan ktlintFormat:
plugins {
id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
}
ktlint {
version.set("1.2.1")
android.set(false)
outputToConsole.set(true)
ignoreFailures.set(false) // build gagal bila ada pelanggaran
}.editorconfig #
Beberapa aturan ktlint dapat dikonfigurasi via .editorconfig di root project:
root = true
[*.{kt,kts}]
max_line_length = 120
indent_size = 4
insert_final_newline = true
ktlint_standard_no-wildcard-imports = disabled
ktlint_standard_function-signature = enabledReferensi Lanjutan #
- ktlint — repository resmi ktlint untuk formatting dan style Kotlin
Penutup #
ktlint dan detekt saling melengkapi: ktlint untuk style, detekt untuk code smell. Banyak tim memakai keduanya bersamaan. Pada chapter berikutnya kita akan membahas compiler warnings — peringatan yang diberikan compiler Kotlin sendiri, dan cara terbaik menanganinya.