Add new `sorted_enum_cases` rule (#4845)
This commit is contained in:
parent
58a07eb452
commit
b0cbb440c3
|
@ -42,6 +42,7 @@ disabled_rules:
|
||||||
- prefixed_toplevel_constant
|
- prefixed_toplevel_constant
|
||||||
- required_deinit
|
- required_deinit
|
||||||
- self_binding
|
- self_binding
|
||||||
|
- sorted_enum_cases
|
||||||
- strict_fileprivate
|
- strict_fileprivate
|
||||||
- switch_case_on_newline
|
- switch_case_on_newline
|
||||||
- trailing_closure
|
- trailing_closure
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
|
|
||||||
#### Enhancements
|
#### Enhancements
|
||||||
|
|
||||||
* None.
|
* Add `sorted_enum_cases` rule which warns when enum cases are not sorted.
|
||||||
|
[kimdv](https://github.com/kimdv)
|
||||||
|
|
||||||
#### Bug Fixes
|
#### Bug Fixes
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,7 @@ let builtInRules: [Rule.Type] = [
|
||||||
ShorthandOperatorRule.self,
|
ShorthandOperatorRule.self,
|
||||||
ShorthandOptionalBindingRule.self,
|
ShorthandOptionalBindingRule.self,
|
||||||
SingleTestClassRule.self,
|
SingleTestClassRule.self,
|
||||||
|
SortedEnumCasesRule.self,
|
||||||
SortedFirstLastRule.self,
|
SortedFirstLastRule.self,
|
||||||
SortedImportsRule.self,
|
SortedImportsRule.self,
|
||||||
StatementPositionRule.self,
|
StatementPositionRule.self,
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
import SwiftSyntax
|
||||||
|
|
||||||
|
struct SortedEnumCasesRule: ConfigurationProviderRule, SwiftSyntaxRule, OptInRule {
|
||||||
|
var configuration = SeverityConfiguration(.warning)
|
||||||
|
|
||||||
|
init() {}
|
||||||
|
|
||||||
|
static let description = RuleDescription(
|
||||||
|
identifier: "sorted_enum_cases",
|
||||||
|
name: "Sorted Enum Cases",
|
||||||
|
description: "Enum cases should be sorted",
|
||||||
|
kind: .style,
|
||||||
|
nonTriggeringExamples: [
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
case a
|
||||||
|
case b
|
||||||
|
case c
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
case a, b, c
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
case a
|
||||||
|
case b, c
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
case a(foo: Foo)
|
||||||
|
case b(String), c
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
@frozen
|
||||||
|
enum foo {
|
||||||
|
case b
|
||||||
|
case a
|
||||||
|
case c, f, d
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
],
|
||||||
|
triggeringExamples: [
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
↓case b
|
||||||
|
↓case a
|
||||||
|
case c
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
case ↓b, ↓a, c
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
↓case b, c
|
||||||
|
↓case a
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
case a
|
||||||
|
case b, ↓d, ↓c
|
||||||
|
}
|
||||||
|
"""),
|
||||||
|
Example("""
|
||||||
|
enum foo {
|
||||||
|
case a(foo: Foo)
|
||||||
|
case ↓c, ↓b(String)
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
||||||
|
Visitor(viewMode: .sourceAccurate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension SortedEnumCasesRule {
|
||||||
|
final class Visitor: ViolationsSyntaxVisitor {
|
||||||
|
override var skippableDeclarations: [DeclSyntaxProtocol.Type] {
|
||||||
|
return .allExcept(EnumDeclSyntax.self)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
|
||||||
|
guard !node.attributes.contains(attributeNamed: "frozen") else {
|
||||||
|
return .skipChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
let cases = node.members.members.compactMap { $0.decl.as(EnumCaseDeclSyntax.self) }
|
||||||
|
let sortedCases = cases
|
||||||
|
.sorted(by: { $0.elements.first!.identifier.text < $1.elements.first!.identifier.text })
|
||||||
|
|
||||||
|
zip(sortedCases, cases).forEach { sortedCase, currentCase in
|
||||||
|
if sortedCase.elements.first?.identifier.text != currentCase.elements.first?.identifier.text {
|
||||||
|
violations.append(currentCase.positionAfterSkippingLeadingTrivia)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .visitChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
override func visitPost(_ node: EnumCaseDeclSyntax) {
|
||||||
|
let sortedElements = node.elements.sorted(by: { $0.identifier.text < $1.identifier.text })
|
||||||
|
|
||||||
|
zip(sortedElements, node.elements).forEach { sortedElement, currentElement in
|
||||||
|
if sortedElement.identifier.text != currentElement.identifier.text {
|
||||||
|
violations.append(currentElement.positionAfterSkippingLeadingTrivia)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1058,6 +1058,12 @@ class SingleTestClassRuleGeneratedTests: XCTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SortedEnumCasesRuleGeneratedTests: XCTestCase {
|
||||||
|
func testWithDefaultConfiguration() {
|
||||||
|
verifyRule(SortedEnumCasesRule.description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SortedFirstLastRuleGeneratedTests: XCTestCase {
|
class SortedFirstLastRuleGeneratedTests: XCTestCase {
|
||||||
func testWithDefaultConfiguration() {
|
func testWithDefaultConfiguration() {
|
||||||
verifyRule(SortedFirstLastRule.description)
|
verifyRule(SortedFirstLastRule.description)
|
||||||
|
|
Loading…
Reference in New Issue