Add new `sorted_enum_cases` rule (#4845)

This commit is contained in:
Kim de Vos 2023-04-02 11:33:20 +02:00 committed by GitHub
parent 58a07eb452
commit b0cbb440c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 1 deletions

View File

@ -42,6 +42,7 @@ disabled_rules:
- prefixed_toplevel_constant
- required_deinit
- self_binding
- sorted_enum_cases
- strict_fileprivate
- switch_case_on_newline
- trailing_closure

View File

@ -10,7 +10,8 @@
#### Enhancements
* None.
* Add `sorted_enum_cases` rule which warns when enum cases are not sorted.
[kimdv](https://github.com/kimdv)
#### Bug Fixes

View File

@ -179,6 +179,7 @@ let builtInRules: [Rule.Type] = [
ShorthandOperatorRule.self,
ShorthandOptionalBindingRule.self,
SingleTestClassRule.self,
SortedEnumCasesRule.self,
SortedFirstLastRule.self,
SortedImportsRule.self,
StatementPositionRule.self,

View File

@ -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)
}
}
}
}
}

View File

@ -1058,6 +1058,12 @@ class SingleTestClassRuleGeneratedTests: XCTestCase {
}
}
class SortedEnumCasesRuleGeneratedTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(SortedEnumCasesRule.description)
}
}
class SortedFirstLastRuleGeneratedTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(SortedFirstLastRule.description)