Skip to content

Commit e82cdd7

Browse files
authored
Merge pull request #910 from TTOzzi/fix-NoEmptyLinesOpeningClosingBraces
Fix NoEmptyLinesOpeningClosingBraces to respect consecutive newlines when a function starts or ends with a comment
2 parents 2b7e6d8 + 3d1e03b commit e82cdd7

File tree

2 files changed

+91
-8
lines changed

2 files changed

+91
-8
lines changed

Sources/SwiftFormat/Rules/NoEmptyLineOpeningClosingBraces.swift

+35-8
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule {
6969
}
7070

7171
func rewritten(_ token: TokenSyntax) -> TokenSyntax {
72-
let (trimmedLeadingTrivia, count) = token.leadingTrivia.trimmingSuperfluousNewlines()
72+
let (trimmedLeadingTrivia, count) = token.leadingTrivia.trimmingSuperfluousNewlines(
73+
fromClosingBrace: token.tokenKind == .rightBrace
74+
)
7375
if trimmedLeadingTrivia.sourceLength != token.leadingTriviaLength {
7476
diagnose(.removeEmptyLinesBefore(count), on: token, anchor: .start)
7577
return token.with(\.leadingTrivia, trimmedLeadingTrivia)
@@ -83,7 +85,7 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule {
8385
if let first = collection.first, first.leadingTrivia.containsNewlines,
8486
let index = collection.index(of: first)
8587
{
86-
let (trimmedLeadingTrivia, count) = first.leadingTrivia.trimmingSuperfluousNewlines()
88+
let (trimmedLeadingTrivia, count) = first.leadingTrivia.trimmingSuperfluousNewlines(fromClosingBrace: false)
8789
if trimmedLeadingTrivia.sourceLength != first.leadingTriviaLength {
8890
diagnose(.removeEmptyLinesAfter(count), on: first, anchor: .leadingTrivia(0))
8991
var first = first
@@ -96,24 +98,49 @@ public final class NoEmptyLinesOpeningClosingBraces: SyntaxFormatRule {
9698
}
9799

98100
extension Trivia {
99-
func trimmingSuperfluousNewlines() -> (Trivia, Int) {
101+
func trimmingSuperfluousNewlines(fromClosingBrace: Bool) -> (Trivia, Int) {
100102
var trimmmed = 0
103+
var pendingNewlineCount = 0
101104
let pieces = self.indices.reduce([TriviaPiece]()) { (partialResult, index) in
102105
let piece = self[index]
103106
// Collapse consecutive newlines into a single one
104107
if case .newlines(let count) = piece {
105-
if let last = partialResult.last, last.isNewline {
106-
trimmmed += count
107-
return partialResult
108+
if fromClosingBrace {
109+
if index == self.count - 1 {
110+
// For the last index(newline right before the closing brace), collapse into a single newline
111+
trimmmed += count - 1
112+
return partialResult + [.newlines(1)]
113+
} else {
114+
pendingNewlineCount += count
115+
return partialResult
116+
}
108117
} else {
109-
trimmmed += count - 1
110-
return partialResult + [.newlines(1)]
118+
if let last = partialResult.last, last.isNewline {
119+
trimmmed += count
120+
return partialResult
121+
} else if index == 0 {
122+
// For leading trivia not associated with a closing brace, collapse the first newline into a single one
123+
trimmmed += count - 1
124+
return partialResult + [.newlines(1)]
125+
} else {
126+
return partialResult + [piece]
127+
}
111128
}
112129
}
113130
// Remove spaces/tabs surrounded by newlines
114131
if piece.isSpaceOrTab, index > 0, index < self.count - 1, self[index - 1].isNewline, self[index + 1].isNewline {
115132
return partialResult
116133
}
134+
// Handle pending newlines if there are any
135+
if pendingNewlineCount > 0 {
136+
if index < self.count - 1 {
137+
let newlines = TriviaPiece.newlines(pendingNewlineCount)
138+
pendingNewlineCount = 0
139+
return partialResult + [newlines] + [piece]
140+
} else {
141+
return partialResult + [.newlines(1)] + [piece]
142+
}
143+
}
117144
// Retain other trivia pieces
118145
return partialResult + [piece]
119146
}

Tests/SwiftFormatTests/Rules/NoEmptyLinesOpeningClosingBracesTests.swift

+56
Original file line numberDiff line numberDiff line change
@@ -136,4 +136,60 @@ final class NoEmptyLinesOpeningClosingBracesTests: LintOrFormatRuleTestCase {
136136
]
137137
)
138138
}
139+
140+
func testNoEmptyLinesOpeningClosingBracesInFunctionBeginningAndEndingWithComment() {
141+
assertFormatting(
142+
NoEmptyLinesOpeningClosingBraces.self,
143+
input: """
144+
func myFunc() {
145+
// Some comment here
146+
147+
// Do a thing
148+
var x = doAThing()
149+
150+
// Do a thing
151+
152+
var y = doAThing()
153+
154+
// Some other comment here
155+
}
156+
""",
157+
expected: """
158+
func myFunc() {
159+
// Some comment here
160+
161+
// Do a thing
162+
var x = doAThing()
163+
164+
// Do a thing
165+
166+
var y = doAThing()
167+
168+
// Some other comment here
169+
}
170+
"""
171+
)
172+
}
173+
174+
func testNoEmptyLinesOpeningClosingBracesInFunctionWithEmptyLinesOnly() {
175+
assertFormatting(
176+
NoEmptyLinesOpeningClosingBraces.self,
177+
input: """
178+
func myFunc() {
179+
180+
181+
182+
183+
184+
1️⃣}
185+
""",
186+
expected: """
187+
func myFunc() {
188+
}
189+
""",
190+
findings: [
191+
FindingSpec("1️⃣", message: "remove empty lines before '}'")
192+
]
193+
)
194+
}
139195
}

0 commit comments

Comments
 (0)