maintainability-agent docs
Languages

Kotlin

Functions, constructors, init blocks, and the types — class, interface, object, and the data, sealed, enum, annotation, value and inner forms, each of which is a modifier on class or object rather than a keyword of its own. Types and objects are descended into, because that is where members live; a function body is stepped over.

Kotlin is keyword-led and braced, so the walk is the one C, C++, C#, Java and Swift share. Three things make its reading its own.

An extension function writes its own receiver. fun Widget.draw() is Widget.draw in the report because it is Widget.draw in the source. This is the one place Kotlin is easier than Swift, which spells the same idea extension Widget { func draw() } and needs a second pass to carry the type name onto the member. A work order saying "shorten draw" against a tree with eleven of them is not a bounded instruction, and Kotlin hands over the qualification for free.

An expression body has no brace. fun area() = w * h is idiomatic Kotlin rather than a corner, and two shared mechanisms reach the wrong answer on it. The end-finder bounds a body by its braces and falls back to indentation, and that fallback walks to the closing } of the enclosing class — so a one-line function came back two lines long, with the brace that ends its container counted as its last line. And the shared bodyless check looks for =>, the arrow JavaScript and C# use for an expression member; Kotlin writes =, so every expression-bodied function in the tree would have been dropped as a signature with nothing behind it. Kotlin therefore supplies its own end-finder and answers the bodyless question itself.

A type with no body is still a type. data class Point(val x: Int) is a complete, useful class and mints a declaration. fun draw() inside an interface is a requirement and mints nothing, as in C, C++, C# and Swift. The bodyless rule is about functions, not about declarations in general — which is where C++ and Swift, whose bodyless types are forward declarations, are not a guide.

Everything it misses, it misses in the safe direction:

What counts as a decision

when is counted at its arms, not at its header — the rule shared with switch in Go, PHP, Ruby, Python, Swift and Fortran, and bought by the Fortran select case defect where an N-way branch scored 1. Kotlin spells an arm ->, which is also how it spells a lambda's parameter list and a function type, so both are consumed before the arms are counted. The cost is a when written entirely on one line, whose first arm is eaten with the brace — under-reporting, the direction this project errs in. The else arm is not counted, as Go declines default and Rust declines _ =>.

?: decides and ?. does not. Elvis is Kotlin's ??: both halves are written, which is the test the C family already applies. A safe call yields null and carries on down the same path, and is the optional chaining C, C#, TypeScript and Java all decline to count. Rust's ? is counted here and the difference is real rather than a taste — Rust's returns early from the function.

There is no ternary. if is an expression in Kotlin, so the C-family ?…: alternative is absent rather than merely unused. With it, fun find(id: Int?, name: String?) would score two: D115's defect in a sixth spelling, after C#, TypeScript, PHP, Java and Swift.

do is not counted. do { … } while (cond) is one loop with one condition and the while carries it, as PHP's do and Swift's repeat already do.

Second opinion

Checked construct by construct against lizard 1.24.0, which reads Kotlin although the upstream analyzer inventory never said so — the same stale-row problem that left Fortran without a metric emitter for two releases. The two readings agree on if, &&, for, while, elvis, and on a when with an else.

They differ in one place, declared in tests/test_grammar_constructs.py: lizard counts a when's arms minus one, treating else as an arm. That is right for an exhaustive when and one short for a statement when without one, where falling through to the next line is a path that exists and is not counted.

Analyzer coverage

Kotlin has it. lizard reads it for complexity and jscpd for duplication, both in the shipped pool, so a Kotlin repository gets analyzer-primary evidence rather than depending on the built-in scanner alone.

Anchored

Kotlin is in the reference corpus since 4.0.0: fifteen mature Kotlin repositories, selected by the same query as every other language, so a Kotlin grade is no longer provisional. The scanner shipped ahead of the corpus in 3.8.0 and anchored at the next recalibration, as the corpus policy promised. Of the fifteen, twelve grade B and three C.