Split unit test, add test to find duplicated SinceKtlint annotations
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule.kt
index 0e70f5b..cefd627 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/ClassNamingRule.kt
@@ -22,7 +22,6 @@
* well as it is more consistent with name of test functions.
*/
@SinceKtlint("0.48", EXPERIMENTAL)
-@SinceKtlint("0.49", EXPERIMENTAL)
@SinceKtlint("1.0", STABLE)
public class ClassNamingRule : StandardRule("class-naming") {
private var allowBacktickedClassName = false
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule.kt
index da24d29..1eeb6c9 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/FunctionTypeReferenceSpacingRule.kt
@@ -17,7 +17,6 @@
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
@SinceKtlint("0.45", EXPERIMENTAL)
-@SinceKtlint("0.49", EXPERIMENTAL)
@SinceKtlint("1.0", STABLE)
public class FunctionTypeReferenceSpacingRule : StandardRule("function-type-reference-spacing") {
override fun beforeVisitChildNodes(
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/SinceKtlintAnnotationTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/SinceKtlintAnnotationTest.kt
index 46fca44..883e820 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/SinceKtlintAnnotationTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/SinceKtlintAnnotationTest.kt
@@ -3,76 +3,108 @@
import com.pinterest.ktlint.rule.engine.core.api.Rule
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint
import org.assertj.core.api.Assertions.assertThat
+import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.params.Parameter
+import org.junit.jupiter.params.ParameterizedClass
+import org.junit.jupiter.params.provider.Arguments
+import org.junit.jupiter.params.provider.MethodSource
+import java.util.stream.Stream
class SinceKtlintAnnotationTest {
- @Test
- fun `Given all rules then each rule should have proper SinceKtlint annotations`() {
- val ruleSetProvider = StandardRuleSetProvider()
- val rules = ruleSetProvider.getRuleProviders().map { it.createNewRuleInstance() }
+ @ParameterizedClass(name = "{argumentSetName}")
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @MethodSource("allRules")
+ @Nested
+ inner class `Given a STABLE or EXPERIMENTAL rule` {
+ @Parameter
+ lateinit var rule: Rule
- val violations =
- rules.flatMap { rule ->
- val ruleClass = rule::class
- val annotations = ruleClass.annotations.filterIsInstance<SinceKtlint>()
- val isExperimental = rule is Rule.Experimental
+ @Test
+ fun `The rule has a valid version number not containing the patch level`() {
+ val actual =
+ rule
+ .sinceKtlintAnnotations()
+ .all { isValidVersionFormat(it.version) }
- val annotationViolations =
- when {
- annotations.isEmpty() -> {
- listOf("${ruleClass.simpleName} has no @SinceKtlint annotations")
- }
+ assertThat(actual).isTrue
+ }
- isExperimental -> {
- val experimentalAnnotations = annotations.filter { it.status == SinceKtlint.Status.EXPERIMENTAL }
- val stableAnnotations = annotations.filter { it.status == SinceKtlint.Status.STABLE }
+ fun allRules(): Stream<Arguments> = rules { true }
- when {
- experimentalAnnotations.isEmpty() -> {
- listOf(
- "${ruleClass.simpleName} implements Experimental but has no EXPERIMENTAL @SinceKtlint annotation",
- )
- }
-
- stableAnnotations.isNotEmpty() -> {
- listOf("${ruleClass.simpleName} implements Experimental but has STABLE @SinceKtlint annotation")
- }
-
- else -> {
- emptyList()
- }
- }
- }
-
- else -> {
- val stableAnnotations = annotations.filter { it.status == SinceKtlint.Status.STABLE }
- if (stableAnnotations.isEmpty()) {
- listOf("${ruleClass.simpleName} is stable but has no STABLE @SinceKtlint annotation")
- } else {
- emptyList()
- }
- }
- }
-
- val versionViolations =
- annotations.mapNotNull { annotation ->
- if (!isValidVersionFormat(annotation.version)) {
- "${ruleClass.simpleName} has invalid version format '${annotation.version}' - should be 'X.Y' format without patch level"
- } else {
- null
- }
- }
-
- annotationViolations + versionViolations
- }
-
- assertThat(violations)
- .withFailMessage("Found @SinceKtlint annotation violations:\n${violations.joinToString("\n")}")
- .isEmpty()
+ private fun isValidVersionFormat(version: String): Boolean {
+ val versionRegex = Regex("""^\d+\.\d+$""")
+ return versionRegex.matches(version)
+ }
}
- private fun isValidVersionFormat(version: String): Boolean {
- val versionRegex = Regex("""^\d+\.\d+$""")
- return versionRegex.matches(version)
+ @ParameterizedClass(name = "{argumentSetName}")
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @MethodSource("stableRules")
+ @Nested
+ inner class `Given a STABLE rule, eg not implementing interface Experimental` {
+ @Parameter
+ lateinit var stableRule: Rule
+
+ @Test
+ fun `The rule has exactly 1 @SinceKtlint annotation with status STABLE`() {
+ val actual =
+ stableRule
+ .sinceKtlintAnnotations()
+ .count { it.status == SinceKtlint.Status.STABLE }
+
+ assertThat(actual).isEqualTo(1)
+ }
+
+ @Test
+ fun `The rule has at most 1 @SinceKtlint annotation with status EXPERIMENTAL`() {
+ val actual =
+ stableRule
+ .sinceKtlintAnnotations()
+ .count { it.status == SinceKtlint.Status.EXPERIMENTAL }
+
+ assertThat(actual).isLessThanOrEqualTo(1)
+ }
+
+ fun stableRules(): Stream<Arguments> = rules { it !is Rule.Experimental }
}
+
+ @ParameterizedClass(name = "{argumentSetName}")
+ @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+ @MethodSource("experimentalRules")
+ @Nested
+ inner class `Given an EXPERIMENTAL rule, eg implementing interface Experimental` {
+ @Parameter
+ lateinit var experimentalRule: Rule
+
+ @Test
+ fun `The rule has exactly 1 @SinceKtlint annotation with status EXPERIMENTAL`() {
+ val actual =
+ experimentalRule
+ .sinceKtlintAnnotations()
+ .count { it.status == SinceKtlint.Status.EXPERIMENTAL }
+
+ assertThat(actual).isEqualTo(1)
+ }
+
+ @Test
+ fun `The rule should not have @SinceKtlint annotation with status STABLE`() {
+ val actual = experimentalRule.sinceKtlintAnnotations().none { it.status == SinceKtlint.Status.STABLE }
+
+ assertThat(actual).isTrue
+ }
+
+ fun experimentalRules(): Stream<Arguments> = rules { it is Rule.Experimental }
+ }
+
+ private fun Rule.sinceKtlintAnnotations(): List<SinceKtlint> = this::class.annotations.filterIsInstance<SinceKtlint>()
+
+ private fun rules(predicate2: (Rule) -> Boolean): Stream<Arguments> =
+ StandardRuleSetProvider()
+ .getRuleProviders()
+ .map { it.createNewRuleInstance() }
+ .filter { predicate2(it) }
+ .map { Arguments.argumentSet(it.ruleId.value, it) }
+ .let { argumentSets -> Stream.of(*argumentSets.toTypedArray()) }
}