diff --git a/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityMetric.kt b/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityMetric.kt index 431e715e7..f86393c6c 100644 --- a/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityMetric.kt +++ b/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityMetric.kt @@ -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) diff --git a/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportGenerator.kt b/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportGenerator.kt index 8d7e883cf..75f581d1b 100644 --- a/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportGenerator.kt +++ b/detekt-cli/src/main/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportGenerator.kt @@ -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 } } diff --git a/detekt-cli/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.FileProcessListener b/detekt-cli/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.FileProcessListener index 73e9e3e28..1601e272c 100644 --- a/detekt-cli/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.FileProcessListener +++ b/detekt-cli/src/main/resources/META-INF/services/io.gitlab.arturbosch.detekt.api.FileProcessListener @@ -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 diff --git a/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportGeneratorSpec.kt b/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportGeneratorSpec.kt index 85fdda45b..591f7e0a2 100644 --- a/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportGeneratorSpec.kt +++ b/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportGeneratorSpec.kt @@ -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) diff --git a/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportSpec.kt b/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportSpec.kt index 2812b692d..cb92228b4 100644 --- a/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportSpec.kt +++ b/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/console/ComplexityReportSpec.kt @@ -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) diff --git a/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/out/HtmlOutputReportSpec.kt b/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/out/HtmlOutputReportSpec.kt index f6041e82f..0895bb52c 100644 --- a/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/out/HtmlOutputReportSpec.kt +++ b/detekt-cli/src/test/kotlin/io/gitlab/arturbosch/detekt/cli/out/HtmlOutputReportSpec.kt @@ -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) diff --git a/detekt-cli/src/test/resources/complexity-report.txt b/detekt-cli/src/test/resources/complexity-report.txt index 6621ad9f8..bff106d76 100644 --- a/detekt-cli/src/test/resources/complexity-report.txt +++ b/detekt-cli/src/test/resources/complexity-report.txt @@ -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 diff --git a/detekt-metrics/src/main/kotlin/io/github/detekt/metrics/CognitiveComplexity.kt b/detekt-metrics/src/main/kotlin/io/github/detekt/metrics/CognitiveComplexity.kt index d12e6b1b9..78a1bddb8 100644 --- a/detekt-metrics/src/main/kotlin/io/github/detekt/metrics/CognitiveComplexity.kt +++ b/detekt-metrics/src/main/kotlin/io/github/detekt/metrics/CognitiveComplexity.kt @@ -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("detekt.metrics.cognitive_complexity") + private val logicalOps = setOf(KtTokens.ANDAND, KtTokens.OROR) fun calculate(element: KtElement): Int { diff --git a/detekt-metrics/src/main/kotlin/io/github/detekt/metrics/processors/ProjectCognitiveComplexityProcessor.kt b/detekt-metrics/src/main/kotlin/io/github/detekt/metrics/processors/ProjectCognitiveComplexityProcessor.kt new file mode 100644 index 000000000..87b3425bf --- /dev/null +++ b/detekt-metrics/src/main/kotlin/io/github/detekt/metrics/processors/ProjectCognitiveComplexityProcessor.kt @@ -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 +} diff --git a/detekt-metrics/src/test/kotlin/io/github/detekt/metrics/processors/CognitiveComplexityProcessorSpec.kt b/detekt-metrics/src/test/kotlin/io/github/detekt/metrics/processors/CognitiveComplexityProcessorSpec.kt new file mode 100644 index 000000000..096b2cd85 --- /dev/null +++ b/detekt-metrics/src/test/kotlin/io/github/detekt/metrics/processors/CognitiveComplexityProcessorSpec.kt @@ -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) + } + } +}) diff --git a/detekt-metrics/src/test/kotlin/io/github/detekt/metrics/processors/MetricProcessorTester.kt b/detekt-metrics/src/test/kotlin/io/github/detekt/metrics/processors/MetricProcessorTester.kt new file mode 100644 index 000000000..bbfdb25fb --- /dev/null +++ b/detekt-metrics/src/test/kotlin/io/github/detekt/metrics/processors/MetricProcessorTester.kt @@ -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 test(processor: AbstractProcessor, key: Key): 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> + get() = throw UnsupportedOperationException() + override val notifications: Collection + get() = throw UnsupportedOperationException() + override val metrics: MutableList = mutableListOf() + + private var data = KeyFMap.EMPTY_MAP + + override fun getData(key: Key): V? = data.get(key) + + override fun addData(key: Key, value: V) { + data = data.plus(key, value) + } + + override fun add(notification: Notification) { + throw UnsupportedOperationException() + } + + override fun add(projectMetric: ProjectMetric) { + metrics.add(projectMetric) + } +}