Skip to content

Commit bbb3abc

Browse files
authored
Merge pull request #674 from allevato/more-trailing-comment-fixes
[OrderedImports] Fix dropped trailing comments on top-level code items.
2 parents a8f5553 + d0c5816 commit bbb3abc

File tree

3 files changed

+87
-15
lines changed

3 files changed

+87
-15
lines changed

Sources/SwiftFormat/Rules/OrderedImports.swift

+10-10
Original file line numberDiff line numberDiff line change
@@ -315,17 +315,17 @@ fileprivate func generateLines(codeBlockItemList: CodeBlockItemListSyntax, conte
315315
blockWithoutTrailingTrivia.trailingTrivia = []
316316
currentLine.syntaxNode = .importCodeBlock(blockWithoutTrailingTrivia, sortable: sortable)
317317
} else {
318-
guard let syntaxNode = currentLine.syntaxNode else {
319-
currentLine.syntaxNode = .nonImportCodeBlocks([block])
320-
continue
321-
}
322-
// Multiple code blocks can be merged, as long as there isn't an import statement.
323-
switch syntaxNode {
324-
case .importCodeBlock:
325-
appendNewLine()
318+
if let syntaxNode = currentLine.syntaxNode {
319+
// Multiple code blocks can be merged, as long as there isn't an import statement.
320+
switch syntaxNode {
321+
case .importCodeBlock:
322+
appendNewLine()
323+
currentLine.syntaxNode = .nonImportCodeBlocks([block])
324+
case .nonImportCodeBlocks(let existingCodeBlocks):
325+
currentLine.syntaxNode = .nonImportCodeBlocks(existingCodeBlocks + [block])
326+
}
327+
} else {
326328
currentLine.syntaxNode = .nonImportCodeBlocks([block])
327-
case .nonImportCodeBlocks(let existingCodeBlocks):
328-
currentLine.syntaxNode = .nonImportCodeBlocks(existingCodeBlocks + [block])
329329
}
330330
}
331331

Tests/SwiftFormatTests/Rules/LintOrFormatRuleTestCase.swift

+43-5
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase {
112112

113113
let formatter = formatType.init(context: context)
114114
let actual = formatter.visit(sourceFileSyntax)
115-
assertStringsEqualWithDiff(actual.description, expected, file: file, line: line)
115+
assertStringsEqualWithDiff("\(actual)", expected, file: file, line: line)
116116

117117
assertFindings(
118118
expected: findings,
@@ -123,15 +123,22 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase {
123123
line: line)
124124

125125
// Verify that the pretty printer can consume the transformed tree (e.g., it does not contain
126-
// any unfolded `SequenceExpr`s). We don't need to check the actual output here (we don't want
127-
// the rule tests to be pretty-printer dependent), but this will catch invariants that aren't
128-
// satisfied.
129-
_ = PrettyPrinter(
126+
// any unfolded `SequenceExpr`s). Then do a whitespace-insensitive comparison of the two trees
127+
// to verify that the format rule didn't transform the tree in such a way that it caused the
128+
// pretty-printer to drop important information (the most likely case is a format rule
129+
// misplacing trivia in a way that the pretty-printer isn't able to handle).
130+
let prettyPrintedSource = PrettyPrinter(
130131
context: context,
131132
node: Syntax(actual),
132133
printTokenStream: false,
133134
whitespaceOnly: false
134135
).prettyPrint()
136+
let prettyPrintedTree = Parser.parse(source: prettyPrintedSource)
137+
XCTAssertEqual(
138+
whitespaceInsensitiveText(of: actual),
139+
whitespaceInsensitiveText(of: prettyPrintedTree),
140+
"After pretty-printing and removing fluid whitespace, the files did not match",
141+
file: file, line: line)
135142

136143
var emittedPipelineFindings = [Finding]()
137144
// Disable default rules, so only select rule runs in pipeline
@@ -149,3 +156,34 @@ class LintOrFormatRuleTestCase: DiagnosingTestCase {
149156
emittedFindings: emittedPipelineFindings, context: context, file: file, line: line)
150157
}
151158
}
159+
160+
/// Returns a string containing a whitespace-insensitive representation of the given source file.
161+
private func whitespaceInsensitiveText(of file: SourceFileSyntax) -> String {
162+
var result = ""
163+
for token in file.tokens(viewMode: .sourceAccurate) {
164+
appendNonspaceTrivia(token.leadingTrivia, to: &result)
165+
result.append(token.text)
166+
appendNonspaceTrivia(token.trailingTrivia, to: &result)
167+
}
168+
return result
169+
}
170+
171+
/// Appends any non-whitespace trivia pieces from the given trivia collection to the output string.
172+
private func appendNonspaceTrivia(_ trivia: Trivia, to string: inout String) {
173+
for piece in trivia {
174+
switch piece {
175+
case .carriageReturnLineFeeds, .carriageReturns, .formfeeds, .newlines, .spaces, .tabs:
176+
break
177+
case .lineComment(let comment), .docLineComment(let comment):
178+
// A tree transforming rule might leave whitespace at the end of a line comment, which the
179+
// pretty printer will remove, so we should ignore that.
180+
if let lastNonWhitespaceIndex = comment.lastIndex(where: { !$0.isWhitespace }) {
181+
string.append(contentsOf: comment[...lastNonWhitespaceIndex])
182+
} else {
183+
string.append(comment)
184+
}
185+
default:
186+
piece.write(to: &string)
187+
}
188+
}
189+
}

Tests/SwiftFormatTests/Rules/OrderedImportsTests.swift

+34
Original file line numberDiff line numberDiff line change
@@ -618,4 +618,38 @@ final class OrderedImportsTests: LintOrFormatRuleTestCase {
618618
]
619619
)
620620
}
621+
622+
func testTrailingCommentsOnTopLevelCodeItems() {
623+
assertFormatting(
624+
OrderedImports.self,
625+
input: """
626+
import Zebras
627+
1️⃣import Apples
628+
#if canImport(Darwin)
629+
import Darwin
630+
#elseif canImport(Glibc)
631+
import Glibc
632+
#endif // canImport(Darwin)
633+
634+
foo() // calls the foo
635+
bar() // calls the bar
636+
""",
637+
expected: """
638+
import Apples
639+
import Zebras
640+
641+
#if canImport(Darwin)
642+
import Darwin
643+
#elseif canImport(Glibc)
644+
import Glibc
645+
#endif // canImport(Darwin)
646+
647+
foo() // calls the foo
648+
bar() // calls the bar
649+
""",
650+
findings: [
651+
FindingSpec("1️⃣", message: "sort import statements lexicographically"),
652+
]
653+
)
654+
}
621655
}

0 commit comments

Comments
 (0)