Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to ignore overloaded methods for the complex interface rule (#5165) #5173

Merged
merged 3 commits into from Aug 5, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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)
schalkms marked this conversation as resolved.
Show resolved Hide resolved

override fun visitClass(klass: KtClass) {
if (klass.isInterface()) {
val body = klass.body ?: return
Expand Down Expand Up @@ -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<PsiElement>): Int {
val functions = psiElements.filterIsInstance<KtNamedFunction>()
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
}
}
Expand Up @@ -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 {

Expand Down Expand Up @@ -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 code = """
pt2121 marked this conversation as resolved.
Show resolved Hide resolved
interface I {
fun f1()
fun String.f1(i: Int)
val i1: Int
fun fImpl() {}
}
"""
val rule = ComplexInterface(ignoreOverloadedConfig)
assertThat(rule.compileAndLint(code)).hasSize(1)
}

@Test
fun `does not report simple interface with extension methods with the same receiver`() {
val code = """
interface I {
fun String.f1()
fun String.f1(i: Int)
val i1: Int
fun fImpl() {}
}
"""
val rule = ComplexInterface(ignoreOverloadedConfig)
assertThat(rule.compileAndLint(code)).isEmpty()
}
}
}

@Nested
Expand Down