Skip to content

Commit ecdd9f7

Browse files
committed
Add AllowWhitespaceOnlyLines configuration
1 parent 4a3def9 commit ecdd9f7

File tree

7 files changed

+191
-3
lines changed

7 files changed

+191
-3
lines changed

Documentation/Configuration.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ top-level keys and values:
9494

9595
* `multiElementCollectionTrailingCommas` _(boolean)_: Determines whether multi-element collection literals should have trailing commas.
9696
Defaults to `true`.
97+
98+
* `allowWhitespaceOnlyLines` _(boolean)_: Determines whether lines containing only whitespace should be preserved. When this setting is true, lines that consist solely of whitespace will not have the whitespace removed but will be modified to match the indentation.
99+
Defaults to `false`
97100

98101
> TODO: Add support for enabling/disabling specific syntax transformations in
99102
> the pipeline.

Sources/SwiftFormat/API/Configuration+Default.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,6 @@ extension Configuration {
4141
self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
4242
self.multiElementCollectionTrailingCommas = true
4343
self.reflowMultilineStringLiterals = .never
44+
self.allowWhitespaceOnlyLines = false
4445
}
4546
}

Sources/SwiftFormat/API/Configuration.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public struct Configuration: Codable, Equatable {
4646
case noAssignmentInExpressions
4747
case multiElementCollectionTrailingCommas
4848
case reflowMultilineStringLiterals
49+
case allowWhitespaceOnlyLines
4950
}
5051

5152
/// A dictionary containing the default enabled/disabled states of rules, keyed by the rules'
@@ -259,6 +260,12 @@ public struct Configuration: Codable, Equatable {
259260
}
260261

261262
public var reflowMultilineStringLiterals: MultilineStringReflowBehavior
263+
264+
/// Determines whether lines containing only whitespace should be preserved or removed.
265+
///
266+
/// If true, lines that consist solely of whitespace will be modified to match the current indentation
267+
/// without removing the whitespace. If false (the default), the whitespace on such lines will be completely removed.
268+
public var allowWhitespaceOnlyLines: Bool
262269

