From e5112cda91390bd8ed5f4b070d0af3862d37ddd6 Mon Sep 17 00:00:00 2001 From: sanggggg Date: Wed, 19 Oct 2022 15:58:47 +0900 Subject: [PATCH 01/10] feature: add rule CognitiveComplexMethod --- .../complexity/CognitiveComplexMethod.kt | 62 +++++++++++++++++++ .../rules/complexity/ComplexityProvider.kt | 1 + 2 files changed, 63 insertions(+) create mode 100644 detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt new file mode 100644 index 00000000000..5466b9460a2 --- /dev/null +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt @@ -0,0 +1,62 @@ +package io.gitlab.arturbosch.detekt.rules.complexity + +import io.github.detekt.metrics.CognitiveComplexity +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 org.jetbrains.kotlin.psi.KtNamedFunction + + +/** + * Complex methods are hard to understand and read. It might not be obvious what side-effects a complex method has. + * Prefer splitting up complex methods into smaller methods that are in turn easier to understand. + * Smaller methods can also be named much clearer which leads to improved readability of the code. + * + * This rule measures and restricts the complexity of the method through the [Cognitive Complexity metric of Sonasource](https://www.sonarsource.com/docs/CognitiveComplexity.pdf). + * Which improves McCabe's Cyclomatic Complexity ({@link ComplexMethod}) considering the programmer's mental model. + * + * Similar to cyclomatic complexity, it is a mathematical model that increases +1 complexity for flow control statements, + * but increases additional complexity when the statements are deeply nested. + * + * The statements that increase the complexity or the nesting level are as follows. + * - __Complexity Increments__ - `if`, `when`, `for`, `while`, `do while`, `catch`, `labeled break`, `labeled continue`, `labeled return`, `recursion call`, `&&`, `||` + * - __Nesting Level Increments__ - `if`, `when`, `for`, `while`, `do while`, `catch`, `nested function` + * - __Additional Complexity Increments by Nesting Level__ - `if`, `when`, `for`, `while`, `do while`, `catch` + */ +@ActiveByDefault(since = "1.22.0") +class CognitiveComplexMethod(config: Config = Config.empty) : Rule(config) { + + override val issue = Issue( + "CognitiveComplexMethod", + Severity.Maintainability, + "Prefer splitting up complex methods into smaller, easier to understand methods.", + Debt.TWENTY_MINS + ) + + @Configuration("Cognitive Complexity number for a method.") + private val threshold: Int by config(defaultValue = 15) + + override fun visitNamedFunction(function: KtNamedFunction) { + val complexity = CognitiveComplexity.calculate(function) + + if (complexity >= threshold) { + report( + ThresholdedCodeSmell( + issue, + Entity.atName(function), + Metric("CC", complexity, threshold), + "The function ${function.nameAsSafeName} appears to be too complex based on Cognitive Complexity (complexity: $complexity). " + + "Defined complexity threshold for methods is set to '$threshold'" + ) + ) + } + } +} diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexityProvider.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexityProvider.kt index ff00e730793..5ff8390dfa3 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexityProvider.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexityProvider.kt @@ -21,6 +21,7 @@ class ComplexityProvider : DefaultRuleSetProvider { LargeClass(config), ComplexInterface(config), ComplexMethod(config), + CognitiveComplexMethod(config), StringLiteralDuplication(config), MethodOverloading(config), NestedBlockDepth(config), From d71cc8eb00cc6384a0dd1ca507e6254454cb3a22 Mon Sep 17 00:00:00 2001 From: sanggggg Date: Wed, 19 Oct 2022 17:00:04 +0900 Subject: [PATCH 02/10] test: add test for CognitiveComplexMethod rule --- .../complexity/CognitiveComplexMethodSpec.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt diff --git a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt new file mode 100644 index 00000000000..75fe962bbf0 --- /dev/null +++ b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt @@ -0,0 +1,38 @@ +package io.gitlab.arturbosch.detekt.rules.complexity + +import io.gitlab.arturbosch.detekt.api.SourceLocation +import io.gitlab.arturbosch.detekt.test.TestConfig +import io.gitlab.arturbosch.detekt.test.assertThat +import io.gitlab.arturbosch.detekt.test.compileAndLint +import io.gitlab.arturbosch.detekt.test.isThresholded +import org.junit.jupiter.api.Test + +class CognitiveComplexMethodSpec { + val code = """ + fun sumOfPrimes(max: Int): Int { // total cognitive complexity is 7 + var total = 0 + next@ for (i in 1..max) { // +1 + for (j in 2 until i) { // +1 +1 + if (i % j == 0) { // +1 +2 + continue@next // +1 + } + } + println(i) + total++ + } + return total + } + + fun simpleTest(): Boolean { + return true + } + """.trimIndent() + + @Test + fun `should report complex function`() { + val findings = CognitiveComplexMethod(TestConfig("threshold" to "1")).compileAndLint(code) + assertThat(findings).hasSize(1) + assertThat(findings).hasStartSourceLocations(SourceLocation(1, 5)) + assertThat(findings.first()).isThresholded().withValue(7) + } +} From 74749ba1bfae1d26c96e700cb19c43d93afed8a3 Mon Sep 17 00:00:00 2001 From: sanggggg Date: Wed, 19 Oct 2022 17:30:59 +0900 Subject: [PATCH 03/10] move: ComplexMethod -> CyclomaticComplexMethod --- .../rules/complexity/ComplexityProvider.kt | 2 +- ...lexMethod.kt => CyclomaticComplexMethod.kt} | 12 ++++++------ ...dSpec.kt => CyclomaticComplexMethodSpec.kt} | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) rename detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/{ComplexMethod.kt => CyclomaticComplexMethod.kt} (90%) rename detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/{ComplexMethodSpec.kt => CyclomaticComplexMethodSpec.kt} (92%) diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexityProvider.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexityProvider.kt index 5ff8390dfa3..b021e737577 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexityProvider.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexityProvider.kt @@ -20,7 +20,7 @@ class ComplexityProvider : DefaultRuleSetProvider { LongMethod(config), LargeClass(config), ComplexInterface(config), - ComplexMethod(config), + CyclomaticComplexMethod(config), CognitiveComplexMethod(config), StringLiteralDuplication(config), MethodOverloading(config), diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexMethod.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt similarity index 90% rename from detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexMethod.kt rename to detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt index 4e271bbec2f..3d727d70197 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexMethod.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt @@ -38,10 +38,10 @@ import org.jetbrains.kotlin.psi.KtWhenExpression * [Reference](https://kotlinlang.org/docs/scope-functions.html) */ @ActiveByDefault(since = "1.0.0") -class ComplexMethod(config: Config = Config.empty) : Rule(config) { +class CyclomaticComplexMethod(config: Config = Config.empty) : Rule(config) { override val issue = Issue( - "ComplexMethod", + "CyclomaticComplexMethod", Severity.Maintainability, "Prefer splitting up complex methods into smaller, easier to understand methods.", Debt.TWENTY_MINS @@ -68,9 +68,9 @@ class ComplexMethod(config: Config = Config.empty) : Rule(config) { } val complexity = CyclomaticComplexity.calculate(function) { - this.ignoreSimpleWhenEntries = this@ComplexMethod.ignoreSimpleWhenEntries - this.ignoreNestingFunctions = this@ComplexMethod.ignoreNestingFunctions - this.nestingFunctions = this@ComplexMethod.nestingFunctions + this.ignoreSimpleWhenEntries = this@CyclomaticComplexMethod.ignoreSimpleWhenEntries + this.ignoreNestingFunctions = this@CyclomaticComplexMethod.ignoreNestingFunctions + this.nestingFunctions = this@CyclomaticComplexMethod.nestingFunctions } if (complexity >= threshold) { @@ -79,7 +79,7 @@ class ComplexMethod(config: Config = Config.empty) : Rule(config) { issue, Entity.atName(function), Metric("MCC", complexity, threshold), - "The function ${function.nameAsSafeName} appears to be too complex ($complexity). " + + "The function ${function.nameAsSafeName} appears to be too complex based on Cyclomatic Complexity (complexity: $complexity). " + "Defined complexity threshold for methods is set to '$threshold'" ) ) diff --git a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexMethodSpec.kt b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethodSpec.kt similarity index 92% rename from detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexMethodSpec.kt rename to detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethodSpec.kt index 744f94da651..a306ea5478b 100644 --- a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexMethodSpec.kt +++ b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethodSpec.kt @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test private val defaultConfigMap: Map = mapOf("threshold" to "1") -class ComplexMethodSpec { +class CyclomaticComplexMethodSpec { val defaultComplexity = 1 @@ -21,7 +21,7 @@ class ComplexMethodSpec { @Test fun `counts different loops`() { - val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint( + val findings = CyclomaticComplexMethod(TestConfig(defaultConfigMap)).compileAndLint( """ fun test() { for (i in 1..10) {} @@ -37,7 +37,7 @@ class ComplexMethodSpec { @Test fun `counts catch blocks`() { - val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint( + val findings = CyclomaticComplexMethod(TestConfig(defaultConfigMap)).compileAndLint( """ fun test() { try {} catch(e: IllegalArgumentException) {} catch(e: Exception) {} finally {} @@ -50,7 +50,7 @@ class ComplexMethodSpec { @Test fun `counts nested conditional statements`() { - val findings = ComplexMethod(TestConfig(defaultConfigMap)).compileAndLint( + val findings = CyclomaticComplexMethod(TestConfig(defaultConfigMap)).compileAndLint( """ fun test() { try { @@ -127,7 +127,7 @@ class ComplexMethodSpec { "ignoreSingleWhenExpression" to "true" ) ) - val subject = ComplexMethod(config) + val subject = CyclomaticComplexMethod(config) assertThat(subject.lint(path)).hasStartSourceLocations(SourceLocation(43, 5)) } @@ -135,7 +135,7 @@ class ComplexMethodSpec { @Test fun `reports all complex methods`() { val config = TestConfig(mapOf("threshold" to "4")) - val subject = ComplexMethod(config) + val subject = CyclomaticComplexMethod(config) assertThat(subject.lint(path)).hasStartSourceLocations( SourceLocation(6, 5), @@ -149,7 +149,7 @@ class ComplexMethodSpec { @Test fun `does not trip for a reasonable amount of simple when entries when ignoreSimpleWhenEntries is true`() { val config = TestConfig(mapOf("ignoreSimpleWhenEntries" to "true")) - val subject = ComplexMethod(config) + val subject = CyclomaticComplexMethod(config) val code = """ fun f() { val map = HashMap() @@ -227,13 +227,13 @@ class ComplexMethodSpec { @Test fun `should not count these overridden functions to base functions complexity`() { - assertThat(ComplexMethod().compileAndLint(code)).isEmpty() + assertThat(CyclomaticComplexMethod().compileAndLint(code)).isEmpty() } } } private fun assertExpectedComplexityValue(code: String, config: TestConfig, expectedValue: Int) { - val findings = ComplexMethod(config).lint(code) + val findings = CyclomaticComplexMethod(config).lint(code) assertThat(findings).hasStartSourceLocations(SourceLocation(1, 5)) From 812b11966df81c4e0f735937aed11ce2ffb82f3c Mon Sep 17 00:00:00 2001 From: sanggggg Date: Wed, 19 Oct 2022 17:33:36 +0900 Subject: [PATCH 04/10] fix: remove referencing ComplexMethod --- detekt-core/src/main/resources/default-detekt-config.yml | 5 ++++- detekt-core/src/main/resources/deprecation.properties | 1 + .../arturbosch/detekt/generator/printer/DeprecatedPrinter.kt | 1 + .../detekt/rules/complexity/CognitiveComplexMethod.kt | 2 +- .../arturbosch/detekt/rules/style/RedundantExplicitType.kt | 2 +- .../arturbosch/detekt/rules/style/UnusedPrivateMember.kt | 2 +- .../io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt | 2 +- .../arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt | 2 +- 8 files changed, 11 insertions(+), 6 deletions(-) diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml index 0a176a33aaf..27f6d8911c6 100644 --- a/detekt-core/src/main/resources/default-detekt-config.yml +++ b/detekt-core/src/main/resources/default-detekt-config.yml @@ -91,6 +91,9 @@ comments: complexity: active: true + CognitiveComplexMethod: + active: true + threshold: 15 ComplexCondition: active: true threshold: 4 @@ -100,7 +103,7 @@ complexity: includeStaticDeclarations: false includePrivateDeclarations: false ignoreOverloaded: false - ComplexMethod: + CyclomaticComplexMethod: active: true threshold: 15 ignoreSingleWhenExpression: false diff --git a/detekt-core/src/main/resources/deprecation.properties b/detekt-core/src/main/resources/deprecation.properties index 6fcf5c93d11..48edd262890 100644 --- a/detekt-core/src/main/resources/deprecation.properties +++ b/detekt-core/src/main/resources/deprecation.properties @@ -16,3 +16,4 @@ formatting>ParameterListWrapping>indentSize=`indentSize` is ignored by KtLint an style>ForbiddenPublicDataClass=Rule migrated to `libraries` ruleset plugin style>LibraryCodeMustSpecifyReturnType=Rule migrated to `libraries` ruleset plugin style>LibraryEntitiesShouldNotBePublic=Rule migrated to `libraries` ruleset plugin +complexity>ComplexMethod=Rule is renamed to `CyclomaticComplexMethod` to distinguish between Cyclomatic Complexity and Cognitive Complexity diff --git a/detekt-generator/src/main/kotlin/io/gitlab/arturbosch/detekt/generator/printer/DeprecatedPrinter.kt b/detekt-generator/src/main/kotlin/io/gitlab/arturbosch/detekt/generator/printer/DeprecatedPrinter.kt index 1acf5bf9ff7..ce963abcb80 100644 --- a/detekt-generator/src/main/kotlin/io/gitlab/arturbosch/detekt/generator/printer/DeprecatedPrinter.kt +++ b/detekt-generator/src/main/kotlin/io/gitlab/arturbosch/detekt/generator/printer/DeprecatedPrinter.kt @@ -40,5 +40,6 @@ internal fun writeMigratedRules(): String { style>ForbiddenPublicDataClass=Rule migrated to `libraries` ruleset plugin style>LibraryCodeMustSpecifyReturnType=Rule migrated to `libraries` ruleset plugin style>LibraryEntitiesShouldNotBePublic=Rule migrated to `libraries` ruleset plugin + complexity>ComplexMethod=Rule is renamed to `CyclomaticComplexMethod` to distinguish between Cyclomatic Complexity and Cognitive Complexity """.trimIndent() } diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt index 5466b9460a2..26731d7e988 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.psi.KtNamedFunction * Smaller methods can also be named much clearer which leads to improved readability of the code. * * This rule measures and restricts the complexity of the method through the [Cognitive Complexity metric of Sonasource](https://www.sonarsource.com/docs/CognitiveComplexity.pdf). - * Which improves McCabe's Cyclomatic Complexity ({@link ComplexMethod}) considering the programmer's mental model. + * Which improves McCabe's Cyclomatic Complexity ({@link CyclomaticComplexMethod}) considering the programmer's mental model. * * Similar to cyclomatic complexity, it is a mathematical model that increases +1 complexity for flow control statements, * but increases additional complexity when the statements are deeply nested. diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/RedundantExplicitType.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/RedundantExplicitType.kt index 5c709b2898b..de0cdd6b859 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/RedundantExplicitType.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/RedundantExplicitType.kt @@ -54,7 +54,7 @@ class RedundantExplicitType(config: Config) : Rule(config) { Debt.FIVE_MINS ) - @Suppress("ReturnCount", "ComplexMethod") + @Suppress("ReturnCount", "CyclomaticComplexMethod") override fun visitProperty(property: KtProperty) { if (!property.isLocal) return val typeReference = property.typeReference ?: return diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt index 7e0e454cb60..4555a722b85 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt @@ -99,7 +99,7 @@ private class UnusedFunctionVisitor( private val functionReferences = mutableMapOf>() private val propertyDelegates = mutableListOf() - @Suppress("ComplexMethod") + @Suppress("CyclomaticComplexMethod") override fun getUnusedReports(issue: Issue): List { val propertyDelegateResultingDescriptors by lazy(LazyThreadSafetyMode.NONE) { propertyDelegates.flatMap { it.resultingDescriptors() } diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt index 365cd0989e3..a15d31adb84 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt @@ -74,7 +74,7 @@ class UseDataClass(config: Config = Config.empty) : Rule(config) { root.forEachDescendantOfType { visitKlass(it, annotationExcluder) } } - @Suppress("ComplexMethod") + @Suppress("CyclomaticComplexMethod") private fun visitKlass(klass: KtClass, annotationExcluder: AnnotationExcluder) { if (isIncorrectClassType(klass) || hasOnlyPrivateConstructors(klass)) { return diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt index 7ea3826042f..9f6250bffa2 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt @@ -47,7 +47,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull * */ @RequiresTypeResolution -@Suppress("ReturnCount", "ComplexMethod") +@Suppress("ReturnCount", "CyclomaticComplexMethod") class UseIfEmptyOrIfBlank(config: Config = Config.empty) : Rule(config) { override val issue: Issue = Issue( "UseIfEmptyOrIfBlank", From 7609ec46978f3d81bd03dccafb45aae90207a4e8 Mon Sep 17 00:00:00 2001 From: sanggggg Date: Wed, 19 Oct 2022 17:48:22 +0900 Subject: [PATCH 05/10] fix: detekt correct --- config/detekt/detekt.yml | 2 +- .../detekt/rules/complexity/CognitiveComplexMethod.kt | 4 ++-- .../detekt/rules/complexity/CyclomaticComplexMethod.kt | 3 ++- .../detekt/rules/complexity/CognitiveComplexMethodSpec.kt | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/config/detekt/detekt.yml b/config/detekt/detekt.yml index e186770584d..901df76e90b 100644 --- a/config/detekt/detekt.yml +++ b/config/detekt/detekt.yml @@ -23,7 +23,7 @@ complexity: threshold: 10 includeStaticDeclarations: false includePrivateDeclarations: false - ComplexMethod: + CyclomaticComplexMethod: active: true ignoreSingleWhenExpression: true LargeClass: diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt index 26731d7e988..e5f837ac45f 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt @@ -14,7 +14,6 @@ import io.gitlab.arturbosch.detekt.api.internal.ActiveByDefault import io.gitlab.arturbosch.detekt.api.internal.Configuration import org.jetbrains.kotlin.psi.KtNamedFunction - /** * Complex methods are hard to understand and read. It might not be obvious what side-effects a complex method has. * Prefer splitting up complex methods into smaller methods that are in turn easier to understand. @@ -53,7 +52,8 @@ class CognitiveComplexMethod(config: Config = Config.empty) : Rule(config) { issue, Entity.atName(function), Metric("CC", complexity, threshold), - "The function ${function.nameAsSafeName} appears to be too complex based on Cognitive Complexity (complexity: $complexity). " + + "The function ${function.nameAsSafeName} appears to be too complex " + + "based on Cognitive Complexity (complexity: $complexity). " + "Defined complexity threshold for methods is set to '$threshold'" ) ) diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt index 3d727d70197..12320076404 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt @@ -79,7 +79,8 @@ class CyclomaticComplexMethod(config: Config = Config.empty) : Rule(config) { issue, Entity.atName(function), Metric("MCC", complexity, threshold), - "The function ${function.nameAsSafeName} appears to be too complex based on Cyclomatic Complexity (complexity: $complexity). " + + "The function ${function.nameAsSafeName} appears to be too complex " + + "based on Cyclomatic Complexity (complexity: $complexity). " + "Defined complexity threshold for methods is set to '$threshold'" ) ) diff --git a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt index 75fe962bbf0..4387c0cb50c 100644 --- a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt +++ b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt @@ -26,7 +26,7 @@ class CognitiveComplexMethodSpec { fun simpleTest(): Boolean { return true } - """.trimIndent() + """.trimIndent() @Test fun `should report complex function`() { From cde13419fdf1a3bbaca45d90a9faeca78fdc22cd Mon Sep 17 00:00:00 2001 From: sanggggg Date: Wed, 19 Oct 2022 19:41:00 +0900 Subject: [PATCH 06/10] fix: set alias --- .../detekt/rules/complexity/CyclomaticComplexMethod.kt | 2 ++ .../arturbosch/detekt/rules/style/RedundantExplicitType.kt | 2 +- .../gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt | 2 +- .../io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt | 2 +- .../gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt index 12320076404..97167523896 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt @@ -47,6 +47,8 @@ class CyclomaticComplexMethod(config: Config = Config.empty) : Rule(config) { Debt.TWENTY_MINS ) + override val defaultRuleIdAliases: Set = setOf("ComplexMethod") + @Configuration("McCabe's Cyclomatic Complexity (MCC) number for a method.") private val threshold: Int by config(defaultValue = 15) diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/RedundantExplicitType.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/RedundantExplicitType.kt index de0cdd6b859..5c709b2898b 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/RedundantExplicitType.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/RedundantExplicitType.kt @@ -54,7 +54,7 @@ class RedundantExplicitType(config: Config) : Rule(config) { Debt.FIVE_MINS ) - @Suppress("ReturnCount", "CyclomaticComplexMethod") + @Suppress("ReturnCount", "ComplexMethod") override fun visitProperty(property: KtProperty) { if (!property.isLocal) return val typeReference = property.typeReference ?: return diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt index 4555a722b85..7e0e454cb60 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UnusedPrivateMember.kt @@ -99,7 +99,7 @@ private class UnusedFunctionVisitor( private val functionReferences = mutableMapOf>() private val propertyDelegates = mutableListOf() - @Suppress("CyclomaticComplexMethod") + @Suppress("ComplexMethod") override fun getUnusedReports(issue: Issue): List { val propertyDelegateResultingDescriptors by lazy(LazyThreadSafetyMode.NONE) { propertyDelegates.flatMap { it.resultingDescriptors() } diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt index a15d31adb84..365cd0989e3 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseDataClass.kt @@ -74,7 +74,7 @@ class UseDataClass(config: Config = Config.empty) : Rule(config) { root.forEachDescendantOfType { visitKlass(it, annotationExcluder) } } - @Suppress("CyclomaticComplexMethod") + @Suppress("ComplexMethod") private fun visitKlass(klass: KtClass, annotationExcluder: AnnotationExcluder) { if (isIncorrectClassType(klass) || hasOnlyPrivateConstructors(klass)) { return diff --git a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt index 9f6250bffa2..7ea3826042f 100644 --- a/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt +++ b/detekt-rules-style/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/style/UseIfEmptyOrIfBlank.kt @@ -47,7 +47,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull * */ @RequiresTypeResolution -@Suppress("ReturnCount", "CyclomaticComplexMethod") +@Suppress("ReturnCount", "ComplexMethod") class UseIfEmptyOrIfBlank(config: Config = Config.empty) : Rule(config) { override val issue: Issue = Issue( "UseIfEmptyOrIfBlank", From 4545cd729781d94f8d8dc219f2e6b3add1c278a4 Mon Sep 17 00:00:00 2001 From: sanggggg Date: Wed, 19 Oct 2022 23:02:21 +0900 Subject: [PATCH 07/10] config: deactivate default cognitive complex method rule --- detekt-core/src/main/resources/default-detekt-config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml index 27f6d8911c6..5dfcfa6afbf 100644 --- a/detekt-core/src/main/resources/default-detekt-config.yml +++ b/detekt-core/src/main/resources/default-detekt-config.yml @@ -92,8 +92,7 @@ comments: complexity: active: true CognitiveComplexMethod: - active: true - threshold: 15 + active: false ComplexCondition: active: true threshold: 4 From b7bd1b08bc5bef29a1c1f56838fa0aae498ae473 Mon Sep 17 00:00:00 2001 From: sanggggg Date: Wed, 19 Oct 2022 23:08:29 +0900 Subject: [PATCH 08/10] test: add positive and negative test cases --- .../complexity/CognitiveComplexMethodSpec.kt | 49 +++++++++++-------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt index 4387c0cb50c..090b4b4972c 100644 --- a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt +++ b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethodSpec.kt @@ -8,31 +8,40 @@ import io.gitlab.arturbosch.detekt.test.isThresholded import org.junit.jupiter.api.Test class CognitiveComplexMethodSpec { - val code = """ - fun sumOfPrimes(max: Int): Int { // total cognitive complexity is 7 - var total = 0 - next@ for (i in 1..max) { // +1 - for (j in 2 until i) { // +1 +1 - if (i % j == 0) { // +1 +2 - continue@next // +1 - } - } - println(i) - total++ - } - return total - } - - fun simpleTest(): Boolean { - return true - } - """.trimIndent() + + private val testConfig = TestConfig("threshold" to "1") @Test fun `should report complex function`() { - val findings = CognitiveComplexMethod(TestConfig("threshold" to "1")).compileAndLint(code) + val code = """ + fun sumOfPrimes(max: Int): Int { // total cognitive complexity is 7 + var total = 0 + next@ for (i in 1..max) { // +1 + for (j in 2 until i) { // +1 +1 + if (i % j == 0) { // +1 +2 + continue@next // +1 + } + } + println(i) + total++ + } + return total + } + """.trimIndent() + val findings = CognitiveComplexMethod(testConfig).compileAndLint(code) assertThat(findings).hasSize(1) assertThat(findings).hasStartSourceLocations(SourceLocation(1, 5)) assertThat(findings.first()).isThresholded().withValue(7) } + + @Test + fun `should not report simple function`() { + val code = """ + fun add(a: Int, b: Int): Int { // total cognitive complexity is 0 + return a + b + } + """.trimIndent() + val findings = CognitiveComplexMethod(testConfig).compileAndLint(code) + assertThat(findings).isEmpty() + } } From 236d3ceaefa4c5af93b19e68838c1d0a2f11b8f4 Mon Sep 17 00:00:00 2001 From: sanggggg <37951125+sanggggg@users.noreply.github.com> Date: Thu, 20 Oct 2022 10:03:23 +0900 Subject: [PATCH 09/10] Update detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Brais GabĂ­n --- .../detekt/rules/complexity/CyclomaticComplexMethod.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt index 97167523896..59a44a33a39 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CyclomaticComplexMethod.kt @@ -43,7 +43,7 @@ class CyclomaticComplexMethod(config: Config = Config.empty) : Rule(config) { override val issue = Issue( "CyclomaticComplexMethod", Severity.Maintainability, - "Prefer splitting up complex methods into smaller, easier to understand methods.", + "Prefer splitting up complex methods into smaller, easier to test methods.", Debt.TWENTY_MINS ) From 2a0ae60359ff57cbe70c537710ebaf6de548214f Mon Sep 17 00:00:00 2001 From: sanggggg Date: Thu, 20 Oct 2022 09:56:00 +0900 Subject: [PATCH 10/10] fix: remove conflicting activeByDefault and sync --- detekt-core/src/main/resources/default-detekt-config.yml | 1 + .../detekt/rules/complexity/CognitiveComplexMethod.kt | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml index 5dfcfa6afbf..fd1a9102d20 100644 --- a/detekt-core/src/main/resources/default-detekt-config.yml +++ b/detekt-core/src/main/resources/default-detekt-config.yml @@ -93,6 +93,7 @@ complexity: active: true CognitiveComplexMethod: active: false + threshold: 15 ComplexCondition: active: true threshold: 4 diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt index e5f837ac45f..6211aa595d0 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/CognitiveComplexMethod.kt @@ -10,7 +10,6 @@ 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 org.jetbrains.kotlin.psi.KtNamedFunction @@ -30,7 +29,6 @@ import org.jetbrains.kotlin.psi.KtNamedFunction * - __Nesting Level Increments__ - `if`, `when`, `for`, `while`, `do while`, `catch`, `nested function` * - __Additional Complexity Increments by Nesting Level__ - `if`, `when`, `for`, `while`, `do while`, `catch` */ -@ActiveByDefault(since = "1.22.0") class CognitiveComplexMethod(config: Config = Config.empty) : Rule(config) { override val issue = Issue(