Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a configuration to preserve whitespace on lines containing only whitespace #804

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}