@@ -14,98 +14,125 @@ import Foundation
1414import SwiftFormatCore
1515import SwiftSyntax
1616
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 .
1818///
1919/// 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.
2121///
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.
2324///
2425/// - SeeAlso: https://google.github.io/swift#enum-cases
2526public final class OneCasePerLine : SyntaxFormatRule {
2627
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+
2790 public override func visit( _ node: EnumDeclSyntax ) -> DeclSyntax {
28- let enumMembers = node. members. members
2991 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 {
6197 newMembers. append ( member)
62- newIndx += 1
98+ continue
6399 }
64- newIndx += numNewMembers
65- }
66100
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)
73102
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)
87110
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+ }
93114
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+ }
97122
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+ }
102127 }
103- return fullDecl. withElements ( SyntaxFactory . makeEnumCaseElementList ( newList) )
128+
129+ let newMemberBlock = node. members. withMembers ( SyntaxFactory . makeMemberDeclList ( newMembers) )
130+ return node. withMembers ( newMemberBlock)
104131 }
105132}
106133
107134extension Diagnostic . Message {
108135 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 " )
110137 }
111138}
0 commit comments