Skip to content

Commit 4461dd1

Browse files
committed
Make the NoBlockComments rule lint-only.
After some real world use, this rule feels a bit too aggressive as a format rule. The opportunities where the automatic fix are known to be harmless are fairly limited—e.g., end-of-line or single-line block comments. When block comments spanned multiple lines, or started at the end of a line and continued on multiple lines, determining the appropriate amount of leading indentation to preserve for the subsequent lines is not always trivial (consider the case of a multi-line comment where some lines are indented less than the first). In other situations, block comments might be used to temporarily comment out large blocks of code, and transforming these to line comments is impolite, to say the least.
1 parent cbc37a0 commit 4461dd1

File tree

6 files changed

+92
-156
lines changed

6 files changed

+92
-156
lines changed

Sources/SwiftFormat/Pipelines+Generated.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,6 @@ extension FormatPipeline {
280280
node = FullyIndirectEnum(context: context).visit(node)
281281
node = GroupNumericLiterals(context: context).visit(node)
282282
node = NoAccessLevelOnExtensionDeclaration(context: context).visit(node)
283-
node = NoBlockComments(context: context).visit(node)
284283
node = NoCasesWithOnlyFallthrough(context: context).visit(node)
285284
node = NoEmptyTrailingClosureParentheses(context: context).visit(node)
286285
node = NoLabelsInCasePatterns(context: context).visit(node)

Sources/SwiftFormatCore/Syntax+Convenience.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,51 @@ extension Syntax {
7272

7373
return startLocation.line == endLocation.line
7474
}
75+
76+
/// Returns the absolute position of the trivia piece at the given index in the receiver's leading
77+
/// trivia collection.
78+
///
79+
/// If the trivia piece spans multiple characters, the value returned is the position of the first
80+
/// character.
81+
///
82+
/// - Precondition: `index` is a valid index in the receiver's leading trivia collection.
83+
///
84+
/// - Parameter index: The index of the trivia piece in the leading trivia whose position should
85+
/// be returned.
86+
/// - Returns: The absolute position of the trivia piece.
87+
public func position(ofLeadingTriviaAt index: Trivia.Index) -> AbsolutePosition {
88+
let leadingTrivia = self.leadingTrivia ?? []
89+
guard leadingTrivia.indices.contains(index) else {
90+
preconditionFailure("Index was out of bounds in the node's leading trivia.")
91+
}
92+
93+
var offset = SourceLength.zero
94+
for currentIndex in leadingTrivia.startIndex..<index {
95+
offset += leadingTrivia[currentIndex].sourceLength
96+
}
97+
return self.position + offset
98+
}
99+
100+
/// Returns the source location of the trivia piece at the given index in the receiver's leading
101+
/// trivia collection.
102+
///
103+
/// If the trivia piece spans multiple characters, the value returned is the location of the first
104+
/// character.
105+
///
106+
/// - Precondition: `index` is a valid index in the receiver's leading trivia collection.
107+
///
108+
/// - Parameters:
109+
/// - index: The index of the trivia piece in the leading trivia whose location should be
110+
/// returned.
111+
/// - converter: The `SourceLocationConverter` that was previously initialized using the root
112+
/// tree of this node.
113+
/// - Returns: The source location of the trivia piece.
114+
public func startLocation(
115+
ofLeadingTriviaAt index: Trivia.Index,
116+
converter: SourceLocationConverter
117+
) -> SourceLocation {
118+
return converter.location(for: position(ofLeadingTriviaAt: index))
119+
}
75120
}
76121

