/
SuspendFunWithCoroutineScopeReceiver.kt
90 lines (84 loc) · 2.99 KB
/
SuspendFunWithCoroutineScopeReceiver.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
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.internal.RequiresTypeResolution
import io.gitlab.arturbosch.detekt.rules.fqNameOrNull
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.supertypes
/**
* Suspend functions that use `CoroutineScope` as receiver should not be marked as `suspend`.
* A `CoroutineScope` provides structured concurrency via its `coroutineContext`. A `suspend`
* function also has its own `coroutineContext`, which is now ambiguous and mixed with the
* receiver`s.
*
* See https://kotlinlang.org/docs/coroutines-basics.html#scope-builder-and-concurrency
*
* <noncompliant>
* suspend fun CoroutineScope.foo() {
* launch {
* delay(1.seconds)
* }
* }
* </noncompliant>
*
* <compliant>
* fun CoroutineScope.foo() {
* launch {
* delay(1.seconds)
* }
* }
*
* // Alternative
* suspend fun foo() = coroutineScope {
* launch {
* delay(1.seconds)
* }
* }
* </compliant>
*
*/
@RequiresTypeResolution
class SuspendFunWithCoroutineScopeReceiver(config: Config) : Rule(config) {
override val issue = Issue(
id = "SuspendFunWithCoroutineScopeReceiver",
severity = Severity.Minor,
description = "The `suspend` modifier should not be used for functions that use a " +
"CoroutinesScope as receiver. You should use suspend functions without the receiver or use plain " +
"functions and use coroutineScope { } instead.",
debt = Debt.TEN_MINS
)
override fun visitNamedFunction(function: KtNamedFunction) {
checkReceiver(function)
}
private fun checkReceiver(function: KtNamedFunction) {
val suspendModifier = function.modifierList?.getModifier(KtTokens.SUSPEND_KEYWORD) ?: return
val receiver = bindingContext[BindingContext.FUNCTION, function]
?.extensionReceiverParameter
?.value
?.type
?: return
if (receiver.isCoroutineScope()) {
report(
CodeSmell(
issue = issue,
entity = Entity.from(suspendModifier),
message = "`suspend` function uses CoroutineScope as receiver."
)
)
}
}
private fun KotlinType.isCoroutineScope() = sequence {
yield(this@isCoroutineScope)
yieldAll(this@isCoroutineScope.supertypes())
}
.mapNotNull { it.fqNameOrNull()?.asString() }
.contains("kotlinx.coroutines.CoroutineScope")
}