-
-
Notifications
You must be signed in to change notification settings - Fork 755
/
IgnoredReturnValue.kt
110 lines (95 loc) · 4.53 KB
/
IgnoredReturnValue.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
102
103
104
105
106
107
108
109
110
package io.gitlab.arturbosch.detekt.rules.bugs
import io.github.detekt.tooling.api.FunctionMatcher
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.api.simplePatternToRegex
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtFile
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.typeUtil.isUnit
/**
* This rule warns on instances where a function, annotated with either `@CheckReturnValue` or `@CheckResult`,
* returns a value but that value is not used in any way. The Kotlin compiler gives no warning for this scenario
* normally so that's the rationale behind this rule.
*
* fun returnsValue() = 42
* fun returnsNoValue() {}
*
* <noncompliant>
* returnsValue()
* </noncompliant>
*
* <compliant>
* if (42 == returnsValue()) {}
* val x = returnsValue()
* </compliant>
*/
@RequiresTypeResolution
@ActiveByDefault(since = "1.21.0")
class IgnoredReturnValue(config: Config = Config.empty) : Rule(config) {
override val issue: Issue = Issue(
"IgnoredReturnValue",
Severity.Defect,
"This call returns a value which is ignored",
Debt.TWENTY_MINS
)
@Configuration("if the rule should check only annotated methods")
private val restrictToAnnotatedMethods: Boolean by config(defaultValue = true)
@Configuration("List of glob patterns to be used as inspection annotation")
private val returnValueAnnotations: List<Regex> by config(listOf("*.CheckResult", "*.CheckReturnValue")) {
it.map(String::simplePatternToRegex)
}
@Configuration("Annotations to skip this inspection")
private val ignoreReturnValueAnnotations: List<Regex> by config(listOf("*.CanIgnoreReturnValue")) {
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 " +
"ignore all function calls matching the name. Specifying fully-qualified function signature with " +
"parameters (i.e. `java.time.LocalDate.now(java.time.Clock)`) will ignore only function calls matching " +
"the name and parameters exactly."
)
private val ignoreFunctionCall: List<FunctionMatcher> by config(emptyList<String>()) {
it.map(FunctionMatcher::fromFunctionSignature)
}
override fun visitCondition(root: KtFile) = bindingContext != BindingContext.EMPTY && super.visitCondition(root)
@Suppress("ReturnCount")
override fun visitCallExpression(expression: KtCallExpression) {
super.visitCallExpression(expression)
if (expression.isUsedAsExpression(bindingContext)) return
val resultingDescriptor = expression.getResolvedCall(bindingContext)?.resultingDescriptor ?: return
if (resultingDescriptor.returnType?.isUnit() == true) return
if (ignoreFunctionCall.any { it.match(resultingDescriptor) }) return
val annotations = resultingDescriptor.annotations
if (annotations.any { it in ignoreReturnValueAnnotations }) return
if (restrictToAnnotatedMethods &&
(annotations + resultingDescriptor.containingDeclaration.annotations).none { it in returnValueAnnotations }
) return
val messageText = expression.calleeExpression?.text ?: expression.text
report(
CodeSmell(
issue,
Entity.from(expression),
message = "The call $messageText is returning a value that is ignored."
)
)
}
@Suppress("UnusedPrivateMember")
private operator fun List<Regex>.contains(annotation: AnnotationDescriptor): Boolean {
val fqName = annotation.fqName?.asString() ?: return false
return any { it.matches(fqName) }
}
}