Skip to content

Commit bcd3869

Browse files
atamez31allevato
authored andcommitted
Initial implementation of the CommentWhitespace rule and tests (swiftlang#52)
1 parent 32e7283 commit bcd3869

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

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

+91
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,96 @@ import SwiftSyntax
1111
///
1212
/// - SeeAlso: https://google.github.io/swift#horizontal-whitespace
1313
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
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
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import SwiftSyntax
2+
import XCTest
3+
4+
@testable import Rules
5+
6+
public class CommentWhitespaceTest: DiagnosingTestCase {
7+
public func testInvalidCommentWhiteSpace() {
8+
XCTAssertFormatting(
9+
CommentWhitespace.self,
10+
input: """
11+
let initialFactor = 2 // Line comment of initialFactor.
12+
let finalFactor = 2//Line comment of finalFactor.
13+
14+
//Lorem ipsum dolor sit amet, at nonumes adipisci sea, natum
15+
// offendit vis ex. Audiam legendos expetenda ei quo, nonumes
16+
// msensibus eloquentiam ex vix.
17+
let fin = 3// End of file.
18+
""",
19+
expected: """
20+
let initialFactor = 2 // Line comment of initialFactor.
21+
let finalFactor = 2 // Line comment of finalFactor.
22+
23+
// Lorem ipsum dolor sit amet, at nonumes adipisci sea, natum
24+
// offendit vis ex. Audiam legendos expetenda ei quo, nonumes
25+
// msensibus eloquentiam ex vix.
26+
let fin = 3 // End of file.
27+
""")
28+
}
29+
30+
public func testInvalidFuncCommentWhiteSpace() {
31+
XCTAssertFormatting(
32+
CommentWhitespace.self,
33+
input: """
34+
func testLineComment(paramA: Int) -> Int {
35+
//LineComment.
36+
if paramA < 50 {
37+
//LineComment.
38+
return paramA - 100
39+
}
40+
//LineComment.
41+
return paramA + 100
42+
}
43+
""",
44+
expected: """
45+
func testLineComment(paramA: Int) -> Int {
46+
// LineComment.
47+
if paramA < 50 {
48+
// LineComment.
49+
return paramA - 100
50+
}
51+
// LineComment.
52+
return paramA + 100
53+
}
54+
""")
55+
}
56+
57+
#if !os(macOS)
58+
static let allTests = [
59+
CommentWhitespaceTest.testInvalidCommentWhiteSpace,
60+
CommentWhitespaceTest.testInvalidFuncCommentWhiteSpace,
61+
]
62+
#endif
63+
}

0 commit comments

Comments
 (0)