Add cognitive complexity in complexity report (#2727)

* Add metric processor for cognitive complexity

* Add cognitive complexity to complexity report
This commit is contained in:
Artur Bosch
2020-05-25 12:20:58 +02:00
committed by GitHub
parent ef02257694
commit 4e65b86cd0
11 changed files with 116 additions and 10 deletions

View File

@@ -1,5 +1,6 @@
package io.gitlab.arturbosch.detekt.cli.console
import io.github.detekt.metrics.CognitiveComplexity
import io.gitlab.arturbosch.detekt.api.Detektion
import io.github.detekt.metrics.processors.commentLinesKey
import io.github.detekt.metrics.processors.complexityKey
@@ -10,6 +11,7 @@ import io.github.detekt.metrics.processors.sourceLinesKey
class ComplexityMetric(detektion: Detektion) {
val mcc = detektion.getData(complexityKey)
val cognitiveComplexity = detektion.getData(CognitiveComplexity.KEY)
val loc = detektion.getData(linesKey)
val sloc = detektion.getData(sourceLinesKey)
val lloc = detektion.getData(logicalLinesKey)

View File

@@ -22,7 +22,8 @@ class ComplexityReportGenerator(private val complexityMetric: ComplexityMetric)
"%,d source lines of code (sloc)".format(Locale.US, complexityMetric.sloc),
"%,d logical lines of code (lloc)".format(Locale.US, complexityMetric.lloc),
"%,d comment lines of code (cloc)".format(Locale.US, complexityMetric.cloc),
"%,d McCabe complexity (mcc)".format(Locale.US, complexityMetric.mcc),
"%,d cyclomatic complexity (mcc)".format(Locale.US, complexityMetric.mcc),
"%,d cognitive complexity".format(Locale.US, complexityMetric.cognitiveComplexity),
"%,d number of total code smells".format(Locale.US, numberOfSmells),
"%,d%% comment source ratio".format(Locale.US, commentSourceRatio),
"%,d mcc per 1,000 lloc".format(Locale.US, mccPerThousandLines),
@@ -32,15 +33,18 @@ class ComplexityReportGenerator(private val complexityMetric: ComplexityMetric)
private fun cannotGenerate(): Boolean {
return when {
complexityMetric.mcc == null -> true
null in setOf(
complexityMetric.mcc,
complexityMetric.cloc,
complexityMetric.cognitiveComplexity
) -> true
complexityMetric.lloc == null || complexityMetric.lloc == 0 -> true
complexityMetric.sloc == null || complexityMetric.sloc == 0 -> true
complexityMetric.cloc == null -> true
else -> {
numberOfSmells = complexityMetric.findings.sumBy { it.value.size }
smellPerThousandLines = numberOfSmells * 1000 / complexityMetric.lloc
mccPerThousandLines = complexityMetric.mcc * 1000 / complexityMetric.lloc
commentSourceRatio = complexityMetric.cloc * 100 / complexityMetric.sloc
mccPerThousandLines = complexityMetric.mcc!! * 1000 / complexityMetric.lloc
commentSourceRatio = complexityMetric.cloc!! * 100 / complexityMetric.sloc
false
}
}

View File

@@ -5,6 +5,7 @@ io.github.detekt.metrics.processors.ClassCountProcessor
io.github.detekt.metrics.processors.FunctionCountProcessor
io.github.detekt.metrics.processors.PropertyCountProcessor
io.github.detekt.metrics.processors.ProjectComplexityProcessor
io.github.detekt.metrics.processors.ProjectCognitiveComplexityProcessor
io.github.detekt.metrics.processors.ProjectLLOCProcessor
io.github.detekt.metrics.processors.ProjectCLOCProcessor
io.github.detekt.metrics.processors.ProjectLOCProcessor

View File

@@ -1,5 +1,6 @@
package io.gitlab.arturbosch.detekt.cli.console
import io.github.detekt.metrics.CognitiveComplexity
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.cli.createFinding
import io.github.detekt.metrics.processors.commentLinesKey
@@ -31,7 +32,8 @@ internal class ComplexityReportGeneratorSpec : Spek({
"6 source lines of code (sloc)",
"5 logical lines of code (lloc)",
"4 comment lines of code (cloc)",
"2 McCabe complexity (mcc)",
"2 cyclomatic complexity (mcc)",
"2 cognitive complexity",
"1 number of total code smells",
"66% comment source ratio",
"400 mcc per 1,000 lloc",
@@ -75,6 +77,7 @@ internal class ComplexityReportGeneratorSpec : Spek({
private fun addData(detektion: Detektion) {
detektion.addData(complexityKey, 2)
detektion.addData(CognitiveComplexity.KEY, 2)
detektion.addData(linesKey, 1000)
detektion.addData(sourceLinesKey, 6)
detektion.addData(logicalLinesKey, 5)

View File

@@ -1,5 +1,6 @@
package io.gitlab.arturbosch.detekt.cli.console
import io.github.detekt.metrics.CognitiveComplexity
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.cli.createFinding
import io.gitlab.arturbosch.detekt.core.DetektResult
@@ -23,9 +24,7 @@ internal class ComplexityReportSpec : Spek({
val expectedContent = readResource("complexity-report.txt")
val detektion = createDetektion()
addData(detektion)
// Casting expectedContent to Any is workaround for
// https://github.com/joel-costigliola/assertj-core/issues/1440#issuecomment-465032464
assertThat(report.render(detektion)).isEqualTo(expectedContent as Any)
assertThat(report.render(detektion)).isEqualTo(expectedContent)
}
it("returns null for missing complexity metrics in report") {
@@ -41,6 +40,7 @@ private fun createDetektion(): Detektion = DetektResult(mapOf(Pair("Key", listOf
private fun addData(detektion: Detektion) {
detektion.addData(complexityKey, 2)
detektion.addData(CognitiveComplexity.KEY, 2)
detektion.addData(linesKey, 10)
detektion.addData(sourceLinesKey, 6)
detektion.addData(logicalLinesKey, 5)

View File

@@ -1,5 +1,6 @@
package io.gitlab.arturbosch.detekt.cli.out
import io.github.detekt.metrics.CognitiveComplexity
import io.github.detekt.metrics.processors.commentLinesKey
import io.github.detekt.metrics.processors.complexityKey
import io.github.detekt.metrics.processors.linesKey
@@ -114,6 +115,7 @@ class HtmlOutputReportSpec : Spek({
it("renders the complexity report correctly") {
val detektion = TestDetektion()
detektion.addData(complexityKey, 10)
detektion.addData(CognitiveComplexity.KEY, 10)
detektion.addData(sourceLinesKey, 20)
detektion.addData(logicalLinesKey, 10)
detektion.addData(commentLinesKey, 2)

View File

@@ -3,7 +3,8 @@ Complexity Report:
- 6 source lines of code (sloc)
- 5 logical lines of code (lloc)
- 4 comment lines of code (cloc)
- 2 McCabe complexity (mcc)
- 2 cyclomatic complexity (mcc)
- 2 cognitive complexity
- 1 number of total code smells
- 66% comment source ratio
- 400 mcc per 1,000 lloc

View File

@@ -1,6 +1,7 @@
package io.github.detekt.metrics
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtBinaryExpression
@@ -175,6 +176,8 @@ class CognitiveComplexity private constructor() : DetektVisitor() {
companion object {
val KEY = Key<Int>("detekt.metrics.cognitive_complexity")
private val logicalOps = setOf(KtTokens.ANDAND, KtTokens.OROR)
fun calculate(element: KtElement): Int {

View File

@@ -0,0 +1,18 @@
package io.github.detekt.metrics.processors
import io.github.detekt.metrics.CognitiveComplexity
import io.gitlab.arturbosch.detekt.api.DetektVisitor
import org.jetbrains.kotlin.psi.KtFile
class ProjectCognitiveComplexityProcessor : AbstractProcessor() {
override val visitor = object : DetektVisitor() {
override fun visitKtFile(file: KtFile) {
val complexity = CognitiveComplexity.calculate(file)
file.putUserData(CognitiveComplexity.KEY, complexity)
}
}
override val key = CognitiveComplexity.KEY
}

View File

@@ -0,0 +1,22 @@
package io.github.detekt.metrics.processors
import io.github.detekt.metrics.CognitiveComplexity
import io.github.detekt.test.utils.compileContentForTest
import org.assertj.core.api.Assertions.assertThat
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
class CognitiveComplexityProcessorSpec : Spek({
describe("CognitiveComplexityProcessor") {
it("counts the complexity for the whole file") {
val file = compileContentForTest(complexClass)
val value = MetricProcessorTester(file)
.test(ProjectCognitiveComplexityProcessor(), CognitiveComplexity.KEY)
assertThat(value).isEqualTo(46)
}
}
})

View File

@@ -0,0 +1,50 @@
package io.github.detekt.metrics.processors
import io.gitlab.arturbosch.detekt.api.Detektion
import io.gitlab.arturbosch.detekt.api.Finding
import io.gitlab.arturbosch.detekt.api.Notification
import io.gitlab.arturbosch.detekt.api.ProjectMetric
import io.gitlab.arturbosch.detekt.api.RuleSetId
import org.jetbrains.kotlin.com.intellij.openapi.util.Key
import org.jetbrains.kotlin.com.intellij.util.keyFMap.KeyFMap
import org.jetbrains.kotlin.psi.KtFile
class MetricProcessorTester(
private val file: KtFile,
private val result: Detektion = MetricResults()
) {
fun <T : Any> test(processor: AbstractProcessor, key: Key<T>): T {
with(processor) {
onStart(listOf(file))
onProcess(file)
onProcessComplete(file, emptyMap())
onFinish(listOf(file), result)
}
return checkNotNull(result.getData(key))
}
}
private class MetricResults : Detektion {
override val findings: Map<RuleSetId, List<Finding>>
get() = throw UnsupportedOperationException()
override val notifications: Collection<Notification>
get() = throw UnsupportedOperationException()
override val metrics: MutableList<ProjectMetric> = mutableListOf()
private var data = KeyFMap.EMPTY_MAP
override fun <V> getData(key: Key<V>): V? = data.get(key)
override fun <V> addData(key: Key<V>, value: V) {
data = data.plus(key, value)
}
override fun add(notification: Notification) {
throw UnsupportedOperationException()
}
override fun add(projectMetric: ProjectMetric) {
metrics.add(projectMetric)
}
}