diff --git a/Rules.md b/Rules.md index f726a2180..81c2ea744 100644 --- a/Rules.md +++ b/Rules.md @@ -1910,6 +1910,7 @@ Option | Description `--operatorfunc` | Spacing for operator funcs: "spaced" (default) or "no-space" `--nospaceoperators` | Comma-delimited list of operators without surrounding space `--ranges` | Spacing for ranges: "spaced" (default) or "no-space" +`--typedelimiter` | "trailing" (default) or "leading-trailing"
Examples diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index 915fab844..14a93ffed 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -696,6 +696,12 @@ struct _Descriptors { } } ) + let spaceAroundDelimiter = OptionDescriptor( + argumentName: "typedelimiter", + displayName: "Spacing around delimiter", + help: "\"trailing\" (default) or \"leading-trailing\"", + keyPath: \.spaceAroundDelimiter + ) let spaceAroundRangeOperators = OptionDescriptor( argumentName: "ranges", displayName: "Ranges", diff --git a/Sources/Options.swift b/Sources/Options.swift index 4290acef4..ac589c631 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -354,6 +354,12 @@ public enum EnumNamespacesMode: String, CaseIterable { case structsOnly = "structs-only" } +/// Whether or not to add spacing around data type delimiter +public enum SpaceAroundDelimiter: String, CaseIterable { + case trailing + case leadingTrailing = "leading-trailing" +} + /// Configuration options for formatting. These aren't actually used by the /// Formatter class itself, but it makes them available to the format rules. public struct FormatOptions: CustomStringConvertible { @@ -440,6 +446,7 @@ public struct FormatOptions: CustomStringConvertible { public var genericTypes: String public var useSomeAny: Bool public var wrapEffects: WrapEffects + public var spaceAroundDelimiter: SpaceAroundDelimiter // Deprecated public var indentComments: Bool @@ -545,7 +552,8 @@ public struct FormatOptions: CustomStringConvertible { ignoreConflictMarkers: Bool = false, swiftVersion: Version = .undefined, fileInfo: FileInfo = FileInfo(), - timeout: TimeInterval = 1) + timeout: TimeInterval = 1, + spaceAroundDelimiter: SpaceAroundDelimiter = .trailing) { self.lineAfterMarks = lineAfterMarks self.indent = indent @@ -631,6 +639,7 @@ public struct FormatOptions: CustomStringConvertible { self.genericTypes = genericTypes self.useSomeAny = useSomeAny self.wrapEffects = wrapEffects + self.spaceAroundDelimiter = spaceAroundDelimiter // Doesn't really belong here, but hard to put elsewhere self.fragment = fragment self.ignoreConflictMarkers = ignoreConflictMarkers diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 1b209117b..8d355b276 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -452,7 +452,7 @@ public struct _FormatRules { /// preceded by a space, unless it appears at the beginning of a line. public let spaceAroundOperators = FormatRule( help: "Add or remove space around operators or delimiters.", - options: ["operatorfunc", "nospaceoperators", "ranges"] + options: ["operatorfunc", "nospaceoperators", "ranges", "typedelimiter"] ) { formatter in formatter.forEachToken { i, token in switch token { @@ -559,11 +559,15 @@ public struct _FormatRules { // Ensure there is a space after the token formatter.insert(.space(" "), at: i + 1) } - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isLinebreak == false - { + + let spaceBeforeToken = formatter.token(at: i - 1)?.isSpace == true + && formatter.token(at: i - 2)?.isLinebreak == false + + if spaceBeforeToken, formatter.options.spaceAroundDelimiter == .trailing { // Remove space before the token formatter.removeToken(at: i - 1) + } else if !spaceBeforeToken, formatter.options.spaceAroundDelimiter == .leadingTrailing { + formatter.insertSpace(" ", at: i) } default: break diff --git a/Tests/RulesTests+Spacing.swift b/Tests/RulesTests+Spacing.swift index c6ba4459a..d1be6aaea 100644 --- a/Tests/RulesTests+Spacing.swift +++ b/Tests/RulesTests+Spacing.swift @@ -1167,6 +1167,63 @@ class SpacingTests: RulesTests { testFormatting(for: input, rule: FormatRules.spaceAroundOperators, options: options) } + func testSpaceAroundDataTypeDelimiterLeadingAdded() { + let input = "class Implementation: ImplementationProtocol {}" + let output = "class Implementation : ImplementationProtocol {}" + let options = FormatOptions(spaceAroundDelimiter: .leadingTrailing) + testFormatting( + for: input, + output, + rule: FormatRules.spaceAroundOperators, + options: options + ) + } + + func testSpaceAroundDataTypeDelimiterLeadingTrailingAdded() { + let input = "class Implementation:ImplementationProtocol {}" + let output = "class Implementation : ImplementationProtocol {}" + let options = FormatOptions(spaceAroundDelimiter: .leadingTrailing) + testFormatting( + for: input, + output, + rule: FormatRules.spaceAroundOperators, + options: options + ) + } + + func testSpaceAroundDataTypeDelimiterLeadingTrailingNotModified() { + let input = "class Implementation : ImplementationProtocol {}" + let options = FormatOptions(spaceAroundDelimiter: .leadingTrailing) + testFormatting( + for: input, + rule: FormatRules.spaceAroundOperators, + options: options + ) + } + + func testSpaceAroundDataTypeDelimiterTrailingAdded() { + let input = "class Implementation:ImplementationProtocol {}" + let output = "class Implementation: ImplementationProtocol {}" + + let options = FormatOptions(spaceAroundDelimiter: .trailing) + testFormatting( + for: input, + output, + rule: FormatRules.spaceAroundOperators, + options: options + ) + } + + func testSpaceAroundDataTypeDelimiterLeadingNotAdded() { + let input = "class Implementation: ImplementationProtocol {}" + let options = FormatOptions(spaceAroundDelimiter: .trailing) + testFormatting( + for: input, + rule: FormatRules.spaceAroundOperators, + options: options + ) + } + // MARK: - spaceAroundComments func testSpaceAroundCommentInParens() {