Skip to content

Commit 071407e

Browse files
committed
Add indentBlankLines configuration
1 parent 4a3def9 commit 071407e

File tree

8 files changed

+242
-7
lines changed

8 files changed

+242
-7
lines changed

Documentation/Configuration.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ 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+
* `indentBlankLines` _(boolean)_: Determines whether blank lines should be modified
99+
to match the current indentation. When this setting is true, blank lines will be modified
100+
to match the indentation level, adding indentation whether or not there is existing whitespace.
101+
When false (the default), all whitespace in blank lines will be completely removed.
97102

98103
> TODO: Add support for enabling/disabling specific syntax transformations in
99104
> 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.indentBlankLines = false
4445
}
4546
}

Sources/SwiftFormat/API/Configuration.swift

Lines changed: 12 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 indentBlankLines
4950
}
5051

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

261262
public var reflowMultilineStringLiterals: MultilineStringReflowBehavior
263+
264+
/// Determines whether to add indentation whitespace to blank lines or remove it entirely.
265+
///
266+
/// If true, blank lines will be modified to match the current indentation level:
267+
/// if they contain whitespace, the existing whitespace will be adjusted, and if they are empty, spaces will be added to match the indentation.
268+
/// If false (the default), the whitespace in blank lines will be removed entirely.
269+
public var indentBlankLines: Bool
262270

263271
/// Creates a new `Configuration` by loading it from a configuration file.
264272
public init(contentsOf url: URL) throws {
@@ -352,10 +360,13 @@ public struct Configuration: Codable, Equatable {
352360
try container.decodeIfPresent(
353361
Bool.self, forKey: .multiElementCollectionTrailingCommas)
354362
?? defaults.multiElementCollectionTrailingCommas
355-
356363
self.reflowMultilineStringLiterals =
357364
try container.decodeIfPresent(MultilineStringReflowBehavior.self, forKey: .reflowMultilineStringLiterals)
358365
?? defaults.reflowMultilineStringLiterals
366+
self.indentBlankLines =
367+
try container.decodeIfPresent(
368+
Bool.self, forKey: .indentBlankLines)
369+
?? defaults.indentBlankLines
359370

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

Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ public class PrettyPrinter {
432432
outputBuffer.enqueueSpaces(size)
433433
outputBuffer.write("\\")
434434
}
435-
outputBuffer.writeNewlines(newline)
435+
outputBuffer.writeNewlines(newline, shouldIndentBlankLines: configuration.indentBlankLines)
436436
lastBreak = true
437437
} else {
438438
if outputBuffer.isAtStartOfLine {
@@ -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.indentBlankLines, 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/PrettyPrintBuffer.swift

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ struct PrettyPrintBuffer {
7070
/// subtract the previously written newlines during the second call so that we end up with the
7171
/// correct number overall.
7272
///
73-
/// - Parameter newlines: The number and type of newlines to write.
74-
mutating func writeNewlines(_ newlines: NewlineBehavior) {
73+
/// - Parameters:
74+
/// - newlines: The number and type of newlines to write.
75+
/// - shouldIndentBlankLines: A Boolean value indicating whether to insert spaces
76+
/// for blank lines based on the current indentation level.
77+
mutating func writeNewlines(_ newlines: NewlineBehavior, shouldIndentBlankLines: Bool) {
7578
let numberToPrint: Int
7679
switch newlines {
7780
case .elective:
@@ -86,7 +89,15 @@ struct PrettyPrintBuffer {
8689
}
8790

8891
guard numberToPrint > 0 else { return }
89-
writeRaw(String(repeating: "\n", count: numberToPrint))
92+
(0..<numberToPrint).forEach { number in
93+
if shouldIndentBlankLines, number >= 1 {
94+
writeRaw(currentIndentation.indentation())
95+
writeRaw("\n")
96+
} else {
97+
writeRaw("\n")
98+
}
99+
}
100+
90101
lineNumber += numberToPrint
91102
isAtStartOfLine = true
92103
consecutiveNewlineCount += numberToPrint

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.indentBlankLines, 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.indentBlankLines = false
4445
return config
4546
}
4647
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import SwiftFormat
2+
3+
final class IndentBlankLinesTests: PrettyPrintTestCase {
4+
func testIndentBlankLinesEnabled() {
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.indentBlankLines = true
33+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
34+
}
35+
36+
func testIndentBlankLinesDisabled() {
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.indentBlankLines = 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.indentBlankLines = 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.indentBlankLines = true
129+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
130+
}
131+
132+
func testLineWithoutWhitespace() {
133+
let input =
134+
"""
135+
class A {
136+
func foo() -> Int {
137+
return 1
138+
}
139+
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.indentBlankLines = true
161+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
162+
}
163+
164+
165+
func testExpressionsWithUnnecessaryWhitespaces() {
166+
let input =
167+
"""
168+
class A {
169+
func foo() -> Int {
170+
return 1
171+
}
172+
\u{0020}\u{0020}
173+
func bar() -> Int {
174+
return 2
175+
}
176+
}
177+
"""
178+
179+
let expected =
180+
"""
181+
class A {
182+
func foo() -> Int {
183+
return 1
184+
}
185+
\u{0020}\u{0020}
186+
func bar() -> Int {
187+
return 2
188+
}
189+
}
190+
191+
"""
192+
var config = Configuration.forTesting
193+
config.indentBlankLines = true
194+
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
195+
}
196+
}

0 commit comments

Comments
 (0)