-
-
Notifications
You must be signed in to change notification settings - Fork 755
/
MissingWhenCase.kt
118 lines (109 loc) · 4.15 KB
/
MissingWhenCase.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.bugs
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.config
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
import io.gitlab.arturbosch.detekt.api.internal.Configuration
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.diagnostics.WhenMissingCase
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.util.getType
/*
* Based on code from Kotlin compiler:
* https://github.com/JetBrains/kotlin/blob/v1.3.30/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt
*/
/**
* Turn on this rule to flag `when` expressions that do not check that all cases are covered when the subject is an enum
* or sealed class and the `when` expression is used as a statement.
*
* When this happens it's unclear what was intended when an unhandled case is reached. It is better to be explicit and
* either handle all cases or use a default `else` statement to cover the unhandled cases.
*
* <noncompliant>
* enum class Color {
* RED,
* GREEN,
* BLUE
* }
*
* fun whenOnEnumFail(c: Color) {
* when(c) {
* Color.BLUE -> {}
* Color.GREEN -> {}
* }
* }
* </noncompliant>
*
* <compliant>
* enum class Color {
* RED,
* GREEN,
* BLUE
* }
*
* fun whenOnEnumCompliant(c: Color) {
* when(c) {
* Color.BLUE -> {}
* Color.GREEN -> {}
* Color.RED -> {}
* }
* }
*
* fun whenOnEnumCompliant2(c: Color) {
* when(c) {
* Color.BLUE -> {}
* else -> {}
* }
* }
* </compliant>
*/
@ActiveByDefault(since = "1.2.0")
@RequiresTypeResolution
class MissingWhenCase(config: Config = Config.empty) : Rule(config) {
override val issue: Issue = Issue(
"MissingWhenCase",
Severity.Defect,
"Check usage of `when` used as a statement and don't compare all enum or sealed class cases.",
Debt.TWENTY_MINS
)
@Configuration("whether `else` can be treated as a valid case for enums and sealed classes")
private val allowElseExpression: Boolean by config(true)
override fun visitCondition(root: KtFile) = bindingContext != BindingContext.EMPTY && super.visitCondition(root)
override fun visitWhenExpression(expression: KtWhenExpression) {
super.visitWhenExpression(expression)
if (allowElseExpression && expression.elseExpression != null) return
checkMissingWhenExpression(expression)
}
private fun checkMissingWhenExpression(expression: KtWhenExpression) {
if (expression.isUsedAsExpression(bindingContext)) return
val subjectExpression = expression.subjectExpression ?: return
val subjectType = subjectExpression.getType(bindingContext)
val enumClassDescriptor = WhenChecker.getClassDescriptorOfTypeIfEnum(subjectType)
val sealedClassDescriptor = WhenChecker.getClassDescriptorOfTypeIfSealed(subjectType)
if (enumClassDescriptor != null || sealedClassDescriptor != null) {
val missingCases = WhenChecker.getMissingCases(expression, bindingContext)
reportMissingCases(missingCases, expression)
}
}
private fun reportMissingCases(
missingCases: List<WhenMissingCase>,
expression: KtWhenExpression
) {
if (missingCases.isNotEmpty()) {
var message = "When expression is missing cases: ${missingCases.joinToString()}."
if (allowElseExpression) {
message += " Either add missing cases or a default `else` case."
}
report(CodeSmell(issue, Entity.from(expression), message))
}
}
}