forked from detekt/detekt
/
MultilineLambdaItParameter.kt
118 lines (111 loc) · 4.15 KB
/
MultilineLambdaItParameter.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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package io.gitlab.arturbosch.detekt.rules.style
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 io.gitlab.arturbosch.detekt.rules.IT_LITERAL
import io.gitlab.arturbosch.detekt.rules.hasImplicitParameterReference
import io.gitlab.arturbosch.detekt.rules.implicitParameter
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.utils.ifEmpty
/**
* Lambda expressions are very useful in a lot of cases, and they often include very small chunks of
* code using only one parameter. In this cases Kotlin can supply the implicit `it` parameter
* to make code more concise. However, when you are dealing with lambdas that contain multiple statements,
* you might end up with code that is hard to read if you don't specify a readable, descriptive parameter name
* explicitly.
*
* <noncompliant>
* val digits = 1234.let {
* println(it)
* listOf(it)
* }
*
* val digits = 1234.let { it ->
* println(it)
* listOf(it)
* }
*
* val flat = listOf(listOf(1), listOf(2)).mapIndexed { index, it ->
* println(it)
* it + index
* }
* </noncompliant>
*
* <compliant>
* val digits = 1234.let { explicitParameterName ->
* println(explicitParameterName)
* listOf(explicitParameterName)
* }
*
* val lambda = { item: Int, that: String ->
* println(item)
* item.toString() + that
* }
*
* val digits = 1234.let { listOf(it) }
* val digits = 1234.let {
* listOf(it)
* }
* val digits = 1234.let { it -> listOf(it) }
* val digits = 1234.let { it ->
* listOf(it)
* }
* val digits = 1234.let { explicit -> listOf(explicit) }
* val digits = 1234.let { explicit ->
* listOf(explicit)
* }
* </compliant>
*
*/
@RequiresTypeResolution
class MultilineLambdaItParameter(val config: Config) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Style,
"Multiline lambdas should not use `it` as a parameter name.",
Debt.FIVE_MINS
)
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
super.visitLambdaExpression(lambdaExpression)
val statements = lambdaExpression.bodyExpression?.statements.orEmpty().ifEmpty { return }
val single = statements.singleOrNull()
if (single != null && (single.hasNoLineBreak() || single.hasNoStatements())) return
val parameterNames = lambdaExpression.valueParameters.map { it.name }
// Explicit `it`
if (IT_LITERAL in parameterNames) {
report(
CodeSmell(
issue,
Entity.from(lambdaExpression),
"The parameter name in a multiline lambda should not be an explicit `it`. " +
"Consider giving your parameter a readable and descriptive name."
)
)
} else if (parameterNames.isEmpty()) { // Implicit `it`
val implicitParameter = lambdaExpression.implicitParameter(bindingContext)
if (implicitParameter != null &&
lambdaExpression.hasImplicitParameterReference(implicitParameter, bindingContext)
) {
report(
CodeSmell(
issue,
Entity.from(lambdaExpression),
"The implicit `it` should not be used in a multiline lambda. " +
"Consider giving your parameter a readable and descriptive name."
)
)
}
}
}
private fun KtExpression.hasNoLineBreak() = !textContains('\n')
private fun KtExpression.hasNoStatements() =
!anyDescendantOfType<KtBlockExpression> { it.statements.isNotEmpty() }
}