@@ -14,98 +14,125 @@ import Foundation
14
14
import SwiftFormatCore
15
15
import SwiftSyntax
16
16
17
- /// Each enum case with associated values should appear on its own line .
17
+ /// Each enum case with associated values or a raw value should appear in its own case declaration .
18
18
///
19
19
/// Lint: If a single `case` declaration declares multiple cases, and any of them have associated
20
- /// values, a lint error is raised.
20
+ /// values or raw values , a lint error is raised.
21
21
///
22
- /// Format: All case declarations with associated values will be moved to a new line.
22
+ /// Format: All case declarations with associated values or raw values will be moved to their own
23
+ /// case declarations.
23
24
///
24
25
/// - SeeAlso: https://google.github.io/swift#enum-cases
25
26
public final class OneCasePerLine : SyntaxFormatRule {
26
27
28
+ /// A state machine that collects case elements encountered during visitation and allows new case
29
+ /// declarations to be created with those elements.
30
+ private struct CaseElementCollector {
31
+
32
+ /// The case declaration used as the source from which additional new declarations will be
33
+ /// created; thus, all new cases will share the same attributes and modifiers as the basis.
34
+ public private( set) var basis : EnumCaseDeclSyntax
35
+
36
+ /// Case elements collected so far.
37
+ private var elements = [ EnumCaseElementSyntax] ( )
38
+
39
+ /// Indicates whether the full leading trivia of basis case declaration should be preserved by
40
+ /// the next case declaration that will be created by copying the basis declaration.
41
+ ///
42
+ /// This is true for the first case (to preserve any leading comments on the original case
43
+ /// declaration) and false for all subsequent cases (so that we don't repeat those comments).
44
+ private var shouldKeepLeadingTrivia = true
45
+
46
+ /// Creates a new case element collector based on the given case declaration.
47
+ init ( basedOn basis: EnumCaseDeclSyntax ) {
48
+ self . basis = basis
49
+ }
50
+
51
+ /// Adds a new case element to the collector.
52
+ mutating func addElement( _ element: EnumCaseElementSyntax ) {
53
+ elements. append ( element)
54
+ }
55
+
56
+ /// Creates a new case declaration with the elements collected so far, then resets the internal
57
+ /// state to start a new empty declaration again.
58
+ ///
59
+ /// This will return nil if there are no elements collected since the last time this was called
60
+ /// (or the collector was created).
61
+ mutating func makeCaseDeclAndReset( ) -> EnumCaseDeclSyntax ? {
62
+ guard let last = elements. last else { return nil }
63
+
64
+ // Remove the trailing comma on the final element, if there was one.
65
+ if last. trailingComma != nil {
66
+ elements [ elements. count - 1 ] = last. withTrailingComma ( nil )
67
+ }
68
+
69
+ defer { elements. removeAll ( ) }
70
+ return makeCaseDeclFromBasis ( elements: elements)
71
+ }
72
+
73
+ /// Creates and returns a new `EnumCaseDeclSyntax` with the given elements, based on the current
74
+ /// basis declaration, and updates the comment preserving state if needed.
75
+ mutating func makeCaseDeclFromBasis( elements: [ EnumCaseElementSyntax ] ) -> EnumCaseDeclSyntax {
76
+ let caseDecl = basis. withElements ( SyntaxFactory . makeEnumCaseElementList ( elements) )
77
+
78
+ if shouldKeepLeadingTrivia {
79
+ shouldKeepLeadingTrivia = false
80
+
81
+ // We don't bother preserving any indentation because the pretty printer will fix that up.
82
+ // All we need to do here is ensure that there is a newline.
83
+ basis = basis. withLeadingTrivia ( Trivia . newlines ( 1 ) )
84
+ }
85
+
86
+ return caseDecl
87
+ }
88
+ }
89
+
27
90
public override func visit( _ node: EnumDeclSyntax ) -> DeclSyntax {
28
- let enumMembers = node. members. members
29
91
var newMembers : [ MemberDeclListItemSyntax ] = [ ]
30
- var newIndx = 0
31
-
32
- for member in enumMembers {
33
- var numNewMembers = 0
34
- if let caseMember = member. decl as? EnumCaseDeclSyntax {
35
- var otherDecl : EnumCaseDeclSyntax ? = caseMember
36
- // Add and skip single element case declarations
37
- guard caseMember. elements. count > 1 else {
38
- newMembers. append ( member. withDecl ( caseMember) )
39
- newIndx += 1
40
- continue
41
- }
42
- // Move all cases with associated/raw values to new declarations
43
- for element in caseMember. elements {
44
- if element. associatedValue != nil || element. rawValue != nil {
45
- diagnose ( . moveAssociatedOrRawValueCase( name: element. identifier. text) , on: element)
46
- let newRemovedDecl = createAssociateOrRawCaseDecl (
47
- fullDecl: caseMember,
48
- removedElement: element)
49
- otherDecl = removeAssociateOrRawCaseDecl ( fullDecl: otherDecl)
50
- newMembers. append ( member. withDecl ( newRemovedDecl) )
51
- numNewMembers += 1
52
- }
53
- }
54
- // Add case declaration of remaining elements without associated/raw values, if any
55
- if let otherDecl = otherDecl {
56
- newMembers. insert ( member. withDecl ( otherDecl) , at: newIndx)
57
- newIndx += 1
58
- }
59
- // Add any member that isn't an enum case declaration
60
- } else {
92
+
93
+ for member in node. members. members {
94
+ // If it's not a case declaration, or it's a case declaration with only one element, leave it
95
+ // alone.
96
+ guard let caseDecl = member. decl as? EnumCaseDeclSyntax , caseDecl. elements. count > 1 else {
61
97
newMembers. append ( member)
62
- newIndx += 1
98
+ continue
63
99
}
64
- newIndx += numNewMembers
65
- }
66
100
67
- let newMemberBlock = SyntaxFactory . makeMemberDeclBlock (
68
- leftBrace: node. members. leftBrace,
69
- members: SyntaxFactory . makeMemberDeclList ( newMembers) ,
70
- rightBrace: node. members. rightBrace)
71
- return node. withMembers ( newMemberBlock)
72
- }
101
+ var collector = CaseElementCollector ( basedOn: caseDecl)
73
102
74
- func createAssociateOrRawCaseDecl(
75
- fullDecl: EnumCaseDeclSyntax ,
76
- removedElement: EnumCaseElementSyntax
77
- ) -> EnumCaseDeclSyntax {
78
- let formattedElement = removedElement. withTrailingComma ( nil )
79
- let newElementList = SyntaxFactory . makeEnumCaseElementList ( [ formattedElement] )
80
- let newDecl = SyntaxFactory . makeEnumCaseDecl (
81
- attributes: fullDecl. attributes,
82
- modifiers: fullDecl. modifiers,
83
- caseKeyword: fullDecl. caseKeyword,
84
- elements: newElementList)
85
- return newDecl
86
- }
103
+ // Collect the elements of the case declaration until we see one that has either an associated
104
+ // value or a raw value.
105
+ for element in caseDecl. elements {
106
+ if element. associatedValue != nil || element. rawValue != nil {
107
+ // Once we reach one of these, we need to write out the ones we've collected so far, then
108
+ // emit a separate case declaration with the associated/raw value element.
109
+ diagnose ( . moveAssociatedOrRawValueCase( name: element. identifier. text) , on: element)
87
110
88
- // Returns formatted declaration of cases without associated/raw values, or nil if all cases had
89
- // a raw or associate value
90
- func removeAssociateOrRawCaseDecl( fullDecl: EnumCaseDeclSyntax ? ) -> EnumCaseDeclSyntax ? {
91
- guard let fullDecl = fullDecl else { return nil }
92
- var newList : [ EnumCaseElementSyntax ] = [ ]
111
+ if let caseDeclForCollectedElements = collector. makeCaseDeclAndReset ( ) {
112
+ newMembers. append ( member. withDecl ( caseDeclForCollectedElements) )
113
+ }
93
114
94
- for element in fullDecl. elements {
95
- if element. associatedValue == nil && element. rawValue == nil { newList. append ( element) }
96
- }
115
+ let separatedCaseDecl =
116
+ collector. makeCaseDeclFromBasis ( elements: [ element. withTrailingComma ( nil ) ] )
117
+ newMembers. append ( member. withDecl ( separatedCaseDecl) )
118
+ } else {
119
+ collector. addElement ( element)
120
+ }
121
+ }
97
122
98
- guard newList . count > 0 else { return nil }
99
- let ( last , indx ) = ( newList [ newList . count - 1 ] , newList . count - 1 )
100
- if last . trailingComma != nil {
101
- newList [ indx ] = last . withTrailingComma ( nil )
123
+ // Make sure to emit any trailing collected elements.
124
+ if let caseDeclForCollectedElements = collector . makeCaseDeclAndReset ( ) {
125
+ newMembers . append ( member . withDecl ( caseDeclForCollectedElements ) )
126
+ }
102
127
}
103
- return fullDecl. withElements ( SyntaxFactory . makeEnumCaseElementList ( newList) )
128
+
129
+ let newMemberBlock = node. members. withMembers ( SyntaxFactory . makeMemberDeclList ( newMembers) )
130
+ return node. withMembers ( newMemberBlock)
104
131
}
105
132
}
106
133
107
134
extension Diagnostic . Message {
108
135
static func moveAssociatedOrRawValueCase( name: String ) -> Diagnostic . Message {
109
- return . init( . warning, " move \( name) case to a new line " )
136
+ return . init( . warning, " move ' \( name) ' to its own case declaration " )
110
137
}
111
138
}
0 commit comments