/
ReplaceSafeCallChainWithRun.kt
69 lines (61 loc) · 2.69 KB
/
ReplaceSafeCallChainWithRun.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package io.gitlab.arturbosch.detekt.rules.complexity
import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.types.isNullable
/**
* 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.
*
* <noncompliant>
* val x = System.getenv()
* ?.getValue("HOME")
* ?.toLowerCase()
* ?.split("/") ?: emptyList()
* </noncompliant>
*
* <compliant>
* val x = getenv()?.run {
* getValue("HOME")
* .toLowerCase()
* .split("/")
* } ?: emptyList()
* </compliant>
*
*/
@RequiresTypeResolution
class ReplaceSafeCallChainWithRun(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Maintainability,
"Chains of safe calls on non-nullable types can be surrounded with `run {}`.",
Debt.TEN_MINS
)
override fun visitSafeQualifiedExpression(expression: KtSafeQualifiedExpression) {
super.visitSafeQualifiedExpression(expression)
/* We want the last safe qualified expression in the chain, so if there are more in this chain then there's no
need to run checks on this one */
if (expression.parent is KtSafeQualifiedExpression) return
var counter = 0
var receiver = expression.receiverExpression
while (receiver is KtSafeQualifiedExpression) {
if (receiver.getResolvedCall(bindingContext)?.resultingDescriptor?.returnType?.isNullable() == true) break
counter++
receiver = receiver.receiverExpression
}
if (counter >= 1) report(CodeSmell(issue, Entity.from(expression), issue.description))
}
}