263270
/// Creates a new `Configuration` by loading it from a configuration file.
264271
public init(contentsOf url: URL) throws {
@@ -352,10 +359,13 @@ public struct Configuration: Codable, Equatable {
352359
try container.decodeIfPresent(
353360
Bool.self, forKey: .multiElementCollectionTrailingCommas)
354361
?? defaults.multiElementCollectionTrailingCommas
355-
356362
self.reflowMultilineStringLiterals =
357363
try container.decodeIfPresent(MultilineStringReflowBehavior.self, forKey: .reflowMultilineStringLiterals)
358364
?? defaults.reflowMultilineStringLiterals
365+
self.allowWhitespaceOnlyLines =
366+
try container.decodeIfPresent(
367+
Bool.self, forKey: .allowWhitespaceOnlyLines)
368+
?? defaults.allowWhitespaceOnlyLines
359369

360370
// If the `rules` key is not present at all, default it to the built-in set
361371
// so that the behavior is the same as if the configuration had been

Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,12 @@ public class PrettyPrinter {
449449

450450
// Print out the number of spaces according to the size, and adjust spaceRemaining.
451451
case .space(let size, _):
452-
outputBuffer.enqueueSpaces(size)
452+
if configuration.allowWhitespaceOnlyLines, outputBuffer.isAtStartOfLine {
453+
// An empty string write is needed to add line-leading indentation that matches the current indentation on a line that contains only whitespaces.
454+
outputBuffer.write("")
455+
} else {
456+
outputBuffer.enqueueSpaces(size)
457+
}
453458

454459
// Print any indentation required, followed by the text content of the syntax token.
455460
case .syntax(let text):

Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3465,7 +3465,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {
34653465

34663466
case .spaces(let n):
34673467
guard leadingIndent == .spaces(0) else { break }
3468-
leadingIndent = .spaces(n)
3468+
if config.allowWhitespaceOnlyLines, trivia.count > index + 1, trivia[index + 1].isNewline {
3469+
appendToken(.space(size: n))
3470+
requiresNextNewline = true
3471+
} else {
3472+
leadingIndent = .spaces(n)
3473+
}
34693474
case .tabs(let n):
34703475
guard leadingIndent == .spaces(0) else { break }
34713476
leadingIndent = .tabs(n)

Sources/_SwiftFormatTestSupport/Configuration+Testing.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ extension Configuration {
4141
config.spacesAroundRangeFormationOperators = false
4242
config.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
4343
config.multiElementCollectionTrailingCommas = true
44+
config.allowWhitespaceOnlyLines = false
4445
return config
4546
}
4647
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import SwiftFormat
2+
3+
final class AllowWhitespaceOnlyLinesTests: PrettyPrintTestCase {
4+
func testAllowWhitespaceOnlyLinesEnabled() {
5+
let input =
6+
"""
7+
class A {
8+
func foo() -> Int {
9+
return 1
10+
}
11+
\u{0020}\u{0020}
12+
func bar() -> Int {
13+
return 2
14+
}
15+
}
16+
"""
17+
18+
let expected =
19+
"""
20+
class A {
21+
func foo() -> Int {
22+
return 1
23+
}
24+
\u{0020}\u{0020}
25+
func bar() -> Int {
26+
return 2
27+
}
28+
}
29+
30+
"""
31+
var config = Configuration.forTesting
32+
config.allowWhitespaceOnlyLines = true
33+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
34+
}
35+
36+
func testAllowWhitespaceOnlyLinesDisabled() {
37+
let input =
38+
"""
39+
class A {
40+
func foo() -> Int {
41+
return 1
42+
}
43+
\u{0020}\u{0020}
44+
func bar() -> Int {
45+
return 2
46+
}
47+
}
48+
"""
49+
50+
let expected =
51+
"""
52+
class A {
53+
func foo() -> Int {
54+
return 1
55+
}
56+
57+
func bar() -> Int {
58+
return 2
59+
}
60+
}
61+
62+
"""
63+
var config = Configuration.forTesting
64+
config.allowWhitespaceOnlyLines = false
65+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
66+
}
67+
68+
func testLineWithMoreWhitespacesThanIndentation() {
69+
let input =
70+
"""
71+
class A {
72+
func foo() -> Int {
73+
return 1
74+
}
75+
\u{0020}\u{0020}\u{0020}\u{0020}\u{0020}
76+
func bar() -> Int {
77+
return 2
78+
}
79+
}
80+
"""
81+
82+
let expected =
83+
"""
84+
class A {
85+
func foo() -> Int {
86+
return 1
87+
}
88+
\u{0020}\u{0020}
89+
func bar() -> Int {
90+
return 2
91+
}
92+
}
93+
94+
"""
95+
var config = Configuration.forTesting
96+
config.allowWhitespaceOnlyLines = true
97+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
98+
}
99+
100+
func testLineWithFewerWhitespacesThanIndentation() {
101+
let input =
102+
"""
103+
class A {
104+
func foo() -> Int {
105+
return 1
106+
}
107+
\u{0020}
108+
func bar() -> Int {
109+
return 2
110+
}
111+
}
112+
"""
113+
114+
let expected =
115+
"""
116+
class A {
117+
func foo() -> Int {
118+
return 1
119+
}
120+
\u{0020}\u{0020}
121+
func bar() -> Int {
122+
return 2
123+
}
124+
}
125+
126+
"""
127+
var config = Configuration.forTesting
128+
config.allowWhitespaceOnlyLines = true
129+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
130+
}
131+
132+
func testExpressionsWithUnnecessaryWhitespaces() {
133+
let input =
134+
"""
135+
class A {
136+
func foo() -> Int {
137+
return 1
138+
}
139+
\u{0020}\u{0020}
140+
func bar() -> Int {
141+
return 2
142+
}
143+
}
144+
"""
145+
146+
let expected =
147+
"""
148+
class A {
149+
func foo() -> Int {
150+
return 1
151+
}
152+
\u{0020}\u{0020}
153+
func bar() -> Int {
154+
return 2
155+
}
156+
}
157+
158+
"""
159+
var config = Configuration.forTesting
160+
config.allowWhitespaceOnlyLines = true
161+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
162+
}
163+
}

0 commit comments

Comments
 (0)