Skip to content

Commit 553479f

Browse files
authored
Merge pull request swiftlang#123 from allevato/lint-block-comments
Make the `NoBlockComments` rule lint-only.
2 parents fe2eebd + 4461dd1 commit 553479f

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)