Skip to content

Commit 0119cd6

Browse files
committed
NoCallbacksInFunctions rule
1 parent 4d78c4d commit 0119cd6

File tree

6 files changed

+85
-2
lines changed

6 files changed

+85
-2
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ HbmartinRuleSet:
3838
active: true
3939
MutableTypeShouldBePrivate:
4040
active: true
41+
NoCallbacksInFunctions:
42+
active: true
43+
ignoreAnnotated: ['Composable']
4144
NoNotNullOperator:
4245
active: true
4346
NoVarsInConstructor:
@@ -68,6 +71,9 @@ Finds uses of `as` to force cast. These are likely to lead to crashes, especiall
6871

6972
Finds publicly exposed mutable types e.g. `MutableStateFlow<>`. These are likely to lead to bugs, prefer to expose a non-mutable `Flow` (e.g. with `_mutableStateFlow.asStateFlow()`) or other non-mutable type. [See here](https://github.com/hbmartin/hbmartin-detekt-rules/blob/main/src/test/kotlin/me/haroldmartin/detektrules/MutableTypeShouldBePrivateTest.kt) for triggering and non-triggering examples.
7073

74+
### NoCallbacksInFunctions
75+
Finds uses of callbacks in functions. This can lead to a mixed concurrency paradigm and are likely to lead to bugs or stalled threads, prefer to use a suspend function instead. Use the `ignoreAnnotated` configuration to allow callbacks in `@Composable` functions.
76+
7177
### NoNotNullOperator
7278

7379
Finds uses of `!!` to force unwrap. These are likely to lead to crashes, prefer to safely unwrap with `?.` or `?:` instead. Otherwise the Kotlin docs will make fun of you for being an [NPE lover](https://kotlinlang.org/docs/null-safety.html#the-operator). [See here](https://github.com/hbmartin/hbmartin-detekt-rules/blob/main/src/test/kotlin/me/haroldmartin/detektrules/NoNotNullOperatorTest.kt) for triggering and non-triggering examples.

config/detekt/detekt.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -662,8 +662,6 @@ style:
662662
active: true
663663
OptionalUnit:
664664
active: true
665-
OptionalWhenBraces:
666-
active: true
667665
PreferToOverPairSyntax:
668666
active: true
669667
ProtectedMemberInFinalClass:

src/main/kotlin/me/haroldmartin/detektrules/HbmartinRuleSetProvider.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class HbmartinRuleSetProvider : RuleSetProvider {
1616
AvoidVarsExceptWithDelegate(config),
1717
DontForceCast(config),
1818
MutableTypeShouldBePrivate(config),
19+
NoCallbacksInFunctions(config),
1920
NoNotNullOperator(config),
2021
NoVarsInConstructor(config),
2122
WhenBranchSingleLineOrBraces(config),
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package me.haroldmartin.detektrules
2+
3+
import io.gitlab.arturbosch.detekt.api.CodeSmell
4+
import io.gitlab.arturbosch.detekt.api.Config
5+
import io.gitlab.arturbosch.detekt.api.Debt
6+
import io.gitlab.arturbosch.detekt.api.Entity
7+
import io.gitlab.arturbosch.detekt.api.Issue
8+
import io.gitlab.arturbosch.detekt.api.Rule
9+
import io.gitlab.arturbosch.detekt.api.Severity
10+
import org.jetbrains.kotlin.psi.KtFunctionType
11+
import org.jetbrains.kotlin.psi.KtNamedFunction
12+
import org.jetbrains.kotlin.psi.KtParameter
13+
14+
class NoCallbacksInFunctions(config: Config) : Rule(config) {
15+
override val issue = Issue(
16+
id = javaClass.simpleName,
17+
severity = Severity.Defect,
18+
description = "Use coroutines instead of callbacks.",
19+
debt = Debt.TWENTY_MINS,
20+
)
21+
22+
override fun visitNamedFunction(function: KtNamedFunction) {
23+
super.visitNamedFunction(function)
24+
function.functionTypeParameters?.takeIf { it.isNotEmpty() }?.let {
25+
report(
26+
CodeSmell(
27+
issue = issue,
28+
entity = Entity.from(function),
29+
message = "${function.name} should not have callbacks: ${it.joinToString()}",
30+
),
31+
)
32+
}
33+
}
34+
}
35+
36+
@Suppress("AvoidMutableCollections")
37+
private val KtNamedFunction.functionTypeParameters: List<String>?
38+
get() = valueParameterList?.run { parameters.flatMap { it.functionTypeParameters } }
39+
40+
private val KtParameter.functionTypeParameters: List<String>
41+
get() = typeReference?.run { children.mapNotNull { if (it is KtFunctionType) this.text else null } }.orEmpty()

src/main/resources/config/config.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,8 @@ HbmartinRuleSet:
1616
active: true
1717
NoVarsInConstructor:
1818
active: true
19+
NoCallbacksInFunctions:
20+
active: true
21+
ignoreAnnotated: ['Composable']
1922
WhenBranchSingleLineOrBraces:
2023
active: true
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package me.haroldmartin.detektrules
2+
3+
import io.gitlab.arturbosch.detekt.api.Config
4+
import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
5+
import io.gitlab.arturbosch.detekt.test.TestConfig
6+
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
7+
import io.kotest.matchers.collections.shouldHaveSize
8+
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
9+
import org.junit.jupiter.api.Test
10+
11+
@KotlinCoreEnvironmentTest
12+
internal class NoCallbacksInFunctionsTest(private val env: KotlinCoreEnvironment) {
13+
@Test
14+
fun `reports callbacks`() {
15+
val code = """
16+
fun doNothing(s: String) {
17+
println(s)
18+
}
19+
fun doSomething(callback1: (Float) -> Unit) {
20+
callback1(1f)
21+
}
22+
private fun doSomethingElse(
23+
parameter: List<Int>,
24+
callback4: (Boolean) -> Unit,
25+
anotherCallback5: (Any) -> Unit,
26+
anotherParameter: List<Int>,
27+
) {
28+
callback(true)
29+
}
30+
"""
31+
val findings = NoCallbacksInFunctions(Config.empty).compileAndLintWithContext(env, code)
32+
findings shouldHaveSize 2
33+
}
34+
}

0 commit comments

Comments
 (0)