@@ -17,5 +17,147 @@ import SwiftSyntax
17
17
///
18
18
/// - SeeAlso: https://google.github.io/swift#vertical-whitespace
19
19
public final class BlankLineBetweenMembers : SyntaxFormatRule {
20
+ public override func visit( _ node: MemberDeclBlockSyntax ) -> Syntax {
21
+ var membersList = [ MemberDeclListItemSyntax] ( )
22
+ var hasValidNumOfBlankLines = true
20
23
24
+ // Iterates through all the declaration of the member, to ensure that the declarations have
25
+ // at least on blank line and doesn't exceed the maximum number of blank lines.
26
+ for member in node. members {
27
+ let currentMember = checkForNestedMembers ( member)
28
+ guard let memberTrivia = currentMember. leadingTrivia else { continue }
29
+ let triviaWithoutTrailingSpaces = memberTrivia. withoutTrailingSpaces ( )
30
+ guard let firstPiece = triviaWithoutTrailingSpaces. first else { continue }
31
+
32
+ if exceedsMaxBlankLines ( triviaWithoutTrailingSpaces) {
33
+ let correctTrivia = removeExtraBlankLines ( triviaWithoutTrailingSpaces, currentMember)
34
+ let newMember = replaceTrivia (
35
+ on: currentMember,
36
+ token: currentMember. firstToken!,
37
+ leadingTrivia: correctTrivia
38
+ ) as! MemberDeclListItemSyntax
39
+
40
+ hasValidNumOfBlankLines = false
41
+ membersList. append ( newMember)
42
+ }
43
+ // Ensures that there is at least one blank line between each member of a type.
44
+ // Unless is a single-line declaration and the format is configured to
45
+ // ignored them.
46
+ else if case . newlines( let numNewLines) = firstPiece,
47
+ !ignoreItem( item: currentMember) ,
48
+ numNewLines == 1 {
49
+ let numBlankLines = member != node. members. first ? 1 : 0
50
+ let correctTrivia = Trivia . newlines ( numBlankLines) + memberTrivia
51
+ let newMember = replaceTrivia (
52
+ on: currentMember, token: currentMember. firstToken!,
53
+ leadingTrivia: correctTrivia
54
+ ) as! MemberDeclListItemSyntax
55
+
56
+ diagnose ( . addBlankLine, on: currentMember)
57
+ hasValidNumOfBlankLines = false
58
+ membersList. append ( newMember)
59
+ }
60
+ else {
61
+ membersList. append ( member)
62
+ }
63
+ }
64
+
65
+ return hasValidNumOfBlankLines ? node :
66
+ node. withMembers ( SyntaxFactory . makeMemberDeclList ( membersList) )
67
+ }
68
+
69
+ /// Indicates if the given trivia has more than
70
+ /// the maximum number of blank lines.
71
+ func exceedsMaxBlankLines( _ trivia: Trivia ) -> Bool {
72
+ let maxBlankLines = context. configuration. maximumBlankLines
73
+
74
+ for piece in trivia {
75
+ if case . newlines( let num) = piece,
76
+ num - 1 > maxBlankLines {
77
+ return true
78
+ }
79
+ }
80
+ return false
81
+ }
82
+
83
+ /// Returns the given trivia without any set of consecutive blank lines
84
+ /// that exceeds the maximumBlankLines.
85
+ func removeExtraBlankLines( _ trivia: Trivia , _ member: MemberDeclListItemSyntax ) -> Trivia {
86
+ let maxBlankLines = context. configuration. maximumBlankLines
87
+ var pieces = [ TriviaPiece] ( )
88
+
89
+ // Iterates through the trivia, verifying that the number of blank
90
+ // lines in the file do not exceed the maximumBlankLines. If it does
91
+ // a lint error is raised.
92
+ for piece in trivia {
93
+ if case . newlines( let num) = piece,
94
+ num - 1 > maxBlankLines {
95
+ pieces. append ( . newlines( maxBlankLines + 1 ) )
96
+ diagnose ( . removeBlankLines( count: num - maxBlankLines) , on: member)
97
+ }
98
+ else {
99
+ pieces. append ( piece)
100
+ }
101
+ }
102
+ return Trivia ( pieces: pieces)
103
+ }
104
+
105
+ /// Indicates if a declaration has to be ignored by checking if it's
106
+ /// a single line and if the format is configured to ignore single lines.
107
+ func ignoreItem( item: MemberDeclListItemSyntax ) -> Bool {
108
+ guard let firstToken = item. firstToken else { return false }
109
+ guard let lastToken = item. lastToken else { return false }
110
+
111
+ let isSingleLine = firstToken. positionAfterSkippingLeadingTrivia. line ==
112
+ lastToken. positionAfterSkippingLeadingTrivia. line
113
+
114
+ let ignoreLine = context. configuration. blankLineBetweenMembers
115
+ . ignoreSingleLineProperties
116
+
117
+ return isSingleLine && ignoreLine
118
+ }
119
+
120
+ /// Recursively ensures all nested member types follows the BlankLineBetweenMembers rule.
121
+ func checkForNestedMembers( _ member: MemberDeclListItemSyntax ) -> MemberDeclListItemSyntax {
122
+ switch member. decl {
123
+ case let nestedEnum as EnumDeclSyntax :
124
+ let nestedMembers = visit ( nestedEnum. members)
125
+ let newDecl = nestedEnum. withMembers ( nestedMembers as? MemberDeclBlockSyntax )
126
+ return member. withDecl ( newDecl)
127
+ case let nestedStruct as StructDeclSyntax :
128
+ let nestedMembers = visit ( nestedStruct. members)
129
+ let newDecl = nestedStruct. withMembers ( nestedMembers as? MemberDeclBlockSyntax )
130
+ return member. withDecl ( newDecl)
131
+ case let nestedClass as ClassDeclSyntax :
132
+ let nestedMembers = visit ( nestedClass. members)
133
+ let newDecl = nestedClass. withMembers ( nestedMembers as? MemberDeclBlockSyntax )
134
+ return member. withDecl ( newDecl)
135
+ case let nestedExtension as ExtensionDeclSyntax :
136
+ let nestedMembers = visit ( nestedExtension. members)
137
+ let newDecl = nestedExtension. withMembers ( nestedMembers as? MemberDeclBlockSyntax )
138
+ return member. withDecl ( newDecl)
139
+ default :
140
+ return member
141
+ }
142
+ }
143
+ }
144
+
145
+ /// Indicates if the given trivia piece is any type of comment.
146
+ func isComment( _ triviaPiece: TriviaPiece ) -> Bool {
147
+ switch triviaPiece {
148
+ case . lineComment( _) , . docLineComment( _) ,
149
+ . blockComment( _) , . docBlockComment( _) :
150
+ return true
151
+ default :
152
+ return false
153
+ }
154
+ }
155
+
156
+ extension Diagnostic . Message {
157
+ static let addBlankLine = Diagnostic . Message ( . warning, " add one blank line between declarations " )
158
+
159
+ static func removeBlankLines( count: Int ) -> Diagnostic . Message {
160
+ let ending = count > 1 ? " s " : " "
161
+ return Diagnostic . Message ( . warning, " remove \( count) blank line \( ending) " )
162
+ }
21
163
}
0 commit comments