diff --git a/Documentation/Configuration.md b/Documentation/Configuration.md index a0afbd65d..6f1092103 100644 --- a/Documentation/Configuration.md +++ b/Documentation/Configuration.md @@ -79,6 +79,9 @@ top-level keys and values: true, a line break is forced before the "." of the component and after the component's closing delimiter (i.e. right paren, right bracket, right brace, etc.). +* `spacesAroundRangeFormationOperators` _(boolean)_: Determines whether whitespace should be forced + before and after the range formation operators `...` and `..<`. + > TODO: Add support for enabling/disabling specific syntax transformations in > the pipeline. diff --git a/Sources/SwiftFormatConfiguration/Configuration.swift b/Sources/SwiftFormatConfiguration/Configuration.swift index 3648f6973..f698a3982 100644 --- a/Sources/SwiftFormatConfiguration/Configuration.swift +++ b/Sources/SwiftFormatConfiguration/Configuration.swift @@ -35,6 +35,7 @@ public struct Configuration: Codable, Equatable { case fileScopedDeclarationPrivacy case indentSwitchCaseLabels case rules + case spacesAroundRangeFormationOperators } /// The version of this configuration. @@ -142,6 +143,10 @@ public struct Configuration: Codable, Equatable { ///``` public var indentSwitchCaseLabels = false + /// Determines whether whitespace should be forced before and after the range formation operators + /// `...` and `..<`. + public var spacesAroundRangeFormationOperators = false + /// Constructs a Configuration with all default values. public init() { self.version = highestSupportedConfigurationVersion @@ -194,6 +199,9 @@ public struct Configuration: Codable, Equatable { self.lineBreakAroundMultilineExpressionChainComponents = try container.decodeIfPresent( Bool.self, forKey: .lineBreakAroundMultilineExpressionChainComponents) ?? false + self.spacesAroundRangeFormationOperators = + try container.decodeIfPresent( + Bool.self, forKey: .spacesAroundRangeFormationOperators) ?? false self.fileScopedDeclarationPrivacy = try container.decodeIfPresent( FileScopedDeclarationPrivacyConfiguration.self, forKey: .fileScopedDeclarationPrivacy) @@ -226,6 +234,8 @@ public struct Configuration: Codable, Equatable { try container.encode( lineBreakAroundMultilineExpressionChainComponents, forKey: .lineBreakAroundMultilineExpressionChainComponents) + try container.encode( + spacesAroundRangeFormationOperators, forKey: .spacesAroundRangeFormationOperators) try container.encode(fileScopedDeclarationPrivacy, forKey: .fileScopedDeclarationPrivacy) try container.encode(indentSwitchCaseLabels, forKey: .indentSwitchCaseLabels) try container.encode(rules, forKey: .rules) diff --git a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift index 857f5bc31..fa592e4cf 100644 --- a/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift +++ b/Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift @@ -3296,7 +3296,8 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { return nil } - /// Returns a value indicating whether whitespace should be required around the given operator. + /// Returns a value indicating whether whitespace should be required around the given operator, + /// for the given configuration. /// /// If spaces are not required (for example, range operators), then the formatter will also forbid /// breaks around the operator. This is to prevent situations where a break could occur before an @@ -3308,8 +3309,9 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor { // to ignore that and apply our own rules. if let binaryOperator = operatorExpr.as(BinaryOperatorExprSyntax.self) { let token = binaryOperator.operatorToken - if let binOp = operatorTable.infixOperator(named: token.text), - let precedenceGroup = binOp.precedenceGroup, precedenceGroup == "RangeFormationPrecedence" + if !config.spacesAroundRangeFormationOperators, + let binOp = operatorTable.infixOperator(named: token.text), + let precedenceGroup = binOp.precedenceGroup, precedenceGroup == "RangeFormationPrecedence" { // We want to omit whitespace around range formation operators if possible. We can't do this // if the token is either preceded by a postfix operator, followed by a prefix operator, or diff --git a/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift b/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift index 5c37caf70..1a97cd76e 100644 --- a/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift +++ b/Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift @@ -1,3 +1,5 @@ +import SwiftFormatConfiguration + final class BinaryOperatorExprTests: PrettyPrintTestCase { func testNonRangeFormationOperatorsAreSurroundedByBreaks() { let input = @@ -26,7 +28,7 @@ final class BinaryOperatorExprTests: PrettyPrintTestCase { assertPrettyPrintEqual(input: input, expected: expected10, linelength: 10) } - func testRangeFormationOperatorsAreCompactedWhenPossible() { + func testRangeFormationOperatorCompaction_noSpacesAroundRangeFormation() { let input = """ x = 1...100 @@ -48,7 +50,38 @@ final class BinaryOperatorExprTests: PrettyPrintTestCase { """ - assertPrettyPrintEqual(input: input, expected: expected, linelength: 80) + var configuration = Configuration() + configuration.spacesAroundRangeFormationOperators = false + assertPrettyPrintEqual( + input: input, expected: expected, linelength: 80, configuration: configuration) + } + + func testRangeFormationOperatorCompaction_spacesAroundRangeFormation() { + let input = + """ + x = 1...100 + x = 1..<100 + x = (1++)...(-100) + x = 1 ... 100 + x = 1 ..< 100 + x = (1++) ... (-100) + """ + + let expected = + """ + x = 1 ... 100 + x = 1 ..< 100 + x = (1++) ... (-100) + x = 1 ... 100 + x = 1 ..< 100 + x = (1++) ... (-100) + + """ + + var configuration = Configuration() + configuration.spacesAroundRangeFormationOperators = true + assertPrettyPrintEqual( + input: input, expected: expected, linelength: 80, configuration: configuration) } func testRangeFormationOperatorsAreNotCompactedWhenFollowingAPostfixOperator() {