@@ -14,5 +14,116 @@ import SwiftSyntax
14
14
///
15
15
/// - SeeAlso: https://google.github.io/swift#non-documentation-comments
16
16
public final class NoBlockComments : SyntaxFormatRule {
17
+ public override func visit ( _ token: TokenSyntax ) -> Syntax {
18
+ var pieces = [ TriviaPiece] ( )
19
+ var hasBlockComment = false
20
+ var validToken = token
17
21
22
+ // Ensures that the comments that appear inline with code have
23
+ // at least 2 spaces before the `//`.
24
+ if let nextToken = token. nextToken,
25
+ containsBlockCommentInline ( trivia: nextToken. leadingTrivia) {
26
+ hasBlockComment = true
27
+ validToken = addSpacesBeforeComment ( token)
28
+ }
29
+
30
+ // Ensures that all block comments are replaced with line comment,
31
+ // unless the comment is between tokens on the same line.
32
+ for piece in token. leadingTrivia {
33
+ if case . blockComment( let text) = piece,
34
+ !commentIsBetweenCode( token) {
35
+ diagnose ( . avoidBlockComment, on: token)
36
+ hasBlockComment = true
37
+ let lineCommentText = convertBlockCommentsToLineComments ( text)
38
+ let lineComment = TriviaPiece . lineComment ( lineCommentText)
39
+ pieces. append ( lineComment)
40
+ }
41
+ else {
42
+ pieces. append ( piece)
43
+ }
44
+ }
45
+ validToken = validToken. withLeadingTrivia ( Trivia ( pieces: pieces) )
46
+ return hasBlockComment ? validToken : token
47
+ }
48
+
49
+ /// Returns a Boolean value indicating if the given trivia has a piece trivia
50
+ /// of block comment inline with code.
51
+ private func containsBlockCommentInline( trivia: Trivia ) -> Bool {
52
+ // When the comment isn't inline with code, it doesn't need to
53
+ // to check that there are two spaces before the line comment.
54
+ if let firstPiece = trivia. first {
55
+ if case . newlines( _) = firstPiece {
56
+ return false
57
+ }
58
+ }
59
+ for piece in trivia {
60
+ if case . blockComment( _) = piece {
61
+ return true
62
+ }
63
+ }
64
+ return false
65
+ }
66
+
67
+ /// Indicates if a block comment is between tokens on the same line.
68
+ /// If it does, it should only raise a lint error.
69
+ private func commentIsBetweenCode( _ token: TokenSyntax ) -> Bool {
70
+ let hasCommentBetweenCode = token. leadingTrivia. isBetweenTokens
71
+ if hasCommentBetweenCode {
72
+ diagnose ( . avoidBlockCommentBetweenCode, on: token)
73
+ }
74
+ return hasCommentBetweenCode
75
+ }
76
+
77
+ /// Ensures there is always at least 2 spaces before the comment.
78
+ private func addSpacesBeforeComment( _ token: TokenSyntax ) -> TokenSyntax {
79
+ let numSpaces = token. trailingTrivia. numberOfSpaces
80
+ if numSpaces < 2 {
81
+ let addSpaces = 2 - numSpaces
82
+ return token. withTrailingTrivia ( token. trailingTrivia
83
+ . appending ( . spaces( addSpaces) ) )
84
+ }
85
+ return token
86
+ }
87
+
88
+ /// Receives the text of a Block comment and converts it to a Line Comment format text.
89
+ private func convertBlockCommentsToLineComments( _ text: String ) -> String {
90
+ // Removes the '/*', '*/', the extra spaces and newlines from the comment.
91
+ let textTrim = text. dropFirst ( 2 ) . dropLast ( 2 )
92
+ . trimmingCharacters ( in: . whitespacesAndNewlines)
93
+
94
+ let splitComment = textTrim. split ( separator: " \n " , omittingEmptySubsequences: false )
95
+ var lineCommentText = [ String] ( )
96
+
97
+ for line in splitComment {
98
+ let startsComment = line. starts ( with: " " ) || line. count == 0 ? " // " : " // "
99
+ lineCommentText. append ( startsComment + line)
100
+ }
101
+ return lineCommentText. joined ( separator: " \n " )
102
+ }
103
+ }
104
+
105
+ extension Diagnostic . Message {
106
+ static let avoidBlockComment = Diagnostic . Message ( . warning, " Replace block comment with line comments. " )
107
+ static let avoidBlockCommentBetweenCode = Diagnostic . Message ( . warning, " Remove block comment inline with code " )
108
+ }
109
+
110
+ extension Trivia {
111
+ /// Indicates if the trivia is between tokens, for example
112
+ /// if a leading trivia that contains a comment, doesn't starts
113
+ /// and finishes with a new line then the comment is between tokens.
114
+ var isBetweenTokens : Bool {
115
+ var beginsNewLine = false
116
+ var endsNewLine = false
117
+
118
+ if let firstPiece = self . first,
119
+ let lastPiece = self . reversed ( ) . first {
120
+ if case . newlines( _) = firstPiece {
121
+ beginsNewLine = true
122
+ }
123
+ if case . newlines( _) = lastPiece {
124
+ endsNewLine = true
125
+ }
126
+ }
127
+ return !beginsNewLine && !endsNewLine
128
+ }
18
129
}
0 commit comments