Skip to content

Commit

Permalink
ADd the "DataClassDefaultValues" rule
Browse files Browse the repository at this point in the history
  • Loading branch information
ILIYANGERMANOV committed Jan 29, 2024
1 parent c9bedd9 commit 6096461
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.ivy.explicit

import com.github.ivy.explicit.rule.DataClassDefaultValuesRule
import com.github.ivy.explicit.rule.DataClassFunctionsRule
import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.api.RuleSet
Expand All @@ -13,6 +14,7 @@ class IvyExplicitRuleSetProvider : RuleSetProvider {
ruleSetId,
listOf(
DataClassFunctionsRule(config),
DataClassDefaultValuesRule(config),
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.github.ivy.explicit.rule

import com.github.ivy.explicit.util.Message
import io.gitlab.arturbosch.detekt.api.*
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtParameter

class DataClassDefaultValuesRule(config: Config) : Rule(config) {

override val issue = Issue(
id = "DataClassDefaultValues",
severity = Severity.Maintainability,
description = "Data class properties should not have default values. " +
"Default values lead to implicit instance constructions and problems.",
debt = Debt.TWENTY_MINS,
)

override fun visitClass(klass: KtClass) {
super.visitClass(klass)
if (klass.isData()) {
klass.primaryConstructorParameters.filter {
it.hasDefaultValue()
}.forEach { parameter ->
report(
CodeSmell(
issue = issue,
entity = Entity.from(parameter),
message = failureMessage(klass, parameter)
)
)
}
}
}

private fun failureMessage(klass: KtClass, parameter: KtParameter): String = buildString {
append("Data class '${klass.name}' should not have default values for properties. ")
append("Found default value for property '${Message.parameter(parameter)}'. ")
append("This can lead to implicit instance constructions and problems.")
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package com.github.ivy.explicit.rule

import com.github.ivy.explicit.util.FunctionMessage
import com.github.ivy.explicit.util.Message
import io.gitlab.arturbosch.detekt.api.*
import io.gitlab.arturbosch.detekt.rules.isOverride
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtNamedFunction

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

override val issue = Issue(
id = "DataClassFunctions",
Expand Down Expand Up @@ -44,6 +43,6 @@ class DataClassFunctionsRule(config: Config) : Rule(config) {
): String = buildString {
append("Data class '${klass.name}' should not contain functions. ")
append("Data classes should only model data and not define behavior. ")
append("Found: function '${functionMessage.signature(function)}'.")
append("Found: function '${Message.functionSignature(function)}'.")
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.github.ivy.explicit.util

import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtParameter

class FunctionMessage {
fun signature(function: KtNamedFunction): String {
object Message {
fun functionSignature(function: KtNamedFunction): String {
// Extract the function name
val fName = function.name ?: ""

Expand All @@ -28,4 +29,14 @@ class FunctionMessage {
": $returnType"
} else ""
}

fun parameter(param: KtParameter): String = buildString {
append(param.name)
val type = param.typeReference?.text
if (type != null) {
append(": ")
append(type)
param.defaultValue?.text?.let { append(" = $it") }
}
}
}
2 changes: 2 additions & 0 deletions src/main/resources/config/config.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
IvyExplicit:
DataClassFunctions:
active: true
DataClassDefaultValues:
active: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.github.ivy.explicit.rule

import io.gitlab.arturbosch.detekt.api.Config
import io.gitlab.arturbosch.detekt.rules.KotlinCoreEnvironmentTest
import io.gitlab.arturbosch.detekt.test.compileAndLintWithContext
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.junit.jupiter.api.Test

@KotlinCoreEnvironmentTest
internal class DataClassDefaultValuesRuleTest(private val env: KotlinCoreEnvironment) {

@Test
fun `reports data class with a default value`() {
val code = """
data class A(
val x: Int = 42
)
"""
val findings = DataClassDefaultValuesRule(Config.empty).compileAndLintWithContext(env, code)
findings shouldHaveSize 1
val message = findings.first().message
message shouldBe """
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.
""".trimIndent()
}

@Test
fun `reports data class with a override default value`() {
val code = """
data class A(
override val x: Int = 0,
override val y: Int = 0,
): Point
"""
val findings = DataClassDefaultValuesRule(Config.empty).compileAndLintWithContext(env, code)
findings shouldHaveSize 2
}

@Test
fun `doesn't report class with a default value`() {
val code = """
class A(
val x: Int = 42
)
"""
val findings = DataClassDefaultValuesRule(Config.empty).compileAndLintWithContext(env, code)
findings shouldHaveSize 0
}

@Test
fun `doesn't report data class without default values`() {
val code = """
class Point(
val x: Double,
val y: Double,
)
"""
val findings = DataClassDefaultValuesRule(Config.empty).compileAndLintWithContext(env, code)
findings shouldHaveSize 0
}

}

0 comments on commit 6096461

Please sign in to comment.