Skip to content

Commit

Permalink
Add indentBlankLines configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
TTOzzi committed Sep 19, 2024
1 parent 4a3def9 commit 071407e
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 7 deletions.
5 changes: 5 additions & 0 deletions Documentation/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ top-level keys and values:

* `multiElementCollectionTrailingCommas` _(boolean)_: Determines whether multi-element collection literals should have trailing commas.
Defaults to `true`.

* `indentBlankLines` _(boolean)_: Determines whether blank lines should be modified
to match the current indentation. When this setting is true, blank lines will be modified
to match the indentation level, adding indentation whether or not there is existing whitespace.
When false (the default), all whitespace in blank lines will be completely removed.

> TODO: Add support for enabling/disabling specific syntax transformations in
> the pipeline.
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftFormat/API/Configuration+Default.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ extension Configuration {
self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
self.multiElementCollectionTrailingCommas = true
self.reflowMultilineStringLiterals = .never
self.indentBlankLines = false
}
}
13 changes: 12 additions & 1 deletion Sources/SwiftFormat/API/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public struct Configuration: Codable, Equatable {
case noAssignmentInExpressions
case multiElementCollectionTrailingCommas
case reflowMultilineStringLiterals
case indentBlankLines
}

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

public var reflowMultilineStringLiterals: MultilineStringReflowBehavior

/// Determines whether to add indentation whitespace to blank lines or remove it entirely.
///
/// If true, blank lines will be modified to match the current indentation level:
/// if they contain whitespace, the existing whitespace will be adjusted, and if they are empty, spaces will be added to match the indentation.
/// If false (the default), the whitespace in blank lines will be removed entirely.
public var indentBlankLines: Bool

/// Creates a new `Configuration` by loading it from a configuration file.
public init(contentsOf url: URL) throws {
Expand Down Expand Up @@ -352,10 +360,13 @@ public struct Configuration: Codable, Equatable {
try container.decodeIfPresent(
Bool.self, forKey: .multiElementCollectionTrailingCommas)
?? defaults.multiElementCollectionTrailingCommas

self.reflowMultilineStringLiterals =
try container.decodeIfPresent(MultilineStringReflowBehavior.self, forKey: .reflowMultilineStringLiterals)
?? defaults.reflowMultilineStringLiterals
self.indentBlankLines =
try container.decodeIfPresent(
Bool.self, forKey: .indentBlankLines)
?? defaults.indentBlankLines

// If the `rules` key is not present at all, default it to the built-in set
// so that the behavior is the same as if the configuration had been
Expand Down
9 changes: 7 additions & 2 deletions Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ public class PrettyPrinter {
outputBuffer.enqueueSpaces(size)
outputBuffer.write("\\")
}
outputBuffer.writeNewlines(newline)
outputBuffer.writeNewlines(newline, shouldIndentBlankLines: configuration.indentBlankLines)
lastBreak = true
} else {
if outputBuffer.isAtStartOfLine {
Expand All @@ -449,7 +449,12 @@ public class PrettyPrinter {

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

// Print any indentation required, followed by the text content of the syntax token.
case .syntax(let text):
Expand Down
17 changes: 14 additions & 3 deletions Sources/SwiftFormat/PrettyPrint/PrettyPrintBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,11 @@ struct PrettyPrintBuffer {
/// subtract the previously written newlines during the second call so that we end up with the
/// correct number overall.
///
/// - Parameter newlines: The number and type of newlines to write.
mutating func writeNewlines(_ newlines: NewlineBehavior) {
/// - Parameters:
/// - newlines: The number and type of newlines to write.
/// - shouldIndentBlankLines: A Boolean value indicating whether to insert spaces
/// for blank lines based on the current indentation level.
mutating func writeNewlines(_ newlines: NewlineBehavior, shouldIndentBlankLines: Bool) {
let numberToPrint: Int
switch newlines {
case .elective:
Expand All @@ -86,7 +89,15 @@ struct PrettyPrintBuffer {
}

guard numberToPrint > 0 else { return }
writeRaw(String(repeating: "\n", count: numberToPrint))
(0..<numberToPrint).forEach { number in
if shouldIndentBlankLines, number >= 1 {
writeRaw(currentIndentation.indentation())
writeRaw("\n")
} else {
writeRaw("\n")
}
}

lineNumber += numberToPrint
isAtStartOfLine = true
consecutiveNewlineCount += numberToPrint
Expand Down
7 changes: 6 additions & 1 deletion Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3465,7 +3465,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {

case .spaces(let n):
guard leadingIndent == .spaces(0) else { break }
leadingIndent = .spaces(n)
if config.indentBlankLines, trivia.count > index + 1, trivia[index + 1].isNewline {
appendToken(.space(size: n))
requiresNextNewline = true
} else {
leadingIndent = .spaces(n)
}
case .tabs(let n):
guard leadingIndent == .spaces(0) else { break }
leadingIndent = .tabs(n)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ extension Configuration {
config.spacesAroundRangeFormationOperators = false
config.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration()
config.multiElementCollectionTrailingCommas = true
config.indentBlankLines = false
return config
}
}
196 changes: 196 additions & 0 deletions Tests/SwiftFormatTests/PrettyPrint/IndentBlankLinesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import SwiftFormat

final class IndentBlankLinesTests: PrettyPrintTestCase {
func testIndentBlankLinesEnabled() {
let input =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""

let expected =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""
var config = Configuration.forTesting
config.indentBlankLines = true
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
}

func testIndentBlankLinesDisabled() {
let input =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""

let expected =
"""
class A {
func foo() -> Int {
return 1
}
func bar() -> Int {
return 2
}
}
"""
var config = Configuration.forTesting
config.indentBlankLines = false
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
}

func testLineWithMoreWhitespacesThanIndentation() {
let input =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}\u{0020}\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""

let expected =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""
var config = Configuration.forTesting
config.indentBlankLines = true
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
}

func testLineWithFewerWhitespacesThanIndentation() {
let input =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}
func bar() -> Int {
return 2
}
}
"""

let expected =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""
var config = Configuration.forTesting
config.indentBlankLines = true
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
}

func testLineWithoutWhitespace() {
let input =
"""
class A {
func foo() -> Int {
return 1
}
func bar() -> Int {
return 2
}
}
"""

let expected =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""
var config = Configuration.forTesting
config.indentBlankLines = true
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
}


func testExpressionsWithUnnecessaryWhitespaces() {
let input =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""

let expected =
"""
class A {
func foo() -> Int {
return 1
}
\u{0020}\u{0020}
func bar() -> Int {
return 2
}
}
"""
var config = Configuration.forTesting
config.indentBlankLines = true
assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config)
}
}

0 comments on commit 071407e

Please sign in to comment.