Skip to content

Commit b4a71b4

Browse files
authored
chore(library): add Detekt rule for forbidding enums (#2339)
Part of #1578. The goal is to prevent us from using enums that are problematic when it comes to using values that aren't known when publishing a given version of the library. We should generally use sealed interfaces/classes with sth like `class Custom(val value: String)`. This Detekt rule can be skipped for legitimate cases, on a case basis.
1 parent 6115b59 commit b4a71b4

8 files changed

Lines changed: 122 additions & 0 deletions

File tree

github-workflows-kt/build.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ plugins {
1919
id("org.jetbrains.dokka") version "2.2.0"
2020
}
2121

22+
detekt {
23+
buildUponDefaultConfig = true
24+
config.setFrom("$projectDir/detekt.yml")
25+
}
26+
2227
group = rootProject.group
2328
version = rootProject.version
2429

@@ -35,6 +40,7 @@ dependencies {
3540
testImplementation(kotlin("compiler"))
3641
testImplementation(kotlin("reflect"))
3742
testImplementation(projects.testUtils)
43+
detektPlugins(project(":github-workflows-kt:detekt-rules"))
3844

3945
// GitHub action bindings
4046
testImplementation("actions:checkout:v4")
@@ -85,3 +91,7 @@ tasks.withType<FormatTask> {
8591
dokka {
8692
moduleName.set("github-workflows-kt")
8793
}
94+
95+
apiValidation {
96+
ignoredProjects.addAll(listOf("detekt-rules"))
97+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
plugins {
2+
buildsrc.convention.`kotlin-jvm`
3+
}
4+
5+
dependencies {
6+
compileOnly("io.gitlab.arturbosch.detekt:detekt-api:1.23.8")
7+
8+
testImplementation("io.gitlab.arturbosch.detekt:detekt-test:1.23.8")
9+
testImplementation("org.jetbrains.kotlin:kotlin-compiler-embeddable")
10+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package io.github.typesafegithub.workflows.detekt.rules
2+
3+
import io.gitlab.arturbosch.detekt.api.CodeSmell
4+
import io.gitlab.arturbosch.detekt.api.Config
5+
import io.gitlab.arturbosch.detekt.api.Debt
6+
import io.gitlab.arturbosch.detekt.api.Entity
7+
import io.gitlab.arturbosch.detekt.api.Issue
8+
import io.gitlab.arturbosch.detekt.api.Rule
9+
import io.gitlab.arturbosch.detekt.api.Severity
10+
import org.jetbrains.kotlin.psi.KtClass
11+
import org.jetbrains.kotlin.psi.KtClassOrObject
12+
13+
public class NoEnumRule(
14+
config: Config,
15+
) : Rule(config) {
16+
private companion object {
17+
private const val RATIONALE =
18+
"GitHub Actions' API may require or return values that weren't anticipated when this library was " +
19+
"published. Sealed interfaces/classes allow adding a class that accepts an arbitrary value as " +
20+
"an escape hatch."
21+
}
22+
23+
override val issue: Issue =
24+
Issue(
25+
id = "NoEnum",
26+
severity = Severity.Style,
27+
description = "Enums are forbidden. Use sealed interfaces/classes instead. $RATIONALE",
28+
debt = Debt.FIVE_MINS,
29+
)
30+
31+
override fun visitClassOrObject(classOrObject: KtClassOrObject) {
32+
if (classOrObject is KtClass && classOrObject.isEnum()) {
33+
report(
34+
CodeSmell(
35+
issue = issue,
36+
entity = Entity.from(classOrObject),
37+
message =
38+
"Enum class '${classOrObject.name}' is forbidden. Use a sealed interface/class instead. " +
39+
RATIONALE,
40+
),
41+
)
42+
}
43+
super.visitClassOrObject(classOrObject)
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.github.typesafegithub.workflows.detekt.rules
2+
3+
import io.gitlab.arturbosch.detekt.api.Config
4+
import io.gitlab.arturbosch.detekt.api.RuleSet
5+
import io.gitlab.arturbosch.detekt.api.RuleSetProvider
6+
7+
public class NoEnumRuleSetProvider : RuleSetProvider {
8+
override val ruleSetId: String = "custom-style"
9+
10+
override fun instance(config: Config): RuleSet =
11+
RuleSet(
12+
id = ruleSetId,
13+
rules = listOf(NoEnumRule(config)),
14+
)
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.github.typesafegithub.workflows.detekt.rules.NoEnumRuleSetProvider
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.github.typesafegithub.workflows.detekt.rules
2+
3+
import io.gitlab.arturbosch.detekt.test.TestConfig
4+
import io.gitlab.arturbosch.detekt.test.compileAndLint
5+
import io.kotest.core.spec.style.FunSpec
6+
import io.kotest.matchers.shouldBe
7+
8+
class NoEnumRuleTest :
9+
FunSpec({
10+
test("reports enum class") {
11+
val code = """
12+
enum class Color { RED, GREEN, BLUE }
13+
"""
14+
val findings = NoEnumRule(TestConfig()).compileAndLint(code)
15+
findings.size shouldBe 1
16+
}
17+
18+
test("does not report sealed class") {
19+
val code = """
20+
sealed class Color {
21+
data object RED : Color()
22+
data object GREEN : Color()
23+
data object BLUE : Color()
24+
}
25+
"""
26+
val findings = NoEnumRule(TestConfig()).compileAndLint(code)
27+
findings.size shouldBe 0
28+
}
29+
30+
test("does not report regular class") {
31+
val code = """
32+
class Foo
33+
"""
34+
val findings = NoEnumRule(TestConfig()).compileAndLint(code)
35+
findings.size shouldBe 0
36+
}
37+
})

github-workflows-kt/detekt.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
custom-style:
2+
NoEnum:
3+
active: true

settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ include(
1111
"shared-internal",
1212
"code-generator",
1313
"test-utils",
14+
"github-workflows-kt:detekt-rules",
1415
)
1516

1617
plugins {

0 commit comments

Comments
 (0)