Skip to content

Add an option to allow spaces around range formation operators. #459

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

Merged
merged 9 commits into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 3 additions & 0 deletions Documentation/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -215,15 +215,15 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil {
package.dependencies += [
.package(
url: "https://github.com/apple/swift-argument-parser.git",
branch: "main"
.upToNextMinor(from: "1.1.4")
),
.package(
url: "https://github.com/apple/swift-syntax.git",
branch: "main"
),
.package(
url: "https://github.com/apple/swift-tools-support-core.git",
branch: "main"
.upToNextMinor(from: "0.2.7")
),
]
} else {
Expand Down
10 changes: 10 additions & 0 deletions Sources/SwiftFormatConfiguration/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public struct Configuration: Codable, Equatable {
case fileScopedDeclarationPrivacy
case indentSwitchCaseLabels
case rules
case spacesAroundRangeFormationOperators
}

/// The version of this configuration.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 9 additions & 5 deletions Sources/SwiftFormatPrettyPrint/TokenStreamCreator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1777,7 +1777,7 @@ fileprivate final class TokenStreamCreator: SyntaxVisitor {

let wrapsBeforeOperator = !isAssigningOperator(binOp)

if shouldRequireWhitespace(around: binOp) {
if shouldRequireWhitespace(around: binOp, configuration: config) {
if isAssigningOperator(binOp) {
var beforeTokens: [Token]

Expand Down Expand Up @@ -3296,20 +3296,24 @@ 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
/// unspaced operator (e.g., turning `0...10` into `0<newline>...10`), which would be a breaking
/// change because it would treat it as a prefix operator `...10` instead of an infix operator.
private func shouldRequireWhitespace(around operatorExpr: ExprSyntax) -> Bool {
private func shouldRequireWhitespace(
around operatorExpr: ExprSyntax, configuration: Configuration) -> Bool
{
// Note that we look at the operator itself to make this determination, not the token kind.
// The token kind (spaced or unspaced operator) represents how the *user* wrote it, and we want
// 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
Expand Down
2 changes: 1 addition & 1 deletion Sources/swift-format/VersionOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct VersionOptions: ParsableArguments {
func validate() throws {
if version {
// TODO: Automate updates to this somehow.
print("0.50500.0")
print("0.50700.0")
throw ExitCode.success
}
}
Expand Down
37 changes: 35 additions & 2 deletions Tests/SwiftFormatPrettyPrintTests/BinaryOperatorExprTests.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import SwiftFormatConfiguration

final class BinaryOperatorExprTests: PrettyPrintTestCase {
func testNonRangeFormationOperatorsAreSurroundedByBreaks() {
let input =
Expand Down Expand Up @@ -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
Expand All @@ -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() {
Expand Down