Standard-library Rule Set
Rule Set ID: standard-library
The Standard Library (stdlib) ruleset provides rules that assert the correct usage of the classes in the stdlib.
AlsoCouldBeApply
Detects when an also block contains only it-started expressions.
By refactoring the also block to an apply block makes it so that all its can be removed
thus making the block more concise and easier to read.
Active by default: No
Noncompliant Code:
Buzz().also {
it.init()
it.block()
}
Compliant Code:
Buzz().apply {
init()
block()
}
// Also compliant
fun foo(a: Int): Int {
return a.also { println(it) }
}
ArrayPrimitive
Using Array<Primitive> leads to implicit boxing and performance hit. Prefer using Kotlin specialized Array
Instances.
As stated in the Kotlin documentation Kotlin has
specialized arrays to represent primitive types without boxing overhead, such as IntArray, ByteArray and so on.
Active by default: Yes - Since v1.2.0
Requires Type Resolution
Noncompliant Code:
fun function(array: Array<Int>) { }
fun returningFunction(): Array<Double> { }
Compliant Code:
fun function(array: IntArray) { }
fun returningFunction(): DoubleArray { }
CharArrayToStringCall
Reports CharArray.toString() calls that do not return the expected result.
Active by default: Yes - Since v2.0.0
Requires Type Resolution
Noncompliant Code:
val s = ""
val charArray = "hello😅".toCharArray()
println("$s$charArray") // [C@4f023edb
println(charArray.toString()) // [C@4f023edb
println(s + charArray) // [C@4f023edb
Compliant Code:
println("$s${charArray.concatToString()}") // hello😅
println(charArray.concatToString()) // hello😅
println(s + charArray.concatToString()) // hello😅
CouldBeSequence
Long chains of collection operations will have a performance penalty due to a new list being created for each call. Consider using sequences instead. Read more about this in the documentation
Active by default: No
Requires Type Resolution
Configuration options:
-
allowedOperations(default:2)The maximum number of allowed chained collection operations.
Noncompliant Code:
listOf(1, 2, 3, 4).map { it*2 }.filter { it < 4 }.map { it*it }
Compliant Code:
listOf(1, 2, 3, 4).asSequence().map { it*2 }.filter { it < 4 }.map { it*it }.toList()
listOf(1, 2, 3, 4).map { it*2 }
DontDowncastCollectionTypes
Down-casting immutable types from kotlin.collections should be discouraged.
The result of the downcast is platform specific and can lead to unexpected crashes.
Prefer to use instead the toMutable<Type>() functions.
Active by default: No
Requires Type Resolution
Noncompliant Code:
val list : List<Int> = getAList()
if (list is MutableList) {
list.add(42)
}
(list as MutableList).add(42)
Compliant Code:
val list : List<Int> = getAList()
list.toMutableList().add(42)
DoubleMutabilityForCollection
Using var when declaring a mutable collection or value holder leads to double mutability.
Consider instead declaring your variable with val or switching your declaration to use an
immutable type.
By default, the rule triggers on standard mutable collections, however it can be configured
to trigger on other types of mutable value types, such as MutableState from Jetpack
Compose.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Aliases: DoubleMutability
Configuration options:
-
mutableTypes(default:['kotlin.collections.MutableList', 'kotlin.collections.MutableMap', 'kotlin.collections.MutableSet', 'java.util.ArrayList', 'java.util.LinkedHashSet', 'java.util.HashSet', 'java.util.LinkedHashMap', 'java.util.HashMap'])Define a list of mutable types to trigger on when defined with
var.
Noncompliant Code:
var myList = mutableListOf(1,2,3)
var mySet = mutableSetOf(1,2,3)
var myMap = mutableMapOf("answer" to 42)
Compliant Code:
// Use val
val myList = mutableListOf(1,2,3)
val mySet = mutableSetOf(1,2,3)
val myMap = mutableMapOf("answer" to 42)
// Use immutable types
var myList = listOf(1,2,3)
var mySet = setOf(1,2,3)
var myMap = mapOf("answer" to 42)
ForEachOnRange
Using the forEach method on ranges has a heavy performance cost. Prefer using simple for loops.
Benchmarks have shown that using forEach on a range can have a huge performance cost in comparison to simple for loops. Hence, in most contexts, a simple for loop should be used instead. See more details here: Exploring Kotlin Hidden Costs - Part 1 Exploring Kotlin Hidden Costs - Part 2 Exploring Kotlin Hidden Costs - Part 3
To solve this code smell, the forEach usage should be replaced by a for loop.
Active by default: Yes - Since v1.0.0
Noncompliant Code:
(1..10).forEach {
println(it)
}
(1 until 10).forEach {
println(it)
}
(10 downTo 1).forEach {
println(it)
}
Compliant Code:
for (i in 1..10) {
println(i)
}
IteratorHasNextCallsNextMethod
Verifies implementations of the Iterator interface. The hasNext() method of an Iterator implementation should not have any side effects. This rule reports implementations that call the next() method of the Iterator inside the hasNext() method.
Active by default: Yes - Since v1.2.0
Noncompliant Code:
class MyIterator : Iterator<String> {
override fun hasNext(): Boolean {
return next() != null
}
}
IteratorNotThrowingNoSuchElementException
Reports implementations of the Iterator interface which do not throw a NoSuchElementException in the
implementation of the next() method. When there are no more elements to return an Iterator should throw a
NoSuchElementException.
See: https://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html#next()
Active by default: Yes - Since v1.2.0
Noncompliant Code:
class MyIterator : Iterator<String> {
override fun next(): String {
return ""
}
}
Compliant Code:
class MyIterator : Iterator<String> {
override fun next(): String {
if (!this.hasNext()) {
throw NoSuchElementException()
}
// ...
}
}
MapGetWithNotNullAssertionOperator
Reports calls of the map access methods map[] or map.get() with a not-null assertion operator !!.
This may result in a NullPointerException.
Preferred access methods are map[] without !!, map.getValue(), map.getOrDefault() or map.getOrElse().
Based on an IntelliJ IDEA inspection MapGetWithNotNullAssertionOperatorInspection.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
val map = emptyMap<String, String>()
map["key"]!!
val map = emptyMap<String, String>()
map.get("key")!!
Compliant Code:
val map = emptyMap<String, String>()
map["key"]
val map = emptyMap<String, String>()
map.getValue("key")
val map = emptyMap<String, String>()
map.getOrDefault("key", "")
val map = emptyMap<String, String>()
map.getOrElse("key", { "" })
MissingUseCall
Prefer using the use function with Closeable or AutoCloseable. As use function ensures proper closure of
Closable. It also properly handles exceptions if raised while closing the resource
Active by default: No
Requires Type Resolution
Configuration options:
-
ignoreClass(default:['java.io.ByteArrayInputStream', 'java.io.ByteArrayOutputStream'])List of fully qualified class names that should be excluded from this rule (treated as not-closable).
Noncompliant Code:
val myCloseable = MyCloseable()
// do stuff with myCloseable
MyClosable().doStuff()
functionThatReturnsClosable().doStuff()
Compliant Code:
MyCloseable().use {
// do stuff with myCloseable
}
MyClosable().use { it.doStuff() }
functionThatReturnsClosable().use { it.doStuff() }
MultilineRawStringIndentation
This rule ensures that raw strings have a consistent indentation.
The content of a multi line raw string should have the same indentation as the enclosing expression plus the
configured indentSize. The closing triple-quotes (""") must have the same indentation as the enclosing expression.
Warning: Rule MultilineRawStringIndentation overlaps with StringTemplateIndent from the ktlint rule set.
Active by default: No
Configuration options:
-
indentSize(default:4)indentation size
-
trimmingMethods(default:['trimIndent', 'trimMargin'])allows to provide a list of multiline string trimming methods
Noncompliant Code:
val a = """
Hello World!
How are you?
""".trimMargin()
val a = """
Hello World!
How are you?
""".trimMargin()
Compliant Code:
val a = """
Hello World!
How are you?
""".trimMargin()
val a = """
Hello World!
How are you?
""".trimMargin()
NestedScopeFunctions
Although the scope functions are a way of making the code more concise, avoid overusing them: it can decrease your code readability and lead to errors. Avoid nesting scope functions and be careful when chaining them: it's easy to get confused about the current context object and the value of this or it.
Active by default: No
Requires Type Resolution
Configuration options:
-
allowedDepth(default:1)The maximum allowed depth for nested scope functions.
-
functions(default:['kotlin.apply', 'kotlin.run', 'kotlin.with', 'kotlin.let', 'kotlin.also'])Set of scope function names which add complexity. Function names have to be fully qualified. For example 'kotlin.apply'.
Noncompliant Code:
// Try to figure out, what changed, without knowing the details
first.apply {
second.apply {
b = a
c = b
}
}
Compliant Code:
// 'a' is a property of current class
// 'b' is a property of class 'first'
// 'c' is a property of class 'second'
first.b = this.a
second.c = first.b
RedundantHigherOrderMapUsage
Redundant maps add complexity to the code and accomplish nothing. They should be removed or replaced with the proper operator.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
fun foo(list: List<Int>): List<Int> {
return list
.filter { it > 5 }
.map { it }
}
fun bar(list: List<Int>): List<Int> {
return list
.filter { it > 5 }
.map {
doSomething(it)
it
}
}
fun baz(set: Set<Int>): List<Int> {
return set.map { it }
}
Compliant Code:
fun foo(list: List<Int>): List<Int> {
return list
.filter { it > 5 }
}
fun bar(list: List<Int>): List<Int> {
return list
.filter { it > 5 }
.onEach {
doSomething(it)
}
}
fun baz(set: Set<Int>): List<Int> {
return set.toList()
}
ReplaceSafeCallChainWithRun
Chains of safe calls on non-nullable types are redundant and can be removed by enclosing the redundant safe calls in
a run {} block. This improves code coverage and reduces cyclomatic complexity as redundant null checks are removed.
This rule only checks from the end of a chain and works backwards, so it won't recommend inserting run blocks in the middle of a safe call chain as that is likely to make the code more difficult to understand.
The rule will check for every opportunity to replace a safe call when it sits at the end of a chain, even if there's only one, as that will still improve code coverage and reduce cyclomatic complexity.
Active by default: No
Requires Type Resolution
Noncompliant Code:
val x = System.getenv()
?.getValue("HOME")
?.toLowerCase()
?.split("/") ?: emptyList()
Compliant Code:
val x = getenv()?.run {
getValue("HOME")
.toLowerCase()
.split("/")
} ?: emptyList()
TrimMultilineRawString
All the Raw strings that have more than one line should be followed by trimMargin() or trimIndent().
Active by default: No
Configuration options:
-
trimmingMethods(default:['trimIndent', 'trimMargin'])allows to provide a list of multiline string trimming methods
Noncompliant Code:
"""
Hello World!
How are you?
"""
Compliant Code:
"""
| Hello World!
| How are you?
""".trimMargin()
"""
Hello World!
How are you?
""".trimIndent()
"""Hello World! How are you?"""
UnnecessaryAny
Turn on this rule to flag usage of any which can either be replaced with simple contains call
or can removed entirely to reduce visual complexity.
Active by default: No
Requires Type Resolution
Noncompliant Code:
val a = 1
list.any { it == a }
Compliant Code:
val a = 1
list.contains(a)
UnnecessaryApply
apply expressions are used frequently, but sometimes their usage should be replaced with
an ordinary method/extension function call to reduce visual complexity
Active by default: Yes - Since v1.16.0
Requires Type Resolution
Noncompliant Code:
config.apply { version = "1.2" } // can be replaced with `config.version = "1.2"`
config?.apply { environment = "test" } // can be replaced with `config?.environment = "test"`
config?.apply { println(version) } // `apply` can be replaced by `let`
Compliant Code:
config.apply {
version = "1.2"
environment = "test"
}
UnnecessaryFilter
Unnecessary filters add complexity to the code and accomplish nothing. They should be removed.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
val x = listOf(1, 2, 3)
.filter { it > 1 }
.count()
val x = listOf(1, 2, 3)
.filter { it > 1 }
.isEmpty()
Compliant Code:
val x = listOf(1, 2, 3)
.count { it > 2 }
val x = listOf(1, 2, 3)
.none { it > 1 }
UnnecessaryLet
let expressions are used extensively in our code for null-checking and chaining functions,
but sometimes their usage should be replaced with an ordinary method/extension function call
to reduce visual complexity.
Active by default: No
Requires Type Resolution
Noncompliant Code:
a.let { print(it) } // can be replaced with `print(a)`
a.let { it.plus(1) } // can be replaced with `a.plus(1)`
a?.let { it.plus(1) } // can be replaced with `a?.plus(1)`
a?.let { that -> that.plus(1) }?.let { it.plus(1) } // can be replaced with `a?.plus(1)?.plus(1)`
a.let { 1.plus(1) } // can be replaced with `1.plus(1)`
a?.let { 1.plus(1) } // can be replaced with `if (a != null) 1.plus(1)`
Compliant Code:
a?.let { print(it) }
a?.let { 1.plus(it) } ?.let { msg -> print(msg) }
a?.let { it.plus(it) }
val b = a?.let { 1.plus(1) }
UnnecessaryReversed
If a sort operation followed by a reverse operation or vise versa should be avoided, and both statements should be replaced by single equivalent sort operation.
Active by default: No
Requires Type Resolution
Noncompliant Code:
listOf(1,2)
.sorted()
.asReversed()
Compliant Code:
listOf(1,2)
.sortedDescending()
UseAnyOrNoneInsteadOfFind
Turn on this rule to flag find calls for null check that can be replaced with a any or none call.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
listOf(1, 2, 3).find { it == 4 } != null
listOf(1, 2, 3).find { it == 4 } == null
Compliant Code:
listOf(1, 2, 3).any { it == 4 }
listOf(1, 2, 3).none { it == 4 }
UseCheckNotNull
Turn on this rule to flag check calls for not-null check that can be replaced with a checkNotNull call.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
check(x != null)
Compliant Code:
checkNotNull(x)
UseCheckOrError
Kotlin provides a concise way to check invariants as well as pre- and post-conditions. Prefer them instead of manually throwing an IllegalStateException.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
if (value == null) throw IllegalStateException("value should not be null")
if (value < 0) throw IllegalStateException("value is $value but should be at least 0")
when(a) {
1 -> doSomething()
else -> throw IllegalStateException("Unexpected value")
}
Compliant Code:
checkNotNull(value) { "value should not be null" }
check(value >= 0) { "value is $value but should be at least 0" }
when(a) {
1 -> doSomething()
else -> error("Unexpected value")
}
UseEmptyCounterpart
Instantiation of an object's "empty" state should use the object's "empty" initializer for clarity purposes.
Active by default: Yes - Since v2.0.0
Requires Type Resolution
Noncompliant Code:
arrayOf()
listOf() // or listOfNotNull()
mapOf()
sequenceOf()
setOf()
Compliant Code:
emptyArray()
emptyList()
emptyMap()
emptySequence()
emptySet()
UseIfEmptyOrIfBlank
This rule detects isEmpty or isBlank calls to assign a default value. They can be replaced with ifEmpty or
ifBlank calls.
Active by default: No
Requires Type Resolution
Noncompliant Code:
fun test(list: List<Int>, s: String) {
val a = if (list.isEmpty()) listOf(1) else list
val b = if (list.isNotEmpty()) list else listOf(2)
val c = if (s.isBlank()) "foo" else s
val d = if (s.isNotBlank()) s else "bar"
}
Compliant Code:
fun test(list: List<Int>, s: String) {
val a = list.ifEmpty { listOf(1) }
val b = list.ifEmpty { listOf(2) }
val c = s.ifBlank { "foo" }
val d = s.ifBlank { "bar" }
}
UseIsNullOrEmpty
This rule detects null or empty checks that can be replaced with isNullOrEmpty() call.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
fun foo(x: List<Int>?) {
if (x == null || x.isEmpty()) return
}
fun bar(x: List<Int>?) {
if (x == null || x.count() == 0) return
}
fun baz(x: List<Int>?) {
if (x == null || x.size == 0) return
}
Compliant Code:
if (x.isNullOrEmpty()) return
UseLet
if expressions that either check for not-null and return null in the false case or check for null and returns
null in the truthy case are better represented as ?.let {} blocks.
Active by default: No
Noncompliant Code:
if (x != null) { transform(x) } else null
if (x == null) null else y
Compliant Code:
x?.let { transform(it) }
x?.let { y }
UseOrEmpty
This rule detects ?: emptyList() that can be replaced with orEmpty() call.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
fun test(x: List<Int>?, s: String?) {
val a = x ?: emptyList()
val b = s ?: ""
}
Compliant Code:
fun test(x: List<Int>?, s: String?) {
val a = x.orEmpty()
val b = s.orEmpty()
}
UseRequire
Kotlin provides a much more concise way to check preconditions than to manually throw an IllegalArgumentException.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
if (value == null) throw IllegalArgumentException("value should not be null")
if (value < 0) throw IllegalArgumentException("value is $value but should be at least 0")
Compliant Code:
requireNotNull(value) { "value should not be null" }
require(value >= 0) { "value is $value but should be at least 0" }
UseRequireNotNull
Turn on this rule to flag require calls for not-null check that can be replaced with a requireNotNull call.
Active by default: Yes - Since v1.21.0
Requires Type Resolution
Noncompliant Code:
require(x != null)
Compliant Code:
requireNotNull(x)
UseSumOfInsteadOfFlatMapSize
Turn on this rule to flag flatMap and size/count calls that can be replaced with a sumOf call.
Active by default: No
Requires Type Resolution
Noncompliant Code:
class Foo(val foo: List<Int>)
list.flatMap { it.foo }.size
list.flatMap { it.foo }.count()
list.flatMap { it.foo }.count { it > 2 }
listOf(listOf(1), listOf(2, 3)).flatten().size
Compliant Code:
list.sumOf { it.foo.size }
list.sumOf { it.foo.count() }
list.sumOf { it.foo.count { foo -> foo > 2 } }
listOf(listOf(1), listOf(2, 3)).sumOf { it.size }
UselessCallOnNotNull
The Kotlin stdlib provides some functions that are designed to operate on references that may be null. These functions can also be called on non-nullable references or on collections or sequences that are known to be empty - the calls are redundant in this case and can be removed or should be changed to a call that does not check whether the value is null or not.
Active by default: Yes - Since v1.2.0
Requires Type Resolution
Noncompliant Code:
val testList = listOf("string").orEmpty()
val testList2 = listOf("string").orEmpty().map { _ }
val testList3 = listOfNotNull("string")
val testString = ""?.isNullOrBlank()
Compliant Code:
val testList = listOf("string")
val testList2 = listOf("string").map { }
val testList3 = listOf("string")
val testString = ""?.isBlank()