From 829fae2484d86e9e9e3b0b3c79721c54339c83e1 Mon Sep 17 00:00:00 2001 From: Prat <616399+pt2121@users.noreply.github.com> Date: Fri, 5 Aug 2022 14:13:04 -0700 Subject: [PATCH] Allow to ignore overloaded methods for the complex interface rule (#5165) (#5173) * Allow to ignore overloaded methods for complex interface rule (#5165) * Fix name shadowing in complex interface tests * Generate documentation for ignoreOverloaded in complex interface Co-authored-by: Prat T --- .../main/resources/default-detekt-config.yml | 1 + .../rules/complexity/ComplexInterface.kt | 26 +++++++-- .../rules/complexity/ComplexInterfaceSpec.kt | 54 +++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) 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