Skip to content

Commit

Permalink
Add returnValueTypes option to declare types that should not be ignored
Browse files Browse the repository at this point in the history
  • Loading branch information
osipxd committed Jul 31, 2022
1 parent 585a958 commit 2896f59
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 4 deletions.
2 changes: 2 additions & 0 deletions detekt-core/src/main/resources/default-detekt-config.yml
Expand Up @@ -437,6 +437,8 @@ potential-bugs:
- '*.CheckReturnValue'
ignoreReturnValueAnnotations:
- '*.CanIgnoreReturnValue'
returnValueTypes:
- 'kotlinx.coroutines.flow.Flow'
ignoreFunctionCall: []
ImplicitDefaultLocale:
active: true
Expand Down
Expand Up @@ -13,11 +13,14 @@ 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.api.simplePatternToRegex
import io.gitlab.arturbosch.detekt.rules.fqNameOrNull
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.isUsedAsExpression
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isUnit

/**
Expand Down Expand Up @@ -61,6 +64,11 @@ class IgnoredReturnValue(config: Config = Config.empty) : Rule(config) {
it.map(String::simplePatternToRegex)
}

@Configuration("List of return types that should not be ignored")
private val returnValueTypes: List<Regex> by config(listOf("kotlinx.coroutines.flow.Flow")) {
it.map(String::simplePatternToRegex)
}

@Configuration(
"List of function signatures which should be ignored by this rule. " +
"Specifying fully-qualified function signature with name only (i.e. `java.time.LocalDate.now`) will " +
Expand All @@ -87,6 +95,7 @@ class IgnoredReturnValue(config: Config = Config.empty) : Rule(config) {
val annotations = resultingDescriptor.annotations
if (annotations.any { it in ignoreReturnValueAnnotations }) return
if (restrictToAnnotatedMethods &&
resultingDescriptor.returnType !in returnValueTypes &&
(annotations + resultingDescriptor.containingDeclaration.annotations).none { it in returnValueAnnotations }
) return

Expand All @@ -100,9 +109,11 @@ class IgnoredReturnValue(config: Config = Config.empty) : Rule(config) {
)
}

@Suppress("UnusedPrivateMember")
private operator fun List<Regex>.contains(annotation: AnnotationDescriptor): Boolean {
val fqName = annotation.fqName?.asString() ?: return false
return any { it.matches(fqName) }
private operator fun List<Regex>.contains(type: KotlinType?) = contains(type?.fqNameOrNull())
private operator fun List<Regex>.contains(annotation: AnnotationDescriptor) = contains(annotation.fqName)

private operator fun List<Regex>.contains(fqName: FqName?): Boolean {
val name = fqName?.asString() ?: return false
return any { it.matches(name) }
}
}
Expand Up @@ -881,4 +881,70 @@ class IgnoredReturnValueSpec(private val env: KotlinCoreEnvironment) {
assertThat(findings).isEmpty()
}
}

@Nested
inner class `return value types default config` {
private val subject = IgnoredReturnValue()

@Test
fun `reports when result of function returning Flow is ignored`() {
val code = """
import kotlinx.coroutines.flow.flowOf
fun foo() {
flowOf(1, 2, 3)
}
"""
val findings = subject.compileAndLintWithContext(env, code)

assertThat(findings)
.singleElement()
.hasSourceLocation(line = 4, column = 5)
.hasMessage("The call flowOf is returning a value that is ignored.")
}

@Test
fun `reports when a function returned result is used in a chain that returns a Flow`() {
val code = """
import kotlinx.coroutines.flow.*
fun foo() {
flowOf(1, 2, 3)
.onEach { println(it) }
}
"""
val findings = subject.compileAndLintWithContext(env, code)

assertThat(findings)
.singleElement()
.hasSourceLocation(line = 5, column = 10)
.hasMessage("The call onEach is returning a value that is ignored.")
}

@Test
fun `does not report when a function returned value is used to be returned`() {
val code = """
import kotlinx.coroutines.flow.flowOf
fun foo() = flowOf(1, 2, 3)
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}

@Test
fun `does not report when a function returned value is consumed in a chain that returns an Unit`() {
val code = """
import kotlinx.coroutines.flow.*
suspend fun foo() {
flowOf(1, 2, 3)
.onEach { println(it) }
.collect()
}
"""
val findings = subject.compileAndLintWithContext(env, code)
assertThat(findings).isEmpty()
}
}
}

0 comments on commit 2896f59

Please sign in to comment.