/
ComplexInterface.kt
110 lines (98 loc) · 4.52 KB
/
ComplexInterface.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.complexity
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.Metric
import io.gitlab.arturbosch.detekt.api.Rule
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
import io.gitlab.arturbosch.detekt.api.config
import io.gitlab.arturbosch.detekt.api.internal.Configuration
import io.gitlab.arturbosch.detekt.rules.companionObject
import org.jetbrains.kotlin.com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassBody
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtTypeParameterListOwner
import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration
import org.jetbrains.kotlin.psi.psiUtil.isPrivate
/**
* Complex interfaces which contain too many functions and/or properties indicate that this interface is handling too
* many things at once. Interfaces should follow the single-responsibility principle to also encourage implementations
* of this interface to not handle too many things at once.
*
* Large interfaces should be split into smaller interfaces which have a clear responsibility and are easier
* to understand and implement.
*/
class ComplexInterface(
config: Config = Config.empty,
) : Rule(config) {
override val issue = Issue(
javaClass.simpleName,
Severity.Maintainability,
"An interface contains too many functions and properties. " +
"Large classes tend to handle many things at once. " +
"An interface should have one responsibility. " +
"Split up large interfaces into smaller ones that are easier to understand.",
Debt.TWENTY_MINS
)
@Configuration("the amount of definitions in an interface to trigger the rule")
private val threshold: Int by config(defaultValue = 10)
@Configuration("whether static declarations should be included")
private val includeStaticDeclarations: Boolean by config(defaultValue = false)
@Configuration("whether private declarations should be included")
private val includePrivateDeclarations: Boolean by config(defaultValue = false)
@Configuration("ignore overloaded methods - only count once")
private val ignoreOverloaded: Boolean by config(defaultValue = false)
override fun visitClass(klass: KtClass) {
if (klass.isInterface()) {
val body = klass.body ?: return
var size = calculateMembers(body)
if (includeStaticDeclarations) {
size += countStaticDeclarations(klass.companionObject())
}
if (size >= threshold) {
report(
ThresholdedCodeSmell(
issue,
Entity.atName(klass),
Metric("SIZE: ", size, threshold),
"The interface ${klass.name} is too complex. Consider splitting it up."
)
)
}
}
super.visitClass(klass)
}
private fun countStaticDeclarations(companionObject: KtObjectDeclaration?): Int {
val body = companionObject?.body
return if (body != null) calculateMembers(body) else 0
}
private fun calculateMembers(body: KtClassBody): Int {
fun PsiElement.considerPrivate() = includePrivateDeclarations ||
this is KtTypeParameterListOwner && !this.isPrivate()
fun countFunctions(psiElements: List<PsiElement>): Int {
val functions = psiElements.filterIsInstance<KtNamedFunction>()
return if (ignoreOverloaded) {
functions.distinctBy { function ->
val receiver = function.receiverTypeReference
if (function.isExtensionDeclaration() && receiver != null) {
"${receiver.text}.${function.name}"
} else {
function.name
}
}.size
} else {
functions.size
}
}
val psiElements = body.children
.filter(PsiElement::considerPrivate)
val propertyCount = psiElements.count { it is KtProperty }
val functionCount = countFunctions(psiElements)
return propertyCount + functionCount
}
}