forked from detekt/detekt
/
TooManyFunctions.kt
173 lines (154 loc) · 6.79 KB
/
TooManyFunctions.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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.ActiveByDefault
import io.gitlab.arturbosch.detekt.api.internal.Configuration
import io.gitlab.arturbosch.detekt.rules.hasAnnotation
import io.gitlab.arturbosch.detekt.rules.isOverride
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtObjectDeclaration
import org.jetbrains.kotlin.psi.psiUtil.isPrivate
/**
* This rule reports files, classes, interfaces, objects and enums which contain too many functions.
* Each element can be configured with different thresholds.
*
* Too many functions indicate a violation of the single responsibility principle. Prefer extracting functionality
* which clearly belongs together in separate parts of the code.
*/
@ActiveByDefault(since = "1.0.0")
class TooManyFunctions(config: Config = Config.empty) : Rule(config) {
override val issue = Issue(
"TooManyFunctions",
Severity.Maintainability,
"Too many functions inside a/an file/class/object/interface always indicate a violation of " +
"the single responsibility principle. Maybe the file/class/object/interface wants to manage too " +
"many things at once. Extract functionality which clearly belongs together.",
Debt.TWENTY_MINS
)
@Configuration("threshold in files")
private val thresholdInFiles: Int by config(DEFAULT_THRESHOLD)
@Configuration("threshold in classes")
private val thresholdInClasses: Int by config(DEFAULT_THRESHOLD)
@Configuration("threshold in interfaces")
private val thresholdInInterfaces: Int by config(DEFAULT_THRESHOLD)
@Configuration("threshold in objects")
private val thresholdInObjects: Int by config(DEFAULT_THRESHOLD)
@Configuration("threshold in enums")
private val thresholdInEnums: Int by config(DEFAULT_THRESHOLD)
@Configuration("ignore deprecated functions")
private val ignoreDeprecated: Boolean by config(false)
@Configuration("ignore private functions")
private val ignorePrivate: Boolean by config(false)
@Configuration("ignore overridden functions")
private val ignoreOverridden: Boolean by config(false)
private var amountOfTopLevelFunctions: Int = 0
override fun visitKtFile(file: KtFile) {
super.visitKtFile(file)
if (amountOfTopLevelFunctions >= thresholdInFiles) {
report(
ThresholdedCodeSmell(
issue,
Entity.atPackageOrFirstDecl(file),
Metric("SIZE", amountOfTopLevelFunctions, thresholdInFiles),
"File '${file.name}' with '$amountOfTopLevelFunctions' functions detected. " +
"Defined threshold inside files is set to '$thresholdInFiles'"
)
)
}
amountOfTopLevelFunctions = 0
}
override fun visitNamedFunction(function: KtNamedFunction) {
if (function.isTopLevel && !isIgnoredFunction(function)) {
amountOfTopLevelFunctions++
}
}
override fun visitClass(klass: KtClass) {
val amount = calcFunctions(klass)
when {
klass.isInterface() -> {
if (amount >= thresholdInInterfaces) {
report(
ThresholdedCodeSmell(
issue,
Entity.atName(klass),
Metric("SIZE", amount, thresholdInInterfaces),
"Interface '${klass.name}' with '$amount' functions detected. " +
"Defined threshold inside interfaces is set to " +
"'$thresholdInInterfaces'"
)
)
}
}
klass.isEnum() -> {
if (amount >= thresholdInEnums) {
report(
ThresholdedCodeSmell(
issue,
Entity.atName(klass),
Metric("SIZE", amount, thresholdInEnums),
"Enum class '${klass.name}' with '$amount' functions detected. " +
"Defined threshold inside enum classes is set to " +
"'$thresholdInEnums'"
)
)
}
}
else -> {
if (amount >= thresholdInClasses) {
report(
ThresholdedCodeSmell(
issue,
Entity.atName(klass),
Metric("SIZE", amount, thresholdInClasses),
"Class '${klass.name}' with '$amount' functions detected. " +
"Defined threshold inside classes is set to '$thresholdInClasses'"
)
)
}
}
}
super.visitClass(klass)
}
override fun visitObjectDeclaration(declaration: KtObjectDeclaration) {
val amount = calcFunctions(declaration)
if (amount >= thresholdInObjects) {
report(
ThresholdedCodeSmell(
issue,
Entity.atName(declaration),
Metric("SIZE", amount, thresholdInObjects),
"Object '${declaration.name}' with '$amount' functions detected. " +
"Defined threshold inside objects is set to '$thresholdInObjects'"
)
)
}
super.visitObjectDeclaration(declaration)
}
private fun calcFunctions(classOrObject: KtClassOrObject): Int = classOrObject.body
?.run {
declarations
.filterIsInstance<KtNamedFunction>()
.count { !isIgnoredFunction(it) }
}
?: 0
private fun isIgnoredFunction(function: KtNamedFunction): Boolean = when {
ignoreDeprecated && function.hasAnnotation(DEPRECATED) -> true
ignorePrivate && function.isPrivate() -> true
ignoreOverridden && function.isOverride() -> true
else -> false
}
companion object {
const val DEFAULT_THRESHOLD = 11
private const val DEPRECATED = "Deprecated"
}
}