Introduce NamedArguments rule (#3167)

* Introduce NamedArguments rule when function is invoked with more than default number of non-named parameters

* Change the documentation to make the intent of default threshold clear

* Add compliant and non-compliant code documentation. Add tests for constructor
This commit is contained in:
Sowmya Viswanathan
2020-10-26 17:14:55 +05:30
committed by GitHub
parent 7364edc494
commit b6d298a6ad
5 changed files with 200 additions and 0 deletions

View File

@@ -28,6 +28,7 @@ class ComplexityProvider : DefaultRuleSetProvider {
ComplexCondition(config),
LabeledExpression(config),
ReplaceSafeCallChainWithRun(config),
NamedArguments(config)
)
)
}

View File

@@ -0,0 +1,52 @@
package io.gitlab.arturbosch.detekt.rules.complexity
import io.gitlab.arturbosch.detekt.api.CodeSmell
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.Debt
import io.gitlab.arturbosch.detekt.api.Entity
import io.gitlab.arturbosch.detekt.api.Issue
import io.gitlab.arturbosch.detekt.api.Severity
import io.gitlab.arturbosch.detekt.api.ThresholdRule
import org.jetbrains.kotlin.psi.KtCallExpression
/**
* Reports function invocations which have more parameters than a certain threshold and are all not named.
*
* <noncompliant>
* fun sum(a: Int, b: Int, c: Int, d: Int) {
* }
* sum(1, 2, 3, 4)
* </noncompliant>
*
* <compliant>
* fun sum(a: Int, b: Int, c: Int, d: Int) {
* }
* sum(a = 1, b = 2, c = 3, d = 4)
* </compliant>
*
* @configuration threshold - number of parameters that triggers this inspection (default: `3`)
*/
class NamedArguments(
config: Config = Config.empty,
threshold: Int = DEFAULT_FUNCTION_THRESHOLD
) : ThresholdRule(config, threshold) {
override val issue = Issue(
"NamedArguments", Severity.Maintainability,
"Function invocation with more than $threshold parameters must all be named",
Debt.FIVE_MINS
)
override fun visitCallExpression(expression: KtCallExpression) {
val valueArguments = expression.valueArguments
if (valueArguments.size > threshold && valueArguments.any { !it.isNamed() }) {
report(CodeSmell(issue, Entity.from(expression), issue.description))
} else {
super.visitCallExpression(expression)
}
}
companion object {
const val DEFAULT_FUNCTION_THRESHOLD = 3
}
}

View File

@@ -0,0 +1,114 @@
package io.gitlab.arturbosch.detekt.rules.complexity
import io.gitlab.arturbosch.detekt.test.compileAndLint
import org.assertj.core.api.Assertions.assertThat
import org.spekframework.spek2.Spek
import org.spekframework.spek2.style.specification.describe
class NamedArgumentsSpec : Spek({
val defaultThreshold = 2
val namedArguments by memoized { NamedArguments(threshold = defaultThreshold) }
describe("NameArguments rule") {
val errorMessage = "Function invocation with more than $defaultThreshold parameters must all be named"
it("invocation with more than 2 parameters should throw error") {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(1, 2, 3)
}
"""
val findings = namedArguments.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(errorMessage)
}
it("Function invocation with more than 2 parameters should not throw error if named") {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(a = 1, b = 2, c = 3)
}
"""
val findings = namedArguments.compileAndLint(code)
assertThat(findings).hasSize(0)
}
it("invocation with more than 2 parameters should throw error if even one is not named") {
val code = """
fun sum(a: Int, b:Int, c:Int) {
println(a + b + c)
}
fun call() {
sum(1, b = 2, c = 3)
}
"""
val findings = namedArguments.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(errorMessage)
}
it("invocation with less than 3 parameters should not throw error") {
val code = """
fun sum(a: Int, b:Int) {
println(a + b)
}
fun call() {
sum(1, 2)
}
"""
val findings = namedArguments.compileAndLint(code)
assertThat(findings).hasSize(0)
}
it("invocation with less than 3 named parameters should not throw error") {
val code = """
fun sum(a: Int, b:Int) {
println(a + b)
}
fun call() {
sum(a = 1, b = 2)
}
"""
val findings = namedArguments.compileAndLint(code)
assertThat(findings).hasSize(0)
}
it("constructor invocation with more than 3 non-named parameters should throw error") {
val code = """
class C(val a: Int, val b:Int, val c:Int)
val obj = C(1, 2, 3)
"""
val findings = namedArguments.compileAndLint(code)
assertThat(findings).hasSize(1)
assertThat(findings.first().message).isEqualTo(errorMessage)
}
it("constructor invocation with more than 3 named parameters should not throw error") {
val code = """
class C(val a: Int, val b:Int, val c:Int)
val obj = C(a = 1, b = 2, c= 3)
"""
val findings = namedArguments.compileAndLint(code)
assertThat(findings).hasSize(0)
}
it("constructor invocation with less than 3 non-named parameters should not throw error") {
val code = """
class C(val a: Int, val b:Int)
val obj = C(1, 2)
"""
val findings = namedArguments.compileAndLint(code)
assertThat(findings).hasSize(0)
}
}
})