diff --git a/detekt-core/src/main/resources/default-detekt-config.yml b/detekt-core/src/main/resources/default-detekt-config.yml index 204ef6a5150..59de0ada0e4 100644 --- a/detekt-core/src/main/resources/default-detekt-config.yml +++ b/detekt-core/src/main/resources/default-detekt-config.yml @@ -98,6 +98,7 @@ complexity: threshold: 10 includeStaticDeclarations: false includePrivateDeclarations: false + ignoreOverloaded: false ComplexMethod: active: true threshold: 15 diff --git a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexInterface.kt b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexInterface.kt index 98624c72d4e..1f212f18d78 100644 --- a/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexInterface.kt +++ b/detekt-rules-complexity/src/main/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexInterface.kt @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.KtProperty import org.jetbrains.kotlin.psi.KtTypeParameterListOwner +import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration import org.jetbrains.kotlin.psi.psiUtil.isPrivate /** @@ -51,6 +52,9 @@ class ComplexInterface( @Configuration("whether private declarations should be included") private val includePrivateDeclarations: Boolean by config(defaultValue = false) + @Configuration("ignore overloaded methods - only count once") + private val ignoreOverloaded: Boolean by config(defaultValue = false) + override fun visitClass(klass: KtClass) { if (klass.isInterface()) { val body = klass.body ?: return @@ -81,10 +85,26 @@ class ComplexInterface( fun PsiElement.considerPrivate() = includePrivateDeclarations || this is KtTypeParameterListOwner && !this.isPrivate() - fun PsiElement.isMember() = this is KtNamedFunction || this is KtProperty + fun countFunctions(psiElements: List): Int { + val functions = psiElements.filterIsInstance() + return if (ignoreOverloaded) { + functions.distinctBy { function -> + val receiver = function.receiverTypeReference + if (function.isExtensionDeclaration() && receiver != null) { + "${receiver.text}.${function.name}" + } else { + function.name + } + }.size + } else { + functions.size + } + } - return body.children + val psiElements = body.children .filter(PsiElement::considerPrivate) - .count(PsiElement::isMember) + val propertyCount = psiElements.count { it is KtProperty } + val functionCount = countFunctions(psiElements) + return propertyCount + functionCount } } diff --git a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexInterfaceSpec.kt b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexInterfaceSpec.kt index e2e56bf7368..466df744877 100644 --- a/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexInterfaceSpec.kt +++ b/detekt-rules-complexity/src/test/kotlin/io/gitlab/arturbosch/detekt/rules/complexity/ComplexInterfaceSpec.kt @@ -14,6 +14,9 @@ private val staticDeclarationsConfig = TestConfig( private val privateDeclarationsConfig = TestConfig( defaultConfigMap + ("includePrivateDeclarations" to true) ) +private val ignoreOverloadedConfig = TestConfig( + defaultConfigMap + ("ignoreOverloaded" to true) +) class ComplexInterfaceSpec { @@ -141,6 +144,57 @@ class ComplexInterfaceSpec { assertThat(rule.compileAndLint(code)).hasSize(1) } } + + @Nested + inner class `overloaded methods` { + val code = """ + interface I { + fun f1() + fun f1(i: Int) + val i1: Int + fun fImpl() {} + } + """ + + @Test + fun `reports complex interface with overloaded methods`() { + assertThat(subject.compileAndLint(code)).hasSize(1) + } + + @Test + fun `does not report simple interface with ignoreOverloaded`() { + val rule = ComplexInterface(ignoreOverloadedConfig) + assertThat(rule.compileAndLint(code)).isEmpty() + } + + @Test + fun `reports complex interface with extension methods with a different receiver`() { + val interfaceWithExtension = """ + interface I { + fun f1() + fun String.f1(i: Int) + val i1: Int + fun fImpl() {} + } + """ + val rule = ComplexInterface(ignoreOverloadedConfig) + assertThat(rule.compileAndLint(interfaceWithExtension)).hasSize(1) + } + + @Test + fun `does not report simple interface with extension methods with the same receiver`() { + val interfaceWithOverloadedExtensions = """ + interface I { + fun String.f1() + fun String.f1(i: Int) + val i1: Int + fun fImpl() {} + } + """ + val rule = ComplexInterface(ignoreOverloadedConfig) + assertThat(rule.compileAndLint(interfaceWithOverloadedExtensions)).isEmpty() + } + } } @Nested