mirror of
https://github.com/jlengrand/detekt.git
synced 2026-03-10 08:11:23 +00:00
Rule Configuration using annotations (#3637)
* Create initial idea for @Configuration annotation * Use config parameter constants again * Generate documentation from @Configuration * Introduce config delegate and extract default value from it * Exclude rules configured with annotation from checks * Remove support for factory methods in RuleSetProviderCollector * Add support for lists that are empty by default * Update documentation for rule contribution * Restrict config delegate to supported types * Update .github/CONTRIBUTING.md Co-authored-by: Brais Gabín <braisgabin@gmail.com> Co-authored-by: Markus Schwarz <post@markus-schwarz.net> Co-authored-by: Chao Zhang <zhangchao6865@gmail.com>
This commit is contained in:
@@ -1,17 +1,17 @@
|
||||
package io.gitlab.arturbosch.detekt.rules.complexity
|
||||
|
||||
import io.github.detekt.metrics.CyclomaticComplexity
|
||||
import io.github.detekt.metrics.CyclomaticComplexity.Companion.DEFAULT_NESTING_FUNCTIONS
|
||||
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.ThresholdRule
|
||||
import io.gitlab.arturbosch.detekt.api.ThresholdedCodeSmell
|
||||
import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault
|
||||
import io.gitlab.arturbosch.detekt.api.internal.valueOrDefaultCommaSeparated
|
||||
import io.gitlab.arturbosch.detekt.api.internal.Configuration
|
||||
import io.gitlab.arturbosch.detekt.api.internal.config
|
||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
@@ -36,21 +36,24 @@ import org.jetbrains.kotlin.psi.KtWhenExpression
|
||||
* - __Exceptions__ - `catch`, `use`
|
||||
* - __Scope Functions__ - `let`, `run`, `with`, `apply`, and `also` ->
|
||||
* [Reference](https://kotlinlang.org/docs/reference/scope-functions.html)
|
||||
*
|
||||
* @configuration threshold - McCabe's Cyclomatic Complexity (MCC) number for a method (default: `15`)
|
||||
* @configuration ignoreSingleWhenExpression - Ignores a complex method if it only contains a single when expression.
|
||||
* (default: `false`)
|
||||
* @configuration ignoreSimpleWhenEntries - Whether to ignore simple (braceless) when entries. (default: `false`)
|
||||
* @configuration ignoreNestingFunctions - Whether to ignore functions which are often used instead of an `if` or
|
||||
* `for` statement (default: `false`)
|
||||
* @configuration nestingFunctions - Comma separated list of function names which add complexity
|
||||
* (default: `[run, let, apply, with, also, use, forEach, isNotNull, ifNull]`)
|
||||
*/
|
||||
@ActiveByDefault(since = "1.0.0")
|
||||
class ComplexMethod(
|
||||
config: Config = Config.empty,
|
||||
threshold: Int = DEFAULT_THRESHOLD_METHOD_COMPLEXITY
|
||||
) : ThresholdRule(config, threshold) {
|
||||
class ComplexMethod(config: Config = Config.empty) : Rule(config) {
|
||||
|
||||
@Configuration("McCabe's Cyclomatic Complexity (MCC) number for a method.")
|
||||
private val threshold: Int by config(DEFAULT_THRESHOLD_METHOD_COMPLEXITY)
|
||||
|
||||
@Configuration("Ignores a complex method if it only contains a single when expression.")
|
||||
private val ignoreSingleWhenExpression: Boolean by config(false)
|
||||
|
||||
@Configuration("Whether to ignore simple (braceless) when entries.")
|
||||
private val ignoreSimpleWhenEntries: Boolean by config(false)
|
||||
|
||||
@Configuration("Whether to ignore functions which are often used instead of an `if` or `for` statement.")
|
||||
private val ignoreNestingFunctions: Boolean by config(false)
|
||||
|
||||
@Configuration("Comma separated list of function names which add complexity.")
|
||||
private val nestingFunctions: List<String> by config(DEFAULT_NESTING_FUNCTIONS)
|
||||
|
||||
override val issue = Issue(
|
||||
"ComplexMethod",
|
||||
@@ -59,11 +62,7 @@ class ComplexMethod(
|
||||
Debt.TWENTY_MINS
|
||||
)
|
||||
|
||||
private val ignoreSingleWhenExpression = valueOrDefault(IGNORE_SINGLE_WHEN_EXPRESSION, false)
|
||||
private val ignoreSimpleWhenEntries = valueOrDefault(IGNORE_SIMPLE_WHEN_ENTRIES, false)
|
||||
private val ignoreNestingFunctions = valueOrDefault(IGNORE_NESTING_FUNCTIONS, false)
|
||||
private val nestingFunctions = valueOrDefaultCommaSeparated(NESTING_FUNCTIONS, DEFAULT_NESTING_FUNCTIONS.toList())
|
||||
.toSet()
|
||||
private val nestingFunctionsAsSet: Set<String> = nestingFunctions.toSet()
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
if (ignoreSingleWhenExpression && hasSingleWhenExpression(function.bodyExpression)) {
|
||||
@@ -73,7 +72,7 @@ class ComplexMethod(
|
||||
val complexity = CyclomaticComplexity.calculate(function) {
|
||||
this.ignoreSimpleWhenEntries = this@ComplexMethod.ignoreSimpleWhenEntries
|
||||
this.ignoreNestingFunctions = this@ComplexMethod.ignoreNestingFunctions
|
||||
this.nestingFunctions = this@ComplexMethod.nestingFunctions
|
||||
this.nestingFunctions = this@ComplexMethod.nestingFunctionsAsSet
|
||||
}
|
||||
|
||||
if (complexity >= threshold) {
|
||||
@@ -104,9 +103,16 @@ class ComplexMethod(
|
||||
|
||||
companion object {
|
||||
const val DEFAULT_THRESHOLD_METHOD_COMPLEXITY = 15
|
||||
const val IGNORE_SINGLE_WHEN_EXPRESSION = "ignoreSingleWhenExpression"
|
||||
const val IGNORE_SIMPLE_WHEN_ENTRIES = "ignoreSimpleWhenEntries"
|
||||
const val IGNORE_NESTING_FUNCTIONS = "ignoreNestingFunctions"
|
||||
const val NESTING_FUNCTIONS = "nestingFunctions"
|
||||
val DEFAULT_NESTING_FUNCTIONS = listOf(
|
||||
"run",
|
||||
"let",
|
||||
"apply",
|
||||
"with",
|
||||
"also",
|
||||
"use",
|
||||
"forEach",
|
||||
"isNotNull",
|
||||
"ifNull"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import io.gitlab.arturbosch.detekt.test.lint
|
||||
import org.spekframework.spek2.Spek
|
||||
import org.spekframework.spek2.style.specification.describe
|
||||
|
||||
private val defaultConfigMap: Map<String, Any> = mapOf("threshold" to "1")
|
||||
|
||||
class ComplexMethodSpec : Spek({
|
||||
|
||||
val defaultComplexity = 1
|
||||
@@ -19,7 +21,7 @@ class ComplexMethodSpec : Spek({
|
||||
context("different complex constructs") {
|
||||
|
||||
it("counts different loops") {
|
||||
val findings = ComplexMethod(threshold = 1).compileAndLint(
|
||||
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
|
||||
"""
|
||||
fun test() {
|
||||
for (i in 1..10) {}
|
||||
@@ -34,7 +36,7 @@ class ComplexMethodSpec : Spek({
|
||||
}
|
||||
|
||||
it("counts catch blocks") {
|
||||
val findings = ComplexMethod(threshold = 1).compileAndLint(
|
||||
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
|
||||
"""
|
||||
fun test() {
|
||||
try {} catch(e: IllegalArgumentException) {} catch(e: Exception) {} finally {}
|
||||
@@ -46,7 +48,7 @@ class ComplexMethodSpec : Spek({
|
||||
}
|
||||
|
||||
it("counts nested conditional statements") {
|
||||
val findings = ComplexMethod(threshold = 1).compileAndLint(
|
||||
val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint(
|
||||
"""
|
||||
fun test() {
|
||||
try {
|
||||
@@ -79,27 +81,27 @@ class ComplexMethodSpec : Spek({
|
||||
"""
|
||||
|
||||
it("counts three with nesting function 'forEach'") {
|
||||
val config = TestConfig(mapOf(ComplexMethod.IGNORE_NESTING_FUNCTIONS to "false"))
|
||||
val config = TestConfig(defaultConfigMap.plus("ignoreNestingFunctions" to "false"))
|
||||
assertExpectedComplexityValue(code, config, expectedValue = 3)
|
||||
}
|
||||
|
||||
it("can ignore nesting functions like 'forEach'") {
|
||||
val config = TestConfig(mapOf(ComplexMethod.IGNORE_NESTING_FUNCTIONS to "true"))
|
||||
val config = TestConfig(defaultConfigMap.plus("ignoreNestingFunctions" to "true"))
|
||||
assertExpectedComplexityValue(code, config, expectedValue = 2)
|
||||
}
|
||||
|
||||
it("skips all if if the nested functions is empty") {
|
||||
val config = TestConfig(mapOf(ComplexMethod.NESTING_FUNCTIONS to ""))
|
||||
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to ""))
|
||||
assertExpectedComplexityValue(code, config, expectedValue = 2)
|
||||
}
|
||||
|
||||
it("skips 'forEach' as it is not specified") {
|
||||
val config = TestConfig(mapOf(ComplexMethod.NESTING_FUNCTIONS to "let,apply,also"))
|
||||
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to "let,apply,also"))
|
||||
assertExpectedComplexityValue(code, config, expectedValue = 2)
|
||||
}
|
||||
|
||||
it("skips 'forEach' as it is not specified list") {
|
||||
val config = TestConfig(mapOf(ComplexMethod.NESTING_FUNCTIONS to listOf("let", "apply", "also")))
|
||||
val config = TestConfig(defaultConfigMap.plus("nestingFunctions" to listOf("let", "apply", "also")))
|
||||
assertExpectedComplexityValue(code, config, expectedValue = 2)
|
||||
}
|
||||
}
|
||||
@@ -111,16 +113,18 @@ class ComplexMethodSpec : Spek({
|
||||
it("does not report complex methods with a single when expression") {
|
||||
val config = TestConfig(
|
||||
mapOf(
|
||||
ComplexMethod.IGNORE_SINGLE_WHEN_EXPRESSION to "true"
|
||||
"threshold" to "4",
|
||||
"ignoreSingleWhenExpression" to "true"
|
||||
)
|
||||
)
|
||||
val subject = ComplexMethod(config, threshold = 4)
|
||||
val subject = ComplexMethod(config)
|
||||
|
||||
assertThat(subject.lint(path)).hasSourceLocations(SourceLocation(43, 5))
|
||||
}
|
||||
|
||||
it("reports all complex methods") {
|
||||
val subject = ComplexMethod(threshold = 4)
|
||||
val config = TestConfig(mapOf("threshold" to "4"))
|
||||
val subject = ComplexMethod(config)
|
||||
|
||||
assertThat(subject.lint(path)).hasSourceLocations(
|
||||
SourceLocation(6, 5),
|
||||
@@ -132,7 +136,7 @@ class ComplexMethodSpec : Spek({
|
||||
}
|
||||
|
||||
it("does not trip for a reasonable amount of simple when entries when ignoreSimpleWhenEntries is true") {
|
||||
val config = TestConfig(mapOf(ComplexMethod.IGNORE_SIMPLE_WHEN_ENTRIES to "true"))
|
||||
val config = TestConfig(mapOf("ignoreSimpleWhenEntries" to "true"))
|
||||
val subject = ComplexMethod(config)
|
||||
val code = """
|
||||
fun f() {
|
||||
@@ -216,7 +220,7 @@ class ComplexMethodSpec : Spek({
|
||||
})
|
||||
|
||||
private fun assertExpectedComplexityValue(code: String, config: TestConfig, expectedValue: Int) {
|
||||
val findings = ComplexMethod(config, threshold = 1).lint(code)
|
||||
val findings = ComplexMethod(config).lint(code)
|
||||
|
||||
assertThat(findings).hasSourceLocations(SourceLocation(1, 5))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user