-
-
Notifications
You must be signed in to change notification settings - Fork 755
/
ElseCaseInsteadOfExhaustiveWhen.kt
101 lines (91 loc) · 3.74 KB
/
ElseCaseInsteadOfExhaustiveWhen.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
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.internal.RequiresTypeResolution
import org.jetbrains.kotlin.cfg.WhenChecker
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtWhenExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isBooleanOrNullableBoolean
/**
* This rule reports `when` expressions that contain an `else` case even though they have an exhaustive set of cases.
*
* This occurs when the subject of the `when` expression is either an enum class, sealed class or of type boolean.
* Using `else` cases for these expressions can lead to unintended behavior when adding new enum types, sealed subtypes
* or changing the nullability of a boolean, since this will be implicitly handled by the `else` case.
*
* <noncompliant>
* enum class Color {
* RED,
* GREEN,
* BLUE
* }
*
* when(c) {
* Color.RED -> {}
* Color.GREEN -> {}
* else -> {}
* }
* </noncompliant>
*
* <compliant>
* enum class Color {
* RED,
* GREEN,
* BLUE
* }
*
* when(c) {
* Color.RED -> {}
* Color.GREEN -> {}
* Color.BLUE -> {}
* }
* </compliant>
*/
@RequiresTypeResolution
class ElseCaseInsteadOfExhaustiveWhen(config: Config = Config.empty) : Rule(config) {
override val issue: Issue = Issue(
"ElseCaseInsteadOfExhaustiveWhen",
Severity.Warning,
"A `when` expression that has an exhaustive set of cases should not contain an `else` case.",
Debt.FIVE_MINS
)
override fun visitCondition(root: KtFile) = bindingContext != BindingContext.EMPTY && super.visitCondition(root)
override fun visitWhenExpression(whenExpression: KtWhenExpression) {
super.visitWhenExpression(whenExpression)
checkForElseCaseInsteadOfExhaustiveWhenExpression(whenExpression)
}
private fun checkForElseCaseInsteadOfExhaustiveWhenExpression(whenExpression: KtWhenExpression) {
val subjectExpression = whenExpression.subjectExpression ?: return
if (whenExpression.elseExpression == null) return
val subjectType = subjectExpression.getType(bindingContext)
val isEnumSubject = WhenChecker.getClassDescriptorOfTypeIfEnum(subjectType) != null
val isSealedSubject = isNonExpectedSealedClass(subjectType)
val isBooleanSubject = subjectType?.isBooleanOrNullableBoolean() == true
if (isEnumSubject || isSealedSubject || isBooleanSubject) {
val subjectTypeName = when {
isEnumSubject -> "enum class"
isSealedSubject -> "sealed class"
else -> "boolean"
}
val message = "When expression with $subjectTypeName subject should not contain an `else` case."
report(CodeSmell(issue, Entity.from(whenExpression), message))
}
}
/**
* `when` expressions on `expect` sealed classes in the common code of multiplatform projects still require an
* `else` branch. This happens because subclasses of `actual` platform implementations aren't known in the common
* code.
*/
private fun isNonExpectedSealedClass(type: KotlinType?): Boolean {
val sealedClassDescriptor = WhenChecker.getClassDescriptorOfTypeIfSealed(type)
return sealedClassDescriptor != null && !sealedClassDescriptor.isExpect
}
}