/
InjectDispatcher.kt
79 lines (73 loc) · 3.17 KB
/
InjectDispatcher.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
package io.gitlab.arturbosch.detekt.rules.coroutines
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 io.gitlab.arturbosch.detekt.rules.fqNameOrNull
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtConstructorDelegationCall
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.calls.util.getType
import org.jetbrains.kotlin.types.typeUtil.supertypes
/**
* Always use dependency injection to inject dispatchers for easier testing.
* This rule is based on the recommendation
* https://developer.android.com/kotlin/coroutines/coroutines-best-practices#inject-dispatchers
*
* <noncompliant>
* fun myFunc() {
* coroutineScope(Dispatchers.IO)
* }
* </noncompliant>
*
* <compliant>
* fun myFunc(dispatcher: CoroutineDispatcher = Dispatchers.IO) {
* coroutineScope(dispatcher)
* }
*
* class MyRepository(dispatchers: CoroutineDispatcher = Dispatchers.IO)
* </compliant>
*/
@RequiresTypeResolution
@ActiveByDefault(since = "1.21.0")
class InjectDispatcher(config: Config) : Rule(config) {
@Configuration("The names of dispatchers to detect by this rule")
private val dispatcherNames: Set<String> by config(listOf("IO", "Default", "Unconfined")) { it.toSet() }
override val issue = Issue(
"InjectDispatcher",
Severity.Defect,
"Don't hardcode dispatchers when creating new coroutines or calling `withContext`. " +
"Use dependency injection for dispatchers to make testing easier.",
Debt.FIVE_MINS
)
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
super.visitSimpleNameExpression(expression)
if (expression.getReferencedName() !in dispatcherNames) return
val type = expression.getType(bindingContext) ?: return
val isCoroutineDispatcher = type.fqNameOrNull() == COROUTINE_DISPATCHER_FQCN ||
type.supertypes().any { it.fqNameOrNull() == COROUTINE_DISPATCHER_FQCN }
val isUsedAsParameter = expression.getStrictParentOfType<KtParameter>() != null ||
expression.getStrictParentOfType<KtConstructorDelegationCall>() != null
if (isCoroutineDispatcher && !isUsedAsParameter) {
report(
CodeSmell(
issue,
Entity.from(expression),
"Dispatcher ${expression.getReferencedName()} is used without dependency injection."
)
)
}
}
companion object {
private val COROUTINE_DISPATCHER_FQCN = FqName("kotlinx.coroutines.CoroutineDispatcher")
}
}