@@ -11,5 +11,96 @@ import SwiftSyntax
11
11
///
12
12
/// - SeeAlso: https://google.github.io/swift#horizontal-whitespace
13
13
public final class CommentWhitespace : SyntaxFormatRule {
14
+ public override func visit ( _ token: TokenSyntax ) -> Syntax {
15
+ var pieces = [ TriviaPiece] ( )
16
+ var validToken = token
17
+ var needsWhitespaceFix = false
14
18
19
+ guard let nextToken = token. nextToken else {
20
+ // In the case there is a line comment at the end of the file, it ensures
21
+ // that the line comment has a single space after the `//`.
22
+ pieces = checksSpacesAfterLineComment ( isInvalid: & needsWhitespaceFix, token: token)
23
+ return needsWhitespaceFix ? token. withLeadingTrivia ( Trivia . init ( pieces: pieces) ) : token
24
+ }
25
+
26
+ // Ensures the line comment has at least 2 spaces before the `//`.
27
+ if hasInlineLineComment ( trivia: nextToken. leadingTrivia) {
28
+ let numSpaces = token. trailingTrivia. numberOfSpaces
29
+ if numSpaces < 2 {
30
+ needsWhitespaceFix = true
31
+ let addSpaces = 2 - numSpaces
32
+ diagnose ( . addSpacesBeforeLineComment( count: addSpaces) , on: token)
33
+ validToken = token. withTrailingTrivia ( token. trailingTrivia. appending ( . spaces( addSpaces) ) )
34
+ }
35
+ }
36
+
37
+ pieces = checksSpacesAfterLineComment ( isInvalid: & needsWhitespaceFix, token: token)
38
+ return needsWhitespaceFix ? validToken. withLeadingTrivia ( Trivia . init ( pieces: pieces) ) : token
39
+ }
40
+
41
+ /// Returns a boolean indicating if the given trivia contains
42
+ /// a line comment inline with code.
43
+ private func hasInlineLineComment ( trivia: Trivia ) -> Bool {
44
+ // Comments are inline unless the trivia begins with a
45
+ // with a newline.
46
+ if let firstPiece = trivia. reversed ( ) . last {
47
+ if case . newlines( _) = firstPiece {
48
+ return false
49
+ }
50
+ }
51
+ for piece in trivia {
52
+ if case . lineComment( _) = piece {
53
+ return true
54
+ }
55
+ }
56
+ return false
57
+ }
58
+
59
+ /// Ensures the line comment has exactly one space after the `//`.
60
+ private func checksSpacesAfterLineComment( isInvalid: inout Bool , token: TokenSyntax ) -> [ TriviaPiece ] {
61
+ var pieces = [ TriviaPiece] ( )
62
+
63
+ for piece in token. leadingTrivia {
64
+ // Checks if the line comment has exactly one space after the `//`,
65
+ // if it doesn't it removes or add an space, depending on what the
66
+ // comment needs in order to follow the right format.
67
+ if case . lineComment( let text) = piece,
68
+ let formatText = formatLineComment ( textLineComment: text, token: token) {
69
+ isInvalid = true
70
+ pieces. append ( TriviaPiece . lineComment ( formatText) )
71
+ }
72
+ else {
73
+ pieces. append ( piece)
74
+ }
75
+ }
76
+ return pieces
77
+ }
78
+
79
+ /// Given a string with the text of a line comment, it ensures there
80
+ /// is exactly one space after the `//`. If the string doesn't follow
81
+ /// this rule a new string is returned with the right format.
82
+ private func formatLineComment ( textLineComment: String , token: TokenSyntax ) -> String ? {
83
+ let text = textLineComment. dropFirst ( 2 )
84
+ if text. first != " " {
85
+ diagnose ( . addSpaceAfterLineComment, on: token)
86
+ return " // " + text. trimmingCharacters ( in: . whitespaces)
87
+ }
88
+ else if text. dropFirst ( 1 ) . first == " " {
89
+ diagnose ( . removeSpacesAfterLineComment, on: token)
90
+ return " // " + text. trimmingCharacters ( in: . whitespaces)
91
+ }
92
+ return nil
93
+ }
94
+ }
95
+
96
+ extension Diagnostic . Message {
97
+ static func addSpacesBeforeLineComment( count: Int ) -> Diagnostic . Message {
98
+ let ending = count == 1 ? " " : " s "
99
+ return Diagnostic . Message ( . warning, " add \( count) space \( ending) before the // " )
100
+ }
101
+
102
+ static let addSpaceAfterLineComment =
103
+ Diagnostic . Message ( . warning, " add one space after `//` " )
104
+ static let removeSpacesAfterLineComment =
105
+ Diagnostic . Message ( . warning, " remove excess of spaces after the `//` " )
15
106
}
0 commit comments