-
-
Notifications
You must be signed in to change notification settings - Fork 758
/
ObjectLiteralToLambda.kt
112 lines (98 loc) · 4.41 KB
/
ObjectLiteralToLambda.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
111
112
package io.gitlab.arturbosch.detekt.rules.style
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.ActiveByDefault
import io.gitlab.arturbosch.detekt.api.internal.RequiresTypeResolution
import io.gitlab.arturbosch.detekt.rules.isOverride
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.load.java.sam.JavaSingleAbstractMethodUtils
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtObjectLiteralExpression
import org.jetbrains.kotlin.psi.KtThisExpression
import org.jetbrains.kotlin.psi.psiUtil.anyDescendantOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.KotlinType
/**
* An anonymous object that does nothing other than the implementation of a single method
* can be used as a lambda.
*
* See [SAM conversions](https://kotlinlang.org/docs/java-interop.html#sam-conversions),
* [Functional (SAM) interfaces](https://kotlinlang.org/docs/fun-interfaces.html)
*
* <noncompliant>
* object : Foo {
* override fun bar() {
* }
* }
* </noncompliant>
*
* <compliant>
* Foo {
* }
* </compliant>
*/
@RequiresTypeResolution
@ActiveByDefault(since = "1.21.0")
class ObjectLiteralToLambda(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Style,
"Report object literals that can be changed to lambdas.",
Debt.FIVE_MINS
)
private val KotlinType.couldBeSamInterface
get() = JavaSingleAbstractMethodUtils.isSamType(this)
private fun KotlinType.singleSuperTypeOrNull(): KotlinType? =
constructor.supertypes.singleOrNull()
private fun KtObjectDeclaration.singleNamedMethodOrNull(): KtNamedFunction? =
declarations.singleOrNull() as? KtNamedFunction
private fun KtExpression.containsThisReference(descriptor: DeclarationDescriptor) =
anyDescendantOfType<KtThisExpression> { thisReference ->
bindingContext[BindingContext.REFERENCE_TARGET, thisReference.instanceReference] == descriptor
}
private fun KtExpression.containsOwnMethodCall(descriptor: DeclarationDescriptor) =
anyDescendantOfType<KtExpression> {
it.getResolvedCall(bindingContext)?.let { resolvedCall ->
resolvedCall.dispatchReceiver.isImplicitClassFor(descriptor) ||
resolvedCall.extensionReceiver.isImplicitClassFor(descriptor)
} == true
}
private fun ReceiverValue?.isImplicitClassFor(descriptor: DeclarationDescriptor) =
this is ImplicitClassReceiver && classDescriptor == descriptor
private fun KtExpression.containsMethodOf(declaration: KtObjectDeclaration): Boolean {
val objectDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration]
?: return false
return containsThisReference(objectDescriptor) ||
containsOwnMethodCall(objectDescriptor)
}
private fun KtObjectDeclaration.hasConvertibleMethod(): Boolean {
val singleNamedMethod = singleNamedMethodOrNull()
val functionBody = singleNamedMethod?.bodyExpression ?: return false
return singleNamedMethod.isOverride() &&
!functionBody.containsMethodOf(this)
}
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
super.visitObjectLiteralExpression(expression)
if (bindingContext == BindingContext.EMPTY) return
val declaration = expression.objectDeclaration
if (
declaration.name == null &&
bindingContext.getType(expression)
?.singleSuperTypeOrNull()
?.couldBeSamInterface == true &&
declaration.hasConvertibleMethod()
) {
report(CodeSmell(issue, Entity.from(expression), issue.description))
}
}
}