Skip to content

Commit 6050094

Browse files
atamez31allevato
authored andcommitted
Implementation of NoBlockComments (swiftlang#70)
1 parent ced4b4e commit 6050094

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed

tools/swift-format/Sources/Rules/NoBlockComments.swift

+111
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,116 @@ import SwiftSyntax
1414
///
1515
/// - SeeAlso: https://google.github.io/swift#non-documentation-comments
1616
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
1721

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+
}
18129
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import SwiftSyntax
2+
import XCTest
3+
4+
@testable import Rules
5+
6+
public class NoBlockCommentsTests: DiagnosingTestCase {
7+
public func testRemoveBlockComments() {
8+
XCTAssertFormatting(
9+
NoBlockComments.self,
10+
input: """
11+
/*
12+
Lorem ipsum dolor sit amet, at nonumes adipisci sea, natum
13+
offendit vis ex. Audiam legendos expetenda ei quo, nonumes
14+
15+
msensibus eloquentiam ex vix.
16+
*/
17+
let a = /*ff*/10 /*ff*/ + 10
18+
var b = 0/*Block Comment inline with code*/
19+
20+
/*
21+
22+
Block Comment
23+
*/
24+
let c = a + b
25+
/* This is the end
26+
of a file
27+
28+
*/
29+
""",
30+
expected: """
31+
// Lorem ipsum dolor sit amet, at nonumes adipisci sea, natum
32+
// offendit vis ex. Audiam legendos expetenda ei quo, nonumes
33+
//
34+
// msensibus eloquentiam ex vix.
35+
let a = /*ff*/10 /*ff*/ + 10
36+
var b = 0 // Block Comment inline with code
37+
38+
// Block Comment
39+
let c = a + b
40+
// This is the end
41+
// of a file
42+
""")
43+
}
44+
45+
#if !os(macOS)
46+
static let allTests = [
47+
NoBlockCommentsTests.testRemoveBlockComments,
48+
]
49+
#endif
50+
}

0 commit comments

Comments
 (0)