@@ -27,78 +27,95 @@ public final class FullyIndirectEnum: SyntaxFormatRule {
27
27
28
28
public override func visit( _ node: EnumDeclSyntax ) -> DeclSyntax {
29
29
let enumMembers = node. members. members
30
- guard allAreIndirectCases ( members: enumMembers) else { return node }
31
- diagnose ( . reassignIndirectKeyword( name: node. identifier. text) , on: node. identifier)
30
+ guard let enumModifiers = node. modifiers,
31
+ !enumModifiers. has ( modifier: " indirect " ) ,
32
+ allCasesAreIndirect ( in: enumMembers)
33
+ else {
34
+ return node
35
+ }
36
+
37
+ diagnose ( . moveIndirectKeywordToEnumDecl( name: node. identifier. text) , on: node. identifier)
32
38
33
39
// Removes 'indirect' keyword from cases, reformats
34
- var newMembers : [ DeclSyntax ] = [ ]
35
- for member in enumMembers {
36
- if let caseMember = member as? EnumCaseDeclSyntax {
37
- guard let caseModifiers = caseMember. modifiers else { continue }
38
- guard let firstModifier = caseModifiers. first else { continue }
39
- let newCase = caseMember. withModifiers ( caseModifiers. remove ( name: " indirect " ) )
40
- let formattedCase = formatCase ( unformattedCase: newCase,
41
- leadingTrivia: firstModifier. leadingTrivia)
42
- newMembers. append ( formattedCase)
43
- } else {
44
- newMembers. append ( member)
40
+ let newMembers = enumMembers. map { ( member: DeclSyntax ) -> DeclSyntax in
41
+ guard let caseMember = member as? EnumCaseDeclSyntax ,
42
+ let modifiers = caseMember. modifiers,
43
+ modifiers. has ( modifier: " indirect " ) ,
44
+ let firstModifier = modifiers. first
45
+ else {
46
+ return member
45
47
}
48
+
49
+ let newCase = caseMember. withModifiers ( modifiers. remove ( name: " indirect " ) )
50
+ let formattedCase = formatCase (
51
+ unformattedCase: newCase, leadingTrivia: firstModifier. leadingTrivia)
52
+ return formattedCase
46
53
}
47
54
48
- let newMemberBlock = SyntaxFactory . makeMemberDeclBlock (
49
- leftBrace: node. members. leftBrace,
50
- members: SyntaxFactory . makeDeclList ( newMembers) ,
51
- rightBrace: node. members. rightBrace)
55
+ // If the `indirect` keyword being added would be the first token in the decl, we need to move
56
+ // the leading trivia from the `enum` keyword to the new modifier to preserve the existing
57
+ // line breaks/comments/indentation.
58
+ let firstTok = node. firstToken!
59
+ let leadingTrivia : Trivia
60
+ let newEnumDecl : EnumDeclSyntax
52
61
53
- // Format indirect keyword and following token, if necessary
54
- guard let firstTok = node. firstToken else { return node }
55
- var leadingTrivia : Trivia = [ ]
56
- var newDecl = node
57
62
if firstTok. tokenKind == . enumKeyword {
58
63
leadingTrivia = firstTok. leadingTrivia
59
- newDecl = replaceTrivia ( on: node,
60
- token: node. firstToken,
61
- leadingTrivia: [ ] ) as! EnumDeclSyntax
64
+ newEnumDecl = replaceTrivia (
65
+ on: node, token: node. firstToken, leadingTrivia: [ ] ) as! EnumDeclSyntax
66
+ } else {
67
+ leadingTrivia = [ ]
68
+ newEnumDecl = node
62
69
}
63
70
64
71
let newModifier = SyntaxFactory . makeDeclModifier (
65
- name: SyntaxFactory . makeIdentifier ( " indirect " ,
66
- leadingTrivia: leadingTrivia,
67
- trailingTrivia: . spaces( 1 ) ) ,
68
- detail: nil )
72
+ name: SyntaxFactory . makeIdentifier (
73
+ " indirect " , leadingTrivia: leadingTrivia, trailingTrivia: . spaces( 1 ) ) ,
74
+ detail: nil )
69
75
70
- return newDecl. addModifier ( newModifier) . withMembers ( newMemberBlock)
76
+ let newMemberBlock = node. members. withMembers ( SyntaxFactory . makeDeclList ( newMembers) )
77
+ return newEnumDecl. addModifier ( newModifier) . withMembers ( newMemberBlock)
71
78
}
72
79
73
- // Determines if all given cases are indirect
74
- func allAreIndirectCases( members: DeclListSyntax ) -> Bool {
80
+ /// Returns a value indicating whether all enum cases in the given list are indirect.
81
+ ///
82
+ /// Note that if the enum has no cases, this returns false.
83
+ private func allCasesAreIndirect( in members: DeclListSyntax ) -> Bool {
84
+ var hadCases = false
75
85
for member in members {
76
86
if let caseMember = member as? EnumCaseDeclSyntax {
77
- guard let caseModifiers = caseMember. modifiers else { return false }
78
- if caseModifiers. has ( modifier: " indirect " ) { continue }
79
- else { return false }
87
+ hadCases = true
88
+ guard let modifiers = caseMember. modifiers, modifiers. has ( modifier: " indirect " ) else {
89
+ return false
90
+ }
80
91
}
81
92
}
82
- return true
93
+ return hadCases
83
94
}
84
95
85
- // Transfers given leading trivia to the first token in the case declaration
86
- func formatCase( unformattedCase: EnumCaseDeclSyntax ,
87
- leadingTrivia: Trivia ? ) -> EnumCaseDeclSyntax {
96
+ /// Transfers given leading trivia to the first token in the case declaration.
97
+ private func formatCase(
98
+ unformattedCase: EnumCaseDeclSyntax ,
99
+ leadingTrivia: Trivia ?
100
+ ) -> EnumCaseDeclSyntax {
88
101
if let modifiers = unformattedCase. modifiers, let first = modifiers. first {
89
- return replaceTrivia ( on : unformattedCase ,
90
- token: first. firstToken,
91
- leadingTrivia : leadingTrivia ) as! EnumCaseDeclSyntax
102
+ return replaceTrivia (
103
+ on : unformattedCase , token: first. firstToken, leadingTrivia : leadingTrivia
104
+ ) as! EnumCaseDeclSyntax
92
105
} else {
93
- return replaceTrivia ( on : unformattedCase ,
94
- token: unformattedCase. caseKeyword,
95
- leadingTrivia : leadingTrivia ) as! EnumCaseDeclSyntax
106
+ return replaceTrivia (
107
+ on : unformattedCase , token: unformattedCase. caseKeyword, leadingTrivia : leadingTrivia
108
+ ) as! EnumCaseDeclSyntax
96
109
}
97
110
}
98
111
}
99
112
100
113
extension Diagnostic . Message {
101
- static func reassignIndirectKeyword( name: String ) -> Diagnostic . Message {
102
- return . init( . warning, " move 'indirect' to \( name) enum declaration " )
114
+
115
+ static func moveIndirectKeywordToEnumDecl( name: String ) -> Diagnostic . Message {
116
+ return . init(
117
+ . warning,
118
+ " move 'indirect' to \( name) enum declaration when all cases are indirect "
119
+ )
103
120
}
104
121
}
0 commit comments