Skip to content

Commit 6096461

Browse files
ADd the "DataClassDefaultValues" rule
1 parent c9bedd9 commit 6096461

File tree

6 files changed

+123
-5
lines changed

6 files changed

+123
-5
lines changed

src/main/kotlin/com/github/ivy/explicit/IvyExplicitRuleSetProvider.kt

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.ivy.explicit
22

3+
import com.github.ivy.explicit.rule.DataClassDefaultValuesRule
34
import com.github.ivy.explicit.rule.DataClassFunctionsRule
45
import io.gitlab.arturbosch.detekt.api.Config
56
import io.gitlab.arturbosch.detekt.api.RuleSet
@@ -13,6 +14,7 @@ class IvyExplicitRuleSetProvider : RuleSetProvider {
1314
ruleSetId,
1415
listOf(
1516
DataClassFunctionsRule(config),
17+
DataClassDefaultValuesRule(config),
1618
),
1719
)
1820
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.github.ivy.explicit.rule
2+
3+
import com.github.ivy.explicit.util.Message
4+
import io.gitlab.arturbosch.detekt.api.*
5+
import org.jetbrains.kotlin.psi.KtClass
6+
import org.jetbrains.kotlin.psi.KtParameter
7+
8+
class DataClassDefaultValuesRule(config: Config) : Rule(config) {
9+
10+
override val issue = Issue(
11+
id = "DataClassDefaultValues",
12+
severity = Severity.Maintainability,
13+
description = "Data class properties should not have default values. " +
14+
"Default values lead to implicit instance constructions and problems.",
15+
debt = Debt.TWENTY_MINS,
16+
)
17+
18+
override fun visitClass(klass: KtClass) {
19+
super.visitClass(klass)
20+
if (klass.isData()) {
21+
klass.primaryConstructorParameters.filter {
22+
it.hasDefaultValue()
23+
}.forEach { parameter ->
24+
report(
25+
CodeSmell(
26+
issue = issue,
27+
entity = Entity.from(parameter),
28+
message = failureMessage(klass, parameter)
29+
)
30+
)
31+
}
32+
}
33+
}
34+
35+
private fun failureMessage(klass: KtClass, parameter: KtParameter): String = buildString {
36+
append("Data class '${klass.name}' should not have default values for properties. ")
37+
append("Found default value for property '${Message.parameter(parameter)}'. ")
38+
append("This can lead to implicit instance constructions and problems.")
39+
}
40+
}

src/main/kotlin/com/github/ivy/explicit/rule/DataClassFunctionsRule.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package com.github.ivy.explicit.rule
22

3-
import com.github.ivy.explicit.util.FunctionMessage
3+
import com.github.ivy.explicit.util.Message
44
import io.gitlab.arturbosch.detekt.api.*
55
import io.gitlab.arturbosch.detekt.rules.isOverride
66
import org.jetbrains.kotlin.psi.KtClass
77
import org.jetbrains.kotlin.psi.KtNamedFunction
88

99
class DataClassFunctionsRule(config: Config) : Rule(config) {
10-
private val functionMessage: FunctionMessage by lazy { FunctionMessage() }
1110

1211
override val issue = Issue(
1312
id = "DataClassFunctions",
@@ -44,6 +43,6 @@ class DataClassFunctionsRule(config: Config) : Rule(config) {
4443
): String = buildString {
4544
append("Data class '${klass.name}' should not contain functions. ")
4645
append("Data classes should only model data and not define behavior. ")
47-
append("Found: function '${functionMessage.signature(function)}'.")
46+
append("Found: function '${Message.functionSignature(function)}'.")
4847
}
4948
}

src/main/kotlin/com/github/ivy/explicit/util/FunctionMessage.kt src/main/kotlin/com/github/ivy/explicit/util/Message.kt

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package com.github.ivy.explicit.util
22

33
import org.jetbrains.kotlin.psi.KtNamedFunction
4+
import org.jetbrains.kotlin.psi.KtParameter
45

5-
class FunctionMessage {
6-
fun signature(function: KtNamedFunction): String {
6+
object Message {
7+
fun functionSignature(function: KtNamedFunction): String {
78
// Extract the function name
89
val fName = function.name ?: ""
910

@@ -28,4 +29,14 @@ class FunctionMessage {
2829
": $returnType"
2930
} else ""
3031
}
32+
33+
fun parameter(param: KtParameter): String = buildString {
34+
append(param.name)
35+
val type = param.typeReference?.text
36+
if (type != null) {
37+
append(": ")
38+
append(type)
39+
param.defaultValue?.text?.let { append(" = $it") }
40+
}
41+
}
3142
}

src/main/resources/config/config.yml

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
IvyExplicit:
22
DataClassFunctions:
33
active: true
4+
DataClassDefaultValues:
5+
active: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.github.ivy.explicit.rule
2+
3+
import io.gitlab.arturbosch.detekt.api.Config
4+
import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
5+
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
6+
import io.kotest.matchers.collections.shouldHaveSize
7+
import io.kotest.matchers.shouldBe
8+
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
9+
import org.junit.jupiter.api.Test
10+
11+
@KotlinCoreEnvironmentTest
12+
internal class DataClassDefaultValuesRuleTest(private val env: KotlinCoreEnvironment) {
13+
14+
@Test
15+
fun `reports data class with a default value`() {
16+
val code = """
17+
data class A(
18+
val x: Int = 42
19+
)
20+
"""
21+
val findings = DataClassDefaultValuesRule(Config.empty).compileAndLintWithContext(env, code)
22+
findings shouldHaveSize 1
23+
val message = findings.first().message
24+
message shouldBe """
25+
Data class 'A' should not have default values for properties. Found default value for property 'x: Int = 42'. This can lead to implicit instance constructions and problems.
26+
""".trimIndent()
27+
}
28+
29+
@Test
30+
fun `reports data class with a override default value`() {
31+
val code = """
32+
data class A(
33+
override val x: Int = 0,
34+
override val y: Int = 0,
35+
): Point
36+
"""
37+
val findings = DataClassDefaultValuesRule(Config.empty).compileAndLintWithContext(env, code)
38+
findings shouldHaveSize 2
39+
}
40+
41+
@Test
42+
fun `doesn't report class with a default value`() {
43+
val code = """
44+
class A(
45+
val x: Int = 42
46+
)
47+
"""
48+
val findings = DataClassDefaultValuesRule(Config.empty).compileAndLintWithContext(env, code)
49+
findings shouldHaveSize 0
50+
}
51+
52+
@Test
53+
fun `doesn't report data class without default values`() {
54+
val code = """
55+
class Point(
56+
val x: Double,
57+
val y: Double,
58+
)
59+
"""
60+
val findings = DataClassDefaultValuesRule(Config.empty).compileAndLintWithContext(env, code)
61+
findings shouldHaveSize 0
62+
}
63+
64+
}

0 commit comments

Comments
 (0)