From ecdd9f79ce3cf6aaf1fb57737aad0ebcbe56ad3a Mon Sep 17 00:00:00 2001 From: TTOzzi Date: Thu, 5 Sep 2024 23:16:55 +0900 Subject: [PATCH] Add AllowWhitespaceOnlyLines configuration --- Documentation/Configuration.md | 3 + .../API/Configuration+Default.swift | 1 + Sources/SwiftFormat/API/Configuration.swift | 12 +- .../SwiftFormat/PrettyPrint/PrettyPrint.swift | 7 +- .../PrettyPrint/TokenStreamCreator.swift | 7 +- .../Configuration+Testing.swift | 1 + .../AllowWhitespaceOnlyLinesTests.swift | 163 ++++++++++++++++++ 7 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 Tests/SwiftFormatTests/PrettyPrint/AllowWhitespaceOnlyLinesTests.swift diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index d31f98a02..1853b51c0 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -94,6 +94,9 @@ top-level keys and values: * `multiElementCollectionTrailingCommas` _(boolean)_: Determines whether multi-element collection literals should have trailing commas. Defaults to `true`. + +* `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. + Defaults to `false` > TODO: Add support for enabling/disabling specific syntax transformations in > the pipeline. diff --git a/Sources/SwiftFormat/API/Configuration+Default.swift b/Sources/SwiftFormat/API/Configuration+Default.swift index d18164f39..1afad609d 100644 --- a/Sources/SwiftFormat/API/Configuration+Default.swift +++ b/Sources/SwiftFormat/API/Configuration+Default.swift @@ -41,5 +41,6 @@ extension Configuration { self.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() self.multiElementCollectionTrailingCommas = true self.reflowMultilineStringLiterals = .never + self.allowWhitespaceOnlyLines = false } } diff --git a/Sources/SwiftFormat/API/Configuration.swift b/Sources/SwiftFormat/API/Configuration.swift index c6836ab8a..63fc70a9e 100644 --- a/Sources/SwiftFormat/API/Configuration.swift +++ b/Sources/SwiftFormat/API/Configuration.swift @@ -46,6 +46,7 @@ public struct Configuration: Codable, Equatable { case noAssignmentInExpressions case multiElementCollectionTrailingCommas case reflowMultilineStringLiterals + case allowWhitespaceOnlyLines } /// A dictionary containing the default enabled/disabled states of rules, keyed by the rules' @@ -259,6 +260,12 @@ public struct Configuration: Codable, Equatable { } public var reflowMultilineStringLiterals: MultilineStringReflowBehavior + + /// Determines whether lines containing only whitespace should be preserved or removed. + /// + /// If true, lines that consist solely of whitespace will be modified to match the current indentation + /// without removing the whitespace. If false (the default), the whitespace on such lines will be completely removed. + public var allowWhitespaceOnlyLines: Bool /// Creates a new `Configuration` by loading it from a configuration file. public init(contentsOf url: URL) throws { @@ -352,10 +359,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.allowWhitespaceOnlyLines = + try container.decodeIfPresent( + Bool.self, forKey: .allowWhitespaceOnlyLines) + ?? defaults.allowWhitespaceOnlyLines // 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 diff --git a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift index 8ad1c04c9..b3c451cf1 100644 --- a/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift +++ b/Sources/SwiftFormat/PrettyPrint/PrettyPrint.swift @@ -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.allowWhitespaceOnlyLines, 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): diff --git a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift index c3709c60c..4be028d36 100644 --- a/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormat/PrettyPrint/TokenStreamCreator.swift @@ -3465,7 +3465,12 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { case .spaces(let n): guard leadingIndent == .spaces(0) else { break } - leadingIndent = .spaces(n) + if config.allowWhitespaceOnlyLines, 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) diff --git a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift index 8d095767b..8a5baa4da 100644 --- a/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift +++ b/Sources/_SwiftFormatTestSupport/Configuration+Testing.swift @@ -41,6 +41,7 @@ extension Configuration { config.spacesAroundRangeFormationOperators = false config.noAssignmentInExpressions = NoAssignmentInExpressionsConfiguration() config.multiElementCollectionTrailingCommas = true + config.allowWhitespaceOnlyLines = false return config } } diff --git a/Tests/SwiftFormatTests/PrettyPrint/AllowWhitespaceOnlyLinesTests.swift b/Tests/SwiftFormatTests/PrettyPrint/AllowWhitespaceOnlyLinesTests.swift new file mode 100644 index 000000000..9100df2a6 --- /dev/null +++ b/Tests/SwiftFormatTests/PrettyPrint/AllowWhitespaceOnlyLinesTests.swift @@ -0,0 +1,163 @@ +import SwiftFormat + +final class AllowWhitespaceOnlyLinesTests: PrettyPrintTestCase { + func testAllowWhitespaceOnlyLinesEnabled() { + 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.allowWhitespaceOnlyLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } + + func testAllowWhitespaceOnlyLinesDisabled() { + 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.allowWhitespaceOnlyLines = 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.allowWhitespaceOnlyLines = 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.allowWhitespaceOnlyLines = 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.allowWhitespaceOnlyLines = true + assertPrettyPrintEqual(input: input, expected: expected, linelength: 80, configuration: config) + } +}