Skip to content

Commit 244aa39

Browse files
Add property .editorconfig property ktlint_enum_entry_name_casing (#2839)
This property allows enum entry names to be restricted to: * `upper_cases`: an enum entry may only contain uppercases, and underscores, and digits, and dicritics on letters and strokes * `camel_cases`: an enum entry may only contain CamelCase values, including digits, and dicritics on letters and strokes) * `upper_or_camel_case`: allows both the `upper_cases` and `camel_cases` styles as defined in the Kotlin Coding Conventions Closes #2835
1 parent 80c4d5d commit 244aa39

File tree

6 files changed

+145
-22
lines changed

6 files changed

+145
-22
lines changed

documentation/snapshot/docs/rules/standard.md

+4
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,10 @@ Enum entry names should be uppercase underscore-separated or upper camel-case se
611611
}
612612
```
613613

614+
| Configuration setting | ktlint_official | intellij_idea | android_studio |
615+
|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------:|:-------------:|:--------------:|
616+
| `ktlint_enum_entry_name_casing`</br><i>Choose any of `upper_cases` (an enum entry may only contain uppercases, and underscores, and digits, and dicritics on letters and strokes), `camel_cases` (an enum entry may only contain CamelCase values, including digits, and dicritics on letters and strokes), or `upper_or_camel_case` (allows mixing of uppercase and CamelCase entries as per Kotlin Coding Conventions).</i> | `upper_or_camel_cases` | `upper_or_camel_cases` | `upper_or_camel_cases` |
617+
614618
Rule id: `standard:enum-entry-name-case`
615619

616620
Suppress or disable rule (1)

ktlint-rule-engine-core/api/ktlint-rule-engine-core.api

+5
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,11 @@ public final class com/pinterest/ktlint/rule/engine/core/api/editorconfig/RuleEx
618618
public static final fun ktLintRuleSetExecutionPropertyName (Lcom/pinterest/ktlint/rule/engine/core/api/RuleSetId;)Ljava/lang/String;
619619
}
620620

621+
public final class com/pinterest/ktlint/rule/engine/core/api/editorconfig/SafeEnumValueParser : org/ec4j/core/model/PropertyType$PropertyValueParser {
622+
public fun <init> (Ljava/lang/Class;)V
623+
public fun parse (Ljava/lang/String;Ljava/lang/String;)Lorg/ec4j/core/model/PropertyType$PropertyValue;
624+
}
625+
621626
public final class com/pinterest/ktlint/rule/engine/core/api/editorconfig/ec4j/EditorConfigPropertyKt {
622627
public static final fun toPropertyBuilderWithValue (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfigProperty;Ljava/lang/String;)Lorg/ec4j/core/model/Property$Builder;
623628
public static final fun toPropertyBuilderWithValue (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfigProperty;Lorg/ec4j/core/model/PropertyType$PropertyValue;)Lorg/ec4j/core/model/Property$Builder;

ktlint-rule-engine-core/src/main/kotlin/com/pinterest/ktlint/rule/engine/core/api/editorconfig/SafeEnumValueParser.kt

+5-13
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,17 @@ import org.ec4j.core.model.PropertyType.PropertyValueParser
55
import java.util.Locale
66

77
/**
8-
* A [PropertyValueParser] implementation that allows only members of a given [Enum] type. This class is almost
9-
* identical to the original [EnumValueParser] provided by ec4j. Difference is that values are trimmed before trying to
10-
* match the enum values.
8+
* A [PropertyValueParser] implementation that allows only members of a given [Enum] type. This class is almost identical to the original
9+
* [EnumValueParser] provided by ec4j. Difference is that values are trimmed before trying to match the enum values.
1110
*
12-
* As the ec4j project has not provided any new release since version 1.0 (2019-08-01) a custom implementation has been
13-
* added.
11+
* As the ec4j project has not provided any new release since version 1.0 (2019-08-01) a custom implementation has been added.
1412
*
1513
* @param <T> the type of the value <T>
1614
*
1715
*/
18-
internal class SafeEnumValueParser<T : Enum<T>>(
19-
enumType: Class<T>,
16+
public class SafeEnumValueParser<T : Enum<T>>(
17+
private val enumType: Class<T>,
2018
) : PropertyValueParser<T> {
21-
private val enumType: Class<T>
22-
23-
init {
24-
this.enumType = enumType
25-
}
26-
2719
override fun parse(
2820
name: String?,
2921
value: String?,

ktlint-ruleset-standard/api/ktlint-ruleset-standard.api

+16
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,26 @@ public final class com/pinterest/ktlint/ruleset/standard/rules/DiscouragedCommen
195195
}
196196

197197
public final class com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule : com/pinterest/ktlint/ruleset/standard/StandardRule {
198+
public static final field Companion Lcom/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule$Companion;
198199
public fun <init> ()V
200+
public fun beforeFirstNode (Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfig;)V
199201
public fun beforeVisitChildNodes (Lorg/jetbrains/kotlin/com/intellij/lang/ASTNode;Lkotlin/jvm/functions/Function3;)V
200202
}
201203

204+
public final class com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule$Companion {
205+
public final fun getENUM_ENTRY_NAME_CASING_PROPERTY ()Lcom/pinterest/ktlint/rule/engine/core/api/editorconfig/EditorConfigProperty;
206+
public final fun getENUM_ENTRY_NAME_CASING_PROPERTY_TYPE ()Lorg/ec4j/core/model/PropertyType$LowerCasingPropertyType;
207+
}
208+
209+
public final class com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule$Companion$EnumEntryNameCasing : java/lang/Enum {
210+
public static final field camel_cases Lcom/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule$Companion$EnumEntryNameCasing;
211+
public static final field upper_cases Lcom/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule$Companion$EnumEntryNameCasing;
212+
public static final field upper_or_camel_cases Lcom/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule$Companion$EnumEntryNameCasing;
213+
public static fun getEntries ()Lkotlin/enums/EnumEntries;
214+
public static fun valueOf (Ljava/lang/String;)Lcom/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule$Companion$EnumEntryNameCasing;
215+
public static fun values ()[Lcom/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule$Companion$EnumEntryNameCasing;
216+
}
217+
202218
public final class com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRuleKt {
203219
public static final fun getENUM_ENTRY_NAME_CASE_RULE_ID ()Lcom/pinterest/ktlint/rule/engine/core/api/RuleId;
204220
}

ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRule.kt

+71-9
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,12 @@ import com.pinterest.ktlint.rule.engine.core.api.RuleId
55
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint
66
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.EXPERIMENTAL
77
import com.pinterest.ktlint.rule.engine.core.api.SinceKtlint.Status.STABLE
8+
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfig
9+
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty
10+
import com.pinterest.ktlint.rule.engine.core.api.editorconfig.SafeEnumValueParser
811
import com.pinterest.ktlint.ruleset.standard.StandardRule
912
import com.pinterest.ktlint.ruleset.standard.rules.internal.regExIgnoringDiacriticsAndStrokesOnLetters
13+
import org.ec4j.core.model.PropertyType
1014
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
1115
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement
1216
import org.jetbrains.kotlin.psi.KtEnumEntry
@@ -16,9 +20,34 @@ import org.jetbrains.kotlin.psi.KtEnumEntry
1620
*/
1721
@SinceKtlint("0.36", EXPERIMENTAL)
1822
@SinceKtlint("0.46", STABLE)
19-
public class EnumEntryNameCaseRule : StandardRule("enum-entry-name-case") {
20-
internal companion object {
21-
val ENUM_ENTRY_IDENTIFIER_REGEX = "[A-Z]([A-Za-z\\d]*|[A-Z_\\d]*)".regExIgnoringDiacriticsAndStrokesOnLetters()
23+
public class EnumEntryNameCaseRule :
24+
StandardRule(
25+
id = "enum-entry-name-case",
26+
usesEditorConfigProperties = setOf(ENUM_ENTRY_NAME_CASING_PROPERTY),
27+
) {
28+
private lateinit var enumEntryCasingRegex: Regex
29+
private lateinit var enumEntryCasingViolation: String
30+
private var x = ENUM_ENTRY_NAME_CASING_PROPERTY.defaultValue
31+
32+
override fun beforeFirstNode(editorConfig: EditorConfig) {
33+
x = editorConfig[ENUM_ENTRY_NAME_CASING_PROPERTY]
34+
when (editorConfig[ENUM_ENTRY_NAME_CASING_PROPERTY]) {
35+
EnumEntryNameCasing.upper_cases -> {
36+
enumEntryCasingRegex = "[A-Z][A-Z_\\d]*".regExIgnoringDiacriticsAndStrokesOnLetters()
37+
enumEntryCasingViolation = "Enum entry name should be uppercase underscore-separated names like \"ENUM_ENTRY\""
38+
}
39+
40+
EnumEntryNameCasing.camel_cases -> {
41+
enumEntryCasingRegex = "[A-Z]([A-Za-z\\d]*)".regExIgnoringDiacriticsAndStrokesOnLetters()
42+
enumEntryCasingViolation = "Enum entry name should be upper camel-case like \"EnumEntry\""
43+
}
44+
45+
EnumEntryNameCasing.upper_or_camel_cases -> {
46+
enumEntryCasingRegex = "[A-Z]([A-Za-z\\d]*|[A-Z_\\d]*)".regExIgnoringDiacriticsAndStrokesOnLetters()
47+
enumEntryCasingViolation =
48+
"Enum entry name should be uppercase underscore-separated names like \"ENUM_ENTRY\" or upper camel-case like \"EnumEntry\""
49+
}
50+
}
2251
}
2352

2453
override fun beforeVisitChildNodes(
@@ -31,14 +60,47 @@ public class EnumEntryNameCaseRule : StandardRule("enum-entry-name-case") {
3160
val enumEntry = node.psi as? KtEnumEntry ?: return
3261
val name = enumEntry.name ?: return
3362

34-
if (!name.matches(ENUM_ENTRY_IDENTIFIER_REGEX)) {
35-
emit(
36-
node.startOffset,
37-
"Enum entry name should be uppercase underscore-separated names like \"ENUM_ENTRY\" or upper camel-case like \"EnumEntry\"",
38-
false,
39-
)
63+
if (!name.matches(enumEntryCasingRegex)) {
64+
emit(node.startOffset, enumEntryCasingViolation, false)
4065
}
4166
}
67+
68+
public companion object {
69+
@Suppress("EnumEntryName")
70+
public enum class EnumEntryNameCasing {
71+
/**
72+
* Enforce all enum entry names to be uppercase underscore-separated names like "ENUM_ENTRY". Digits, diacritics and strokes are
73+
* allowed.
74+
*/
75+
upper_cases,
76+
77+
/**
78+
* Enforce all enum entry names to be upper camel-case like "EnumEntry". Digits, diacritics and strokes are allowed.
79+
*/
80+
camel_cases,
81+
82+
/**
83+
* Enforce all enum entry names to be uppercase underscore-separated names like "ENUM_ENTRY" or upper camel-case like
84+
* "EnumEntry". Digits, diacritics and strokes are allowed.
85+
*/
86+
upper_or_camel_cases,
87+
}
88+
89+
public val ENUM_ENTRY_NAME_CASING_PROPERTY_TYPE: PropertyType.LowerCasingPropertyType<EnumEntryNameCasing> =
90+
PropertyType.LowerCasingPropertyType(
91+
"ktlint_enum_entry_name_casing",
92+
"Enforce all enum entry names to be uppercase underscore-separated names like \"ENUM_ENTRY\" and/or upper " +
93+
"camel-case like \"EnumEntry\". Digits, diacritics and strokes are always allowed.",
94+
SafeEnumValueParser(EnumEntryNameCasing::class.java),
95+
EnumEntryNameCasing.entries.map { it.name.lowercase() }.toSet(),
96+
)
97+
98+
public val ENUM_ENTRY_NAME_CASING_PROPERTY: EditorConfigProperty<EnumEntryNameCasing> =
99+
EditorConfigProperty(
100+
type = ENUM_ENTRY_NAME_CASING_PROPERTY_TYPE,
101+
defaultValue = EnumEntryNameCasing.upper_or_camel_cases,
102+
)
103+
}
42104
}
43105

44106
public val ENUM_ENTRY_NAME_CASE_RULE_ID: RuleId = EnumEntryNameCaseRule().ruleId

ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/rules/EnumEntryNameCaseRuleTest.kt

+44
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package com.pinterest.ktlint.ruleset.standard.rules
22

3+
import com.pinterest.ktlint.ruleset.standard.rules.EnumEntryNameCaseRule.Companion.ENUM_ENTRY_NAME_CASING_PROPERTY
4+
import com.pinterest.ktlint.ruleset.standard.rules.EnumEntryNameCaseRule.Companion.EnumEntryNameCasing.camel_cases
5+
import com.pinterest.ktlint.ruleset.standard.rules.EnumEntryNameCaseRule.Companion.EnumEntryNameCasing.upper_cases
6+
import com.pinterest.ktlint.ruleset.standard.rules.EnumEntryNameCaseRule.Companion.EnumEntryNameCasing.upper_or_camel_cases
37
import com.pinterest.ktlint.test.KtLintAssertThat.Companion.assertThatRule
48
import com.pinterest.ktlint.test.LintViolation
9+
import org.junit.jupiter.api.Nested
510
import org.junit.jupiter.api.Test
611

712
class EnumEntryNameCaseRuleTest {
@@ -75,4 +80,43 @@ class EnumEntryNameCaseRuleTest {
7580
""".trimIndent()
7681
enumEntryNameCaseRuleAssertThat(code).hasNoLintViolations()
7782
}
83+
84+
@Nested
85+
inner class `Issue 2835 - Given enum entries in both upper cases and camel cases` {
86+
val code =
87+
"""
88+
enum class SomeEnum {
89+
UPPER_CASE,
90+
CamelCase,
91+
}
92+
""".trimIndent()
93+
94+
@Test
95+
fun `Given that 'ktlint_enum_entry_name_casing' is not set, then allow both upper cases and camel cases`() {
96+
enumEntryNameCaseRuleAssertThat(code).hasNoLintViolations()
97+
}
98+
99+
@Test
100+
fun `Given that 'ktlint_enum_entry_name_casing' is set to 'UPPER_OR_CAMEL_CASES', then allow both upper cases and camel cases`() {
101+
enumEntryNameCaseRuleAssertThat(code)
102+
.withEditorConfigOverride(ENUM_ENTRY_NAME_CASING_PROPERTY to upper_or_camel_cases)
103+
.hasNoLintViolations()
104+
}
105+
106+
@Test
107+
fun `Given that 'ktlint_enum_entry_name_casing' is set to 'UPPER_CASES', then allow only upper cases`() {
108+
@Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
109+
enumEntryNameCaseRuleAssertThat(code)
110+
.withEditorConfigOverride(ENUM_ENTRY_NAME_CASING_PROPERTY to upper_cases)
111+
.hasLintViolationWithoutAutoCorrect(3, 5, "Enum entry name should be uppercase underscore-separated names like \"ENUM_ENTRY\"")
112+
}
113+
114+
@Test
115+
fun `Given that 'ktlint_enum_entry_name_casing' is set to 'CAMEL_CASES', then allow only camel cases`() {
116+
@Suppress("ktlint:standard:argument-list-wrapping", "ktlint:standard:max-line-length")
117+
enumEntryNameCaseRuleAssertThat(code)
118+
.withEditorConfigOverride(ENUM_ENTRY_NAME_CASING_PROPERTY to camel_cases)
119+
.hasLintViolationWithoutAutoCorrect(2, 5, "Enum entry name should be upper camel-case like \"EnumEntry\"")
120+
}
121+
}
78122
}

0 commit comments

Comments
 (0)