@@ -11,5 +11,96 @@ import SwiftSyntax
1111///
1212/// - SeeAlso: https://google.github.io/swift#horizontal-whitespace
1313public final class CommentWhitespace : SyntaxFormatRule {
14+ public override func visit ( _ token: TokenSyntax ) -> Syntax {
15+ var pieces = [ TriviaPiece] ( )
16+ var validToken = token
17+ var needsWhitespaceFix = false
1418
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 `//` " )
15106}
0 commit comments