77122
extension SyntaxCollection {

Sources/SwiftFormatCore/SyntaxLintRule.swift

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,25 @@ extension Rule {
3030
///
3131
/// - Parameters:
3232
/// - message: The diagnostic message to emit.
33-
/// - location: The source location which the diagnostic should be attached.
33+
/// - node: The syntax node to which the diagnostic should be attached. The diagnostic will be
34+
/// placed at the start of the node (excluding leading trivia).
35+
/// - leadingTriviaIndex: If non-nil, the index of a trivia piece in the node's leading trivia
36+
/// that should be used to determine the location of the diagnostic. Otherwise, the
37+
/// diagnostic's location will be the start of the node after any leading trivia.
3438
/// - actions: A set of actions to add notes, highlights, and fix-its to diagnostics.
3539
public func diagnose(
3640
_ message: Diagnostic.Message,
3741
on node: Syntax?,
42+
leadingTriviaIndex: Trivia.Index? = nil,
3843
actions: ((inout Diagnostic.Builder) -> Void)? = nil
3944
) {
40-
context.diagnosticEngine?.diagnose(
41-
message.withRule(self),
42-
location: node?.startLocation(converter: context.sourceLocationConverter),
43-
actions: actions
44-
)
45+
let location: SourceLocation?
46+
if let leadingTriviaIndex = leadingTriviaIndex {
47+
location = node?.startLocation(
48+
ofLeadingTriviaAt: leadingTriviaIndex, converter: context.sourceLocationConverter)
49+
} else {
50+
location = node?.startLocation(converter: context.sourceLocationConverter)
51+
}
52+
context.diagnosticEngine?.diagnose(message.withRule(self), location: location, actions: actions)
4553
}
4654
}

Sources/SwiftFormatRules/NoBlockComments.swift

Lines changed: 7 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -18,129 +18,20 @@ import SwiftSyntax
1818
///
1919
/// Lint: If a block comment appears, a lint error is raised.
2020
///
21-
/// Format: If a block comment appears on its own on a line, or if a block comment spans multiple
22-
/// lines without appearing on the same line as code, it will be replaced with multiple
23-
/// single-line comments.
24-
/// If a block comment appears inline with code, it will be removed and hoisted to the line
25-
/// above the code it appears on.
26-
///
2721
/// - SeeAlso: https://google.github.io/swift#non-documentation-comments
28-
public final class NoBlockComments: SyntaxFormatRule {
29-
public override func visit(_ token: TokenSyntax) -> Syntax {
30-
var pieces = [TriviaPiece]()
31-
var hasBlockComment = false
32-
var validToken = token
33-
34-
// Ensures that the comments that appear inline with code have
35-
// at least 2 spaces before the `//`.
36-
if let nextToken = token.nextToken,
37-
containsBlockCommentInline(trivia: nextToken.leadingTrivia)
38-
{
39-
hasBlockComment = true
40-
validToken = addSpacesBeforeComment(token)
41-
}
42-
43-
// Ensures that all block comments are replaced with line comment,
44-
// unless the comment is between tokens on the same line.
45-
for piece in token.leadingTrivia {
46-
if case .blockComment(let text) = piece,
47-
!commentIsBetweenCode(token)
48-
{
49-
diagnose(.avoidBlockComment, on: token)
50-
hasBlockComment = true
51-
let lineCommentText = convertBlockCommentsToLineComments(text)
52-
let lineComment = TriviaPiece.lineComment(lineCommentText)
53-
pieces.append(lineComment)
54-
} else {
55-
pieces.append(piece)
22+
public final class NoBlockComments: SyntaxLintRule {
23+
public override func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
24+
for triviaIndex in token.leadingTrivia.indices {
25+
let piece = token.leadingTrivia[triviaIndex]
26+
if case .blockComment = piece {
27+
diagnose(.avoidBlockComment, on: token, leadingTriviaIndex: triviaIndex)
5628
}
5729
}
58-
validToken = validToken.withLeadingTrivia(Trivia(pieces: pieces))
59-
return hasBlockComment ? validToken : token
60-
}
61-
62-
/// Returns a Boolean value indicating if the given trivia has a piece trivia
63-
/// of block comment inline with code.
64-
private func containsBlockCommentInline(trivia: Trivia) -> Bool {
65-
// When the comment isn't inline with code, it doesn't need to
66-
// to check that there are two spaces before the line comment.
67-
if let firstPiece = trivia.first {
68-
if case .newlines(_) = firstPiece {
69-
return false
70-
}
71-
}
72-
for piece in trivia {
73-
if case .blockComment(_) = piece {
74-
return true
75-
}
76-
}
77-
return false
78-
}
79-
80-
/// Indicates if a block comment is between tokens on the same line.
81-
/// If it does, it should only raise a lint error.
82-
private func commentIsBetweenCode(_ token: TokenSyntax) -> Bool {
83-
let hasCommentBetweenCode = token.leadingTrivia.isBetweenTokens
84-
if hasCommentBetweenCode {
85-
diagnose(.avoidBlockCommentBetweenCode, on: token)
86-
}
87-
return hasCommentBetweenCode
88-
}
89-
90-
/// Ensures there is always at least 2 spaces before the comment.
91-
private func addSpacesBeforeComment(_ token: TokenSyntax) -> TokenSyntax {
92-
let numSpaces = token.trailingTrivia.numberOfSpaces
93-
if numSpaces < 2 {
94-
let addSpaces = 2 - numSpaces
95-
return token.withTrailingTrivia(
96-
token.trailingTrivia.appending(.spaces(addSpaces)))
97-
}
98-
return token
99-
}
100-
101-
/// Receives the text of a Block comment and converts it to a Line Comment format text.
102-
private func convertBlockCommentsToLineComments(_ text: String) -> String {
103-
// Removes the '/*', '*/', the extra spaces and newlines from the comment.
104-
let textTrim = text.dropFirst(2).dropLast(2)
105-
.trimmingCharacters(in: .whitespacesAndNewlines)
106-
107-
let splitComment = textTrim.split(separator: "\n", omittingEmptySubsequences: false)
108-
var lineCommentText = [String]()
109-
110-
for line in splitComment {
111-
let startsComment = line.starts(with: " ") || line.count == 0 ? "//" : "// "
112-
lineCommentText.append(startsComment + line)
113-
}
114-
return lineCommentText.joined(separator: "\n")
30+
return .skipChildren
11531
}
11632
}
11733

11834
extension Diagnostic.Message {
11935
static let avoidBlockComment = Diagnostic.Message(
12036
.warning, "replace block comment with line comments")
121-
122-
static let avoidBlockCommentBetweenCode = Diagnostic.Message(
123-
.warning, "remove block comment inline with code")
124-
}
125-
126-
extension Trivia {
127-
/// Indicates if the trivia is between tokens, for example
128-
/// if a leading trivia that contains a comment, doesn't starts
129-
/// and finishes with a new line then the comment is between tokens.
130-
var isBetweenTokens: Bool {
131-
var beginsNewLine = false
132-
var endsNewLine = false
133-
134-
if let firstPiece = self.first,
135-
let lastPiece = self.reversed().first
136-
{
137-
if case .newlines(_) = firstPiece {
138-
beginsNewLine = true
139-
}
140-
if case .newlines(_) = lastPiece {
141-
endsNewLine = true
142-
}
143-
}
144-
return !beginsNewLine && !endsNewLine
145-
}
14637
}

Tests/SwiftFormatRulesTests/NoBlockCommentsTests.swift

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,34 @@ import XCTest
44
@testable import SwiftFormatRules
55

66
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
7+
public func testDiagnoseBlockComments() {
8+
let input =
9+
"""
10+
/*
11+
Lorem ipsum dolor sit amet, at nonumes adipisci sea, natum
12+
offendit vis ex. Audiam legendos expetenda ei quo, nonumes
1413

15-
msensibus eloquentiam ex vix.
16-
*/
17-
let a = /*ff*/10 /*ff*/ + 10
18-
var b = 0/*Block Comment inline with code*/
14+
msensibus eloquentiam ex vix.
15+
*/
16+
let a = /*ff*/10 /*ff*/ + 10
17+
var b = 0/*Block Comment inline with code*/
1918
20-
/*
19+
/*
2120
22-
Block Comment
23-
*/
24-
let c = a + b
25-
/* This is the end
26-
of a file
21+
Block Comment
22+
*/
23+
let c = a + b
24+
/* This is the end
25+
of a file
2726
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-
""")
27+
*/
28+
"""
29+
performLint(NoBlockComments.self, input: input)
30+
XCTAssertDiagnosed(.avoidBlockComment)
31+
XCTAssertDiagnosed(.avoidBlockComment)
32+
XCTAssertDiagnosed(.avoidBlockComment)
33+
XCTAssertDiagnosed(.avoidBlockComment)
34+
XCTAssertDiagnosed(.avoidBlockComment)
35+
XCTAssertDiagnosed(.avoidBlockComment)
4336
}
4437
}

Tests/SwiftFormatRulesTests/XCTestManifests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ extension NoBlockCommentsTests {
157157
// `swift test --generate-linuxmain`
158158
// to regenerate.
159159
static let __allTests__NoBlockCommentsTests = [
160-
("testRemoveBlockComments", testRemoveBlockComments),
160+
("testDiagnoseBlockComments", testDiagnoseBlockComments),
161161
]
162162
}
163163

0 commit comments

Comments
 (0)