From e5692e16826c07a08304f2fc450a386ac8970120 Mon Sep 17 00:00:00 2001 From: "a.baranouski" Date: Wed, 27 Jan 2021 22:01:45 +0100 Subject: [PATCH 01/52] `--conditionswrap` option to format condition in Xcode 12 style, in case it's too long or multiline --- Rules.md | 12 ++ Sources/Examples.swift | 11 ++ Sources/FormattingHelpers.swift | 108 ++++++++++-- Sources/OptionDescriptor.swift | 6 + Sources/Options.swift | 9 + Sources/Rules.swift | 4 +- Tests/MetadataTests.swift | 3 +- Tests/RulesTests+Wrapping.swift | 280 ++++++++++++++++++++++++++++++++ 8 files changed, 419 insertions(+), 14 deletions(-) diff --git a/Rules.md b/Rules.md index f09d24d29..945e4875b 100644 --- a/Rules.md +++ b/Rules.md @@ -2721,6 +2721,7 @@ Option | Description `--wrapconditions` | Wrap conditions: "before-first", "after-first", "preserve" `--wraptypealiases` | Wrap typealiases: "before-first", "after-first", "preserve" `--wrapeffects` | Wrap effects: "if-multiline", "never", "preserve" +`--conditionswrap` | Wrap conditions as Xcode 12:"auto", "always", "disabled"
Examples @@ -2781,6 +2782,17 @@ provided for `--wrapparameters`, the value for `--wraparguments` will be used. + ] ``` +`--conditionswrap auto`: + +```diff +- guard let foo = foo, let bar = bar, let third = third ++ guard let foo = foo, ++ let bar = bar, ++ let third = third + else {} +``` + +

diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 35dc95bb9..31fc441e9 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1163,6 +1163,17 @@ private struct Examples { + quuz + ] ``` + + `--conditionswrap auto`: + + ```diff + - guard let foo = foo, let bar = bar, let third = third + + guard let foo = foo, + + let bar = bar, + + let third = third + else {} + ``` + """ let wrapMultilineStatementBraces = """ diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 9f57f826c..5bc8cba9e 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -483,22 +483,34 @@ extension Formatter { // Remove linebreak after opening paren removeTokens(in: i + 1 ..< firstArgumentIndex) + endOfScope -= (firstArgumentIndex - (i + 1)) firstArgumentIndex = i + 1 // Get indent let start = startOfLine(at: i) let indent = spaceEquivalentToTokens(from: start, upTo: firstArgumentIndex) - removeLinebreakBeforeEndOfScope(at: &endOfScope) + + // Don't remove linebreak if there is one for `guard ... else` conditions + if token(at: endOfScope) != .keyword("else") { + removeLinebreakBeforeEndOfScope(at: &endOfScope) + } // Insert linebreak after each comma var lastBreakIndex: Int? var index = firstArgumentIndex + + let wrapIgnoringMaxWidth = Set([WrapMode.always, WrapMode.auto]).contains(options.conditionsWrap) + while let commaIndex = self.index(of: .delimiter(","), in: index ..< endOfScope), var linebreakIndex = self.index(of: .nonSpaceOrComment, after: commaIndex) { if let index = self.index(of: .nonSpace, before: linebreakIndex) { linebreakIndex = index + 1 } - if maxWidth > 0, lineLength(upTo: commaIndex) >= maxWidth, let breakIndex = lastBreakIndex { + + if maxWidth > 0, + wrapIgnoringMaxWidth || lineLength(upTo: commaIndex) >= maxWidth || wrapIgnoringMaxWidth, + let breakIndex = lastBreakIndex + { endOfScope += 1 + insertSpace(indent, at: breakIndex) insertLinebreak(at: breakIndex) lastBreakIndex = nil @@ -517,7 +529,10 @@ extension Formatter { } index = commaIndex + 1 } - if maxWidth > 0, let breakIndex = lastBreakIndex, lineLength(at: breakIndex) > maxWidth { + + if maxWidth > 0, let breakIndex = lastBreakIndex, + wrapIgnoringMaxWidth || lineLength(at: breakIndex) > maxWidth + { insertSpace(indent, at: breakIndex) insertLinebreak(at: breakIndex) } @@ -614,7 +629,7 @@ extension Formatter { wrapArgumentsAfterFirst(startOfScope: i, endOfScope: endOfScope, allowGrouping: true) - case .disabled, .default: + case .disabled, .default, .auto, .always: assertionFailure() // Shouldn't happen } @@ -670,7 +685,7 @@ extension Formatter { wrapArgumentsAfterFirst(startOfScope: i, endOfScope: endOfScope, allowGrouping: true) - case .disabled, .default: + case .disabled, .default, .auto, .always: assertionFailure() // Shouldn't happen } } @@ -693,7 +708,7 @@ extension Formatter { lastIndex = i } - // -- wrapconditions + // -- wrapconditions && -- conditionswrap forEach(.keyword) { index, token in let indent: String let endOfConditionsToken: Token @@ -711,16 +726,21 @@ extension Formatter { return } - // Only wrap when this is a control flow condition that spans multiple lines guard let endIndex = self.index(of: endOfConditionsToken, after: index), - let nextTokenIndex = self.index(of: .nonSpaceOrLinebreak, after: index), - !(onSameLine(index, endIndex) || self.index(of: .nonSpaceOrLinebreak, after: endOfLine(at: index)) == endIndex) + let nextTokenIndex = self.index(of: .nonSpaceOrLinebreak, after: index) else { return } + // Only wrap when this is a control flow condition that spans multiple lines + let controlFlowConditionSpansMultipleLines = self.index( + of: .nonSpaceOrLinebreak, + after: endOfLine(at: index) + ) != endIndex && !onSameLine(index, endIndex) + switch options.wrapConditions { - case .preserve, .disabled, .default: + case .preserve, .disabled, .default, .auto, .always: break case .beforeFirst: + guard controlFlowConditionSpansMultipleLines else { return } // Wrap if the next non-whitespace-or-comment // is on the same line as the control flow keyword if onSameLine(index, nextTokenIndex) { @@ -734,6 +754,7 @@ extension Formatter { linebreakIndex = self.index(of: .linebreak, after: index) } case .afterFirst: + guard controlFlowConditionSpansMultipleLines else { return } // Unwrap if the next non-whitespace-or-comment // is not on the same line as the control flow keyword if !onSameLine(index, nextTokenIndex), @@ -751,6 +772,71 @@ extension Formatter { lastIndex = index } } + + switch options.conditionsWrap { + case .auto, .always: + if !onSameLine(index, nextTokenIndex), + let linebreakIndex = self.index(of: .linebreak, in: index ..< nextTokenIndex) + { + removeToken(at: linebreakIndex) + } + + insertSpace(" ", at: index + 1) + + let isCaseForAutoWrap = lineLength(at: index) > maxWidth || controlFlowConditionSpansMultipleLines + if !(options.conditionsWrap == .always || isCaseForAutoWrap) { + return + } + + wrapArgumentsAfterFirst(startOfScope: index + 1, + endOfScope: endIndex, + allowGrouping: true) + + // Xcode 12 wraps guard's else on a new line + guard token == .keyword("guard") else { break } + + // Leave only one breakline before else + if let endOfConditionsTokenIndexAfterChanges = self.index(of: endOfConditionsToken, after: index), + let lastArgumentIndex = self.index(of: .nonSpaceOrLinebreak, before: endOfConditionsTokenIndexAfterChanges) + { + let slice = tokens[lastArgumentIndex ..< endOfConditionsTokenIndexAfterChanges] + let breaklineIndexes = slice.indices.filter { tokens[$0].isLinebreak } + + if breaklineIndexes.isEmpty { + insertLinebreak(at: endOfConditionsTokenIndexAfterChanges - 1) + } else if breaklineIndexes.count > 1 { + for breaklineIndex in breaklineIndexes.dropFirst() { + removeToken(at: breaklineIndex) + } + } + } + + // Space token before `else` should match space token before `guard` + if let endOfConditionsTokenIndexAfterChanges = self.index(of: endOfConditionsToken, after: index), + let lastArgumentIndex = self.index(of: .nonSpaceOrLinebreak, before: endOfConditionsTokenIndexAfterChanges) + { + let slice = tokens[lastArgumentIndex ..< endOfConditionsTokenIndexAfterChanges] + let spaceIndexes = slice.indices.filter { tokens[$0].isSpace } + + if let spaceToken = self.token(at: index - 1), spaceToken.isSpace { + if spaceIndexes.count == 1, let spaceIndex = spaceIndexes.first, + let existedSpaceToken = self.token(at: spaceIndex), spaceToken == existedSpaceToken + { + /* Nothing to do here */ + break + } else { + spaceIndexes.forEach { removeToken(at: $0) } + insertSpace(spaceToken.string, at: endOfConditionsTokenIndexAfterChanges) + } + } else { + spaceIndexes.forEach { removeToken(at: $0) } + } + } + + default: + /* Nothing to do here */ + break + } } // Wraps / re-wraps a multi-line statement where each delimiter index @@ -827,7 +913,7 @@ extension Formatter { wrapIndices = andTokenIndices case .beforeFirst: wrapIndices = [equalsIndex] + andTokenIndices - case .default, .disabled, .preserve: + case .default, .disabled, .preserve, .auto, .always: return } diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index c888ae1f7..3a14e2b99 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -510,6 +510,12 @@ struct _Descriptors { help: "Wrap ternary operators: \"default\", \"before-operators\"", keyPath: \.wrapTernaryOperators ) + let conditionsWrap = OptionDescriptor( + argumentName: "conditionswrap", + displayName: "Conditions Wrap", + help: "Wrap conditions as Xcode 12:\"auto\", \"always\", \"disabled\"", + keyPath: \.conditionsWrap + ) let closingParenPosition = OptionDescriptor( argumentName: "closingparen", displayName: "Closing Paren Position", diff --git a/Sources/Options.swift b/Sources/Options.swift index cbe3cb6bb..d4b590c01 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -56,6 +56,8 @@ public enum WrapMode: String, CaseIterable { case beforeFirst = "before-first" case afterFirst = "after-first" case preserve + case auto + case always case disabled case `default` @@ -71,6 +73,10 @@ public enum WrapMode: String, CaseIterable { self = .disabled case "default": self = .default + case "auto": + self = .auto + case "always": + self = .always default: return nil } @@ -609,6 +615,7 @@ public struct FormatOptions: CustomStringConvertible { public var wrapReturnType: WrapReturnType public var wrapConditions: WrapMode public var wrapTernaryOperators: TernaryOperatorWrapMode + public var conditionsWrap: WrapMode public var uppercaseHex: Bool public var uppercaseExponent: Bool public var decimalGrouping: Grouping @@ -724,6 +731,7 @@ public struct FormatOptions: CustomStringConvertible { wrapReturnType: WrapReturnType = .preserve, wrapConditions: WrapMode = .preserve, wrapTernaryOperators: TernaryOperatorWrapMode = .default, + conditionsWrap: WrapMode = .disabled, uppercaseHex: Bool = true, uppercaseExponent: Bool = false, decimalGrouping: Grouping = .group(3, 6), @@ -829,6 +837,7 @@ public struct FormatOptions: CustomStringConvertible { self.wrapReturnType = wrapReturnType self.wrapConditions = wrapConditions self.wrapTernaryOperators = wrapTernaryOperators + self.conditionsWrap = conditionsWrap self.uppercaseHex = uppercaseHex self.uppercaseExponent = uppercaseExponent self.decimalGrouping = decimalGrouping diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 5a47b4c75..ef3bd0e01 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -3919,7 +3919,7 @@ public struct _FormatRules { options: ["maxwidth", "nowrapoperators", "assetliterals", "wrapternary"], sharedOptions: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", "indent", "trimwhitespace", "linebreaks", "tabwidth", "maxwidth", "smarttabs", "wrapreturntype", - "wrapconditions", "wraptypealiases", "wrapternary", "wrapeffects"] + "wrapconditions", "wraptypealiases", "wrapternary", "wrapeffects", "conditionswrap"] ) { formatter in let maxWidth = formatter.options.maxWidth guard maxWidth > 0 else { return } @@ -3976,7 +3976,7 @@ public struct _FormatRules { help: "Align wrapped function arguments or collection elements.", orderAfter: ["wrap"], options: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", - "wrapreturntype", "wrapconditions", "wraptypealiases", "wrapeffects"], + "wrapreturntype", "wrapconditions", "wraptypealiases", "wrapeffects", "conditionswrap"], sharedOptions: ["indent", "trimwhitespace", "linebreaks", "tabwidth", "maxwidth", "smarttabs", "assetliterals", "wrapternary"] ) { formatter in diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index d808f52a6..d21db8924 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -202,7 +202,7 @@ class MetadataTests: XCTestCase { Descriptors.linebreak, Descriptors.truncateBlankLines, Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, Descriptors.maxWidth, Descriptors.assetLiteralWidth, Descriptors.wrapReturnType, Descriptors.wrapEffects, - Descriptors.wrapConditions, Descriptors.wrapTypealiases, Descriptors.wrapTernaryOperators, + Descriptors.wrapConditions, Descriptors.wrapTypealiases, Descriptors.wrapTernaryOperators, Descriptors.conditionsWrap, ] case .identifier("wrapStatementBody"): referencedOptions += [Descriptors.indent, Descriptors.linebreak] @@ -241,6 +241,7 @@ class MetadataTests: XCTestCase { continue } } + for option in referencedOptions { XCTAssert(ruleOptions.contains(option.argumentName) || option.isDeprecated, "\(option.argumentName) not listed in \(name) rule") diff --git a/Tests/RulesTests+Wrapping.swift b/Tests/RulesTests+Wrapping.swift index 050dd02c0..727eae592 100644 --- a/Tests/RulesTests+Wrapping.swift +++ b/Tests/RulesTests+Wrapping.swift @@ -4247,6 +4247,286 @@ class WrappingTests: RulesTests { ) } + // MARK: conditionsWrap auto + + func testConditionsWrapAutoForLongGuard() { + let input = """ + guard let foo = foo, let bar = bar, let third = third else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoForLongGuardWithoutChanges() { + let input = """ + guard let foo = foo, let bar = bar, let third = third else {} + """ + testFormatting( + for: input, + rules: [FormatRules.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120) + ) + } + + func testConditionsWrapAutoForMultilineGuard() { + let input = """ + guard let foo = foo, + let bar = bar, let third = third else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments, FormatRules.indent], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoOptionForGuardStyledAsBeforeArgument() { + let input = """ + guard + let foo = foo, + let bar = bar, + let third = third + else {} + + guard + let foo = foo, + let bar = bar, + let third = third + else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoOptionForGuardWhenElseOnNewLine() { + let input = """ + guard let foo = foo, let bar = bar, let third = third + else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoOptionForGuardWhenElseOnNewLineAndNotAligned() { + let input = """ + guard let foo = foo, let bar = bar, let third = third + else {} + + guard let foo = foo, let bar = bar, let third = third + + else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoOptionForGuardInMethod() { + let input = """ + func doSmth() { + let a = smth as? SmthElse + + guard + let foo = foo, + let bar = bar, + let third = third + else { + return nil + } + + let value = a.doSmth() + } + """ + + let output = """ + func doSmth() { + let a = smth as? SmthElse + + guard let foo = foo, + let bar = bar, + let third = third + else { + return nil + } + + let value = a.doSmth() + } + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120) + ) + } + + func testConditionsWrapAutoOptionForIfInsideMethod() { + let input = """ + func doSmth() { + let a = smth as? SmthElse + + if + let foo = foo, + let bar = bar, + let third = third { + return nil + } + + let value = a.doSmth() + } + """ + + let output = """ + func doSmth() { + let a = smth as? SmthElse + + if let foo = foo, + let bar = bar, + let third = third { + return nil + } + + let value = a.doSmth() + } + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120), + exclude: ["wrapMultilineStatementBraces"] + ) + } + + func testConditionsWrapAutoOptionForLongIf() { + let input = """ + if let foo = foo, let bar = bar, let third = third {} + """ + + let output = """ + if let foo = foo, + let bar = bar, + let third = third {} + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments, FormatRules.indent], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 25) + ) + } + + func testConditionsWrapAutoOptionForLongMultilineIf() { + let input = """ + if let foo = foo, + let bar = bar, let third = third {} + """ + + let output = """ + if let foo = foo, + let bar = bar, + let third = third {} + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments, FormatRules.indent], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 25) + ) + } + + // MARK: conditionsWrap always + + func testConditionWrapAlwaysOptionForLongGuard() { + let input = """ + guard let foo = foo, let bar = bar, let third = third else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [FormatRules.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .always, maxWidth: 120) + ) + } + // MARK: - wrapAttributes func testPreserveWrappedFuncAttributeByDefault() { From 44d79dd0edc71b8b29c236072c95700463cdd19e Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Tue, 12 Mar 2024 16:46:14 -0700 Subject: [PATCH 02/52] Add preferInferredTypes rule --- Rules.md | 26 +++++++++++++++ Sources/Examples.swift | 17 ++++++++++ Sources/Rules.swift | 32 ++++++++++++++++++- Tests/RulesTests+Redundancy.swift | 38 +++++++++++----------- Tests/RulesTests+Spacing.swift | 2 +- Tests/RulesTests+Syntax.swift | 53 +++++++++++++++++++++++++++++++ Tests/RulesTests+Wrapping.swift | 8 ++--- 7 files changed, 151 insertions(+), 25 deletions(-) diff --git a/Rules.md b/Rules.md index 945e4875b..e4b756e70 100644 --- a/Rules.md +++ b/Rules.md @@ -101,6 +101,7 @@ * [markTypes](#markTypes) * [noExplicitOwnership](#noExplicitOwnership) * [organizeDeclarations](#organizeDeclarations) +* [preferInferredTypes](#preferInferredTypes) * [redundantProperty](#redundantProperty) * [sortSwitchCases](#sortSwitchCases) * [wrapConditionalBodies](#wrapConditionalBodies) @@ -1503,6 +1504,31 @@ Option | Description
+## preferInferredTypes + +Prefer using inferred types on property definitions (`let foo = Foo()`) rather than explicit types (`let foo: Foo = .init()`). + +
+Examples + +```diff +- let foo: Foo = .init() ++ let foo: Foo = .init() + +- let bar: Bar = .defaultValue ++ let bar = .defaultValue + +- let baaz: Baaz = .buildBaaz(foo: foo, bar: bar) ++ let baaz = Baaz.buildBaaz(foo: foo, bar: bar) + + let float: CGFloat = 10.0 + let array: [String] = [] + let anyFoo: AnyFoo = foo +``` + +
+
+ ## preferKeyPath Convert trivial `map { $0.foo }` closures to keyPath-based syntax. diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 31fc441e9..d16eb3b88 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1923,4 +1923,21 @@ private struct Examples { } ``` """ + + let preferInferredTypes = """ + ```diff + - let foo: Foo = .init() + + let foo: Foo = .init() + + - let bar: Bar = .defaultValue + + let bar = .defaultValue + + - let baaz: Baaz = .buildBaaz(foo: foo, bar: bar) + + let baaz = Baaz.buildBaaz(foo: foo, bar: bar) + + let float: CGFloat = 10.0 + let array: [String] = [] + let anyFoo: AnyFoo = foo + ``` + """ } diff --git a/Sources/Rules.swift b/Sources/Rules.swift index ef3bd0e01..a1b4a3a4e 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -4460,7 +4460,8 @@ public struct _FormatRules { /// Strip redundant `.init` from type instantiations public let redundantInit = FormatRule( - help: "Remove explicit `init` if not required." + help: "Remove explicit `init` if not required.", + orderAfter: ["preferInferredTypes"] ) { formatter in formatter.forEach(.identifier("init")) { initIndex, _ in guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: initIndex, if: { @@ -8070,4 +8071,33 @@ public struct _FormatRules { } } } + + public let preferInferredTypes = FormatRule( + help: "Prefer using inferred types on property definitions (`let foo = Foo()`) rather than explicit types (`let foo: Foo = .init()`).", + disabledByDefault: true, + orderAfter: ["redundantType"] + ) { formatter in + formatter.forEach(.operator("=", .infix)) { equalsIndex, _ in + guard // Parse and validate the LHS of the property declaration. + // It should take the form `(let|var) propertyName: (Type) = .staticMember` + let introducerIndex = formatter.indexOfLastSignificantKeyword(at: equalsIndex), + ["var", "let"].contains(formatter.tokens[introducerIndex].string), + let colonIndex = formatter.index(of: .delimiter(":"), before: equalsIndex), + introducerIndex < colonIndex, + let typeStartIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex), + let type = formatter.parseType(at: typeStartIndex), + // If the RHS starts with a leading dot, then we know its accessing some static member on this type. + let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex), + formatter.tokens[dotIndex] == .operator(".", .prefix) + else { return } + + let typeTokens = formatter.tokens[type.range] + + // Insert a copy of the type on the RHS before the dot + formatter.insert(typeTokens, at: dotIndex) + + // Remove the colon and explicit type before the equals token + formatter.removeTokens(in: colonIndex ... type.range.upperBound) + } + } } diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index e36fbe07e..24c238c1a 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -531,7 +531,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["preferInferredTypes"]) } func testFileprivateInitNotChangedToPrivateWhenUsingTrailingClosureInit() { @@ -1513,7 +1513,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testVarRedundantTypeRemovalExplicitType2() { @@ -1521,7 +1521,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = .init /* foo */()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["spaceAroundComments"]) + options: options, exclude: ["spaceAroundComments", "preferInferredTypes"]) } func testLetRedundantGenericTypeRemovalExplicitType() { @@ -1529,7 +1529,7 @@ class RedundancyTests: RulesTests { let output = "let relay: BehaviourRelay = .init(value: nil)" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testLetRedundantGenericTypeRemovalExplicitTypeIfValueOnNextLine() { @@ -1537,7 +1537,7 @@ class RedundancyTests: RulesTests { let output = "let relay: Foo = \n .default" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["trailingSpace"]) + options: options, exclude: ["trailingSpace", "preferInferredTypes"]) } func testVarNonRedundantTypeDoesNothingExplicitType() { @@ -1551,7 +1551,7 @@ class RedundancyTests: RulesTests { let output = "let view: UIView = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeRemovedIfValueOnNextLineExplicitType() { @@ -1565,7 +1565,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeRemovedIfValueOnNextLine2ExplicitType() { @@ -1579,7 +1579,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeRemovalWithCommentExplicitType() { @@ -1587,7 +1587,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView /* view */ = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeRemovalWithComment2ExplicitType() { @@ -1595,7 +1595,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = /* view */ .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeRemovalWithStaticMember() { @@ -1617,7 +1617,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeRemovalWithStaticFunc() { @@ -1639,7 +1639,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeDoesNothingWithChainedMember() { @@ -1653,7 +1653,7 @@ class RedundancyTests: RulesTests { let output = "let session: URLSession = .default.makeCopy()" let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeDoesNothingWithChainedMember2() { @@ -1672,7 +1672,7 @@ class RedundancyTests: RulesTests { let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" let output = "let url: URL = .init(fileURLWithPath: #file).deletingLastPathComponent()" let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeDoesNothingIfLet() { @@ -1704,7 +1704,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeIfVoid() { @@ -1712,7 +1712,7 @@ class RedundancyTests: RulesTests { let output = "let foo: [Void] = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeWithIntegerLiteralNotMangled() { @@ -1794,7 +1794,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(redundantType: .inferLocalsOnly) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options) + options: options, exclude: ["preferInferredTypes"]) } // MARK: - redundantNilInit @@ -3899,7 +3899,7 @@ class RedundancyTests: RulesTests { func testNoRemoveBackticksAroundInitPropertyInSwift5() { let input = "let foo: Foo = .`init`" let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, rule: FormatRules.redundantBackticks, options: options) + testFormatting(for: input, rule: FormatRules.redundantBackticks, options: options, exclude: ["preferInferredTypes"]) } func testNoRemoveBackticksAroundAnyProperty() { @@ -7465,7 +7465,7 @@ class RedundancyTests: RulesTests { case networkOnly case cacheFirst - static let defaultCacheAge: TimeInterval = .minutes(5) + static let defaultCacheAge = TimeInterval.minutes(5) func requestStrategy() -> SingleRequestStrategy { switch self { diff --git a/Tests/RulesTests+Spacing.swift b/Tests/RulesTests+Spacing.swift index 7c66ac100..ff55d7b45 100644 --- a/Tests/RulesTests+Spacing.swift +++ b/Tests/RulesTests+Spacing.swift @@ -846,7 +846,7 @@ class SpacingTests: RulesTests { } func testGenericBracketAroundAttributeNotConfusedWithLessThan() { - let input = "let example = Example<(@MainActor () -> Void)?>(nil)" + let input = "Example<(@MainActor () -> Void)?>(nil)" testFormatting(for: input, rule: FormatRules.spaceAroundOperators) } diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index c8ce38096..791d92fc2 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -4948,4 +4948,57 @@ class SyntaxTests: RulesTests { testFormatting(for: input, output, rule: FormatRules.preferForLoop) } + + // MARK: preferInferredTypes + + func testConvertsExplicitTypeToImplicitType() { + let input = """ + let foo: Foo = .init() + let bar: Bar = .staticBar + let baaz: Baaz = .Example.default + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + let optional: String? = .init("Foo") + """ + + let output = """ + let foo = Foo.init() + let bar = Bar.staticBar + let baaz = Baaz.Example.default + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar].init() + let array = [Foo].init() + let genericType = MyGenericType.init() + let optional = String?.init("Foo") + """ + + testFormatting(for: input, output, rule: FormatRules.preferInferredTypes, exclude: ["redundantInit"]) + } + + func testPreservesExplicitTypeIfNoRHS() { + let input = """ + let foo: Foo + let bar: Bar + """ + + testFormatting(for: input, rule: FormatRules.preferInferredTypes) + } + + func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { + let input = """ + let foo: Foo = localFoo + let bar: Bar = localBar + let int: Int64 = 1234 + let number: CGFloat = 12.345 + let array: [String] = [] + let dictionary: [String: Int] = [:] + let tuple: (String, Int) = ("foo", 123) + """ + + testFormatting(for: input, rule: FormatRules.preferInferredTypes) + } } diff --git a/Tests/RulesTests+Wrapping.swift b/Tests/RulesTests+Wrapping.swift index 727eae592..ca9d0fc3e 100644 --- a/Tests/RulesTests+Wrapping.swift +++ b/Tests/RulesTests+Wrapping.swift @@ -3644,7 +3644,7 @@ class WrappingTests: RulesTests { func testMultilineBraceAppliedToGetterBody_wrapBeforeFirst() { let input = """ - var items: Adaptive = .adaptive( + var items = Adaptive.adaptive( compact: Sizes.horizontalPaddingTiny_8, regular: Sizes.horizontalPaddingLarge_64) { didSet { updateAccessoryViewSpacing() } @@ -3652,7 +3652,7 @@ class WrappingTests: RulesTests { """ let output = """ - var items: Adaptive = .adaptive( + var items = Adaptive.adaptive( compact: Sizes.horizontalPaddingTiny_8, regular: Sizes.horizontalPaddingLarge_64) { @@ -3696,8 +3696,8 @@ class WrappingTests: RulesTests { func testMultilineBraceAppliedToGetterBody_wrapAfterFirst() { let input = """ - var items: Adaptive = .adaptive(compact: Sizes.horizontalPaddingTiny_8, - regular: Sizes.horizontalPaddingLarge_64) + var items = Adaptive.adaptive(compact: Sizes.horizontalPaddingTiny_8, + regular: Sizes.horizontalPaddingLarge_64) { didSet { updateAccessoryViewSpacing() } } From 2b8d27d74538aabce502604e75f262b77fac69a0 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Fri, 15 Mar 2024 16:08:19 -0700 Subject: [PATCH 03/52] Make preferInferredTypes rule more compatible with redundantType rule --- Sources/Rules.swift | 15 ++++++-- Tests/RulesTests+Redundancy.swift | 19 +++++----- Tests/RulesTests+Syntax.swift | 60 +++++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 15 deletions(-) diff --git a/Sources/Rules.swift b/Sources/Rules.swift index a1b4a3a4e..877954277 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -783,7 +783,9 @@ public struct _FormatRules { isInferred = true declarationKeywordIndex = nil case .explicit: - isInferred = false + // If the `preferInferredTypes` rule is also enabled, it takes precedence + // over the `--redundanttype explicit` option. + isInferred = formatter.options.enabledRules.contains("preferInferredTypes") declarationKeywordIndex = formatter.declarationIndexAndScope(at: equalsIndex).index case .inferLocalsOnly: let (index, scope) = formatter.declarationIndexAndScope(at: equalsIndex) @@ -8005,7 +8007,8 @@ public struct _FormatRules { public let redundantProperty = FormatRule( help: "Simplifies redundant property definitions that are immediately returned.", - disabledByDefault: true + disabledByDefault: true, + orderAfter: ["preferInferredTypes"] ) { formatter in formatter.forEach(.keyword) { introducerIndex, introducerToken in // Find properties like `let identifier = value` followed by `return identifier` @@ -8075,7 +8078,8 @@ public struct _FormatRules { public let preferInferredTypes = FormatRule( help: "Prefer using inferred types on property definitions (`let foo = Foo()`) rather than explicit types (`let foo: Foo = .init()`).", disabledByDefault: true, - orderAfter: ["redundantType"] + orderAfter: ["redundantType"], + sharedOptions: ["redundanttype"] ) { formatter in formatter.forEach(.operator("=", .infix)) { equalsIndex, _ in guard // Parse and validate the LHS of the property declaration. @@ -8091,6 +8095,11 @@ public struct _FormatRules { formatter.tokens[dotIndex] == .operator(".", .prefix) else { return } + // Respect the `.inferLocalsOnly` option if enabled + if formatter.options.redundantType == .inferLocalsOnly, + formatter.declarationScope(at: equalsIndex) != .local + { return } + let typeTokens = formatter.tokens[type.range] // Insert a copy of the type on the RHS before the dot diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index 24c238c1a..f19390606 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -1406,7 +1406,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "preferInferredTypes"]) } func testRedundantTypeWithNestedIfExpression_inferred() { @@ -1484,7 +1484,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "preferInferredTypes"]) } func testRedundantTypeWithLiteralsInIfExpression() { @@ -1645,7 +1645,7 @@ class RedundancyTests: RulesTests { func testRedundantTypeDoesNothingWithChainedMember() { let input = "let session: URLSession = URLSession.default.makeCopy()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) } func testRedundantRedundantChainedMemberTypeRemovedOnSwift5_4() { @@ -1659,13 +1659,13 @@ class RedundancyTests: RulesTests { func testRedundantTypeDoesNothingWithChainedMember2() { let input = "let color: UIColor = UIColor.red.withAlphaComponent(0.5)" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeDoesNothingWithChainedMember3() { let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeRemovedWithChainedMemberOnSwift5_4() { @@ -1678,19 +1678,19 @@ class RedundancyTests: RulesTests { func testRedundantTypeDoesNothingIfLet() { let input = "if let foo: Foo = Foo() {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeDoesNothingGuardLet() { let input = "guard let foo: Foo = Foo() else {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeDoesNothingIfLetAfterComma() { let input = "if check == true, let foo: Foo = Foo() {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) } func testRedundantTypeWorksAfterIf() { @@ -1752,7 +1752,8 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, + exclude: ["preferInferredTypes"]) } // --redundanttype infer-locals-only diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index 791d92fc2..ab1f5f152 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -4976,7 +4976,8 @@ class SyntaxTests: RulesTests { let optional = String?.init("Foo") """ - testFormatting(for: input, output, rule: FormatRules.preferInferredTypes, exclude: ["redundantInit"]) + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: FormatRules.preferInferredTypes, options: options, exclude: ["redundantInit"]) } func testPreservesExplicitTypeIfNoRHS() { @@ -4985,7 +4986,8 @@ class SyntaxTests: RulesTests { let bar: Bar """ - testFormatting(for: input, rule: FormatRules.preferInferredTypes) + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) } func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { @@ -4999,6 +5001,58 @@ class SyntaxTests: RulesTests { let tuple: (String, Int) = ("foo", 123) """ - testFormatting(for: input, rule: FormatRules.preferInferredTypes) + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options, exclude: ["redundantType"]) + } + + func testCompatibleWithRedundantTypeInferred() { + let input = """ + let foo: Foo = Foo() + """ + + let output = """ + let foo = Foo() + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.preferInferredTypes], options: options) + } + + func testCompatibleWithRedundantTypeExplicit() { + let input = """ + let foo: Foo = Foo() + """ + + let output = """ + let foo = Foo() + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.preferInferredTypes], options: options) + } + + func testCompatibleWithRedundantTypeInferLocalsOnly() { + let input = """ + let foo: Foo = Foo.init() + let foo: Foo = .init() + + func bar() { + let baaz: Baaz = Baaz.init() + let baaz: Baaz = .init() + } + """ + + let output = """ + let foo: Foo = .init() + let foo: Foo = .init() + + func bar() { + let baaz = Baaz.init() + let baaz = Baaz.init() + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.preferInferredTypes], options: options, exclude: ["redundantInit"]) } } From b873d7d6045c23de40c9d21eb8e28b1c676cfa79 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sat, 23 Mar 2024 19:45:58 -0700 Subject: [PATCH 04/52] Extend preferInferredTypes rule to support if/switch expressions --- Rules.md | 15 ++++++ Sources/Examples.swift | 11 ++++ Sources/OptionDescriptor.swift | 8 +++ Sources/Options.swift | 3 ++ Sources/Rules.swift | 74 +++++++++++++++++--------- Tests/RulesTests+Syntax.swift | 94 ++++++++++++++++++++++++++++++---- 6 files changed, 172 insertions(+), 33 deletions(-) diff --git a/Rules.md b/Rules.md index e4b756e70..e166e1411 100644 --- a/Rules.md +++ b/Rules.md @@ -1508,6 +1508,10 @@ Option | Description Prefer using inferred types on property definitions (`let foo = Foo()`) rather than explicit types (`let foo: Foo = .init()`). +Option | Description +--- | --- +`--inferredtypes` | "exclude-cond-exprs" (default) or "always" +
Examples @@ -1524,6 +1528,17 @@ Prefer using inferred types on property definitions (`let foo = Foo()`) rather t let float: CGFloat = 10.0 let array: [String] = [] let anyFoo: AnyFoo = foo + + // with --inferredtypes always: +- let foo: Foo = ++ let foo = + if condition { +- .init(bar) ++ Foo(bar) + } else { +- .init(baaz) ++ Foo(baaz) + } ```
diff --git a/Sources/Examples.swift b/Sources/Examples.swift index d16eb3b88..ae238170d 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1938,6 +1938,17 @@ private struct Examples { let float: CGFloat = 10.0 let array: [String] = [] let anyFoo: AnyFoo = foo + + // with --inferredtypes always: + - let foo: Foo = + + let foo = + if condition { + - .init(bar) + + Foo(bar) + } else { + - .init(baaz) + + Foo(baaz) + } ``` """ } diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index 3a14e2b99..5a7eb416f 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -923,6 +923,14 @@ struct _Descriptors { help: "\"inferred\", \"explicit\", or \"infer-locals-only\" (default)", keyPath: \.redundantType ) + let inferredTypesInConditionalExpressions = OptionDescriptor( + argumentName: "inferredtypes", + displayName: "Prefer Inferred Types", + help: "\"exclude-cond-exprs\" (default) or \"always\"", + keyPath: \.inferredTypesInConditionalExpressions, + trueValues: ["exclude-cond-exprs"], + falseValues: ["always"] + ) let emptyBracesSpacing = OptionDescriptor( argumentName: "emptybraces", displayName: "Empty Braces", diff --git a/Sources/Options.swift b/Sources/Options.swift index d4b590c01..d35e1ff23 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -670,6 +670,7 @@ public struct FormatOptions: CustomStringConvertible { public var yodaSwap: YodaMode public var extensionACLPlacement: ExtensionACLPlacement public var redundantType: RedundantType + public var inferredTypesInConditionalExpressions: Bool public var emptyBracesSpacing: EmptyBracesSpacing public var acronyms: Set public var indentStrings: Bool @@ -786,6 +787,7 @@ public struct FormatOptions: CustomStringConvertible { yodaSwap: YodaMode = .always, extensionACLPlacement: ExtensionACLPlacement = .onExtension, redundantType: RedundantType = .inferLocalsOnly, + inferredTypesInConditionalExpressions: Bool = false, emptyBracesSpacing: EmptyBracesSpacing = .noSpace, acronyms: Set = ["ID", "URL", "UUID"], indentStrings: Bool = false, @@ -892,6 +894,7 @@ public struct FormatOptions: CustomStringConvertible { self.yodaSwap = yodaSwap self.extensionACLPlacement = extensionACLPlacement self.redundantType = redundantType + self.inferredTypesInConditionalExpressions = inferredTypesInConditionalExpressions self.emptyBracesSpacing = emptyBracesSpacing self.acronyms = acronyms self.indentStrings = indentStrings diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 877954277..453c51e84 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -7334,15 +7334,10 @@ public struct _FormatRules { // matches the identifier assigned on each conditional branch. if let introducerIndex = formatter.indexOfLastSignificantKeyword(at: startOfConditional, excluding: ["if", "switch"]), ["let", "var"].contains(formatter.tokens[introducerIndex].string), - let propertyIdentifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: introducerIndex), - let propertyIdentifier = formatter.token(at: propertyIdentifierIndex), - propertyIdentifier.isIdentifier, - formatter.tokens[lvalueRange.lowerBound] == propertyIdentifier, - lvalueRange.count == 1, - let colonIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: propertyIdentifierIndex), - formatter.tokens[colonIndex] == .delimiter(":"), - let startOfTypeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex), - let typeRange = formatter.parseType(at: startOfTypeIndex)?.range, + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + formatter.tokens[lvalueRange.lowerBound].string == property.identifier, + property.value == nil, + let typeRange = property.type?.range, let nextTokenAfterProperty = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: typeRange.upperBound), nextTokenAfterProperty == startOfConditional { @@ -8079,34 +8074,65 @@ public struct _FormatRules { help: "Prefer using inferred types on property definitions (`let foo = Foo()`) rather than explicit types (`let foo: Foo = .init()`).", disabledByDefault: true, orderAfter: ["redundantType"], + options: ["inferredtypes"], sharedOptions: ["redundanttype"] ) { formatter in formatter.forEach(.operator("=", .infix)) { equalsIndex, _ in + // Respect the `.inferLocalsOnly` option if enabled + if formatter.options.redundantType == .inferLocalsOnly, + formatter.declarationScope(at: equalsIndex) != .local + { return } + guard // Parse and validate the LHS of the property declaration. // It should take the form `(let|var) propertyName: (Type) = .staticMember` let introducerIndex = formatter.indexOfLastSignificantKeyword(at: equalsIndex), ["var", "let"].contains(formatter.tokens[introducerIndex].string), - let colonIndex = formatter.index(of: .delimiter(":"), before: equalsIndex), - introducerIndex < colonIndex, - let typeStartIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex), - let type = formatter.parseType(at: typeStartIndex), - // If the RHS starts with a leading dot, then we know its accessing some static member on this type. - let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex), - formatter.tokens[dotIndex] == .operator(".", .prefix) + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + let type = property.type, + let rhsStartIndex = property.value?.expressionRange.lowerBound else { return } - // Respect the `.inferLocalsOnly` option if enabled - if formatter.options.redundantType == .inferLocalsOnly, - formatter.declarationScope(at: equalsIndex) != .local - { return } - let typeTokens = formatter.tokens[type.range] - // Insert a copy of the type on the RHS before the dot - formatter.insert(typeTokens, at: dotIndex) + // If the RHS starts with a leading dot, then we know its accessing some static member on this type. + if formatter.tokens[rhsStartIndex].isOperator(".") { + // Insert a copy of the type on the RHS before the dot + formatter.insert(typeTokens, at: rhsStartIndex) + } + + // If the RHS is an if/switch expression, check that each branch starts with a leading dot + else if formatter.options.inferredTypesInConditionalExpressions, + ["if", "switch"].contains(formatter.tokens[rhsStartIndex].string), + let conditonalBranches = formatter.conditionalBranches(at: rhsStartIndex) + { + var hasInvalidConditionalBranch = false + formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in + guard let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { + hasInvalidConditionalBranch = true + return + } + + if !formatter.tokens[firstTokenInBranch].isOperator(".") { + hasInvalidConditionalBranch = true + } + } + + guard !hasInvalidConditionalBranch else { return } + + // Insert a copy of the type on the RHS before the dot in each branch + formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in + guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return } + + formatter.insert(typeTokens, at: dotIndex) + } + } + + else { + return + } // Remove the colon and explicit type before the equals token - formatter.removeTokens(in: colonIndex ... type.range.upperBound) + formatter.removeTokens(in: type.colonIndex ... type.range.upperBound) } } } diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index ab1f5f152..ad9e083dd 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -4965,19 +4965,19 @@ class SyntaxTests: RulesTests { """ let output = """ - let foo = Foo.init() + let foo = Foo() let bar = Bar.staticBar let baaz = Baaz.Example.default let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - let dictionary = [Foo: Bar].init() - let array = [Foo].init() - let genericType = MyGenericType.init() - let optional = String?.init("Foo") + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + let optional = String?("Foo") """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.preferInferredTypes, options: options, exclude: ["redundantInit"]) + testFormatting(for: input, [output], rules: [FormatRules.preferInferredTypes, FormatRules.redundantInit], options: options) } func testPreservesExplicitTypeIfNoRHS() { @@ -5047,12 +5047,88 @@ class SyntaxTests: RulesTests { let foo: Foo = .init() func bar() { - let baaz = Baaz.init() - let baaz = Baaz.init() + let baaz = Baaz() + let baaz = Baaz() } """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.preferInferredTypes], options: options, exclude: ["redundantInit"]) + testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.preferInferredTypes, FormatRules.redundantInit], options: options) + } + + func testPreferInferredTypesWithIfExpressionDisabledByDefault() { + let input = """ + let foo: SomeTypeWithALongGenrericName = + if condition { + .init(bar) + } else { + .init(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + } + + func testPreferInferredTypesWithIfExpression() { + let input = """ + let foo: Foo = + if condition { + .init(bar) + } else { + .init(baaz) + } + """ + + let output = """ + let foo = + if condition { + Foo(bar) + } else { + Foo(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, [output], rules: [FormatRules.preferInferredTypes, FormatRules.redundantInit], options: options) + } + + func testPreferInferredTypesWithSwitchExpression() { + let input = """ + let foo: Foo = + switch condition { + case true: + .init(bar) + case false: + .init(baaz) + } + """ + + let output = """ + let foo = + switch condition { + case true: + Foo(bar) + case false: + Foo(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, [output], rules: [FormatRules.preferInferredTypes, FormatRules.redundantInit], options: options) + } + + func testPreservesNonMatchingIfExpression() { + let input = """ + let foo: Foo = + if condition { + .init(bar) + } else { + [] // e.g. using ExpressibleByArrayLiteral + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) } } From 0bf5531b968d47dd6f205ee6e1e359631e72d62a Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 28 Mar 2024 10:47:37 -0700 Subject: [PATCH 05/52] Fix issue where preferInferredTypes would cause build failure if property has optional type --- README.md | 2 ++ Sources/Rules.swift | 14 ++++++++++++++ Tests/RulesTests+Syntax.swift | 27 +++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 652243ef8..d98d61837 100644 --- a/README.md +++ b/README.md @@ -953,6 +953,8 @@ Known issues * If compiling for macOS with Xcode 14.0 and configuring SwiftFormat with `--swift-version 5.7`, the `genericExtensions` rule may cause a build failure by updating extensions of the format `extension Collection where Element == Foo` to `extension Collection`. This fails to compile for macOS in Xcode 14.0, because the macOS SDK in that version of Xcode [does not include](https://forums.swift.org/t/xcode-14-rc-cannot-specialize-protocol-type/60171) the Swift 5.7 standard library. Workarounds include using `--swift-version 5.6` instead, updating to Xcode 14.1+, or disabling the `genericExtensions` rule (e.g. with `// swiftformat:next:disable genericExtensions`). +* The `preferInferredTypes` rule can cause a build failure in cases where there are multiple static overloads with the same name but different return types. + Tip Jar ----------- diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 453c51e84..594b0d099 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -8094,8 +8094,18 @@ public struct _FormatRules { let typeTokens = formatter.tokens[type.range] + // Preserve the existing formatting if the LHS type is optional. + // - `let foo: Foo? = .foo` is valid, but `let foo = Foo?.foo` + // is invalid if `.foo` is defined on `Foo` but not `Foo?`. + guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { + return + } + // If the RHS starts with a leading dot, then we know its accessing some static member on this type. if formatter.tokens[rhsStartIndex].isOperator(".") { + // Update the . token from a prefix operator to an infix operator. + formatter.replaceToken(at: rhsStartIndex, with: .operator(".", .infix)) + // Insert a copy of the type on the RHS before the dot formatter.insert(typeTokens, at: rhsStartIndex) } @@ -8123,6 +8133,10 @@ public struct _FormatRules { formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return } + // Update the . token from a prefix operator to an infix operator. + formatter.replaceToken(at: dotIndex, with: .operator(".", .infix)) + + // Insert a copy of the type on the RHS before the dot formatter.insert(typeTokens, at: dotIndex) } } diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index ad9e083dd..203c394c9 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -4961,7 +4961,6 @@ class SyntaxTests: RulesTests { let dictionary: [Foo: Bar] = .init() let array: [Foo] = .init() let genericType: MyGenericType = .init() - let optional: String? = .init("Foo") """ let output = """ @@ -4973,7 +4972,6 @@ class SyntaxTests: RulesTests { let dictionary = [Foo: Bar]() let array = [Foo]() let genericType = MyGenericType() - let optional = String?("Foo") """ let options = FormatOptions(redundantType: .inferred) @@ -5131,4 +5129,29 @@ class SyntaxTests: RulesTests { let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) } + + func testPreservesExplicitOptionalType() { + // `let foo = Foo?.foo` doesn't work if `.foo` is defined on `Foo` but not `Foo?` + let input = """ + let optionalFoo1: Foo? = .foo + let optionalFoo2: Foo? = Foo.foo + let optionalFoo3: Foo! = .foo + let optionalFoo4: Foo! = Foo.foo + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + } + + func testPreservesTypeWithSeparateDeclarationAndProperty() { + let input = """ + var foo: Foo! + foo = Foo(afterDelay: { + print(foo) + }) + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + } } From 08d52d5784ff118ff5d0e31e2edada297c79840d Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 28 Mar 2024 13:31:25 -0700 Subject: [PATCH 06/52] Fix issue where preferInferredTypes could cause a build failure if the property's type is an existential, or if the RHS value has an infix operator --- README.md | 4 +++- Sources/Rules.swift | 20 ++++++++++++++++-- Tests/RulesTests+Syntax.swift | 39 +++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d98d61837..ebb2800e7 100644 --- a/README.md +++ b/README.md @@ -953,7 +953,9 @@ Known issues * If compiling for macOS with Xcode 14.0 and configuring SwiftFormat with `--swift-version 5.7`, the `genericExtensions` rule may cause a build failure by updating extensions of the format `extension Collection where Element == Foo` to `extension Collection`. This fails to compile for macOS in Xcode 14.0, because the macOS SDK in that version of Xcode [does not include](https://forums.swift.org/t/xcode-14-rc-cannot-specialize-protocol-type/60171) the Swift 5.7 standard library. Workarounds include using `--swift-version 5.6` instead, updating to Xcode 14.1+, or disabling the `genericExtensions` rule (e.g. with `// swiftformat:next:disable genericExtensions`). -* The `preferInferredTypes` rule can cause a build failure in cases where there are multiple static overloads with the same name but different return types. +* The `preferInferredTypes` rule can cause a build failure in cases where there are multiple static overloads with the same name but different return types. As a workaround, disable the rule with `// swiftformat:next:disable preferInferredTypes` or rename the overloads to no longer conflict. + +* The `preferInferredTypes` rule can cause a build failure in cases where the property's type is a protocol / existential like `let shapeStyle: ShapeStyle = .myShapeStyle`, and the value used on the right-hand side is defined in an extension like `extension ShapeStyle where Self == MyShapeStyle { static var myShapeStyle: MyShapeStyle { ... } }`. As a workaround you can use the existential `any` syntax (`let shapeStyle: any ShapeStyle = .myShapeStyle`), which the rule will preserve as-is. Tip Jar diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 594b0d099..88f87fce2 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -8089,15 +8089,31 @@ public struct _FormatRules { ["var", "let"].contains(formatter.tokens[introducerIndex].string), let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), let type = property.type, - let rhsStartIndex = property.value?.expressionRange.lowerBound + let rhsExpressionRange = property.value?.expressionRange else { return } + let rhsStartIndex = rhsExpressionRange.lowerBound let typeTokens = formatter.tokens[type.range] // Preserve the existing formatting if the LHS type is optional. // - `let foo: Foo? = .foo` is valid, but `let foo = Foo?.foo` // is invalid if `.foo` is defined on `Foo` but not `Foo?`. - guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { + guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { return } + + // Preserve the existing formatting if the LHS type is an existential (indicated with `any`). + // - The `extension MyProtocol where Self == MyType { ... }` syntax + // creates static members where `let foo: any MyProtocol = .myType` + // is valid, but `let foo = (any MyProtocol).myType` isn't. + guard typeTokens.first?.string != "any" else { return } + + // Preserve the existing formatting if the RHS expression has a top-level infix operator. + // - `let value: ClosedRange = .zero ... 10` would not be valid to convert to + // `let value = ClosedRange.zero ... 10`. + if let nextInfixOperatorIndex = formatter.index(after: rhsStartIndex, where: { token in + token.isOperator(ofType: .infix) && token != .operator(".", .infix) + }), + rhsExpressionRange.contains(nextInfixOperatorIndex) + { return } diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index 203c394c9..e2bb8d4aa 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -5154,4 +5154,43 @@ class SyntaxTests: RulesTests { let options = FormatOptions(redundantType: .inferred) testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) } + + func testPreservesTypeWithExistentialAny() { + let input = """ + protocol ShapeStyle {} + struct MyShapeStyle: ShapeStyle {} + + extension ShapeStyle where Self == MyShapeStyle { + static var myShape: MyShapeStyle { MyShapeStyle() } + } + + /// This compiles + let myShape1: any ShapeStyle = .myShape + + // This would fail with "error: static member 'myShape' cannot be used on protocol metatype '(any ShapeStyle).Type'" + // let myShape2 = (any ShapeStyle).myShape + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + } + + func testPreservesRightHandSideWithOperator() { + let input = """ + let value: ClosedRange = .zero ... 10 + let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge + let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() + let dynamicTypeSizeRange: ClosedRange = .convertFromLiteral(.large ... .xxxLarge) + """ + + let output = """ + let value: ClosedRange = .zero ... 10 + let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge + let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() + let dynamicTypeSizeRange = ClosedRange.convertFromLiteral(.large ... .xxxLarge) + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: FormatRules.preferInferredTypes, options: options) + } } From 19e2685188100c1395e273be6d8baf0830c14b13 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Fri, 29 Mar 2024 12:39:40 -0700 Subject: [PATCH 07/52] Update preferInferedTypes rule to propertyType, add support for explicit option --- README.md | 8 +- Rules.md | 43 ++-- Sources/Examples.swift | 2 +- Sources/OptionDescriptor.swift | 6 + Sources/Options.swift | 3 + Sources/ParsingHelpers.swift | 50 +++- Sources/Rules.swift | 249 +++++++++++++------ Tests/ParsingHelpersTests.swift | 55 +++++ Tests/RulesTests+General.swift | 6 +- Tests/RulesTests+Indentation.swift | 24 +- Tests/RulesTests+Organization.swift | 7 +- Tests/RulesTests+Parens.swift | 4 +- Tests/RulesTests+Redundancy.swift | 119 +++++---- Tests/RulesTests+Syntax.swift | 367 +++++++++++++++++++++++++--- Tests/RulesTests+Wrapping.swift | 31 +-- 15 files changed, 750 insertions(+), 224 deletions(-) diff --git a/README.md b/README.md index ebb2800e7..072c126b4 100644 --- a/README.md +++ b/README.md @@ -951,11 +951,13 @@ Known issues * If you have a generic typealias that defines a closure (e.g. `typealias ResultCompletion = (Result) -> Void`) and use this closure as an argument in a generic function (e.g. `func handle(_ completion: ResultCompletion)`), the `opaqueGenericParameters` rule may update the function definition to use `some` syntax (e.g. `func handle(_ completion: ResultCompletion)`). `some` syntax is not permitted in closure parameters, so this will no longer compile. Workarounds include spelling out the closure explicitly in the generic function (instead of using a `typealias`) or disabling the `opaqueGenericParameters` rule (e.g. with `// swiftformat:disable:next opaqueGenericParameters`). -* If compiling for macOS with Xcode 14.0 and configuring SwiftFormat with `--swift-version 5.7`, the `genericExtensions` rule may cause a build failure by updating extensions of the format `extension Collection where Element == Foo` to `extension Collection`. This fails to compile for macOS in Xcode 14.0, because the macOS SDK in that version of Xcode [does not include](https://forums.swift.org/t/xcode-14-rc-cannot-specialize-protocol-type/60171) the Swift 5.7 standard library. Workarounds include using `--swift-version 5.6` instead, updating to Xcode 14.1+, or disabling the `genericExtensions` rule (e.g. with `// swiftformat:next:disable genericExtensions`). +* If compiling for macOS with Xcode 14.0 and configuring SwiftFormat with `--swift-version 5.7`, the `genericExtensions` rule may cause a build failure by updating extensions of the format `extension Collection where Element == Foo` to `extension Collection`. This fails to compile for macOS in Xcode 14.0, because the macOS SDK in that version of Xcode [does not include](https://forums.swift.org/t/xcode-14-rc-cannot-specialize-protocol-type/60171) the Swift 5.7 standard library. Workarounds include using `--swift-version 5.6` instead, updating to Xcode 14.1+, or disabling the `genericExtensions` rule (e.g. with `// swiftformat:disable:next genericExtensions`). -* The `preferInferredTypes` rule can cause a build failure in cases where there are multiple static overloads with the same name but different return types. As a workaround, disable the rule with `// swiftformat:next:disable preferInferredTypes` or rename the overloads to no longer conflict. +* The `propertyType` rule can cause a build failure in cases where there are multiple static overloads with the same name but different return types. As a workaround you can rename the overloads to no longer conflict, or exclude the property name with `--preservesymbols propertyName,otherPropertyName,etc`. -* The `preferInferredTypes` rule can cause a build failure in cases where the property's type is a protocol / existential like `let shapeStyle: ShapeStyle = .myShapeStyle`, and the value used on the right-hand side is defined in an extension like `extension ShapeStyle where Self == MyShapeStyle { static var myShapeStyle: MyShapeStyle { ... } }`. As a workaround you can use the existential `any` syntax (`let shapeStyle: any ShapeStyle = .myShapeStyle`), which the rule will preserve as-is. +* The `propertyType` rule can cause a build failure in cases where the property's type is a protocol / existential like `let shapeStyle: ShapeStyle = .myShapeStyle`, and the value used on the right-hand side is defined in an extension like `extension ShapeStyle where Self == MyShapeStyle { static var myShapeStyle: MyShapeStyle { ... } }`. As a workaround you can use the existential `any` syntax (`let shapeStyle: any ShapeStyle = .myShapeStyle`), which the rule will preserve as-is, or exclude the type name and/or property name with `--preservesymbols ShapeStyle,myShapeStyle,etc`. + +* The `propertyType` rule can cause a build failure in cases like `let foo = Foo.bar` where the value is a static member that doesn't return the same time. For example, `let foo: Foo = .bar` would be invalid if the `bar` property was defined as `static var bar: Bar`. As a workaround you can write the name of the type explicitly, like `let foo: Bar = Foo.bar`, or exclude the type name and/or property name with `--preservesymbols Bar,bar,etc`. Tip Jar diff --git a/Rules.md b/Rules.md index e166e1411..1f337269e 100644 --- a/Rules.md +++ b/Rules.md @@ -101,7 +101,7 @@ * [markTypes](#markTypes) * [noExplicitOwnership](#noExplicitOwnership) * [organizeDeclarations](#organizeDeclarations) -* [preferInferredTypes](#preferInferredTypes) +* [propertyType](#propertyType) * [redundantProperty](#redundantProperty) * [sortSwitchCases](#sortSwitchCases) * [wrapConditionalBodies](#wrapConditionalBodies) @@ -1504,13 +1504,32 @@ Option | Description
-## preferInferredTypes +## preferKeyPath + +Convert trivial `map { $0.foo }` closures to keyPath-based syntax. + +
+Examples + +```diff +- let barArray = fooArray.map { $0.bar } ++ let barArray = fooArray.map(\.bar) + +- let barArray = fooArray.compactMap { $0.optionalBar } ++ let barArray = fooArray.compactMap(\.optionalBar) +``` + +
+
+ +## propertyType -Prefer using inferred types on property definitions (`let foo = Foo()`) rather than explicit types (`let foo: Foo = .init()`). +Convert property declarations to use inferred types (`let foo = Foo()`) or explicit types (`let foo: Foo = .init()`). Option | Description --- | --- `--inferredtypes` | "exclude-cond-exprs" (default) or "always" +`--preservesymbols` | Comma-delimited list of symbol names to preserve
Examples @@ -1544,24 +1563,6 @@ Option | Description

-## preferKeyPath - -Convert trivial `map { $0.foo }` closures to keyPath-based syntax. - -
-Examples - -```diff -- let barArray = fooArray.map { $0.bar } -+ let barArray = fooArray.map(\.bar) - -- let barArray = fooArray.compactMap { $0.optionalBar } -+ let barArray = fooArray.compactMap(\.optionalBar) -``` - -
-
- ## redundantBackticks Remove redundant backticks around identifiers. diff --git a/Sources/Examples.swift b/Sources/Examples.swift index ae238170d..72849ae37 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1924,7 +1924,7 @@ private struct Examples { ``` """ - let preferInferredTypes = """ + let propertyType = """ ```diff - let foo: Foo = .init() + let foo: Foo = .init() diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index 5a7eb416f..0fcf8c5a3 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -1074,6 +1074,12 @@ struct _Descriptors { help: "The version of Swift used in the files being formatted", keyPath: \.swiftVersion ) + let preserveSymbols = OptionDescriptor( + argumentName: "preservesymbols", + displayName: "Preserve Symbols", + help: "Comma-delimited list of symbol names to preserve", + keyPath: \.preserveSymbols + ) // MARK: - DEPRECATED diff --git a/Sources/Options.swift b/Sources/Options.swift index d35e1ff23..eb99ae550 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -670,6 +670,7 @@ public struct FormatOptions: CustomStringConvertible { public var yodaSwap: YodaMode public var extensionACLPlacement: ExtensionACLPlacement public var redundantType: RedundantType + public var preserveSymbols: Set public var inferredTypesInConditionalExpressions: Bool public var emptyBracesSpacing: EmptyBracesSpacing public var acronyms: Set @@ -787,6 +788,7 @@ public struct FormatOptions: CustomStringConvertible { yodaSwap: YodaMode = .always, extensionACLPlacement: ExtensionACLPlacement = .onExtension, redundantType: RedundantType = .inferLocalsOnly, + preserveSymbols: Set = [], inferredTypesInConditionalExpressions: Bool = false, emptyBracesSpacing: EmptyBracesSpacing = .noSpace, acronyms: Set = ["ID", "URL", "UUID"], @@ -894,6 +896,7 @@ public struct FormatOptions: CustomStringConvertible { self.yodaSwap = yodaSwap self.extensionACLPlacement = extensionACLPlacement self.redundantType = redundantType + self.preserveSymbols = preserveSymbols self.inferredTypesInConditionalExpressions = inferredTypesInConditionalExpressions self.emptyBracesSpacing = emptyBracesSpacing self.acronyms = acronyms diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index abb22c32c..5ce86d1ea 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -1314,8 +1314,13 @@ extension Formatter { /// - `borrowing ...` /// - `consuming ...` /// - `(type).(type)` - func parseType(at startOfTypeIndex: Int) -> (name: String, range: ClosedRange)? { - guard let baseType = parseNonOptionalType(at: startOfTypeIndex) else { return nil } + func parseType( + at startOfTypeIndex: Int, + excludeLowercaseIdentifiers: Bool = false + ) + -> (name: String, range: ClosedRange)? + { + guard let baseType = parseNonOptionalType(at: startOfTypeIndex, excludeLowercaseIdentifiers: excludeLowercaseIdentifiers) else { return nil } // Any type can be optional, so check for a trailing `?` or `!`. // There cannot be any other tokens between the type and the operator: @@ -1336,7 +1341,7 @@ extension Formatter { if let nextTokenIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: baseType.range.upperBound), tokens[nextTokenIndex] == .operator(".", .infix), let followingToken = index(of: .nonSpaceOrCommentOrLinebreak, after: nextTokenIndex), - let followingType = parseType(at: followingToken) + let followingType = parseType(at: followingToken, excludeLowercaseIdentifiers: excludeLowercaseIdentifiers) { let typeRange = startOfTypeIndex ... followingType.range.upperBound return (name: tokens[typeRange].string, range: typeRange) @@ -1345,10 +1350,33 @@ extension Formatter { return baseType } - private func parseNonOptionalType(at startOfTypeIndex: Int) -> (name: String, range: ClosedRange)? { - // Parse types of the form `[...]` + private func parseNonOptionalType( + at startOfTypeIndex: Int, + excludeLowercaseIdentifiers: Bool + ) + -> (name: String, range: ClosedRange)? + { let startToken = tokens[startOfTypeIndex] + + // Parse types of the form `[...]` if startToken == .startOfScope("["), let endOfScope = endOfScope(at: startOfTypeIndex) { + // Validate that the inner type is also valid + guard let innerTypeStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: startOfTypeIndex), + let innerType = parseType(at: innerTypeStartIndex, excludeLowercaseIdentifiers: excludeLowercaseIdentifiers), + let indexAfterType = index(of: .nonSpaceOrCommentOrLinebreak, after: innerType.range.upperBound) + else { return nil } + + // This is either an array type of the form `[Element]`, + // or a dictionary type of the form `[Key: Value]`. + if indexAfterType != endOfScope { + guard tokens[indexAfterType] == .delimiter(":"), + let secondTypeIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterType), + let secondType = parseType(at: secondTypeIndex, excludeLowercaseIdentifiers: excludeLowercaseIdentifiers), + let indexAfterSecondType = index(of: .nonSpaceOrCommentOrLinebreak, after: secondType.range.upperBound), + indexAfterSecondType == endOfScope + else { return nil } + } + let typeRange = startOfTypeIndex ... endOfScope return (name: tokens[typeRange].string, range: typeRange) } @@ -1390,6 +1418,14 @@ extension Formatter { // Otherwise this is just a single identifier if startToken.isIdentifier || startToken.isKeywordOrAttribute, startToken != .identifier("init") { + let firstCharacter = startToken.string.first.flatMap(String.init) ?? "" + let isLowercaseIdentifier = firstCharacter.uppercased() != firstCharacter + + guard !(excludeLowercaseIdentifiers && isLowercaseIdentifier), + // Don't parse macro invocations or `#selector` as a type. + !["#"].contains(firstCharacter) + else { return nil } + return (name: startToken.string, range: startOfTypeIndex ... startOfTypeIndex) } @@ -2178,7 +2214,7 @@ extension Formatter { case type /// The declaration is within some local scope, - /// like a function body. + /// like a function body or closure. case local } @@ -2189,7 +2225,7 @@ extension Formatter { let typeDeclarations = Set(["class", "actor", "struct", "enum", "extension"]) // Declarations which have `DeclarationScope.local` - let localDeclarations = Set(["let", "var", "func", "subscript", "init", "deinit"]) + let localDeclarations = Set(["let", "var", "func", "subscript", "init", "deinit", "get", "set", "willSet", "didSet"]) let allDeclarationScopes = typeDeclarations.union(localDeclarations) diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 88f87fce2..3ce01a92e 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -783,9 +783,7 @@ public struct _FormatRules { isInferred = true declarationKeywordIndex = nil case .explicit: - // If the `preferInferredTypes` rule is also enabled, it takes precedence - // over the `--redundanttype explicit` option. - isInferred = formatter.options.enabledRules.contains("preferInferredTypes") + isInferred = false declarationKeywordIndex = formatter.declarationIndexAndScope(at: equalsIndex).index case .inferLocalsOnly: let (index, scope) = formatter.declarationIndexAndScope(at: equalsIndex) @@ -4463,7 +4461,7 @@ public struct _FormatRules { /// Strip redundant `.init` from type instantiations public let redundantInit = FormatRule( help: "Remove explicit `init` if not required.", - orderAfter: ["preferInferredTypes"] + orderAfter: ["propertyType"] ) { formatter in formatter.forEach(.identifier("init")) { initIndex, _ in guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: initIndex, if: { @@ -8003,7 +8001,7 @@ public struct _FormatRules { public let redundantProperty = FormatRule( help: "Simplifies redundant property definitions that are immediately returned.", disabledByDefault: true, - orderAfter: ["preferInferredTypes"] + orderAfter: ["propertyType"] ) { formatter in formatter.forEach(.keyword) { introducerIndex, introducerToken in // Find properties like `let identifier = value` followed by `return identifier` @@ -8070,99 +8068,200 @@ public struct _FormatRules { } } - public let preferInferredTypes = FormatRule( - help: "Prefer using inferred types on property definitions (`let foo = Foo()`) rather than explicit types (`let foo: Foo = .init()`).", + public let propertyType = FormatRule( + help: "Convert property declarations to use inferred types (`let foo = Foo()`) or explicit types (`let foo: Foo = .init()`).", disabledByDefault: true, orderAfter: ["redundantType"], - options: ["inferredtypes"], + options: ["inferredtypes", "preservesymbols"], sharedOptions: ["redundanttype"] ) { formatter in formatter.forEach(.operator("=", .infix)) { equalsIndex, _ in - // Respect the `.inferLocalsOnly` option if enabled - if formatter.options.redundantType == .inferLocalsOnly, - formatter.declarationScope(at: equalsIndex) != .local - { return } - - guard // Parse and validate the LHS of the property declaration. - // It should take the form `(let|var) propertyName: (Type) = .staticMember` - let introducerIndex = formatter.indexOfLastSignificantKeyword(at: equalsIndex), - ["var", "let"].contains(formatter.tokens[introducerIndex].string), - let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), - let type = property.type, - let rhsExpressionRange = property.value?.expressionRange - else { return } + // Preserve all properties in conditional statements like `if let foo = Bar() { ... }` + guard !formatter.isConditionalStatement(at: equalsIndex) else { return } - let rhsStartIndex = rhsExpressionRange.lowerBound - let typeTokens = formatter.tokens[type.range] - - // Preserve the existing formatting if the LHS type is optional. - // - `let foo: Foo? = .foo` is valid, but `let foo = Foo?.foo` - // is invalid if `.foo` is defined on `Foo` but not `Foo?`. - guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { return } - - // Preserve the existing formatting if the LHS type is an existential (indicated with `any`). - // - The `extension MyProtocol where Self == MyType { ... }` syntax - // creates static members where `let foo: any MyProtocol = .myType` - // is valid, but `let foo = (any MyProtocol).myType` isn't. - guard typeTokens.first?.string != "any" else { return } - - // Preserve the existing formatting if the RHS expression has a top-level infix operator. - // - `let value: ClosedRange = .zero ... 10` would not be valid to convert to - // `let value = ClosedRange.zero ... 10`. - if let nextInfixOperatorIndex = formatter.index(after: rhsStartIndex, where: { token in - token.isOperator(ofType: .infix) && token != .operator(".", .infix) - }), - rhsExpressionRange.contains(nextInfixOperatorIndex) - { - return - } + // Determine whether the type should use the inferred syntax (`let foo = Foo()`) + // of the explicit syntax (`let foo: Foo = .init()`). + let useInferredType: Bool + switch formatter.options.redundantType { + case .inferred: + useInferredType = true - // If the RHS starts with a leading dot, then we know its accessing some static member on this type. - if formatter.tokens[rhsStartIndex].isOperator(".") { - // Update the . token from a prefix operator to an infix operator. - formatter.replaceToken(at: rhsStartIndex, with: .operator(".", .infix)) + case .explicit: + useInferredType = false - // Insert a copy of the type on the RHS before the dot - formatter.insert(typeTokens, at: rhsStartIndex) + case .inferLocalsOnly: + switch formatter.declarationScope(at: equalsIndex) { + case .global, .type: + useInferredType = false + case .local: + useInferredType = true + } } - // If the RHS is an if/switch expression, check that each branch starts with a leading dot - else if formatter.options.inferredTypesInConditionalExpressions, - ["if", "switch"].contains(formatter.tokens[rhsStartIndex].string), - let conditonalBranches = formatter.conditionalBranches(at: rhsStartIndex) - { - var hasInvalidConditionalBranch = false - formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in - guard let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { - hasInvalidConditionalBranch = true - return - } + guard let introducerIndex = formatter.indexOfLastSignificantKeyword(at: equalsIndex), + ["var", "let"].contains(formatter.tokens[introducerIndex].string), + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + let rhsExpressionRange = property.value?.expressionRange + else { return } - if !formatter.tokens[firstTokenInBranch].isOperator(".") { - hasInvalidConditionalBranch = true - } + let rhsStartIndex = rhsExpressionRange.lowerBound + + if useInferredType { + guard let type = property.type else { return } + let typeTokens = formatter.tokens[type.range] + + // Preserve the existing formatting if the LHS type is optional. + // - `let foo: Foo? = .foo` is valid, but `let foo = Foo?.foo` + // is invalid if `.foo` is defined on `Foo` but not `Foo?`. + guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { return } + + // Preserve the existing formatting if the LHS type is an existential (indicated with `any`). + // - The `extension MyProtocol where Self == MyType { ... }` syntax + // creates static members where `let foo: any MyProtocol = .myType` + // is valid, but `let foo = (any MyProtocol).myType` isn't. + guard typeTokens.first?.string != "any" else { return } + + // Preserve the existing formatting if the RHS expression has a top-level infix operator. + // - `let value: ClosedRange = .zero ... 10` would not be valid to convert to + // `let value = ClosedRange.zero ... 10`. + if let nextInfixOperatorIndex = formatter.index(after: rhsStartIndex, where: { token in + token.isOperator(ofType: .infix) && token != .operator(".", .infix) + }), + rhsExpressionRange.contains(nextInfixOperatorIndex) + { + return } - guard !hasInvalidConditionalBranch else { return } + // Preserve the formatting as-is if the type is manually excluded + if formatter.options.preserveSymbols.contains(type.name) { + return + } - // Insert a copy of the type on the RHS before the dot in each branch - formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in - guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return } + // If the RHS starts with a leading dot, then we know its accessing some static member on this type. + if formatter.tokens[rhsStartIndex].isOperator(".") { + // Preserve the formatting as-is if the identifier is manually excluded + if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), + formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) + { return } // Update the . token from a prefix operator to an infix operator. - formatter.replaceToken(at: dotIndex, with: .operator(".", .infix)) + formatter.replaceToken(at: rhsStartIndex, with: .operator(".", .infix)) // Insert a copy of the type on the RHS before the dot - formatter.insert(typeTokens, at: dotIndex) + formatter.insert(typeTokens, at: rhsStartIndex) } + + // If the RHS is an if/switch expression, check that each branch starts with a leading dot + else if formatter.options.inferredTypesInConditionalExpressions, + ["if", "switch"].contains(formatter.tokens[rhsStartIndex].string), + let conditonalBranches = formatter.conditionalBranches(at: rhsStartIndex) + { + var hasInvalidConditionalBranch = false + formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in + guard let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { + hasInvalidConditionalBranch = true + return + } + + if !formatter.tokens[firstTokenInBranch].isOperator(".") { + hasInvalidConditionalBranch = true + } + + // Preserve the formatting as-is if the identifier is manually excluded + if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), + formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) + { + hasInvalidConditionalBranch = true + } + } + + guard !hasInvalidConditionalBranch else { return } + + // Insert a copy of the type on the RHS before the dot in each branch + formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in + guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return } + + // Update the . token from a prefix operator to an infix operator. + formatter.replaceToken(at: dotIndex, with: .operator(".", .infix)) + + // Insert a copy of the type on the RHS before the dot + formatter.insert(typeTokens, at: dotIndex) + } + } + + else { + return + } + + // Remove the colon and explicit type before the equals token + formatter.removeTokens(in: type.colonIndex ... type.range.upperBound) } + // If using explicit types, convert properties to the format `let foo: Foo = .init()`. else { - return - } + guard // When parsing the type, exclude lowercase identifiers so `foo` isn't parsed as a type, + // and so `Foo.init` is parsed as `Foo` instead of `Foo.init`. + let rhsType = formatter.parseType(at: rhsStartIndex, excludeLowercaseIdentifiers: true), + property.type == nil, + let indexAfterIdentifier = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: property.identifierIndex), + formatter.tokens[indexAfterIdentifier] != .delimiter(":"), + let indexAfterType = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsType.range.upperBound), + [.operator(".", .infix), .startOfScope("(")].contains(formatter.tokens[indexAfterType]), + !rhsType.name.contains(".") + else { return } - // Remove the colon and explicit type before the equals token - formatter.removeTokens(in: type.colonIndex ... type.range.upperBound) + // Preserve the existing formatting if the RHS expression has a top-level operator. + // - `let foo = Foo.foo.bar` would not be valid to convert to `let foo: Foo = .foo.bar`. + let operatorSearchIndex = formatter.tokens[indexAfterType].isStartOfScope ? (indexAfterType - 1) : indexAfterType + if let nextInfixOperatorIndex = formatter.index(after: operatorSearchIndex, where: { token in + token.isOperator(ofType: .infix) + }), + rhsExpressionRange.contains(nextInfixOperatorIndex) + { + return + } + + // Preserve any types that have been manually excluded. + // Preserve any `Void` types and tuples, since they're special and don't support things like `.init` + guard !(formatter.options.preserveSymbols + ["Void"]).contains(rhsType.name), + !rhsType.name.hasPrefix("(") + else { return } + + // A type name followed by a `(` is an implicit `.init(`. Insert a `.init` + // so that the init call stays valid after we move the type to the LHS. + if formatter.tokens[indexAfterType] == .startOfScope("(") { + // Preserve the existing format if `init` is manually excluded + if formatter.options.preserveSymbols.contains("init") { + return + } + + formatter.insert([.operator(".", .prefix), .identifier("init")], at: indexAfterType) + } + + // If the type name is followed by an infix `.` operator, convert it to a prefix operator. + else if formatter.tokens[indexAfterType] == .operator(".", .infix) { + // Exclude types with dots followed by a member access. + // - For example with something like `Color.Theme.themeColor`, we don't know + // if the property is `static var themeColor: Color` or `static var themeColor: Color.Theme`. + // - This isn't a problem with something like `Color.Theme()`, which we can reasonably assume + // is an initializer + if rhsType.name.contains(".") { return } + + // Preserve the formatting as-is if the identifier is manually excluded. + // Don't convert `let foo = Foo.self` to `let foo: Foo = .self`, since `.self` returns the metatype + let symbolsToExclude = formatter.options.preserveSymbols + ["self"] + if let indexAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterType), + symbolsToExclude.contains(formatter.tokens[indexAfterDot].string) + { return } + + formatter.replaceToken(at: indexAfterType, with: .operator(".", .prefix)) + } + + // Move the type name to the LHS of the property, followed by a colon + let typeTokens = formatter.tokens[rhsType.range] + formatter.removeTokens(in: rhsType.range) + formatter.insert([.delimiter(":"), .space(" ")] + typeTokens, at: property.identifierIndex + 1) + } } } } diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index e1aa1c34f..834ccef53 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -1620,6 +1620,10 @@ class ParsingHelpersTests: XCTestCase { } let instanceMember3 = Bar() + + let instanceMemberClosure = Foo { + let localMember2 = Bar() + } } """ @@ -1632,6 +1636,8 @@ class ParsingHelpersTests: XCTestCase { XCTAssertEqual(formatter.declarationScope(at: 42), .type) // instanceMethod XCTAssertEqual(formatter.declarationScope(at: 51), .local) // localMember1 XCTAssertEqual(formatter.declarationScope(at: 66), .type) // instanceMember3 + XCTAssertEqual(formatter.declarationScope(at: 78), .type) // instanceMemberClosure + XCTAssertEqual(formatter.declarationScope(at: 89), .local) // localMember2 } // MARK: spaceEquivalentToWidth @@ -1790,6 +1796,48 @@ class ParsingHelpersTests: XCTestCase { XCTAssertEqual(formatter.parseType(at: 0)?.name, "Foo.bar") } + func testDoesntParseMacroInvocationAsType() { + let formatter = Formatter(tokenize(""" + let foo = #colorLiteral(1, 2, 3) + """)) + XCTAssertNil(formatter.parseType(at: 6)) + } + + func testDoesntParseSelectorAsType() { + let formatter = Formatter(tokenize(""" + let foo = #selector(Foo.bar) + """)) + XCTAssertNil(formatter.parseType(at: 6)) + } + + func testDoesntParseArrayAsType() { + let formatter = Formatter(tokenize(""" + let foo = [foo, bar].member() + """)) + XCTAssertNil(formatter.parseType(at: 6)) + } + + func testDoesntParseDictionaryAsType() { + let formatter = Formatter(tokenize(""" + let foo = [foo: bar, baaz: quux].member() + """)) + XCTAssertNil(formatter.parseType(at: 6)) + } + + func testParsesArrayAsType() { + let formatter = Formatter(tokenize(""" + let foo = [Foo]() + """)) + XCTAssertEqual(formatter.parseType(at: 6)?.name, "[Foo]") + } + + func testParsesDictionaryAsType() { + let formatter = Formatter(tokenize(""" + let foo = [Foo: Bar]() + """)) + XCTAssertEqual(formatter.parseType(at: 6)?.name, "[Foo: Bar]") + } + func testParseGenericType() { let formatter = Formatter(tokenize(""" let foo: Foo = .init() @@ -1874,6 +1922,13 @@ class ParsingHelpersTests: XCTestCase { XCTAssertEqual(formatter.parseType(at: 5)?.name, "Foo.Bar.Baaz") } + func testDoesntParseLeadingDotAsType() { + let formatter = Formatter(tokenize(""" + let foo: Foo = .Bar.baaz + """)) + XCTAssertEqual(formatter.parseType(at: 9)?.name, nil) + } + func testParseCompoundGenericType() { let formatter = Formatter(tokenize(""" let foo: Foo.Bar.Baaz diff --git a/Tests/RulesTests+General.swift b/Tests/RulesTests+General.swift index d3f1e2d9e..3c7ab5a9a 100644 --- a/Tests/RulesTests+General.swift +++ b/Tests/RulesTests+General.swift @@ -277,7 +277,7 @@ class GeneralTests: RulesTests { Int ]).self """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) } func testTrailingCommaNotAddedToTypeDeclaration() { @@ -324,7 +324,7 @@ class GeneralTests: RulesTests { String: Int ]]() """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) } func testTrailingCommaNotAddedToTypeDeclaration6() { @@ -337,7 +337,7 @@ class GeneralTests: RulesTests { ]) ]]() """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) } func testTrailingCommaNotAddedToTypeDeclaration7() { diff --git a/Tests/RulesTests+Indentation.swift b/Tests/RulesTests+Indentation.swift index 4d03bb0bb..f25f94b36 100644 --- a/Tests/RulesTests+Indentation.swift +++ b/Tests/RulesTests+Indentation.swift @@ -88,7 +88,7 @@ class IndentTests: RulesTests { paymentFormURL: .paymentForm) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) } func testIndentPreservedForNestedWrappedParameters2() { @@ -99,7 +99,7 @@ class IndentTests: RulesTests { paymentFormURL: .paymentForm)) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) } func testIndentPreservedForNestedWrappedParameters3() { @@ -112,7 +112,7 @@ class IndentTests: RulesTests { ) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) } func testIndentTrailingClosureInParensContainingUnwrappedArguments() { @@ -346,7 +346,7 @@ class IndentTests: RulesTests { return x + y } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) } func testIndentWrappedClosureCaptureListWithUnwrappedParameters() { @@ -373,7 +373,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) } func testIndentAllmanTrailingClosureArguments() { @@ -389,7 +389,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) } func testIndentAllmanTrailingClosureArguments2() { @@ -1190,7 +1190,7 @@ class IndentTests: RulesTests { func testNoIndentAfterDefaultAsIdentifier() { let input = "let foo = FileManager.default\n/// Comment\nlet bar = 0" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) } func testIndentClosureStartingOnIndentedLine() { @@ -1586,7 +1586,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) } func testSingleIndentTrailingClosureBodyThatStartsOnFollowingLine() { @@ -1729,7 +1729,7 @@ class IndentTests: RulesTests { .bar .baz """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) } func testIndentChainedPropertiesAfterFunctionCallWithXcodeIndentation() { @@ -1741,7 +1741,7 @@ class IndentTests: RulesTests { .baz """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) } func testIndentChainedPropertiesAfterFunctionCall2() { @@ -1753,7 +1753,7 @@ class IndentTests: RulesTests { .baz """ testFormatting(for: input, rule: FormatRules.indent, - exclude: ["trailingClosures"]) + exclude: ["trailingClosures", "propertyType"]) } func testIndentChainedPropertiesAfterFunctionCallWithXcodeIndentation2() { @@ -1766,7 +1766,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(xcodeIndentation: true) testFormatting(for: input, rule: FormatRules.indent, options: options, - exclude: ["trailingClosures"]) + exclude: ["trailingClosures", "propertyType"]) } func testIndentChainedMethodsAfterTrailingClosure() { diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index 332044afc..b6019d120 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -1338,7 +1338,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "propertyType"]) } func testOrganizesTypeBelowSymbolImport() { @@ -2018,7 +2018,8 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: FormatRules.extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4") + options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4"), + exclude: ["propertyType"] ) } @@ -3878,7 +3879,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, [output], rules: [FormatRules.sortDeclarations, FormatRules.consecutiveBlankLines], - exclude: ["blankLinesBetweenScopes"]) + exclude: ["blankLinesBetweenScopes", "propertyType"]) } func testSortBetweenDirectiveCommentsInType() { diff --git a/Tests/RulesTests+Parens.swift b/Tests/RulesTests+Parens.swift index 9ff86b5f5..032d90fb1 100644 --- a/Tests/RulesTests+Parens.swift +++ b/Tests/RulesTests+Parens.swift @@ -786,12 +786,12 @@ class ParensTests: RulesTests { func testParensNotRemovedInGenericInstantiation() { let input = "let foo = Foo()" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["propertyType"]) } func testParensNotRemovedInGenericInstantiation2() { let input = "let foo = Foo(bar)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["propertyType"]) } func testRedundantParensRemovedAfterGenerics() { diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index f19390606..f51da998b 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -226,7 +226,7 @@ class RedundancyTests: RulesTests { let kFoo = Foo().foo """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAVar() { @@ -382,7 +382,7 @@ class RedundancyTests: RulesTests { let foo = Foo() """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType2() { @@ -396,7 +396,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateStructMemberNotChangedToPrivateIfConstructorCalledOutsideType() { @@ -408,7 +408,7 @@ class RedundancyTests: RulesTests { let foo = Foo(bar: "test") """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateClassMemberChangedToPrivateEvenIfConstructorCalledOutsideType() { @@ -427,7 +427,7 @@ class RedundancyTests: RulesTests { let foo = Foo() """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateExtensionFuncNotChangedToPrivateIfPartOfProtocolConformance() { @@ -531,7 +531,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateInitNotChangedToPrivateWhenUsingTrailingClosureInit() { @@ -829,7 +829,7 @@ class RedundancyTests: RulesTests { let Foo = Foo.self let foo = Foo.init() """ - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["propertyType"]) } func testNoRemoveInitForLocalLetType2() { @@ -885,7 +885,7 @@ class RedundancyTests: RulesTests { let tupleArray = [(key: String, value: Int)]() let dictionary = [String: Int]() """ - testFormatting(for: input, output, rule: FormatRules.redundantInit) + testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["propertyType"]) } func testPreservesInitAfterTypeOfCall() { @@ -906,7 +906,7 @@ class RedundancyTests: RulesTests { // (String!.init("Foo") isn't valid Swift code, so we don't test for it) """ - testFormatting(for: input, output, rule: FormatRules.redundantInit) + testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["propertyType"]) } func testPreservesTryBeforeInit() { @@ -931,7 +931,7 @@ class RedundancyTests: RulesTests { let atomicDictionary = Atomic<[String: Int]>() """ - testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["typeSugar", "propertyType"]) } func testPreserveNonRedundantInitInTernaryOperator() { @@ -1406,7 +1406,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "preferInferredTypes"]) + testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) } func testRedundantTypeWithNestedIfExpression_inferred() { @@ -1484,7 +1484,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "preferInferredTypes"]) + testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) } func testRedundantTypeWithLiteralsInIfExpression() { @@ -1513,7 +1513,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testVarRedundantTypeRemovalExplicitType2() { @@ -1521,7 +1521,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = .init /* foo */()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["spaceAroundComments", "preferInferredTypes"]) + options: options, exclude: ["spaceAroundComments", "propertyType"]) } func testLetRedundantGenericTypeRemovalExplicitType() { @@ -1529,7 +1529,7 @@ class RedundancyTests: RulesTests { let output = "let relay: BehaviourRelay = .init(value: nil)" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testLetRedundantGenericTypeRemovalExplicitTypeIfValueOnNextLine() { @@ -1537,7 +1537,7 @@ class RedundancyTests: RulesTests { let output = "let relay: Foo = \n .default" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["trailingSpace", "preferInferredTypes"]) + options: options, exclude: ["trailingSpace", "propertyType"]) } func testVarNonRedundantTypeDoesNothingExplicitType() { @@ -1551,7 +1551,7 @@ class RedundancyTests: RulesTests { let output = "let view: UIView = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeRemovedIfValueOnNextLineExplicitType() { @@ -1565,7 +1565,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeRemovedIfValueOnNextLine2ExplicitType() { @@ -1579,7 +1579,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeRemovalWithCommentExplicitType() { @@ -1587,7 +1587,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView /* view */ = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeRemovalWithComment2ExplicitType() { @@ -1595,7 +1595,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = /* view */ .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeRemovalWithStaticMember() { @@ -1617,7 +1617,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeRemovalWithStaticFunc() { @@ -1639,13 +1639,13 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingWithChainedMember() { let input = "let session: URLSession = URLSession.default.makeCopy()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) } func testRedundantRedundantChainedMemberTypeRemovedOnSwift5_4() { @@ -1653,44 +1653,44 @@ class RedundancyTests: RulesTests { let output = "let session: URLSession = .default.makeCopy()" let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingWithChainedMember2() { let input = "let color: UIColor = UIColor.red.withAlphaComponent(0.5)" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingWithChainedMember3() { let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeRemovedWithChainedMemberOnSwift5_4() { let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" let output = "let url: URL = .init(fileURLWithPath: #file).deletingLastPathComponent()" let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingIfLet() { let input = "if let foo: Foo = Foo() {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingGuardLet() { let input = "guard let foo: Foo = Foo() else {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingIfLetAfterComma() { let input = "if check == true, let foo: Foo = Foo() {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeWorksAfterIf() { @@ -1704,7 +1704,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeIfVoid() { @@ -1712,7 +1712,7 @@ class RedundancyTests: RulesTests { let output = "let foo: [Void] = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } func testRedundantTypeWithIntegerLiteralNotMangled() { @@ -1752,8 +1752,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, - exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantType, options: options) } // --redundanttype infer-locals-only @@ -1795,7 +1794,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(redundantType: .inferLocalsOnly) testFormatting(for: input, output, rule: FormatRules.redundantType, - options: options, exclude: ["preferInferredTypes"]) + options: options, exclude: ["propertyType"]) } // MARK: - redundantNilInit @@ -3307,7 +3306,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, rule: FormatRules.redundantClosure, options: options, - exclude: ["redundantReturn"]) + exclude: ["redundantReturn", "propertyType"]) } func testNonRedundantSwitchStatementReturnInFunction() { @@ -3864,19 +3863,19 @@ class RedundancyTests: RulesTests { let input = "var type = Foo.`true`" let output = "var type = Foo.true" let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, options: options) + testFormatting(for: input, output, rule: FormatRules.redundantBackticks, options: options, exclude: ["propertyType"]) } func testRemoveBackticksAroundProperty() { let input = "var type = Foo.`bar`" let output = "var type = Foo.bar" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: FormatRules.redundantBackticks, exclude: ["propertyType"]) } func testRemoveBackticksAroundKeywordProperty() { let input = "var type = Foo.`default`" let output = "var type = Foo.default" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: FormatRules.redundantBackticks, exclude: ["propertyType"]) } func testRemoveBackticksAroundKeypathProperty() { @@ -3900,7 +3899,7 @@ class RedundancyTests: RulesTests { func testNoRemoveBackticksAroundInitPropertyInSwift5() { let input = "let foo: Foo = .`init`" let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, rule: FormatRules.redundantBackticks, options: options, exclude: ["preferInferredTypes"]) + testFormatting(for: input, rule: FormatRules.redundantBackticks, options: options, exclude: ["propertyType"]) } func testNoRemoveBackticksAroundAnyProperty() { @@ -4482,7 +4481,7 @@ class RedundancyTests: RulesTests { let vc = UIHostingController(rootView: InspectionView(inspection: self.inspection)) """ let options = FormatOptions(selfRequired: ["InspectionView"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["propertyType"]) } func testNoMistakeProtocolClassModifierForClassFunction() { @@ -7478,7 +7477,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, rule: FormatRules.redundantStaticSelf, exclude: ["propertyType"]) } func testPreserveStaticSelfInInstanceFunction() { @@ -7855,7 +7854,7 @@ class RedundancyTests: RulesTests { return parser } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty", "propertyType"]) } func testShadowedClosureArgument2() { @@ -8907,7 +8906,7 @@ class RedundancyTests: RulesTests { lazy var bar = Bar() """ - testFormatting(for: input, output, rule: FormatRules.redundantClosure) + testFormatting(for: input, output, rule: FormatRules.redundantClosure, exclude: ["propertyType"]) } func testRemoveRedundantClosureInMultiLinePropertyDeclarationWithString() { @@ -8944,7 +8943,7 @@ class RedundancyTests: RulesTests { """ testFormatting(for: input, [output], rules: [FormatRules.redundantReturn, FormatRules.redundantClosure, - FormatRules.semicolons]) + FormatRules.semicolons], exclude: ["propertyType"]) } func testRemoveRedundantClosureInWrappedPropertyDeclaration_beforeFirst() { @@ -8965,7 +8964,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) testFormatting(for: input, [output], rules: [FormatRules.redundantClosure, FormatRules.wrapArguments], - options: options) + options: options, exclude: ["propertyType"]) } func testRemoveRedundantClosureInWrappedPropertyDeclaration_afterFirst() { @@ -8984,7 +8983,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) testFormatting(for: input, [output], rules: [FormatRules.redundantClosure, FormatRules.wrapArguments], - options: options) + options: options, exclude: ["propertyType"]) } func testRedundantClosureKeepsMultiStatementClosureThatSetsProperty() { @@ -9152,7 +9151,7 @@ class RedundancyTests: RulesTests { lazy var foo = Foo(handle: { fatalError() }) """ - testFormatting(for: input, output, rule: FormatRules.redundantClosure) + testFormatting(for: input, output, rule: FormatRules.redundantClosure, exclude: ["propertyType"]) } func testPreservesClosureWithMultipleVoidMethodCalls() { @@ -9742,7 +9741,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, - exclude: ["redundantReturn", "blankLinesBetweenScopes"]) + exclude: ["redundantReturn", "blankLinesBetweenScopes", "propertyType"]) } func testRedundantClosureWithSwitchExpressionDoesntBreakBuildWithRedundantReturnRuleDisabled() { @@ -9782,7 +9781,8 @@ class RedundancyTests: RulesTests { rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, FormatRules.redundantClosure], options: options, - exclude: ["indent", "blankLinesBetweenScopes", "wrapMultilineConditionalAssignment"]) + exclude: ["indent", "blankLinesBetweenScopes", "wrapMultilineConditionalAssignment", + "propertyType"]) } func testRemovesRedundantClosureWithGenericExistentialTypes() { @@ -10273,6 +10273,23 @@ class RedundancyTests: RulesTests { testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.redundantProperty, FormatRules.redundantReturn, FormatRules.indent], options: options) } + func testRemovesRedundantPropertyWithPreferInferredType() { + let input = """ + func bar() -> Bar { + let bar: Bar = .init(baaz: baaz, quux: quux) + return bar + } + """ + + let output = """ + func bar() -> Bar { + return Bar(baaz: baaz, quux: quux) + } + """ + + testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantProperty, FormatRules.redundantInit], exclude: ["redundantReturn"]) + } + func testRemovesRedundantPropertyWithComments() { let input = """ func foo() -> Foo { diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index e2bb8d4aa..c28c0fba1 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -1833,17 +1833,17 @@ class SyntaxTests: RulesTests { func testAvoidSwiftParserBugWithClosuresInsideArrays() { let input = "var foo = Array<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always)) + testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) } func testAvoidSwiftParserBugWithClosuresInsideDictionaries() { let input = "var foo = Dictionary Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always)) + testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) } func testAvoidSwiftParserBugWithClosuresInsideOptionals() { let input = "var foo = Optional<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always)) + testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) } func testDontOverApplyBugWorkaround() { @@ -1871,21 +1871,21 @@ class SyntaxTests: RulesTests { let input = "var foo = Array<(image: Data?) -> Void>()" let output = "var foo = [(image: Data?) -> Void]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) } func testDontOverApplyBugWorkaround5() { let input = "var foo = Array<(Data?) -> Void>()" let output = "var foo = [(Data?) -> Void]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) } func testDontOverApplyBugWorkaround6() { let input = "var foo = Dictionary Void>>()" let output = "var foo = [Int: Array<(_ image: Data?) -> Void>]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) } // MARK: - preferKeyPath @@ -2080,7 +2080,7 @@ class SyntaxTests: RulesTests { struct ScreenID {} """ - testFormatting(for: input, output, rule: FormatRules.acronyms) + testFormatting(for: input, output, rule: FormatRules.acronyms, exclude: ["propertyType"]) } func testUppercaseCustomAcronym() { @@ -3267,7 +3267,7 @@ class SyntaxTests: RulesTests { """ testFormatting(for: input, output, rule: FormatRules.docComments, - exclude: ["spaceInsideComments"]) + exclude: ["spaceInsideComments", "propertyType"]) } func testConvertDocCommentsToComments() { @@ -3342,7 +3342,7 @@ class SyntaxTests: RulesTests { """ testFormatting(for: input, output, rule: FormatRules.docComments, - exclude: ["spaceInsideComments", "redundantProperty"]) + exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) } func testPreservesDocComments() { @@ -3419,7 +3419,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(preserveDocComments: true) - testFormatting(for: input, output, rule: FormatRules.docComments, options: options, exclude: ["spaceInsideComments", "redundantProperty"]) + testFormatting(for: input, output, rule: FormatRules.docComments, options: options, exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) } func testDoesntConvertCommentBeforeConsecutivePropertiesToDocComment() { @@ -4949,9 +4949,9 @@ class SyntaxTests: RulesTests { testFormatting(for: input, output, rule: FormatRules.preferForLoop) } - // MARK: preferInferredTypes + // MARK: propertyType - func testConvertsExplicitTypeToImplicitType() { + func testConvertsExplicitTypeToInferredType() { let input = """ let foo: Foo = .init() let bar: Bar = .staticBar @@ -4975,7 +4975,106 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, [output], rules: [FormatRules.preferInferredTypes, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) + } + + func testConvertsInferredTypeToExplicitType() { + let input = """ + let foo = Foo() + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + """ + + let output = """ + let foo: Foo = .init() + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) + } + + func testConvertsTypeMembersToExplicitType() { + let input = """ + struct Foo { + let foo = Foo() + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + } + """ + + let output = """ + struct Foo { + let foo: Foo = .init() + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) + } + + func testConvertsLocalsToImplicitType() { + let input = """ + struct Foo { + let foo = Foo() + + func bar() { + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + } + } + """ + + let output = """ + struct Foo { + let foo: Foo = .init() + + func bar() { + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) + } + + func testPreservesInferredTypeFollowingTypeWithDots() { + let input = """ + let baaz = Baaz.Example.default + let color = Color.Theme.default + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) } func testPreservesExplicitTypeIfNoRHS() { @@ -4985,7 +5084,34 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) + } + + func testPreservesImplicitTypeIfNoRHSType() { + let input = """ + let foo = foo() + let bar = bar + let int = 24 + let array = ["string"] + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) + } + + func testPreservesImplicitForVoidAndTuples() { + let input = """ + let foo = Void() + let foo = (foo: "foo", bar: "bar").foo + let foo = ["bar", "baz"].quux(quuz) + let foo = [bar].first + let foo = [bar, baaz].first + let foo = ["foo": "bar"].first + let foo = [foo: bar].first + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: FormatRules.propertyType, options: options, exclude: ["void"]) } func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { @@ -5000,7 +5126,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options, exclude: ["redundantType"]) + testFormatting(for: input, rule: FormatRules.propertyType, options: options, exclude: ["redundantType"]) } func testCompatibleWithRedundantTypeInferred() { @@ -5013,7 +5139,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.preferInferredTypes], options: options) + testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType], options: options) } func testCompatibleWithRedundantTypeExplicit() { @@ -5022,11 +5148,11 @@ class SyntaxTests: RulesTests { """ let output = """ - let foo = Foo() + let foo: Foo = .init() """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.preferInferredTypes], options: options) + testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType], options: options) } func testCompatibleWithRedundantTypeInferLocalsOnly() { @@ -5051,10 +5177,10 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.preferInferredTypes, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType, FormatRules.redundantInit], options: options) } - func testPreferInferredTypesWithIfExpressionDisabledByDefault() { + func testPropertyTypeWithIfExpressionDisabledByDefault() { let input = """ let foo: SomeTypeWithALongGenrericName = if condition { @@ -5065,10 +5191,10 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) } - func testPreferInferredTypesWithIfExpression() { + func testPropertyTypeWithIfExpression() { let input = """ let foo: Foo = if condition { @@ -5088,10 +5214,10 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, [output], rules: [FormatRules.preferInferredTypes, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) } - func testPreferInferredTypesWithSwitchExpression() { + func testPropertyTypeWithSwitchExpression() { let input = """ let foo: Foo = switch condition { @@ -5113,7 +5239,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, [output], rules: [FormatRules.preferInferredTypes, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) } func testPreservesNonMatchingIfExpression() { @@ -5127,7 +5253,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) } func testPreservesExplicitOptionalType() { @@ -5140,7 +5266,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) } func testPreservesTypeWithSeparateDeclarationAndProperty() { @@ -5152,7 +5278,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) } func testPreservesTypeWithExistentialAny() { @@ -5172,10 +5298,10 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.preferInferredTypes, options: options) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) } - func testPreservesRightHandSideWithOperator() { + func testPreservesExplicitRightHandSideWithOperator() { let input = """ let value: ClosedRange = .zero ... 10 let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge @@ -5191,6 +5317,185 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.preferInferredTypes, options: options) + testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) + } + + func testPreservesInferredRightHandSideWithOperators() { + let input = """ + let foo = Foo().bar + let foo = Foo.bar.baaz.quux + let foo = Foo.bar ... baaz + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) + } + + func testPreservesUserProvidedSymbolTypes() { + let input = """ + class Foo { + let foo = Foo() + let bar = Bar() + + func bar() { + let foo: Foo = .foo + let bar: Bar = .bar + let baaz: Baaz = .baaz + let quux: Quux = .quux + } + } + """ + + let output = """ + class Foo { + let foo = Foo() + let bar: Bar = .init() + + func bar() { + let foo: Foo = .foo + let bar = Bar.bar + let baaz: Baaz = .baaz + let quux: Quux = .quux + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["Foo", "Baaz", "quux"]) + testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) + } + + func testPreserveInitIfExplicitlyExcluded() { + let input = """ + class Foo { + let foo = Foo() + let bar = Bar.init() + let baaz = Baaz.baaz() + + func bar() { + let foo: Foo = .init() + let bar: Bar = .init() + let baaz: Baaz = .baaz() + } + } + """ + + let output = """ + class Foo { + let foo = Foo() + let bar = Bar.init() + let baaz: Baaz = .baaz() + + func bar() { + let foo: Foo = .init() + let bar: Bar = .init() + let baaz = Baaz.baaz() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["init"]) + testFormatting(for: input, output, rule: FormatRules.propertyType, options: options, exclude: ["redundantInit"]) + } + + func testClosureBodyIsConsideredLocal() { + let input = """ + foo { + let bar = Bar() + let baaz: Baaz = .init() + } + + foo(bar: bar, baaz: baaz, quux: { + let bar = Bar() + let baaz: Baaz = .init() + }) + + foo { + let bar = Bar() + let baaz: Baaz = .init() + } bar: { + let bar = Bar() + let baaz: Baaz = .init() + } + + class Foo { + let foo = Foo.bar { + let baaz = Baaz() + let baaz: Baaz = .init() + } + } + """ + + let output = """ + foo { + let bar = Bar() + let baaz = Baaz() + } + + foo(bar: bar, baaz: baaz, quux: { + let bar = Bar() + let baaz = Baaz() + }) + + foo { + let bar = Bar() + let baaz = Baaz() + } bar: { + let bar = Bar() + let baaz = Baaz() + } + + class Foo { + let foo: Foo = .bar { + let baaz = Baaz() + let baaz = Baaz() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) + } + + func testIfGuardConditionsPreserved() { + let input = """ + if let foo = Foo(bar) { + let foo = Foo(bar) + } else if let foo = Foo(bar) { + let foo = Foo(bar) + } else { + let foo = Foo(bar) + } + + guard let foo = Foo(bar) else { + return + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) + } + + func testPropertyObserversConsideredLocal() { + let input = """ + class Foo { + var foo: Foo { + get { + let foo = Foo(bar) + } + set { + let foo = Foo(bar) + } + willSet { + let foo = Foo(bar) + } + didSet { + let foo = Foo(bar) + } + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, rule: FormatRules.propertyType, options: options) } } diff --git a/Tests/RulesTests+Wrapping.swift b/Tests/RulesTests+Wrapping.swift index ca9d0fc3e..edb136651 100644 --- a/Tests/RulesTests+Wrapping.swift +++ b/Tests/RulesTests+Wrapping.swift @@ -1410,7 +1410,7 @@ class WrappingTests: RulesTests { Thing(), ]) """ - testFormatting(for: input, output, rule: FormatRules.wrapArguments) + testFormatting(for: input, output, rule: FormatRules.wrapArguments, exclude: ["propertyType"]) } func testWrapArgumentsDoesntIndentTrailingComment() { @@ -1593,7 +1593,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) } // MARK: wrapParameters @@ -3181,7 +3181,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapEffects: .never) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) } func testWrapEffectsNeverPreservesComments() { @@ -3342,7 +3342,7 @@ class WrappingTests: RulesTests { wrapReturnType: .ifMultiline ) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) } func testPreserveReturnOnMultilineFunctionDeclarationByDefault() { @@ -3512,7 +3512,7 @@ class WrappingTests: RulesTests { print("statement body") } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, exclude: ["propertyType"]) } func testSingleLineIfBraceOnSameLine() { @@ -3667,7 +3667,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [output], rules: [ FormatRules.wrapMultilineStatementBraces, FormatRules.indent, - ], options: options) + ], options: options, exclude: ["propertyType"]) } func testMultilineBraceAppliedToTrailingClosure_wrapAfterFirst() { @@ -3710,7 +3710,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [], rules: [ FormatRules.wrapMultilineStatementBraces, FormatRules.wrapArguments, - ], options: options) + ], options: options, exclude: ["propertyType"]) } func testMultilineBraceAppliedToSubscriptBody() { @@ -4085,7 +4085,8 @@ class WrappingTests: RulesTests { """ testFormatting( for: input, rules: [FormatRules.wrapArguments, FormatRules.indent], - options: FormatOptions(closingParenPosition: .sameLine, wrapConditions: .beforeFirst) + options: FormatOptions(closingParenPosition: .sameLine, wrapConditions: .beforeFirst), + exclude: ["propertyType"] ) } @@ -4758,7 +4759,7 @@ class WrappingTests: RulesTests { let myClass = MyClass() """ let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) } func testClassImportAttributeNotTreatedAsType() { @@ -4778,7 +4779,7 @@ class WrappingTests: RulesTests { private(set) dynamic var foo = Foo() """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) } func testWrapPrivateSetVarAttributes() { @@ -4790,7 +4791,7 @@ class WrappingTests: RulesTests { private(set) dynamic var foo = Foo() """ let options = FormatOptions(varAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) } func testDontWrapPrivateSetVarAttributes() { @@ -4802,7 +4803,7 @@ class WrappingTests: RulesTests { @objc private(set) dynamic var foo = Foo() """ let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) } func testWrapConvenienceInitAttribute() { @@ -4967,7 +4968,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) } func testComplexAttributesException() { @@ -5038,7 +5039,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) } func testEscapingClosureNotMistakenForComplexAttribute() { @@ -5110,7 +5111,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) } func testWrapOrDontAttributesInSwiftUIView() { From dc522432e51adfe68146f20a3f620aaff9f8b9c8 Mon Sep 17 00:00:00 2001 From: Ryzhov Evgeniy Igorevich Date: Mon, 10 Jun 2024 03:15:42 +0300 Subject: [PATCH 08/52] Alphabetically sort organized declarations by name pattern --- Rules.md | 1 + Sources/FormattingHelpers.swift | 18 ++++- Sources/OptionDescriptor.swift | 6 ++ Sources/Options.swift | 3 + Sources/Rules.swift | 8 +- Tests/MetadataTests.swift | 1 + Tests/RulesTests+Organization.swift | 121 ++++++++++++++++++++++++++++ 7 files changed, 155 insertions(+), 3 deletions(-) diff --git a/Rules.md b/Rules.md index 1f337269e..f8d408d84 100644 --- a/Rules.md +++ b/Rules.md @@ -1369,6 +1369,7 @@ Option | Description `--enumthreshold` | Minimum line count to organize enum body. Defaults to 0 `--extensionlength` | Minimum line count to organize extension body. Defaults to 0 `--organizationmode` | Organize declarations by "visibility" (default) or "type" +`--sortedpatterns` | List of patterns to sort alphabetically without `:sort` mark.
Examples diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 5bc8cba9e..702a48f86 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -2302,9 +2302,25 @@ extension Formatter { // If this type has a leading :sort directive, we sort alphabetically // within the subcategories (where ordering is otherwise undefined) - let sortAlphabeticallyWithinSubcategories = typeDeclaration.open.contains(where: { + let shouldSortAlphabeticallyBySortingMark = typeDeclaration.open.contains(where: { $0.isCommentBody && $0.string.contains("swiftformat:sort") && !$0.string.contains(":sort:") }) + // If this type declaration name contains pattern — sort as well + let shouldSortAlphabeticallyByDeclarationPattern: Bool = { + let parser = Formatter(typeDeclaration.open) + + guard let kindIndex = parser.index(of: .keyword(typeDeclaration.kind), in: 0 ..< typeDeclaration.open.count), + let identifier = parser.next(.identifier, after: kindIndex) + else { + return false + } + + return options.alphabeticallySortedDeclarationPatterns.contains { + identifier.string.contains($0) + } + }() + let sortAlphabeticallyWithinSubcategories = shouldSortAlphabeticallyBySortingMark + || shouldSortAlphabeticallyByDeclarationPattern // Sorts the given categoried declarations based on their derived metadata func sortDeclarations(_ declarations: CategorizedDeclarations) -> CategorizedDeclarations { diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index 0fcf8c5a3..ef8483524 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -869,6 +869,12 @@ struct _Descriptors { help: "Organize declarations by \"visibility\" (default) or \"type\"", keyPath: \.organizationMode ) + let alphabeticallySortedDeclarationPatterns = OptionDescriptor( + argumentName: "sortedpatterns", + displayName: "Declaration Name Patterns To Sort Alphabetically", + help: "List of patterns to sort alphabetically without `:sort` mark.", + keyPath: \.alphabeticallySortedDeclarationPatterns + ) let funcAttributes = OptionDescriptor( argumentName: "funcattributes", displayName: "Function Attributes", diff --git a/Sources/Options.swift b/Sources/Options.swift index eb99ae550..a9ab64cc3 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -667,6 +667,7 @@ public struct FormatOptions: CustomStringConvertible { public var organizeEnumThreshold: Int public var organizeExtensionThreshold: Int public var organizationMode: DeclarationOrganizationMode + public var alphabeticallySortedDeclarationPatterns: Set public var yodaSwap: YodaMode public var extensionACLPlacement: ExtensionACLPlacement public var redundantType: RedundantType @@ -785,6 +786,7 @@ public struct FormatOptions: CustomStringConvertible { organizeEnumThreshold: Int = 0, organizeExtensionThreshold: Int = 0, organizationMode: DeclarationOrganizationMode = .visibility, + alphabeticallySortedDeclarationPatterns: Set = [], yodaSwap: YodaMode = .always, extensionACLPlacement: ExtensionACLPlacement = .onExtension, redundantType: RedundantType = .inferLocalsOnly, @@ -893,6 +895,7 @@ public struct FormatOptions: CustomStringConvertible { self.organizeEnumThreshold = organizeEnumThreshold self.organizeExtensionThreshold = organizeExtensionThreshold self.organizationMode = organizationMode + self.alphabeticallySortedDeclarationPatterns = alphabeticallySortedDeclarationPatterns self.yodaSwap = yodaSwap self.extensionACLPlacement = extensionACLPlacement self.redundantType = redundantType diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 3ce01a92e..329517a2f 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5684,8 +5684,12 @@ public struct _FormatRules { runOnceOnly: true, disabledByDefault: true, orderAfter: ["extensionAccessControl", "redundantFileprivate"], - options: ["categorymark", "markcategories", "beforemarks", "lifecycle", "organizetypes", - "structthreshold", "classthreshold", "enumthreshold", "extensionlength", "organizationmode"], + options: [ + "categorymark", "markcategories", "beforemarks", + "lifecycle", "organizetypes", "structthreshold", "classthreshold", + "enumthreshold", "extensionlength", "organizationmode", + "sortedpatterns", + ], sharedOptions: ["lineaftermarks"] ) { formatter in guard !formatter.options.fragment else { return } diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index d21db8924..42f83f228 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -232,6 +232,7 @@ class MetadataTests: XCTestCase { Descriptors.organizeExtensionThreshold, Descriptors.lineAfterMarks, Descriptors.organizationMode, + Descriptors.alphabeticallySortedDeclarationPatterns, ] case .identifier("removeSelf"): referencedOptions += [ diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index b6019d120..bfb5c1a47 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -4018,6 +4018,127 @@ class OrganizationTests: RulesTests { exclude: ["blankLinesAtEndOfScope"]) } + func testSortsWithinOrganizeDeclarationsByClassName() { + let input = """ + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA + + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } + } + """ + + let output = """ + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + + case upsellB + + // MARK: Internal + + var aSortedProperty: Foo { + Foo() + } + + var sortedProperty: Foo { + Foo() + } + + } + """ + + testFormatting(for: input, [output], + rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["FeatureFlags"]), + exclude: ["blankLinesAtEndOfScope"]) + } + + func testSortsWithinOrganizeDeclarationsByPartialClassName() { + let input = """ + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA + + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } + } + """ + + let output = """ + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + + case upsellB + + // MARK: Internal + + var aSortedProperty: Foo { + Foo() + } + + var sortedProperty: Foo { + Foo() + } + + } + """ + + testFormatting(for: input, [output], + rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["ureFla"]), + exclude: ["blankLinesAtEndOfScope"]) + } + + func testDontSortsWithinOrganizeDeclarationsByClassNameInComment() { + let input = """ + /// Comment + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA + + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } + } + """ + + testFormatting(for: input, + rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["Comment"]), + exclude: ["blankLinesAtEndOfScope"]) + } + func testSortDeclarationsUsesLocalizedCompare() { let input = """ // swiftformat:sort From 4667e235ac0e426b4bef0cd930b8c9c16d30861c Mon Sep 17 00:00:00 2001 From: oiuhr Date: Tue, 18 Jun 2024 16:28:39 +0300 Subject: [PATCH 09/52] Support automatic sorting by name pattern for `sortDeclarations` rule (#1731) --- Rules.md | 25 ++++++++++++++++- Sources/Examples.swift | 20 ++++++++++++++ Sources/ParsingHelpers.swift | 42 +++++++++++++++++++++-------- Sources/Rules.swift | 36 ++++++++++++++++--------- Tests/RulesTests+Organization.swift | 42 +++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 25 deletions(-) diff --git a/Rules.md b/Rules.md index f8d408d84..0e645066d 100644 --- a/Rules.md +++ b/Rules.md @@ -1369,7 +1369,6 @@ Option | Description `--enumthreshold` | Minimum line count to organize enum body. Defaults to 0 `--extensionlength` | Minimum line count to organize extension body. Defaults to 0 `--organizationmode` | Organize declarations by "visibility" (default) or "type" -`--sortedpatterns` | List of patterns to sort alphabetically without `:sort` mark.
Examples @@ -2175,6 +2174,10 @@ Sorts the body of declarations with // swiftformat:sort and declarations between // swiftformat:sort:begin and // swiftformat:sort:end comments. +Option | Description +--- | --- +`--sortedpatterns` | List of patterns to sort alphabetically without `:sort` mark. +
Examples @@ -2192,6 +2195,26 @@ and declarations between // swiftformat:sort:begin and + case upsellA( + fooConfiguration: Foo, + barConfiguration: Bar) ++ case upsellB + } + +config: +``` + sortedpatterns: 'Feature' +``` + + enum FeatureFlags { +- case upsellB +- case fooFeature +- case barFeature +- case upsellA( +- fooConfiguration: Foo, +- barConfiguration: Bar) ++ case barFeature ++ case fooFeature ++ case upsellA( ++ fooConfiguration: Foo, ++ barConfiguration: Bar) + case upsellB } diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 72849ae37..852bb6654 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1510,6 +1510,26 @@ private struct Examples { + case upsellA( + fooConfiguration: Foo, + barConfiguration: Bar) + + case upsellB + } + + config: + ``` + sortedpatterns: 'Feature' + ``` + + enum FeatureFlags { + - case upsellB + - case fooFeature + - case barFeature + - case upsellA( + - fooConfiguration: Foo, + - barConfiguration: Bar) + + case barFeature + + case fooFeature + + case upsellA( + + fooConfiguration: Foo, + + barConfiguration: Bar) + case upsellB } diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index 5ce86d1ea..20f97ca53 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -2835,6 +2835,23 @@ extension Token { isDeclarationTypeKeyword(excluding: []) } + // All of the keywords defining top-level entity + // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_declaration + static var swiftTypeKeywords: Set { + Set(["struct", "class", "actor", "protocol", "enum", "extension"]) + } + + // All of the keywords that map to individual Declaration grammars + // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_declaration + static var declarationTypeKeywords: Set { + swiftTypeKeywords.union([ + "import", "let", "var", "typealias", "func", "enum", "case", + "struct", "class", "actor", "protocol", "init", "deinit", + "extension", "subscript", "operator", "precedencegroup", + "associatedtype", "macro", + ]) + } + /// Whether or not this token "defines" the specific type of declaration /// - A valid declaration will usually include exactly one of these keywords in its outermost scope. /// - Notable exceptions are `class func` and symbol imports (like `import class Module.Type`) @@ -2844,20 +2861,23 @@ extension Token { return false } - // All of the keywords that map to individual Declaration grammars - // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_declaration - var declarationTypeKeywords = Set([ - "import", "let", "var", "typealias", "func", "enum", "case", - "struct", "class", "actor", "protocol", "init", "deinit", - "extension", "subscript", "operator", "precedencegroup", - "associatedtype", "macro", - ]) + return Self.declarationTypeKeywords + .subtracting(keywordsToExclude) + .contains(keyword) + } - for keywordToExclude in keywordsToExclude { - declarationTypeKeywords.remove(keywordToExclude) + /// Whether or not this token "defines" the specific type of declaration + /// - A valid declaration will usually include exactly one of these keywords in its outermost scope. + /// - Notable exceptions are `class func` and symbol imports (like `import class Module.Type`) + /// which will include two of these keywords. + func isDeclarationTypeKeyword(including keywordsToInclude: [String]) -> Bool { + guard case let .keyword(keyword) = self else { + return false } - return declarationTypeKeywords.contains(keyword) + return Self.declarationTypeKeywords + .intersection(keywordsToInclude) + .contains(keyword) } var isModifierKeyword: Bool { diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 329517a2f..7cd1bfbbb 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5688,9 +5688,8 @@ public struct _FormatRules { "categorymark", "markcategories", "beforemarks", "lifecycle", "organizetypes", "structthreshold", "classthreshold", "enumthreshold", "extensionlength", "organizationmode", - "sortedpatterns", ], - sharedOptions: ["lineaftermarks"] + sharedOptions: ["sortedpatterns", "lineaftermarks"] ) { formatter in guard !formatter.options.fragment else { return } @@ -6071,34 +6070,45 @@ public struct _FormatRules { and declarations between // swiftformat:sort:begin and // swiftformat:sort:end comments. """, + options: ["sortedpatterns"], sharedOptions: ["organizetypes"] ) { formatter in formatter.forEachToken( - where: { $0.isCommentBody && $0.string.contains("swiftformat:sort") } - ) { commentIndex, commentToken in + where: { + $0.isCommentBody && $0.string.contains("swiftformat:sort") + || $0.isDeclarationTypeKeyword(including: Array(Token.swiftTypeKeywords)) + } + ) { index, token in let rangeToSort: ClosedRange let numberOfLeadingLinebreaks: Int // For `:sort:begin`, directives, we sort the declarations // between the `:begin` and and `:end` comments - if commentToken.string.contains("swiftformat:sort:begin") { - guard let endCommentIndex = formatter.tokens[commentIndex...].firstIndex(where: { + let shouldBePartiallySorted = token.string.contains("swiftformat:sort:begin") + + let identifier = formatter.next(.identifier, after: index) + let shouldBeSortedByNamePattern = formatter.options.alphabeticallySortedDeclarationPatterns.contains { + identifier?.string.contains($0) ?? false + } + let shouldBeSortedByMarkComment = token.isCommentBody && !token.string.contains(":sort:") + // For `:sort` directives and types with matching name pattern, we sort the declarations + // between the open and close brace of the following type + let shouldBeFullySorted = shouldBeSortedByNamePattern || shouldBeSortedByMarkComment + + if shouldBePartiallySorted { + guard let endCommentIndex = formatter.tokens[index...].firstIndex(where: { $0.isComment && $0.string.contains("swiftformat:sort:end") }), - let sortRangeStart = formatter.index(of: .nonSpaceOrComment, after: commentIndex), + let sortRangeStart = formatter.index(of: .nonSpaceOrComment, after: index), let firstRangeToken = formatter.index(of: .nonLinebreak, after: sortRangeStart), let lastRangeToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: endCommentIndex - 2) else { return } rangeToSort = sortRangeStart ... lastRangeToken numberOfLeadingLinebreaks = firstRangeToken - sortRangeStart - } - - // For `:sort` directives, we sort the declarations - // between the open and close brace of the following type - else if !commentToken.string.contains(":sort:") { - guard let typeOpenBrace = formatter.index(of: .startOfScope("{"), after: commentIndex), + } else if shouldBeFullySorted { + guard let typeOpenBrace = formatter.index(of: .startOfScope("{"), after: index), let typeCloseBrace = formatter.endOfScope(at: typeOpenBrace), let firstTypeBodyToken = formatter.index(of: .nonLinebreak, after: typeOpenBrace), let lastTypeBodyToken = formatter.index(of: .nonLinebreak, before: typeCloseBrace), diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index bfb5c1a47..e19a2310a 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -4139,6 +4139,48 @@ class OrganizationTests: RulesTests { exclude: ["blankLinesAtEndOfScope"]) } + func testSortDeclarationsSortsByNamePattern() { + let input = """ + enum Namespace {} + + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" + } + """ + + let output = """ + enum Namespace {} + + extension Namespace { + static let baaz = "baaz" + public static let bar = "bar" + static let foo = "foo" + } + """ + + let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Namespace"]) + testFormatting(for: input, [output], rules: [FormatRules.sortDeclarations, FormatRules.blankLinesBetweenScopes], options: options) + } + + func testSortDeclarationsWontSortByNamePatternInComment() { + let input = """ + enum Namespace {} + + /// Constants + /// enum Constants + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" + } + """ + + let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Constants"]) + testFormatting(for: input, rules: [FormatRules.sortDeclarations, FormatRules.blankLinesBetweenScopes], options: options) + } + func testSortDeclarationsUsesLocalizedCompare() { let input = """ // swiftformat:sort From 152da3a31f8ebcb2a17fe90c8da4133f4fa36b09 Mon Sep 17 00:00:00 2001 From: Miguel Jimenez Date: Wed, 3 Jul 2024 15:53:10 -0400 Subject: [PATCH 10/52] Add SwiftUI Property Wrapper declaration type (#1747) --- Sources/FormattingHelpers.swift | 40 +++++ Tests/RulesTests+Organization.swift | 225 ++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 702a48f86..6c9fbe899 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -1374,6 +1374,36 @@ extension Formatter { return branches } + /// Represents all the native SwiftUI property wrappers that conform to `DynamicProperty` and cause a SwiftUI view to re-render. + /// Most of these are listed here: https://developer.apple.com/documentation/swiftui/dynamicproperty + private var swiftUIPropertyWrappers: Set { + [ + "@AccessibilityFocusState", + "@AppStorage", + "@Binding", + "@Environment", + "@EnvironmentObject", + "@NSApplicationDelegateAdaptor", + "@FetchRequest", + "@FocusedBinding", + "@FocusedState", + "@FocusedValue", + "@FocusedObject", + "@GestureState", + "@Namespace", + "@ObservedObject", + "@PhysicalMetric", + "@Query", + "@ScaledMetric", + "@SceneStorage", + "@SectionedFetchRequest", + "@State", + "@StateObject", + "@UIApplicationDelegateAdaptor", + "@WKExtensionDelegateAdaptor", + ] + } + /// Parses the switch statement case starting at the given index, /// which should be one of: `case`, `default`, or `@unknown`. private func parseSwitchStatementCase(caseOrDefaultIndex: Int) -> (startOfBody: Int, endOfBody: Int)? { @@ -1863,6 +1893,7 @@ extension Formatter { case staticPropertyWithBody case classPropertyWithBody case overriddenProperty + case swiftUIPropertyWrapper case instanceProperty case instancePropertyWithBody case instanceLifecycle @@ -1894,6 +1925,8 @@ extension Formatter { return "Overridden Functions" case .swiftUIProperty, .swiftUIMethod: return "Content" + case .swiftUIPropertyWrapper: + return "SwiftUI Properties" case .instanceProperty: return "Properties" case .instancePropertyWithBody: @@ -2033,6 +2066,11 @@ extension Formatter { return declarationParser.index(of: .identifier("View"), after: someKeywordIndex) != nil }() + let isSwiftUIPropertyWrapper = mode == .visibility && + declarationParser.modifiersForDeclaration(at: declarationTypeTokenIndex, contains: { _, modifier in + swiftUIPropertyWrappers.contains(modifier) + }) + switch declarationTypeToken { // Properties and property-like declarations case .keyword("let"), .keyword("var"), @@ -2069,6 +2107,8 @@ extension Formatter { return .classPropertyWithBody } else if isViewDeclaration { return .swiftUIProperty + } else if !hasBody, isSwiftUIPropertyWrapper { + return .swiftUIPropertyWrapper } else { if hasBody { return .instancePropertyWithBody diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index e19a2310a..8690c0aa4 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -4517,4 +4517,229 @@ class OrganizationTests: RulesTests { testFormatting(for: input, output, rule: FormatRules.sortTypealiases) } + + func testSortSingleSwiftUIPropertyWrapper() { + let input = """ + struct ContentView: View { + + private var label: String + + @State + private var isOn: Bool = false + + private var foo = true + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Internal + + @ViewBuilder + var body: some View { + toggle + } + + // MARK: Private + + @State + private var isOn: Bool = false + + private var label: String + + private var foo = true + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + ) + } + + func testSortMultipleSwiftUIPropertyWrappers() { + let input = """ + struct ContentView: View { + + let foo: Foo + @State var bar: Bar + let baaz: Baaz + @State var quux: Quux + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Internal + + @State var bar: Bar + @State var quux: Quux + + let foo: Foo + let baaz: Baaz + + @ViewBuilder + var body: some View { + toggle + } + + // MARK: Private + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + ) + } + + func testSortSwiftUIPropertyWrappersWithDifferentVisibility() { + let input = """ + struct ContentView: View { + + let foo: Foo + @State private var bar: Bar + private let baaz: Baaz + @Binding var isOn: Bool + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Internal + + @Binding var isOn: Bool + + let foo: Foo + + @ViewBuilder + var body: some View { + toggle + } + + // MARK: Private + + @State private var bar: Bar + + private let baaz: Baaz + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + ) + } + + func testSortSwiftUIPropertyWrappersWithArguments() { + let input = """ + struct ContentView: View { + + let foo: Foo + @Environment(\\.colorScheme) var colorScheme + let baaz: Baaz + @Environment(\\.quux) let quux: Quux + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Internal + + @Environment(\\.colorScheme) var colorScheme + @Environment(\\.quux) let quux: Quux + + let foo: Foo + let baaz: Baaz + + @ViewBuilder + var body: some View { + toggle + } + + // MARK: Private + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + ) + } } From fe644d501978016bdf5fe447d7ef9e0c17876e2e Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Wed, 17 Jul 2024 14:48:47 -0700 Subject: [PATCH 11/52] Include original token range when parsing declarations (#1759) --- Sources/FormattingHelpers.swift | 72 ++++++++------ Sources/ParsingHelpers.swift | 161 ++++++++++++++++++++++---------- Sources/Rules.swift | 11 ++- Tests/ParsingHelpersTests.swift | 62 ++++++++++++ 4 files changed, 224 insertions(+), 82 deletions(-) diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 6c9fbe899..d3f7075a7 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -1672,19 +1672,21 @@ extension Formatter { declarations.map { declaration in let mapped = transform(declaration, stack) switch mapped { - case let .type(kind, open, body, close): + case let .type(kind, open, body, close, originalRange): return .type( kind: kind, open: open, body: mapRecursiveDeclarations(body, in: stack + [mapped], with: transform), - close: close + close: close, + originalRange: originalRange ) - case let .conditionalCompilation(open, body, close): + case let .conditionalCompilation(open, body, close, originalRange): return .conditionalCompilation( open: open, body: mapRecursiveDeclarations(body, in: stack + [mapped], with: transform), - close: close + close: close, + originalRange: originalRange ) case .declaration: @@ -1701,19 +1703,21 @@ extension Formatter { with transform: (Declaration) -> Declaration ) -> Declaration { switch declaration { - case let .type(kind, open, body, close): + case let .type(kind, open, body, close, originalRange): return .type( kind: kind, open: open, body: mapBodyDeclarations(body, with: transform), - close: close + close: close, + originalRange: originalRange ) - case let .conditionalCompilation(open, body, close): + case let .conditionalCompilation(open, body, close, originalRange): return .conditionalCompilation( open: open, body: mapBodyDeclarations(body, with: transform), - close: close + close: close, + originalRange: originalRange ) case .declaration: @@ -1751,7 +1755,7 @@ extension Formatter { switch declaration { case .declaration, .type: return [transform(declaration)] - case let .conditionalCompilation(_, body, _): + case let .conditionalCompilation(_, body, _, _): return mapDeclarations(body, with: transform) } } @@ -1765,25 +1769,28 @@ extension Formatter { with transform: ([Token]) -> [Token] ) -> Declaration { switch declaration { - case let .type(kind, open, body, close): + case let .type(kind, open, body, close, originalRange): return .type( kind: kind, open: transform(open), body: body, - close: close + close: close, + originalRange: originalRange ) - case let .conditionalCompilation(open, body, close): + case let .conditionalCompilation(open, body, close, originalRange): return .conditionalCompilation( open: transform(open), body: body, - close: close + close: close, + originalRange: originalRange ) - case let .declaration(kind, tokens): + case let .declaration(kind, tokens, originalRange): return .declaration( kind: kind, - tokens: transform(tokens) + tokens: transform(tokens), + originalRange: originalRange ) } } @@ -1796,25 +1803,28 @@ extension Formatter { with transform: ([Token]) -> [Token] ) -> Declaration { switch declaration { - case let .type(kind, open, body, close): + case let .type(kind, open, body, close, originalRange): return .type( kind: kind, open: open, body: body, - close: transform(close) + close: transform(close), + originalRange: originalRange ) - case let .conditionalCompilation(open, body, close): + case let .conditionalCompilation(open, body, close, originalRange): return .conditionalCompilation( open: open, body: body, - close: transform(close) + close: transform(close), + originalRange: originalRange ) - case let .declaration(kind, tokens): + case let .declaration(kind, tokens, originalRange): return .declaration( kind: kind, - tokens: transform(tokens) + tokens: transform(tokens), + originalRange: originalRange ) } } @@ -1989,7 +1999,7 @@ extension Formatter { func visibility(of declaration: Declaration) -> Visibility? { switch declaration { - case let .declaration(keyword, tokens), let .type(keyword, open: tokens, _, _): + case let .declaration(keyword, tokens, _), let .type(keyword, open: tokens, _, _, _): guard let keywordIndex = tokens.firstIndex(of: .keyword(keyword)) else { return nil } @@ -2009,7 +2019,7 @@ extension Formatter { } return nil - case let .conditionalCompilation(_, body, _): + case let .conditionalCompilation(_, body, _, _): // Conditional compilation blocks themselves don't have a category or visbility-level, // but we still have to assign them a category for the sorting algorithm to function. // A reasonable heuristic here is to simply use the category of the first declaration @@ -2024,10 +2034,10 @@ extension Formatter { func type(of declaration: Declaration, for mode: DeclarationOrganizationMode) -> DeclarationType { switch declaration { - case let .type(keyword, _, _, _): + case let .type(keyword, _, _, _, _): return options.beforeMarks.contains(keyword) ? .beforeMarks : .nestedType - case let .declaration(keyword, tokens): + case let .declaration(keyword, tokens, _): guard let declarationTypeTokenIndex = tokens.firstIndex(of: .keyword(keyword)) else { return .beforeMarks } @@ -2153,7 +2163,7 @@ extension Formatter { return .beforeMarks } - case let .conditionalCompilation(_, body, _): + case let .conditionalCompilation(_, body, _, _): // Prefer treating conditional compliation blocks as having // the property type of the first declaration in their body. if let firstDeclarationInBlock = body.first { @@ -2201,9 +2211,9 @@ extension Formatter { for (declarationIndex, declaration) in typeBody.enumerated() { let tokensToInspect: [Token] switch declaration { - case let .declaration(_, tokens): + case let .declaration(_, tokens, _): tokensToInspect = tokens - case let .type(_, open, _, _), let .conditionalCompilation(open, _, _): + case let .type(_, open, _, _, _), let .conditionalCompilation(open, _, _, _): // Only inspect the opening tokens of declarations with a body tokensToInspect = open } @@ -2497,7 +2507,11 @@ extension Formatter { typeOpeningTokens = endingWithBlankLine(typeOpeningTokens) } - markedDeclarations.append(.declaration(kind: "comment", tokens: markDeclaration)) + markedDeclarations.append(.declaration( + kind: "comment", + tokens: markDeclaration, + originalRange: 0 ... 1 // placeholder value + )) } if let lastIndexOfSameDeclaration = sortedDeclarations.map(\.category).lastIndex(of: category), diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index 20f97ca53..bf960e88d 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -1883,21 +1883,36 @@ extension Formatter { enum Declaration: Equatable { /// A type-like declaration with body of additional declarations (`class`, `struct`, etc) - indirect case type(kind: String, open: [Token], body: [Declaration], close: [Token]) + indirect case type( + kind: String, + open: [Token], + body: [Declaration], + close: [Token], + originalRange: ClosedRange + ) /// A simple declaration (like a property or function) - case declaration(kind: String, tokens: [Token]) + case declaration( + kind: String, + tokens: [Token], + originalRange: ClosedRange + ) /// A #if ... #endif conditional compilation block with a body of additional declarations - indirect case conditionalCompilation(open: [Token], body: [Declaration], close: [Token]) + indirect case conditionalCompilation( + open: [Token], + body: [Declaration], + close: [Token], + originalRange: ClosedRange + ) /// The tokens in this declaration var tokens: [Token] { switch self { - case let .declaration(_, tokens): + case let .declaration(_, tokens, _): return tokens - case let .type(_, openTokens, bodyDeclarations, closeTokens), - let .conditionalCompilation(openTokens, bodyDeclarations, closeTokens): + case let .type(_, openTokens, bodyDeclarations, closeTokens, _), + let .conditionalCompilation(openTokens, bodyDeclarations, closeTokens, _): return openTokens + bodyDeclarations.flatMap { $0.tokens } + closeTokens } } @@ -1907,8 +1922,8 @@ extension Formatter { switch self { case .declaration: return tokens - case let .type(_, open, _, _), - let .conditionalCompilation(open, _, _): + case let .type(_, open, _, _, _), + let .conditionalCompilation(open, _, _, _): return open } } @@ -1918,8 +1933,8 @@ extension Formatter { switch self { case .declaration: return nil - case let .type(_, _, body, _), - let .conditionalCompilation(_, body, _): + case let .type(_, _, body, _, _), + let .conditionalCompilation(_, body, _, _): return body } } @@ -1929,8 +1944,8 @@ extension Formatter { switch self { case .declaration: return [] - case let .type(_, _, _, close), - let .conditionalCompilation(_, _, close): + case let .type(_, _, _, close, _), + let .conditionalCompilation(_, _, close, _): return close } } @@ -1939,8 +1954,8 @@ extension Formatter { /// (`class`, `func`, `let`, `var`, etc.) var keyword: String { switch self { - case let .declaration(kind, _), - let .type(kind, _, _, _): + case let .declaration(kind, _, _), + let .type(kind, _, _, _, _): return kind case .conditionalCompilation: return "#if" @@ -1963,6 +1978,16 @@ extension Formatter { return parser.fullyQualifiedName(startingAt: nameIndex).name } + + /// The original range of the tokens of this declaration in the original source file + var originalRange: ClosedRange { + switch self { + case let .type(_, _, _, _, originalRange), + let .declaration(_, _, originalRange), + let .conditionalCompilation(_, _, _, originalRange): + return originalRange + } + } } /// The fully qualified name starting at the given index @@ -2114,12 +2139,19 @@ extension Formatter { return nil } - /// Parse all declarations in the formatter's token range + /// Parses all of the declarations in the file func parseDeclarations() -> [Declaration] { + guard !tokens.isEmpty else { return [] } + return parseDeclarations(in: ClosedRange(0 ..< tokens.count)) + } + + /// Parses the declarations in the given range. + func parseDeclarations(in range: ClosedRange) -> [Declaration] { var declarations = [Declaration]() - var startOfDeclaration = 0 + var startOfDeclaration = range.lowerBound forEachToken(onlyWhereEnabled: false) { i, token in - guard i >= startOfDeclaration, + guard range.contains(i), + i >= startOfDeclaration, token.isDeclarationTypeKeyword || token == .startOfScope("#if") else { return @@ -2128,76 +2160,109 @@ extension Formatter { let declarationKeyword = declarationType(at: i) ?? "#if" let endOfDeclaration = self.endOfDeclaration(atDeclarationKeyword: i, fallBackToEndOfScope: false) - let declarationRange = startOfDeclaration ... min(endOfDeclaration ?? .max, tokens.count - 1) + let declarationRange = startOfDeclaration ... min(endOfDeclaration ?? .max, range.upperBound) startOfDeclaration = declarationRange.upperBound + 1 - let declaration = Array(tokens[declarationRange]) - declarations.append(.declaration(kind: isEnabled ? declarationKeyword : "", tokens: declaration)) + + declarations.append(.declaration( + kind: isEnabled ? declarationKeyword : "", + tokens: Array(tokens[declarationRange]), + originalRange: declarationRange + )) } - if startOfDeclaration < tokens.count { - let declaration = Array(tokens[startOfDeclaration...]) - declarations.append(.declaration(kind: "", tokens: declaration)) + if startOfDeclaration < range.upperBound { + let declarationRange = startOfDeclaration ..< tokens.count + declarations.append(.declaration( + kind: "", + tokens: Array(tokens[declarationRange]), + originalRange: ClosedRange(declarationRange) + )) } return declarations.map { declaration in - let declarationParser = Formatter(declaration.tokens) - // Parses this declaration into a body of declarations separate from the start and end tokens - func parseBody(in bodyRange: ClosedRange) -> (start: [Token], body: [Declaration], end: [Token]) { - var startTokens = declarationParser.tokens[...bodyRange.lowerBound] - var bodyTokens = declarationParser.tokens[bodyRange.lowerBound + 1 ..< bodyRange.upperBound] - var endTokens = declarationParser.tokens[bodyRange.upperBound...] + func parseBody(in bodyRange: Range) -> (start: [Token], body: [Declaration], end: [Token]) { + var startTokens = Array(tokens[declaration.originalRange.lowerBound ..< bodyRange.lowerBound]) + var endTokens = Array(tokens[bodyRange.upperBound ... declaration.originalRange.upperBound]) + + guard !bodyRange.isEmpty else { + return (start: startTokens, body: [], end: endTokens) + } + + var bodyRange = ClosedRange(bodyRange) // Move the leading newlines from the `body` into the `start` tokens // so the first body token is the start of the first declaration - while bodyTokens.first?.isLinebreak == true { - startTokens.append(bodyTokens.removeFirst()) + while tokens[bodyRange].first?.isLinebreak == true { + startTokens.append(tokens[bodyRange.lowerBound]) + + if bodyRange.count > 1 { + bodyRange = (bodyRange.lowerBound + 1) ... bodyRange.upperBound + } else { + // If this was the last remaining token in the body, just return now. + // We can't have an empty `bodyRange`. + return (start: startTokens, body: [], end: endTokens) + } } // Move the closing brace's indentation token from the `body` into the `end` tokens - if bodyTokens.last?.isSpace == true { - endTokens.insert(bodyTokens.removeLast(), at: endTokens.startIndex) + if tokens[bodyRange].last?.isSpace == true { + endTokens.insert(tokens[bodyRange.upperBound], at: endTokens.startIndex) + + if bodyRange.count > 1 { + bodyRange = bodyRange.lowerBound ... (bodyRange.upperBound - 1) + } else { + // If this was the last remaining token in the body, just return now. + // We can't have an empty `bodyRange`. + return (start: startTokens, body: [], end: endTokens) + } } // Parse the inner body declarations of the type - let bodyDeclarations = Formatter(Array(bodyTokens)).parseDeclarations() + let bodyDeclarations = parseDeclarations(in: bodyRange) - return (Array(startTokens), bodyDeclarations, Array(endTokens)) + return (startTokens, bodyDeclarations, endTokens) } // If this declaration represents a type, we need to parse its inner declarations as well. let typelikeKeywords = ["class", "actor", "struct", "enum", "protocol", "extension"] if typelikeKeywords.contains(declaration.keyword), - let declarationTypeKeywordIndex = declarationParser - .index(after: -1, where: { $0.string == declaration.keyword }), - let startOfBody = declarationParser - .index(of: .startOfScope("{"), after: declarationTypeKeywordIndex), - let endOfBody = declarationParser.endOfScope(at: startOfBody) + let declarationTypeKeywordIndex = index( + in: Range(declaration.originalRange), + where: { $0.string == declaration.keyword } + ), + let bodyOpenBrace = index(of: .startOfScope("{"), after: declarationTypeKeywordIndex), + let bodyClosingBrace = endOfScope(at: bodyOpenBrace) { - let (startTokens, bodyDeclarations, endTokens) = parseBody(in: startOfBody ... endOfBody) + let bodyRange = (bodyOpenBrace + 1) ..< bodyClosingBrace + let (startTokens, bodyDeclarations, endTokens) = parseBody(in: bodyRange) return .type( kind: declaration.keyword, open: startTokens, body: bodyDeclarations, - close: endTokens + close: endTokens, + originalRange: declaration.originalRange ) } // If this declaration represents a conditional compilation block, // we also have to parse its inner declarations. else if declaration.keyword == "#if", - let declarationTypeKeywordIndex = declarationParser - .index(after: -1, where: { $0.string == declaration.keyword }), - let endOfBody = declarationParser.endOfScope(at: declarationTypeKeywordIndex) + let declarationTypeKeywordIndex = index( + in: Range(declaration.originalRange), + where: { $0.string == "#if" } + ), + let endOfBody = endOfScope(at: declarationTypeKeywordIndex) { - let startOfBody = declarationParser.endOfLine(at: declarationTypeKeywordIndex) - let (startTokens, bodyDeclarations, endTokens) = parseBody(in: startOfBody ... endOfBody) + let startOfBody = endOfLine(at: declarationTypeKeywordIndex) + let (startTokens, bodyDeclarations, endTokens) = parseBody(in: startOfBody ..< endOfBody) return .conditionalCompilation( open: startTokens, body: bodyDeclarations, - close: endTokens + close: endTokens, + originalRange: declaration.originalRange ) } else { return declaration diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 7cd1bfbbb..23886f1d9 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5696,13 +5696,14 @@ public struct _FormatRules { formatter.mapRecursiveDeclarations { declaration in switch declaration { // Organize the body of type declarations - case let .type(kind, open, body, close): + case let .type(kind, open, body, close, originalRange): let organizedType = formatter.organizeType((kind, open, body, close)) return .type( kind: organizedType.kind, open: organizedType.open, body: organizedType.body, - close: organizedType.close + close: organizedType.close, + originalRange: originalRange ) case .conditionalCompilation, .declaration: @@ -5719,7 +5720,7 @@ public struct _FormatRules { let declarations = formatter.parseDeclarations() let updatedDeclarations = formatter.mapRecursiveDeclarations(declarations) { declaration, _ in - guard case let .type("extension", open, body, close) = declaration else { + guard case let .type("extension", open, body, close, _) = declaration else { return declaration } @@ -5762,7 +5763,7 @@ public struct _FormatRules { if memberVisibility > extensionVisibility ?? .internal { // Check type being extended does not have lower visibility for d in declarations where d.name == declaration.name { - if case let .type(kind, _, _, _) = d { + if case let .type(kind, _, _, _, _) = d { if kind != "extension", formatter.visibility(of: d) ?? .internal < memberVisibility { // Cannot make extension with greater visibility than type being extended return declaration @@ -5837,7 +5838,7 @@ public struct _FormatRules { } for (index, declaration) in declarations.enumerated() { - guard case let .type(kind, open, body, close) = declaration else { continue } + guard case let .type(kind, open, body, close, _) = declaration else { continue } guard var typeName = declaration.name else { continue diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index 834ccef53..b87d676b1 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -1585,6 +1585,68 @@ class ParsingHelpersTests: XCTestCase { _ = Formatter(tokens).parseDeclarations() } + func testParseDeclarationRangesInType() { + let input = """ + class Foo { + let bar = "bar" + let baaz = "baaz" + } + """ + + let formatter = Formatter(tokenize(input)) + let declarations = formatter.parseDeclarations() + + XCTAssertEqual(declarations.count, 1) + XCTAssertEqual(declarations[0].originalRange, 0 ... 28) + + XCTAssertEqual(declarations[0].body?.count, 2) + + let barDeclarationRange = declarations[0].body![0].originalRange + XCTAssertEqual(barDeclarationRange, 6 ... 16) + XCTAssertEqual( + sourceCode(for: Array(formatter.tokens[barDeclarationRange])), + " let bar = \"bar\"\n" + ) + + let baazDeclarationRange = declarations[0].body![1].originalRange + XCTAssertEqual(baazDeclarationRange, 17 ... 27) + XCTAssertEqual( + sourceCode(for: Array(formatter.tokens[baazDeclarationRange])), + " let baaz = \"baaz\"\n" + ) + } + + func testParseDeclarationRangesInConditionalCompilation() { + let input = """ + #if DEBUG + let bar = "bar" + let baaz = "baaz" + #endif + """ + + let formatter = Formatter(tokenize(input)) + let declarations = formatter.parseDeclarations() + + XCTAssertEqual(declarations.count, 1) + XCTAssertEqual(declarations[0].originalRange, 0 ... 24) + + XCTAssertEqual(declarations[0].body?.count, 2) + + let barDeclarationRange = declarations[0].body![0].originalRange + XCTAssertEqual(barDeclarationRange, 4 ... 13) + XCTAssertEqual( + sourceCode(for: Array(formatter.tokens[barDeclarationRange])), + "let bar = \"bar\"\n" + ) + + let baazDeclarationRange = declarations[0].body![1].originalRange + XCTAssertEqual(baazDeclarationRange, 14 ... 23) + XCTAssertEqual( + sourceCode(for: Array(formatter.tokens[baazDeclarationRange])), + "let baaz = \"baaz\"\n" + ) + } + // MARK: declarationScope func testDeclarationScope_classAndGlobals() { From 6fe2247098d3e87ec580b3ddc38f0e5a8f85f2b9 Mon Sep 17 00:00:00 2001 From: Manny Lopez Date: Wed, 17 Jul 2024 18:23:15 -0700 Subject: [PATCH 12/52] Add unusedPrivateDeclaration rule to remove unused private and fileprivate declarations (#1750) Co-authored-by: Cal Stephens --- Rules.md | 19 +++ Sources/Examples.swift | 10 ++ Sources/FormattingHelpers.swift | 18 +++ Sources/ParsingHelpers.swift | 3 +- Sources/Rules.swift | 37 ++++++ Tests/RulesTests+Redundancy.swift | 199 ++++++++++++++++++++++++++++++ Tests/RulesTests.swift | 1 + 7 files changed, 286 insertions(+), 1 deletion(-) diff --git a/Rules.md b/Rules.md index 0e645066d..bfeb49905 100644 --- a/Rules.md +++ b/Rules.md @@ -104,6 +104,7 @@ * [propertyType](#propertyType) * [redundantProperty](#redundantProperty) * [sortSwitchCases](#sortSwitchCases) +* [unusedPrivateDeclaration](#unusedPrivateDeclaration) * [wrapConditionalBodies](#wrapConditionalBodies) * [wrapEnumCases](#wrapEnumCases) * [wrapMultilineConditionalAssignment](#wrapMultilineConditionalAssignment) @@ -2722,6 +2723,24 @@ Option | Description

+## unusedPrivateDeclaration + +Remove unused private and fileprivate declarations. + +
+Examples + +```diff + struct Foo { +- fileprivate var foo = "foo" +- fileprivate var baz = "baz" + var bar = "bar" + } +``` + +
+
+ ## void Use `Void` for type declarations and `()` for values. diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 852bb6654..e988b886e 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1971,4 +1971,14 @@ private struct Examples { } ``` """ + + let unusedPrivateDeclaration = """ + ```diff + struct Foo { + - fileprivate var foo = "foo" + - fileprivate var baz = "baz" + var bar = "bar" + } + ``` + """ } diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index d3f7075a7..3d0e854f1 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -1655,6 +1655,24 @@ extension Formatter { /// Helpers for recursively traversing the declaration hierarchy extension Formatter { + /// Recursively calls the `operation` for every declaration in the source file + func forEachRecursiveDeclaration(_ operation: (Declaration) -> Void) { + forEachRecursiveDeclarations(parseDeclarations(), operation) + } + + /// Applies `operation` to every recursive declaration of the given declarations + func forEachRecursiveDeclarations( + _ declarations: [Declaration], + _ operation: (Declaration) -> Void + ) { + for declaration in declarations { + operation(declaration) + if let body = declaration.body { + forEachRecursiveDeclarations(body, operation) + } + } + } + /// Applies `mapRecursiveDeclarations` in place func mapRecursiveDeclarations(with transform: (Declaration) -> Declaration) { let updatedDeclarations = mapRecursiveDeclarations(parseDeclarations()) { declaration, _ in diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index bf960e88d..2312a3888 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -1971,7 +1971,8 @@ extension Formatter { var name: String? { let parser = Formatter(openTokens) guard let keywordIndex = openTokens.firstIndex(of: .keyword(keyword)), - let nameIndex = parser.index(of: .identifier, after: keywordIndex) + let nameIndex = parser.index(of: .nonSpaceOrCommentOrLinebreak, after: keywordIndex), + parser.tokens[nameIndex].isIdentifierOrKeyword else { return nil } diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 23886f1d9..a16c51a75 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5163,6 +5163,43 @@ public struct _FormatRules { } } + /// Remove unused private and fileprivate declarations + public let unusedPrivateDeclaration = FormatRule( + help: "Remove unused private and fileprivate declarations.", + disabledByDefault: true + ) { formatter in + guard !formatter.options.fragment else { return } + var privateDeclarations: [Formatter.Declaration] = [] + var usage: [String: Int] = [:] + + formatter.forEachRecursiveDeclaration { declaration in + switch formatter.visibility(of: declaration) { + case .fileprivate, .private: + privateDeclarations.append(declaration) + case .none, .open, .public, .package, .internal: + break + } + } + + formatter.forEach(.identifier) { _, token in + usage[token.string, default: 0] += 1 + } + + for declaration in privateDeclarations.reversed() { + if let name = declaration.name, + let count = usage[name], + count < 2 + { + switch declaration { + case let .declaration(_, _, originalRange): + formatter.removeTokens(in: originalRange) + case .type, .conditionalCompilation: + break + } + } + } + } + /// Replace `fileprivate` with `private` where possible public let redundantFileprivate = FormatRule( help: "Prefer `private` over `fileprivate` where equivalent." diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index f51da998b..f92f5b576 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -10418,4 +10418,203 @@ class RedundancyTests: RulesTests { let options = FormatOptions(swiftVersion: "6.0") testFormatting(for: input, rule: FormatRules.redundantTypedThrows, options: options) } + + // MARK: - unusedPrivateDeclaration + + func testRemoveUnusedPrivate() { + let input = """ + struct Foo { + private var foo = "foo" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + } + + func testRemoveUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoNotRemoveUsedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testRemoveMultipleUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + fileprivate var baz = "baz" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + } + + func testRemoveMixedUsedAndUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + fileprivate var baz = "baz" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + let output = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoNotRemoveFilePrivateUsedInSameStruct() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + + func useFoo() { + print(foo) + } + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testRemoveUnusedFilePrivateInNestedStruct() { + let input = """ + struct Foo { + var bar = "bar" + + struct Inner { + fileprivate var foo = "foo" + } + } + """ + let output = """ + struct Foo { + var bar = "bar" + + struct Inner { + } + } + """ + testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration, exclude: ["emptyBraces"]) + } + + func testDoNotRemoveFilePrivateUsedInNestedStruct() { + let input = """ + struct Foo { + var bar = "bar" + + struct Inner { + fileprivate var foo = "foo" + func useFoo() { + print(foo) + } + } + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testRemoveUnusedFileprivateFunction() { + let input = """ + struct Foo { + var bar = "bar" + + fileprivate func sayHi() { + print("hi") + } + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, [output], rules: [FormatRules.unusedPrivateDeclaration, FormatRules.blankLinesAtEndOfScope]) + } + + func testDoNotRemoveUnusedFileprivateOperatorDefinition() { + let input = """ + private class Foo: Equatable { + fileprivate static func == (_: Foo, _: Foo) -> Bool { + return true + } + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testRemovePrivateDeclarationButDoNotRemoveUnusedPrivateType() { + let input = """ + private struct Foo { + private func bar() { + print("test") + } + } + """ + let output = """ + private struct Foo { + } + """ + + testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration, exclude: ["emptyBraces"]) + } + + func testRemovePrivateDeclarationButDoNotRemovePrivateExtension() { + let input = """ + private extension Foo { + private func doSomething() {} + func anotherFunction() {} + } + """ + let output = """ + private extension Foo { + func anotherFunction() {} + } + """ + + testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + } } diff --git a/Tests/RulesTests.swift b/Tests/RulesTests.swift index 31af304bb..3844b5d4d 100644 --- a/Tests/RulesTests.swift +++ b/Tests/RulesTests.swift @@ -76,6 +76,7 @@ class RulesTests: XCTestCase { + (rules.first?.name == "extensionAccessControl" ? [] : ["extensionAccessControl"]) + (rules.first?.name == "markTypes" ? [] : ["markTypes"]) + (rules.first?.name == "blockComments" ? [] : ["blockComments"]) + + (rules.first?.name == "unusedPrivateDeclaration" ? [] : ["unusedPrivateDeclaration"]) XCTAssertEqual(try format(input, rules: rules, options: options), output, file: file, line: line) XCTAssertEqual(try format(input, rules: FormatRules.all(except: exclude), options: options), output2, file: file, line: line) From 9f29782809499be8669f63bfd6528ab0f042fea0 Mon Sep 17 00:00:00 2001 From: oiuhr Date: Sat, 20 Jul 2024 01:13:25 +0300 Subject: [PATCH 13/52] `organizeDeclarations` custom naming & ordering (#1736) --- Rules.md | 16 ++ Sources/Examples.swift | 12 + Sources/FormattingHelpers.swift | 428 ++++++++++++++++++---------- Sources/OptionDescriptor.swift | 100 ++++++- Sources/Options.swift | 12 + Sources/Rules.swift | 1 + Tests/MetadataTests.swift | 4 + Tests/OptionDescriptorTests.swift | 48 ++++ Tests/RulesTests+Organization.swift | 357 +++++++++++++++++++++-- 9 files changed, 802 insertions(+), 176 deletions(-) diff --git a/Rules.md b/Rules.md index bfeb49905..3cde3c444 100644 --- a/Rules.md +++ b/Rules.md @@ -1370,10 +1370,26 @@ Option | Description `--enumthreshold` | Minimum line count to organize enum body. Defaults to 0 `--extensionlength` | Minimum line count to organize extension body. Defaults to 0 `--organizationmode` | Organize declarations by "visibility" (default) or "type" +`--visibilityorder` | Order for visibility groups inside declaration +`--typeorder` | Order for declaration type groups inside declaration +`--visibilitymarks` | Marks for visibility groups (public:Public Fields,..) +`--typemarks` | Marks for declaration type groups (classMethod:Baaz,..)
Examples +Default value for `--visibilityorder`: +`beforeMarks, instanceLifecycle, open, public, package, internal, fileprivate, private` + +**NOTE:** When providing custom arguments for `--visibilityorder` the following entries **should** be included: +`open, public, package, internal, fileprivate, private` + +Default value for `--typeorder`: +`beforeMarks, nestedType, staticProperty, staticPropertyWithBody, classPropertyWithBody, overriddenProperty, swiftUIPropertyWrapper, instanceProperty, instancePropertyWithBody, instanceLifecycle, swiftUIProperty, swiftUIMethod, overriddenMethod, staticMethod, classMethod, instanceMethod, conditionalCompilation` + +**NOTE:** When providing custom arguments for `--typeorder` the following entries **should** be included: +`beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod, conditionalCompilation` + `--organizationmode visibility` (default) ```diff diff --git a/Sources/Examples.swift b/Sources/Examples.swift index e988b886e..5e99855ab 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1291,6 +1291,18 @@ private struct Examples { """ let organizeDeclarations = """ + Default value for `--visibilityorder`: + `\(Formatter.VisibilityType.allCases.reduce(into: "") { $0 += ", \($1.rawValue)" }.dropFirst(2))` + + **NOTE:** When providing custom arguments for `--visibilityorder` the following entries **should** be included: + `\(Formatter.VisibilityType.essentialCases.reduce(into: "") { $0 += ", \($1.rawValue)" }.dropFirst(2))` + + Default value for `--typeorder`: + `\(Formatter.DeclarationType.allCases.reduce(into: "") { $0 += ", \($1.rawValue)" }.dropFirst(2))` + + **NOTE:** When providing custom arguments for `--typeorder` the following entries **should** be included: + `\(Formatter.DeclarationType.essentialCases.reduce(into: "") { $0 += ", \($1.rawValue)" }.dropFirst(2))` + `--organizationmode visibility` (default) ```diff diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 3d0e854f1..7f61b4667 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -1855,6 +1855,7 @@ extension Formatter { var visibility: VisibilityType var type: DeclarationType var order: Int + var comment: String? = nil func shouldBeMarked(in categories: Set, for mode: DeclarationOrganizationMode) -> Bool { guard type != .beforeMarks else { @@ -1874,13 +1875,13 @@ extension Formatter { "// " + template .replacingOccurrences( of: "%c", - with: mode == .type ? type.rawValue : visibility.rawValue + with: comment ?? (mode == .type ? type.markComment : visibility.markComment) ) } } /// The visibility of a declaration - enum Visibility: String, CaseIterable, Comparable { + public enum Visibility: String, CaseIterable, Comparable { case open case `public` case package @@ -1888,33 +1889,60 @@ extension Formatter { case `fileprivate` case `private` - static func < (lhs: Visibility, rhs: Visibility) -> Bool { + public static func < (lhs: Visibility, rhs: Visibility) -> Bool { allCases.firstIndex(of: lhs)! > allCases.firstIndex(of: rhs)! } } /// The visibility category of a declaration - enum VisibilityType: CaseIterable, Hashable { + /// + /// - Note: When adding a new visibility type, remember to also update the list in `Examples.swift`. + public enum VisibilityType: CaseIterable, Hashable, RawRepresentable { case visibility(Visibility) case explicit(DeclarationType) - var rawValue: String { + public init?(rawValue: String) { + if let visibility = Visibility(rawValue: rawValue) { + self = .visibility(visibility) + } else if let type = DeclarationType(rawValue: rawValue) { + self = .explicit(type) + } else { + return nil + } + } + + public var rawValue: String { + switch self { + case let .visibility(visibility): + return visibility.rawValue + case let .explicit(declarationType): + return declarationType.rawValue + } + } + + var markComment: String { switch self { case let .visibility(type): return type.rawValue.capitalized case let .explicit(type): - return type.rawValue + return type.markComment } } - static var allCases: [VisibilityType] { + public static var allCases: [VisibilityType] { [.explicit(.beforeMarks), .explicit(.instanceLifecycle)] + Visibility.allCases .map { .visibility($0) } } + + public static var essentialCases: [VisibilityType] { + Visibility.allCases.map { .visibility($0) } + } } - /// The type of a declaration - enum DeclarationType: String, CaseIterable { + /// The type of a declaration. + /// + /// - Note: When adding a new declaration type, remember to also update the list in `Examples.swift`. + public enum DeclarationType: String, CaseIterable { case beforeMarks case nestedType case staticProperty @@ -1933,7 +1961,7 @@ extension Formatter { case instanceMethod case conditionalCompilation - var rawValue: String { + var markComment: String { switch self { case .beforeMarks: return "Before Marks" @@ -1951,8 +1979,10 @@ extension Formatter { return "Lifecycle" case .overriddenMethod: return "Overridden Functions" - case .swiftUIProperty, .swiftUIMethod: - return "Content" + case .swiftUIProperty: + return "Content Properties" + case .swiftUIMethod: + return "Content Methods" case .swiftUIPropertyWrapper: return "SwiftUI Properties" case .instanceProperty: @@ -1970,21 +2000,25 @@ extension Formatter { } } - func shouldBeMarked(in declarationTypes: [DeclarationType]) -> Bool { - switch self { - case .beforeMarks, .classPropertyWithBody: - return false - case .swiftUIMethod: - return !declarationTypes.contains(.swiftUIProperty) - default: - return true - } + public static var essentialCases: [DeclarationType] { + [ + .beforeMarks, + .nestedType, + .instanceProperty, + .instanceLifecycle, + .instanceMethod, + .conditionalCompilation, + ] } } - func category(of declaration: Declaration, for mode: DeclarationOrganizationMode) -> Category { + func category( + of declaration: Declaration, + for mode: DeclarationOrganizationMode, + using order: ParsedOrder + ) -> Category { let visibility = self.visibility(of: declaration) ?? .internal - let type = self.type(of: declaration, for: mode) + let type = self.type(of: declaration, for: mode, mapping: order.map(\.type)) let visibilityType: VisibilityType switch mode { @@ -1998,21 +2032,91 @@ extension Formatter { visibilityType = .visibility(visibility) } - let order: Int + return category(from: order, for: visibilityType, with: type) + } + + typealias ParsedOrder = [Category] + func categoryOrder(for mode: DeclarationOrganizationMode) -> ParsedOrder { + typealias ParsedVisibilityMarks = [VisibilityType: String] + typealias ParsedTypeMarks = [DeclarationType: String] + + let visibilityTypes = options.visibilityOrder + .map { VisibilityType(rawValue: $0) } + .compactMap { $0 } + let declarationTypes = options.typeOrder + .map { DeclarationType(rawValue: $0) } + .compactMap { $0 } + + let customVisibilityMarks = options.customVisibilityMarks + let customTypeMarks = options.customTypeMarks + + let parsedVisibilityMarks: ParsedVisibilityMarks = parseMarks(for: customVisibilityMarks) + let parsedTypeMarks: ParsedTypeMarks = parseMarks(for: customTypeMarks) + switch mode { case .visibility: - order = VisibilityType.allCases.firstIndex(of: visibilityType)! * DeclarationType.allCases.count - + DeclarationType.allCases.firstIndex(of: type)! + return flatten(primary: visibilityTypes, using: declarationTypes) + .map { offset, element in + Category( + visibility: element.0, + type: element.1, + order: offset, + comment: parsedVisibilityMarks[element.0] + ) + } case .type: - order = DeclarationType.allCases.firstIndex(of: type)! * VisibilityType.allCases.count - + VisibilityType.allCases.firstIndex(of: visibilityType)! + return flatten(primary: declarationTypes, using: visibilityTypes) + .map { offset, element in + Category( + visibility: element.1, + type: element.0, + order: offset, + comment: parsedTypeMarks[element.0] + ) + } } + } - return Category( - visibility: visibilityType, - type: type, - order: order - ) + func parseMarks( + for options: Set + ) -> [T: String] where T.RawValue == String { + options.map { customMarkEntry -> (T, String)? in + let split = customMarkEntry.split(separator: ":", maxSplits: 1) + + guard split.count == 2, + let rawValue = split.first, + let mark = split.last, + let concreteType = T(rawValue: String(rawValue)) + else { return nil } + + return (concreteType, String(mark)) + } + .compactMap { $0 } + .reduce(into: [:]) { dictionary, option in + dictionary[option.0] = option.1 + } + } + + func flatten( + primary: C1, + using secondary: C2 + ) -> EnumeratedSequence<[(C1.Element, C2.Element)]> { + primary + .map { p in + secondary.map { s in + (p, s) + } + } + .reduce([], +) + .enumerated() + } + + func category( + from order: ParsedOrder, + for visibility: VisibilityType, + with type: DeclarationType + ) -> Category { + order.first { $0.visibility == visibility && $0.type == type }! } func visibility(of declaration: Declaration) -> Visibility? { @@ -2050,145 +2154,158 @@ extension Formatter { } } - func type(of declaration: Declaration, for mode: DeclarationOrganizationMode) -> DeclarationType { + func type( + of declaration: Declaration, + for mode: DeclarationOrganizationMode, + mapping availableTypes: [DeclarationType] + ) -> DeclarationType { switch declaration { case let .type(keyword, _, _, _, _): return options.beforeMarks.contains(keyword) ? .beforeMarks : .nestedType case let .declaration(keyword, tokens, _): - guard let declarationTypeTokenIndex = tokens.firstIndex(of: .keyword(keyword)) else { - return .beforeMarks + return type(of: keyword, with: tokens, mapping: availableTypes) + + case let .conditionalCompilation(_, body, _, _): + // Prefer treating conditional compliation blocks as having + // the property type of the first declaration in their body. + guard let firstDeclarationInBlock = body.first else { + return .conditionalCompilation } - let declarationParser = Formatter(tokens) - let declarationTypeToken = declarationParser.tokens[declarationTypeTokenIndex] + return type(of: firstDeclarationInBlock, for: mode, mapping: availableTypes) + } + } + + func type( + of keyword: String, + with tokens: [Token], + mapping availableTypes: [DeclarationType] + ) -> DeclarationType { + guard let declarationTypeTokenIndex = tokens.firstIndex(of: .keyword(keyword)) else { + return .beforeMarks + } - if keyword == "case" || options.beforeMarks.contains(keyword) { - return .beforeMarks - } + let declarationParser = Formatter(tokens) + let declarationTypeToken = declarationParser.tokens[declarationTypeTokenIndex] - for token in declarationParser.tokens { - if options.beforeMarks.contains(token.string) { return .beforeMarks } - } + if keyword == "case" || options.beforeMarks.contains(keyword) { + return .beforeMarks + } - let isStaticDeclaration = declarationParser.index( - of: .keyword("static"), - before: declarationTypeTokenIndex - ) != nil + for token in declarationParser.tokens { + if options.beforeMarks.contains(token.string) { return .beforeMarks } + } - let isClassDeclaration = declarationParser.index( - of: .keyword("class"), - before: declarationTypeTokenIndex - ) != nil + let isStaticDeclaration = declarationParser.index( + of: .keyword("static"), + before: declarationTypeTokenIndex + ) != nil - let isOverriddenDeclaration = mode == .type && declarationParser.index( - of: .identifier("override"), - before: declarationTypeTokenIndex - ) != nil + let isClassDeclaration = declarationParser.index( + of: .keyword("class"), + before: declarationTypeTokenIndex + ) != nil - let isViewDeclaration = mode == .type && { - guard let someKeywordIndex = declarationParser.index( - of: .identifier("some"), after: declarationTypeTokenIndex - ) else { return false } + let isOverriddenDeclaration = declarationParser.index( + of: .identifier("override"), + before: declarationTypeTokenIndex + ) != nil - return declarationParser.index(of: .identifier("View"), after: someKeywordIndex) != nil - }() + let isDeclarationWithBody: Bool = { + // If there is a code block at the end of the declaration that is _not_ a closure, + // then this declaration has a body. + if let lastClosingBraceIndex = declarationParser.index(of: .endOfScope("}"), before: declarationParser.tokens.count), + let lastOpeningBraceIndex = declarationParser.index(of: .startOfScope("{"), before: lastClosingBraceIndex), + declarationTypeTokenIndex < lastOpeningBraceIndex, + declarationTypeTokenIndex < lastClosingBraceIndex, + !declarationParser.isStartOfClosure(at: lastOpeningBraceIndex) { return true } - let isSwiftUIPropertyWrapper = mode == .visibility && - declarationParser.modifiersForDeclaration(at: declarationTypeTokenIndex, contains: { _, modifier in - swiftUIPropertyWrappers.contains(modifier) - }) + return false + }() - switch declarationTypeToken { - // Properties and property-like declarations - case .keyword("let"), .keyword("var"), - .keyword("operator"), .keyword("precedencegroup"): + let isViewDeclaration: Bool = { + guard let someKeywordIndex = declarationParser.index( + of: .identifier("some"), after: declarationTypeTokenIndex + ) else { return false } - if isOverriddenDeclaration { - return .overriddenProperty - } + return declarationParser.index(of: .identifier("View"), after: someKeywordIndex) != nil + }() - var hasBody: Bool - // If there is a code block at the end of the declaration that is _not_ a closure, - // then this declaration has a body. - if let lastClosingBraceIndex = declarationParser.index(of: .endOfScope("}"), before: declarationParser.tokens.count), - let lastOpeningBraceIndex = declarationParser.index(of: .startOfScope("{"), before: lastClosingBraceIndex), - declarationTypeTokenIndex < lastOpeningBraceIndex, - declarationTypeTokenIndex < lastClosingBraceIndex, - !declarationParser.isStartOfClosure(at: lastOpeningBraceIndex) - { - hasBody = true - } else { - hasBody = false - } + let isSwiftUIPropertyWrapper = declarationParser + .modifiersForDeclaration(at: declarationTypeTokenIndex) { _, modifier in + swiftUIPropertyWrappers.contains(modifier) + } - if isStaticDeclaration { - if hasBody { - return .staticPropertyWithBody - } else { - return .staticProperty - } - } else if isClassDeclaration { - // Interestingly, Swift does not support stored class properties - // so there's no such thing as a class property without a body. - // https://forums.swift.org/t/class-properties/16539/11 - return .classPropertyWithBody - } else if isViewDeclaration { - return .swiftUIProperty - } else if !hasBody, isSwiftUIPropertyWrapper { - return .swiftUIPropertyWrapper - } else { - if hasBody { - return .instancePropertyWithBody - } else { - return .instanceProperty - } - } + switch declarationTypeToken { + // Properties and property-like declarations + case .keyword("let"), .keyword("var"), + .keyword("operator"), .keyword("precedencegroup"): - // Functions and function-like declarations - case .keyword("func"), .keyword("subscript"): - // The user can also provide specific instance method names to place in Lifecycle - // - In the function declaration grammar, the function name always - // immediately follows the `func` keyword: - // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_function-name - if let methodName = declarationParser.next(.nonSpaceOrCommentOrLinebreak, after: declarationTypeTokenIndex), - options.lifecycleMethods.contains(methodName.string) - { - return .instanceLifecycle - } else if isOverriddenDeclaration { - return .overriddenMethod - } else if isStaticDeclaration { - return .staticMethod - } else if isClassDeclaration { - return .classMethod - } else if isViewDeclaration { - return .swiftUIMethod - } else { - return .instanceMethod - } + if isOverriddenDeclaration && availableTypes.contains(.overriddenProperty) { + return .overriddenProperty + } + if isStaticDeclaration && isDeclarationWithBody && availableTypes.contains(.staticPropertyWithBody) { + return .staticPropertyWithBody + } + if isStaticDeclaration && availableTypes.contains(.staticProperty) { + return .staticProperty + } + if isClassDeclaration && availableTypes.contains(.classPropertyWithBody) { + // Interestingly, Swift does not support stored class properties + // so there's no such thing as a class property without a body. + // https://forums.swift.org/t/class-properties/16539/11 + return .classPropertyWithBody + } + if isViewDeclaration && availableTypes.contains(.swiftUIProperty) { + return .swiftUIProperty + } + if !isDeclarationWithBody && isSwiftUIPropertyWrapper && availableTypes.contains(.swiftUIPropertyWrapper) { + return .swiftUIPropertyWrapper + } + if isDeclarationWithBody && availableTypes.contains(.instancePropertyWithBody) { + return .instancePropertyWithBody + } - case .keyword("init"), .keyword("deinit"): + return .instanceProperty + + // Functions and function-like declarations + case .keyword("func"), .keyword("subscript"): + // The user can also provide specific instance method names to place in Lifecycle + // - In the function declaration grammar, the function name always + // immediately follows the `func` keyword: + // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_function-name + let methodName = declarationParser.next(.nonSpaceOrCommentOrLinebreak, after: declarationTypeTokenIndex) + if let methodName = methodName, options.lifecycleMethods.contains(methodName.string) { return .instanceLifecycle + } + if isOverriddenDeclaration && availableTypes.contains(.overriddenMethod) { + return .overriddenMethod + } + if isStaticDeclaration && availableTypes.contains(.staticMethod) { + return .staticMethod + } + if isClassDeclaration && availableTypes.contains(.classMethod) { + return .classMethod + } + if isViewDeclaration && availableTypes.contains(.swiftUIMethod) { + return .swiftUIMethod + } - // Type-like declarations - case .keyword("typealias"): - return .nestedType + return .instanceMethod - case .keyword("case"): - return .beforeMarks + case .keyword("init"), .keyword("deinit"): + return .instanceLifecycle - default: - return .beforeMarks - } + // Type-like declarations + case .keyword("typealias"): + return .nestedType - case let .conditionalCompilation(_, body, _, _): - // Prefer treating conditional compliation blocks as having - // the property type of the first declaration in their body. - if let firstDeclarationInBlock = body.first { - return type(of: firstDeclarationInBlock, for: mode) - } else { - return .conditionalCompilation - } + case .keyword("case"): + return .beforeMarks + + default: + return .beforeMarks } } @@ -2223,7 +2340,11 @@ extension Formatter { } /// Removes any existing category separators from the given declarations - func removeExistingCategorySeparators(from typeBody: [Declaration], with mode: DeclarationOrganizationMode) -> [Declaration] { + func removeExistingCategorySeparators( + from typeBody: [Declaration], + with mode: DeclarationOrganizationMode, + using order: ParsedOrder + ) -> [Declaration] { var typeBody = typeBody for (declarationIndex, declaration) in typeBody.enumerated() { @@ -2243,7 +2364,7 @@ extension Formatter { Category(visibility: $0, type: .classMethod, order: 0) } + DeclarationType.allCases.map { Category(visibility: .visibility(.open), type: $0, order: 0) - } + } + order.filter { $0.comment != nil } ).flatMap { Array(Set([ // The user's specific category separator template @@ -2357,15 +2478,22 @@ extension Formatter { var typeOpeningTokens = typeDeclaration.open let typeClosingTokens = typeDeclaration.close + // Parse category order from options + let categoryOrder = self.categoryOrder(for: mode) + // Remove all of the existing category separators, so they can be readded // at the correct location after sorting the declarations. - let bodyWithoutCategorySeparators = removeExistingCategorySeparators(from: typeDeclaration.body, with: mode) + let bodyWithoutCategorySeparators = removeExistingCategorySeparators( + from: typeDeclaration.body, + with: mode, + using: categoryOrder + ) // Categorize each of the declarations into their primary groups typealias CategorizedDeclarations = [(declaration: Declaration, category: Category)] let categorizedDeclarations = bodyWithoutCategorySeparators.map { - (declaration: $0, category: category(of: $0, for: mode)) + (declaration: $0, category: category(of: $0, for: mode, using: categoryOrder)) } // If this type has a leading :sort directive, we sort alphabetically diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index ef8483524..ff38f183f 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -254,7 +254,7 @@ class OptionDescriptor { help: String, deprecationMessage: String? = nil, keyPath: WritableKeyPath, - validate: @escaping (String) throws -> Void = { _ in }) + validateArray: @escaping ([String]) throws -> Void = { _ in }) { self.argumentName = argumentName self.displayName = displayName @@ -263,12 +263,7 @@ class OptionDescriptor { type = .array toOptions = { value, options in let values = parseCommaDelimitedList(value) - for (index, value) in values.enumerated() { - if values[0 ..< index].contains(value) { - throw FormatError.options("Duplicate value '\(value)'") - } - try validate(value) - } + try validateArray(values) options[keyPath: keyPath] = values } fromOptions = { options in @@ -276,6 +271,29 @@ class OptionDescriptor { } } + convenience init(argumentName: String, + displayName: String, + help: String, + deprecationMessage _: String? = nil, + keyPath: WritableKeyPath, + validate: @escaping (String) throws -> Void = { _ in }) + { + self.init( + argumentName: argumentName, + displayName: displayName, + help: help, + keyPath: keyPath, + validateArray: { values in + for (index, value) in values.enumerated() { + if values[0 ..< index].contains(value) { + throw FormatError.options("Duplicate value '\(value)'") + } + try validate(value) + } + } + ) + } + init(argumentName: String, displayName: String, help: String, @@ -869,6 +887,74 @@ struct _Descriptors { help: "Organize declarations by \"visibility\" (default) or \"type\"", keyPath: \.organizationMode ) + let visibilityOrder = OptionDescriptor( + argumentName: "visibilityorder", + displayName: "Organization Order For Visibility", + help: "Order for visibility groups inside declaration", + keyPath: \.visibilityOrder, + validateArray: { order in + let essentials = Formatter.VisibilityType.essentialCases.map(\.rawValue) + for type in essentials { + guard order.contains(type) else { + throw FormatError.options("--visibilityorder expects \(type) to be included") + } + } + for type in order { + guard let concrete = Formatter.VisibilityType(rawValue: type) else { + let errorMessage = "'\(type)' is not a valid parameter for --visibilityorder" + guard let match = type.bestMatches(in: essentials).first else { + throw FormatError.options(errorMessage) + } + throw FormatError.options(errorMessage + ". Did you mean '\(match)?'") + } + } + } + ) + let typeOrder = OptionDescriptor( + argumentName: "typeorder", + displayName: "Organization Order For Declaration Types", + help: "Order for declaration type groups inside declaration", + keyPath: \.typeOrder, + validateArray: { order in + let essentials = Formatter.DeclarationType.essentialCases.map(\.rawValue) + for type in essentials { + guard order.contains(type) else { + throw FormatError.options("--typeorder expects \(type) to be included") + } + } + for type in order { + guard let concrete = Formatter.DeclarationType(rawValue: type) else { + let errorMessage = "'\(type)' is not a valid parameter for --typeorder" + guard let match = type.bestMatches(in: essentials).first else { + throw FormatError.options(errorMessage) + } + throw FormatError.options(errorMessage + ". Did you mean '\(match)?'") + } + } + } + ) + let customVisibilityMarks = OptionDescriptor( + argumentName: "visibilitymarks", + displayName: "Custom Visibility Marks", + help: "Marks for visibility groups (public:Public Fields,..)", + keyPath: \.customVisibilityMarks, + validate: { + if $0.split(separator: ":", maxSplits: 1).count != 2 { + throw FormatError.options("--visibilitymarks expects : argument") + } + } + ) + let customTypeMarks = OptionDescriptor( + argumentName: "typemarks", + displayName: "Custom Type Marks", + help: "Marks for declaration type groups (classMethod:Baaz,..)", + keyPath: \.customTypeMarks, + validate: { + if $0.split(separator: ":", maxSplits: 1).count != 2 { + throw FormatError.options("--visibilitymarks expects : argument") + } + } + ) let alphabeticallySortedDeclarationPatterns = OptionDescriptor( argumentName: "sortedpatterns", displayName: "Declaration Name Patterns To Sort Alphabetically", diff --git a/Sources/Options.swift b/Sources/Options.swift index a9ab64cc3..6f852f76e 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -667,6 +667,10 @@ public struct FormatOptions: CustomStringConvertible { public var organizeEnumThreshold: Int public var organizeExtensionThreshold: Int public var organizationMode: DeclarationOrganizationMode + public var visibilityOrder: [String] + public var typeOrder: [String] + public var customVisibilityMarks: Set + public var customTypeMarks: Set public var alphabeticallySortedDeclarationPatterns: Set public var yodaSwap: YodaMode public var extensionACLPlacement: ExtensionACLPlacement @@ -786,6 +790,10 @@ public struct FormatOptions: CustomStringConvertible { organizeEnumThreshold: Int = 0, organizeExtensionThreshold: Int = 0, organizationMode: DeclarationOrganizationMode = .visibility, + visibilityOrder: [String] = Formatter.VisibilityType.allCases.map(\.rawValue), + typeOrder: [String] = Formatter.DeclarationType.allCases.map(\.rawValue), + customVisibilityMarks: Set = [], + customTypeMarks: Set = [], alphabeticallySortedDeclarationPatterns: Set = [], yodaSwap: YodaMode = .always, extensionACLPlacement: ExtensionACLPlacement = .onExtension, @@ -895,6 +903,10 @@ public struct FormatOptions: CustomStringConvertible { self.organizeEnumThreshold = organizeEnumThreshold self.organizeExtensionThreshold = organizeExtensionThreshold self.organizationMode = organizationMode + self.visibilityOrder = visibilityOrder + self.typeOrder = typeOrder + self.customVisibilityMarks = customVisibilityMarks + self.customTypeMarks = customTypeMarks self.alphabeticallySortedDeclarationPatterns = alphabeticallySortedDeclarationPatterns self.yodaSwap = yodaSwap self.extensionACLPlacement = extensionACLPlacement diff --git a/Sources/Rules.swift b/Sources/Rules.swift index a16c51a75..a87789644 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5725,6 +5725,7 @@ public struct _FormatRules { "categorymark", "markcategories", "beforemarks", "lifecycle", "organizetypes", "structthreshold", "classthreshold", "enumthreshold", "extensionlength", "organizationmode", + "visibilityorder", "typeorder", "visibilitymarks", "typemarks", ], sharedOptions: ["sortedpatterns", "lineaftermarks"] ) { formatter in diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index 42f83f228..b9135383f 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -233,6 +233,10 @@ class MetadataTests: XCTestCase { Descriptors.lineAfterMarks, Descriptors.organizationMode, Descriptors.alphabeticallySortedDeclarationPatterns, + Descriptors.visibilityOrder, + Descriptors.typeOrder, + Descriptors.customVisibilityMarks, + Descriptors.customTypeMarks, ] case .identifier("removeSelf"): referencedOptions += [ diff --git a/Tests/OptionDescriptorTests.swift b/Tests/OptionDescriptorTests.swift index 06ab486cc..253994544 100644 --- a/Tests/OptionDescriptorTests.swift +++ b/Tests/OptionDescriptorTests.swift @@ -287,6 +287,54 @@ class OptionDescriptorTests: XCTestCase { XCTAssertNoThrow(try descriptor.toOptions(swiftLintDefaults, &options)) } + func testVisibilityOrder() { + let argument = "instanceLifecycle, beforeMarks, open, public, package, internal, private, fileprivate" + + let descriptor = Descriptors.visibilityOrder + var options = FormatOptions() + XCTAssertNoThrow(try descriptor.toOptions(argument, &options)) + } + + func testVisibilityOrderUnparseableArgument() { + let argument = "_instanceLifecycle, lifecycle, open, public, package, internal, private, fileprivate" + + let descriptor = Descriptors.visibilityOrder + var options = FormatOptions() + XCTAssertThrowsError(try descriptor.toOptions(argument, &options)) + } + + func testVisibilityOrderMissingEssentials() { + let argument = "beforemarks, lifecycle, open, public, internal, private, fileprivate" + + let descriptor = Descriptors.visibilityOrder + var options = FormatOptions() + XCTAssertThrowsError(try descriptor.toOptions(argument, &options)) + } + + func testTypeOrder() { + let argument = "beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod, conditionalCompilation" + + let descriptor = Descriptors.typeOrder + var options = FormatOptions() + XCTAssertNoThrow(try descriptor.toOptions(argument, &options)) + } + + func testTypeOrderUnparseableArgument() { + let argument = "_beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod, conditionalCompilation" + + let descriptor = Descriptors.typeOrder + var options = FormatOptions() + XCTAssertThrowsError(try descriptor.toOptions(argument, &options)) + } + + func testTypeOrderMissingEssentials() { + let argument = "beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod" + + let descriptor = Descriptors.typeOrder + var options = FormatOptions() + XCTAssertThrowsError(try descriptor.toOptions(argument, &options)) + } + func testFormatOptionsDescriptionConsistency() { let options1 = FormatOptions(selfRequired: ["foo", "bar", "baz"]) let options2 = FormatOptions(selfRequired: ["baz", "bar", "foo"]) diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index 8690c0aa4..84d2b8de7 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -163,18 +163,18 @@ class OrganizationTests: RulesTests { let input = """ class Test { - var a = "" - override var b: Any? { nil } - func foo() -> Foo { - Foo() - } + var a = "" override func bar() -> Bar { Bar() } + func foo() -> Foo { + Foo() + } + func baaz() -> Baaz { Baaz() } @@ -253,14 +253,14 @@ class OrganizationTests: RulesTests { let input = """ class Test { - func foo() -> Foo { - Foo() - } - func bar() -> some View { EmptyView() } + func foo() -> Foo { + Foo() + } + func baaz() -> Baaz { Baaz() } @@ -303,11 +303,13 @@ class OrganizationTests: RulesTests { let output = """ struct ContentView: View { - // MARK: Properties + // MARK: SwiftUI Properties @State var isOn: Bool = false + // MARK: Properties + private var label: String // MARK: Lifecycle @@ -316,7 +318,7 @@ class OrganizationTests: RulesTests { self.label = label } - // MARK: Content + // MARK: Content Properties @ViewBuilder var body: some View { @@ -371,11 +373,13 @@ class OrganizationTests: RulesTests { let output = """ struct Modifier: ViewModifier { - // MARK: Properties + // MARK: SwiftUI Properties @State var isOn: Bool = false + // MARK: Properties + private var label: String // MARK: Lifecycle @@ -384,7 +388,15 @@ class OrganizationTests: RulesTests { self.label = label } - // MARK: Content + // MARK: Content Properties + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + .fixedSize() + } + + // MARK: Content Methods func body(content: Content) -> some View { content @@ -393,12 +405,6 @@ class OrganizationTests: RulesTests { } } - @ViewBuilder - private var toggle: some View { - Toggle(label, isOn: $isOn) - .fixedSize() - } - } """ @@ -410,6 +416,319 @@ class OrganizationTests: RulesTests { ) } + func testCustomOrganizationInVisibilityOrder() { + let input = """ + class Foo { + public func bar() {} + func baz() {} + private func quux() {} + } + """ + + let output = """ + class Foo { + + // MARK: Private + + private func quux() {} + + // MARK: Internal + + func baz() {} + + // MARK: Public + + public func bar() {} + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + visibilityOrder: ["private", "internal", "public"] + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + + func testCustomOrganizationInVisibilityOrderWithParametrizedTypeOrder() { + let input = """ + class Foo { + + // MARK: Private + + private func quux() {} + + // MARK: Internal + + var baaz: Baaz + + func baz() {} + + // MARK: Public + + public func bar() {} + } + """ + + let output = """ + class Foo { + + // MARK: Private + + private func quux() {} + + // MARK: Internal + + func baz() {} + + var baaz: Baaz + + // MARK: Public + + public func bar() {} + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + visibilityOrder: ["private", "internal", "public"], + typeOrder: ["instanceMethod", "instanceProperty"] + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + + func testCustomOrganizationInTypeOrder() { + let input = """ + class Foo { + private func quux() {} + var baaz: Baaz + func baz() {} + init() + override public func baar() + public func bar() {} + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() + + // MARK: Functions + + public func bar() {} + + func baz() {} + + private func quux() {} + + // MARK: Properties + + var baaz: Baaz + + // MARK: Overridden Functions + + override public func baar() + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + organizationMode: .type, + typeOrder: ["instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"] + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + + func testOrganizeDeclarationsIgnoresNotDefinedCategories() { + let input = """ + class Foo { + private func quux() {} + var baaz: Baaz + func baz() {} + init() + override public func baar() + public func bar() {} + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() + + // MARK: Functions + + override public func baar() + public func bar() {} + + func baz() {} + + private func quux() {} + + // MARK: Properties + + var baaz: Baaz + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + organizationMode: .type, + typeOrder: ["instanceLifecycle", "instanceMethod", "instanceProperty"] + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + + func testCustomOrganizationInTypeOrderWithParametrizedVisibilityOrder() { + let input = """ + class Foo { + private func quux() {} + var baaz: Baaz + private var fooo: Fooo + func baz() {} + init() + override public func baar() + public func bar() {} + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() + + // MARK: Functions + + private func quux() {} + + func baz() {} + + public func bar() {} + + // MARK: Properties + + private var fooo: Fooo + + var baaz: Baaz + + // MARK: Overridden Functions + + override public func baar() + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + organizationMode: .type, + visibilityOrder: ["private", "internal", "public"], + typeOrder: ["instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"] + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + + func testCustomCategoryNamesInVisibilityOrder() { + let input = """ + class Foo { + public var bar: Bar + init(bar: Bar) { + self.bar = bar + } + func baaz() {} + } + """ + + let output = """ + class Foo { + + // MARK: Init + + init(bar: Bar) { + self.bar = bar + } + + // MARK: Public_Group + + public var bar: Bar + + // MARK: Internal + + func baaz() {} + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + organizationMode: .visibility, + customVisibilityMarks: ["instanceLifecycle:Init", "public:Public_Group"] + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + + func testCustomCategoryNamesInTypeOrder() { + let input = """ + class Foo { + public var bar: Bar + init(bar: Bar) { + self.bar = bar + } + func baaz() {} + } + """ + + let output = """ + class Foo { + + // MARK: Bar_Bar + + public var bar: Bar + + // MARK: Init + + init(bar: Bar) { + self.bar = bar + } + + // MARK: Buuuz Lightyeeeaaar + + func baaz() {} + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + organizationMode: .type, + customTypeMarks: ["instanceLifecycle:Init", "instanceProperty:Bar_Bar", "instanceMethod:Buuuz Lightyeeeaaar"] + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + func testClassNestedInClassIsOrganized() { let input = """ public class Foo { From 51b369af8d77c89ee4379502d1091c118b85409e Mon Sep 17 00:00:00 2001 From: Manny Lopez Date: Fri, 19 Jul 2024 21:52:01 -0700 Subject: [PATCH 14/52] Add allowList to unusedPrivateDeclaration (#1761) --- Sources/Rules.swift | 4 +++- Tests/RulesTests+Redundancy.swift | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Sources/Rules.swift b/Sources/Rules.swift index a87789644..b5e4d3c0d 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5169,6 +5169,7 @@ public struct _FormatRules { disabledByDefault: true ) { formatter in guard !formatter.options.fragment else { return } + let allowList = ["let", "var", "func"] var privateDeclarations: [Formatter.Declaration] = [] var usage: [String: Int] = [:] @@ -5191,7 +5192,8 @@ public struct _FormatRules { count < 2 { switch declaration { - case let .declaration(_, _, originalRange): + case let .declaration(kind, _, originalRange): + guard allowList.contains(kind) else { break } formatter.removeTokens(in: originalRange) case .type, .conditionalCompilation: break diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index f92f5b576..0076b6058 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -10617,4 +10617,14 @@ class RedundancyTests: RulesTests { testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) } + + func testDoNotRemovePrivateTypealias() { + let input = """ + enum Foo { + struct Bar {} + private typealias Baz = Bar + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } } From 4781d16300df24951e87a4d70196ccd5bbdf614d Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sat, 20 Jul 2024 09:22:34 -0700 Subject: [PATCH 15/52] Improve expressiveness of --typeorder and --visibilityorder options (#1760) --- Rules.md | 18 ++- Sources/Examples.swift | 22 +-- Sources/FormattingHelpers.swift | 155 +++++++++++++-------- Sources/OptionDescriptor.swift | 60 ++++++-- Sources/Options.swift | 8 +- Tests/OptionDescriptorTests.swift | 22 ++- Tests/RulesTests+Organization.swift | 204 +++++++++++++++++++++++++++- 7 files changed, 399 insertions(+), 90 deletions(-) diff --git a/Rules.md b/Rules.md index 3cde3c444..7610b8074 100644 --- a/Rules.md +++ b/Rules.md @@ -1378,17 +1378,23 @@ Option | Description
Examples -Default value for `--visibilityorder`: +Default value for `--visibilityorder` when using `--organizationmode visibility`: `beforeMarks, instanceLifecycle, open, public, package, internal, fileprivate, private` -**NOTE:** When providing custom arguments for `--visibilityorder` the following entries **should** be included: +Default value for `--visibilityorder` when using `--organizationmode type`: `open, public, package, internal, fileprivate, private` -Default value for `--typeorder`: -`beforeMarks, nestedType, staticProperty, staticPropertyWithBody, classPropertyWithBody, overriddenProperty, swiftUIPropertyWrapper, instanceProperty, instancePropertyWithBody, instanceLifecycle, swiftUIProperty, swiftUIMethod, overriddenMethod, staticMethod, classMethod, instanceMethod, conditionalCompilation` +**NOTE:** When providing custom arguments for `--visibilityorder` the following entries must be included: +`open, public, package, internal, fileprivate, private` + +Default value for `--typeorder` when using `--organizationmode visibility`: +`nestedType, staticProperty, staticPropertyWithBody, classPropertyWithBody, overriddenProperty, swiftUIPropertyWrapper, instanceProperty, instancePropertyWithBody, swiftUIProperty, swiftUIMethod, overriddenMethod, staticMethod, classMethod, instanceMethod` + +Default value for `--typeorder` when using `--organizationmode type`: +`beforeMarks, nestedType, staticProperty, staticPropertyWithBody, classPropertyWithBody, overriddenProperty, swiftUIPropertyWrapper, instanceProperty, instancePropertyWithBody, instanceLifecycle, swiftUIProperty, swiftUIMethod, overriddenMethod, staticMethod, classMethod, instanceMethod` -**NOTE:** When providing custom arguments for `--typeorder` the following entries **should** be included: -`beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod, conditionalCompilation` +**NOTE:** The follow declaration types must be included in either `--typeorder` or `--visibilityorder`: +`beforeMarks, nestedType, instanceLifecycle, instanceProperty, instanceMethod` `--organizationmode visibility` (default) diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 5e99855ab..1e94e42de 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1291,17 +1291,23 @@ private struct Examples { """ let organizeDeclarations = """ - Default value for `--visibilityorder`: - `\(Formatter.VisibilityType.allCases.reduce(into: "") { $0 += ", \($1.rawValue)" }.dropFirst(2))` + Default value for `--visibilityorder` when using `--organizationmode visibility`: + `\(Formatter.VisibilityType.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` - **NOTE:** When providing custom arguments for `--visibilityorder` the following entries **should** be included: - `\(Formatter.VisibilityType.essentialCases.reduce(into: "") { $0 += ", \($1.rawValue)" }.dropFirst(2))` + Default value for `--visibilityorder` when using `--organizationmode type`: + `\(Formatter.VisibilityType.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` - Default value for `--typeorder`: - `\(Formatter.DeclarationType.allCases.reduce(into: "") { $0 += ", \($1.rawValue)" }.dropFirst(2))` + **NOTE:** When providing custom arguments for `--visibilityorder` the following entries must be included: + `\(Formatter.VisibilityType.essentialCases.map { $0.rawValue }.joined(separator: ", "))` - **NOTE:** When providing custom arguments for `--typeorder` the following entries **should** be included: - `\(Formatter.DeclarationType.essentialCases.reduce(into: "") { $0 += ", \($1.rawValue)" }.dropFirst(2))` + Default value for `--typeorder` when using `--organizationmode visibility`: + `\(Formatter.DeclarationType.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` + + Default value for `--typeorder` when using `--organizationmode type`: + `\(Formatter.DeclarationType.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` + + **NOTE:** The follow declaration types must be included in either `--typeorder` or `--visibilityorder`: + `\(Formatter.DeclarationType.essentialCases.map { $0.rawValue }.joined(separator: ", "))` `--organizationmode visibility` (default) diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 7f61b4667..96ebabd62 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -1857,16 +1857,18 @@ extension Formatter { var order: Int var comment: String? = nil - func shouldBeMarked(in categories: Set, for mode: DeclarationOrganizationMode) -> Bool { + /// Whether or not a mark comment should be added for this category, + /// given the set of existing categories with existing mark comments + func shouldBeMarked(in categoriesWithMarkComment: Set, for mode: DeclarationOrganizationMode) -> Bool { guard type != .beforeMarks else { return false } switch mode { case .type: - return !categories.contains(where: { $0.type == type }) + return !categoriesWithMarkComment.contains(where: { $0.type == type || $0.visibility == .explicit(type) }) case .visibility: - return !categories.contains(where: { $0.visibility == visibility }) + return !categoriesWithMarkComment.contains(where: { $0.visibility == visibility }) } } @@ -1930,13 +1932,24 @@ extension Formatter { } public static var allCases: [VisibilityType] { - [.explicit(.beforeMarks), .explicit(.instanceLifecycle)] + Visibility.allCases - .map { .visibility($0) } + Visibility.allCases.map { .visibility($0) } } public static var essentialCases: [VisibilityType] { Visibility.allCases.map { .visibility($0) } } + + public static func defaultOrdering(for mode: DeclarationOrganizationMode) -> [VisibilityType] { + switch mode { + case .type: + return allCases + case .visibility: + return [ + .explicit(.beforeMarks), + .explicit(.instanceLifecycle), + ] + allCases + } + } } /// The type of a declaration. @@ -1959,7 +1972,6 @@ extension Formatter { case staticMethod case classMethod case instanceMethod - case conditionalCompilation var markComment: String { switch self { @@ -1995,8 +2007,6 @@ extension Formatter { return "Class Functions" case .instanceMethod: return "Functions" - case .conditionalCompilation: - return "Conditional Compilation" } } @@ -2004,12 +2014,24 @@ extension Formatter { [ .beforeMarks, .nestedType, - .instanceProperty, .instanceLifecycle, + .instanceProperty, .instanceMethod, - .conditionalCompilation, ] } + + public static func defaultOrdering(for mode: DeclarationOrganizationMode) -> [DeclarationType] { + switch mode { + case .type: + return allCases + case .visibility: + return allCases.filter { type in + // Exclude beforeMarks and instanceLifecycle, since by default + // these are instead treated as top-level categories + type != .beforeMarks && type != .instanceLifecycle + } + } + } } func category( @@ -2040,12 +2062,21 @@ extension Formatter { typealias ParsedVisibilityMarks = [VisibilityType: String] typealias ParsedTypeMarks = [DeclarationType: String] - let visibilityTypes = options.visibilityOrder - .map { VisibilityType(rawValue: $0) } - .compactMap { $0 } - let declarationTypes = options.typeOrder - .map { DeclarationType(rawValue: $0) } - .compactMap { $0 } + let visibilityTypes = options.visibilityOrder?.compactMap { VisibilityType(rawValue: $0) } + ?? VisibilityType.defaultOrdering(for: mode) + + let declarationTypes = options.typeOrder?.compactMap { DeclarationType(rawValue: $0) } + ?? DeclarationType.defaultOrdering(for: mode) + + // Validate that every essential declaration type is included in either `declarationTypes` or `visibilityTypes`. + // Otherwise, we will just crash later when we find a declaration with this type. + for essentialDeclarationType in DeclarationType.essentialCases { + guard declarationTypes.contains(essentialDeclarationType) + || visibilityTypes.contains(.explicit(essentialDeclarationType)) + else { + Swift.fatalError("\(essentialDeclarationType.rawValue) must be included in either --typeorder or --visibilityorder") + } + } let customVisibilityMarks = options.customVisibilityMarks let customTypeMarks = options.customTypeMarks @@ -2055,25 +2086,46 @@ extension Formatter { switch mode { case .visibility: - return flatten(primary: visibilityTypes, using: declarationTypes) - .map { offset, element in - Category( - visibility: element.0, - type: element.1, - order: offset, - comment: parsedVisibilityMarks[element.0] - ) + let categoryPairings = visibilityTypes.flatMap { visibilityType -> [(VisibilityType, DeclarationType)] in + switch visibilityType { + case let .visibility(visibility): + // Each visibility / access control level pairs with all of the declaration types + return declarationTypes.compactMap { declarationType in + (.visibility(visibility), declarationType) + } + + case let .explicit(explicitDeclarationType): + // Each top-level declaration category pairs with all of the visibility types + return visibilityTypes.map { visibilityType in + (visibilityType, explicitDeclarationType) + } } + } + + return categoryPairings.enumerated().map { offset, element in + Category( + visibility: element.0, + type: element.1, + order: offset, + comment: parsedVisibilityMarks[element.0] + ) + } + case .type: - return flatten(primary: declarationTypes, using: visibilityTypes) - .map { offset, element in - Category( - visibility: element.1, - type: element.0, - order: offset, - comment: parsedTypeMarks[element.0] - ) + let categoryPairings = declarationTypes.flatMap { declarationType -> [(VisibilityType, DeclarationType)] in + visibilityTypes.map { visibilityType in + (visibilityType, declarationType) } + } + + return categoryPairings.enumerated().map { offset, element in + Category( + visibility: element.0, + type: element.1, + order: offset, + comment: parsedTypeMarks[element.1] + ) + } } } @@ -2097,26 +2149,20 @@ extension Formatter { } } - func flatten( - primary: C1, - using secondary: C2 - ) -> EnumeratedSequence<[(C1.Element, C2.Element)]> { - primary - .map { p in - secondary.map { s in - (p, s) - } - } - .reduce([], +) - .enumerated() - } - func category( from order: ParsedOrder, for visibility: VisibilityType, with type: DeclarationType ) -> Category { - order.first { $0.visibility == visibility && $0.type == type }! + guard let category = order.first(where: { entry in + entry.visibility == visibility && entry.type == type + || (entry.visibility == .explicit(type) && entry.type == type) + }) + else { + Swift.fatalError("Cannot determine ordering for declaration with visibility=\(visibility.rawValue) and type=\(type.rawValue).") + } + + return category } func visibility(of declaration: Declaration) -> Visibility? { @@ -2167,10 +2213,12 @@ extension Formatter { return type(of: keyword, with: tokens, mapping: availableTypes) case let .conditionalCompilation(_, body, _, _): - // Prefer treating conditional compliation blocks as having + // Prefer treating conditional compilation blocks as having // the property type of the first declaration in their body. guard let firstDeclarationInBlock = body.first else { - return .conditionalCompilation + // It's unusual to have an empty conditional compilation block. + // Pick an arbitrary declaration type as a fallback. + return .nestedType } return type(of: firstDeclarationInBlock, for: mode, mapping: availableTypes) @@ -2360,11 +2408,10 @@ extension Formatter { // Current amount of variants to pair visibility-type is over 300, // so we take only categories that could provide typemark that we want to erase let potentialCategorySeparators = ( - VisibilityType.allCases.map { - Category(visibility: $0, type: .classMethod, order: 0) - } + DeclarationType.allCases.map { - Category(visibility: .visibility(.open), type: $0, order: 0) - } + order.filter { $0.comment != nil } + VisibilityType.allCases.map { Category(visibility: $0, type: .classMethod, order: 0) } + + DeclarationType.allCases.map { Category(visibility: .visibility(.open), type: $0, order: 0) } + + DeclarationType.allCases.map { Category(visibility: .explicit($0), type: .classMethod, order: 0) } + + order.filter { $0.comment != nil } ).flatMap { Array(Set([ // The user's specific category separator template diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index ff38f183f..ccc269bce 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -294,6 +294,56 @@ class OptionDescriptor { ) } + init(argumentName: String, + displayName: String, + help: String, + deprecationMessage: String? = nil, + keyPath: WritableKeyPath, + validateArray: @escaping ([String]) throws -> Void = { _ in }) + { + self.argumentName = argumentName + self.displayName = displayName + self.help = help + self.deprecationMessage = deprecationMessage + type = .array + toOptions = { value, options in + let values = parseCommaDelimitedList(value) + + if values.isEmpty { + options[keyPath: keyPath] = nil + } else { + try validateArray(values) + options[keyPath: keyPath] = values + } + } + fromOptions = { options in + options[keyPath: keyPath]?.joined(separator: ",") ?? "" + } + } + + convenience init(argumentName: String, + displayName: String, + help: String, + deprecationMessage _: String? = nil, + keyPath: WritableKeyPath, + validate: @escaping (String) throws -> Void = { _ in }) + { + self.init( + argumentName: argumentName, + displayName: displayName, + help: help, + keyPath: keyPath, + validateArray: { values in + for (index, value) in values.enumerated() { + if values[0 ..< index].contains(value) { + throw FormatError.options("Duplicate value '\(value)'") + } + try validate(value) + } + } + ) + } + init(argumentName: String, displayName: String, help: String, @@ -902,7 +952,7 @@ struct _Descriptors { for type in order { guard let concrete = Formatter.VisibilityType(rawValue: type) else { let errorMessage = "'\(type)' is not a valid parameter for --visibilityorder" - guard let match = type.bestMatches(in: essentials).first else { + guard let match = type.bestMatches(in: Formatter.VisibilityType.allCases.map(\.rawValue)).first else { throw FormatError.options(errorMessage) } throw FormatError.options(errorMessage + ". Did you mean '\(match)?'") @@ -916,16 +966,10 @@ struct _Descriptors { help: "Order for declaration type groups inside declaration", keyPath: \.typeOrder, validateArray: { order in - let essentials = Formatter.DeclarationType.essentialCases.map(\.rawValue) - for type in essentials { - guard order.contains(type) else { - throw FormatError.options("--typeorder expects \(type) to be included") - } - } for type in order { guard let concrete = Formatter.DeclarationType(rawValue: type) else { let errorMessage = "'\(type)' is not a valid parameter for --typeorder" - guard let match = type.bestMatches(in: essentials).first else { + guard let match = type.bestMatches(in: Formatter.DeclarationType.allCases.map(\.rawValue)).first else { throw FormatError.options(errorMessage) } throw FormatError.options(errorMessage + ". Did you mean '\(match)?'") diff --git a/Sources/Options.swift b/Sources/Options.swift index 6f852f76e..5891ed2b2 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -667,8 +667,8 @@ public struct FormatOptions: CustomStringConvertible { public var organizeEnumThreshold: Int public var organizeExtensionThreshold: Int public var organizationMode: DeclarationOrganizationMode - public var visibilityOrder: [String] - public var typeOrder: [String] + public var visibilityOrder: [String]? + public var typeOrder: [String]? public var customVisibilityMarks: Set public var customTypeMarks: Set public var alphabeticallySortedDeclarationPatterns: Set @@ -790,8 +790,8 @@ public struct FormatOptions: CustomStringConvertible { organizeEnumThreshold: Int = 0, organizeExtensionThreshold: Int = 0, organizationMode: DeclarationOrganizationMode = .visibility, - visibilityOrder: [String] = Formatter.VisibilityType.allCases.map(\.rawValue), - typeOrder: [String] = Formatter.DeclarationType.allCases.map(\.rawValue), + visibilityOrder: [String]? = nil, + typeOrder: [String]? = nil, customVisibilityMarks: Set = [], customTypeMarks: Set = [], alphabeticallySortedDeclarationPatterns: Set = [], diff --git a/Tests/OptionDescriptorTests.swift b/Tests/OptionDescriptorTests.swift index 253994544..08acc8122 100644 --- a/Tests/OptionDescriptorTests.swift +++ b/Tests/OptionDescriptorTests.swift @@ -312,7 +312,7 @@ class OptionDescriptorTests: XCTestCase { } func testTypeOrder() { - let argument = "beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod, conditionalCompilation" + let argument = "beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod" let descriptor = Descriptors.typeOrder var options = FormatOptions() @@ -320,19 +320,31 @@ class OptionDescriptorTests: XCTestCase { } func testTypeOrderUnparseableArgument() { - let argument = "_beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod, conditionalCompilation" + let argument = "_beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod" let descriptor = Descriptors.typeOrder var options = FormatOptions() XCTAssertThrowsError(try descriptor.toOptions(argument, &options)) } - func testTypeOrderMissingEssentials() { - let argument = "beforeMarks, nestedType, instanceProperty, instanceLifecycle, instanceMethod" + func testAcceptsAirbnbSwiftStyleGuideVisibilityOrder() { + // The `visibilityorder` configuration used in Airbnb's Swift Style Guide, + // as defined here: https://github.com/airbnb/swift#subsection-organization + let argument = "beforeMarks,instanceLifecycle,open,public,package,internal,private,fileprivate" + + let descriptor = Descriptors.visibilityOrder + var options = FormatOptions() + XCTAssertNoThrow(try descriptor.toOptions(argument, &options)) + } + + func testAcceptsAirbnbSwiftStyleGuideTypeOrder() { + // The `typeorder` configuration used in Airbnb's Swift Style Guide, + // as defined here: https://github.com/airbnb/swift#subsection-organization + let argument = "nestedType,staticProperty,staticPropertyWithBody,classPropertyWithBody,instanceProperty,instancePropertyWithBody,staticMethod,classMethod,instanceMethod" let descriptor = Descriptors.typeOrder var options = FormatOptions() - XCTAssertThrowsError(try descriptor.toOptions(argument, &options)) + XCTAssertNoThrow(try descriptor.toOptions(argument, &options)) } func testFormatOptionsDescriptionConsistency() { diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index 84d2b8de7..0aeb70cc6 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -35,6 +35,15 @@ class OrganizationTests: RulesTests { /// Doc comment public func publicMethod() {} + + #if DEBUG + private var foo: Foo? { nil } + #endif + } + + enum Bar { + private var bar: Bar { Bar() } + case enumCase } """ @@ -76,9 +85,21 @@ class OrganizationTests: RulesTests { private let bar = 1 + #if DEBUG + private var foo: Foo? { nil } + #endif + private func privateMethod() {} } + + enum Bar { + case enumCase + + // MARK: Private + + private var bar: Bar { Bar() } + } """ testFormatting( @@ -88,6 +109,112 @@ class OrganizationTests: RulesTests { ) } + func testOrganizeClassDeclarationsIntoCategoriesWithCustomTypeOrder() { + let input = """ + class Foo { + private func privateMethod() {} + + private let bar = 1 + public let baz = 1 + open var quack = 2 + package func packageMethod() {} + var quux = 2 + + /// `open` is the only visibility keyword that + /// can also be used as an identifier. + var open = 10 + + /* + * Block comment + */ + + init() {} + + /// Doc comment + public func publicMethod() {} + + #if DEBUG + private var foo: Foo? { nil } + #endif + } + + enum Bar { + private var bar: Bar { Bar() } + case enumCase + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + /* + * Block comment + */ + + init() {} + + // MARK: Open + + open var quack = 2 + + // MARK: Public + + public let baz = 1 + + /// Doc comment + public func publicMethod() {} + + // MARK: Package + + package func packageMethod() {} + + // MARK: Internal + + var quux = 2 + + /// `open` is the only visibility keyword that + /// can also be used as an identifier. + var open = 10 + + // MARK: Private + + private let bar = 1 + + #if DEBUG + private var foo: Foo? { nil } + #endif + + private func privateMethod() {} + + } + + enum Bar { + case enumCase + + // MARK: Private + + private var bar: Bar { Bar() } + } + """ + + // The configuration used in Airbnb's Swift Style Guide, + // as defined here: https://github.com/airbnb/swift#subsection-organization + let airbnbVisibilityOrder = "beforeMarks,instanceLifecycle,open,public,package,internal,private,fileprivate" + let airbnbTypeOrder = "nestedType,staticProperty,staticPropertyWithBody,classPropertyWithBody,instanceProperty,instancePropertyWithBody,staticMethod,classMethod,instanceMethod" + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + visibilityOrder: airbnbVisibilityOrder.components(separatedBy: ","), + typeOrder: airbnbTypeOrder.components(separatedBy: ",") + ), + exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + ) + } + func testOrganizeClassDeclarationsIntoCategoriesInTypeOrder() { let input = """ class Foo { @@ -446,7 +573,8 @@ class OrganizationTests: RulesTests { for: input, output, rule: FormatRules.organizeDeclarations, options: FormatOptions( - visibilityOrder: ["private", "internal", "public"] + visibilityOrder: ["private", "internal", "public"], + typeOrder: Formatter.DeclarationType.allCases.map(\.rawValue) ), exclude: ["blankLinesAtStartOfScope"] ) @@ -496,7 +624,7 @@ class OrganizationTests: RulesTests { rule: FormatRules.organizeDeclarations, options: FormatOptions( visibilityOrder: ["private", "internal", "public"], - typeOrder: ["instanceMethod", "instanceProperty"] + typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] ), exclude: ["blankLinesAtStartOfScope"] ) @@ -544,7 +672,7 @@ class OrganizationTests: RulesTests { rule: FormatRules.organizeDeclarations, options: FormatOptions( organizationMode: .type, - typeOrder: ["instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"] + typeOrder: ["beforeMarks", "instanceLifecycle", "instanceMethod", "nestedType", "instanceProperty", "overriddenMethod"] ), exclude: ["blankLinesAtStartOfScope"] ) @@ -589,7 +717,7 @@ class OrganizationTests: RulesTests { rule: FormatRules.organizeDeclarations, options: FormatOptions( organizationMode: .type, - typeOrder: ["instanceLifecycle", "instanceMethod", "instanceProperty"] + typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] ), exclude: ["blankLinesAtStartOfScope"] ) @@ -641,7 +769,73 @@ class OrganizationTests: RulesTests { options: FormatOptions( organizationMode: .type, visibilityOrder: ["private", "internal", "public"], - typeOrder: ["instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"] + typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"] + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + + func testCustomDeclarationTypeUsedAsTopLevelCategory() { + let input = """ + class Test { + private let foo = "foo" + func bar() {} + } + """ + + let output = """ + class Test { + + // MARK: Functions + + func bar() {} + + // MARK: Private + + private let foo = "foo" + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + organizationMode: .visibility, + visibilityOrder: ["instanceMethod"] + Formatter.Visibility.allCases.map(\.rawValue), + typeOrder: Formatter.DeclarationType.allCases.map(\.rawValue).filter { $0 != "instanceMethod" } + ), + exclude: ["blankLinesAtStartOfScope"] + ) + } + + func testVisibilityModeWithoutInstanceLifecycle() { + let input = """ + class Test { + init() {} + private func bar() {} + } + """ + + let output = """ + class Test { + + // MARK: Internal + + init() {} + + // MARK: Private + + private func bar() {} + } + """ + + testFormatting( + for: input, output, + rule: FormatRules.organizeDeclarations, + options: FormatOptions( + organizationMode: .visibility, + visibilityOrder: Formatter.Visibility.allCases.map(\.rawValue), + typeOrder: Formatter.DeclarationType.allCases.map(\.rawValue) ), exclude: ["blankLinesAtStartOfScope"] ) From 758d010150c5f164d5d7117b7fb3e796a4061bcb Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sat, 20 Jul 2024 12:52:15 -0700 Subject: [PATCH 16/52] Add typealias to unusedPrivateDeclaration allowlist (#1764) --- Sources/Rules.swift | 35 ++++++++++++++++++------------- Tests/RulesTests+Redundancy.swift | 28 ++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Sources/Rules.swift b/Sources/Rules.swift index b5e4d3c0d..0bc5c41cc 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5169,11 +5169,18 @@ public struct _FormatRules { disabledByDefault: true ) { formatter in guard !formatter.options.fragment else { return } - let allowList = ["let", "var", "func"] - var privateDeclarations: [Formatter.Declaration] = [] - var usage: [String: Int] = [:] + // Only remove unused properties, functions, or typealiases. + // - This rule doesn't currently support removing unused types, + // and it's more difficult to track the usage of other declaration + // types like `init`, `subscript`, `operator`, etc. + let allowlist = ["let", "var", "func", "typealias"] + + // Collect all of the `private` or `fileprivate` declarations in the file + var privateDeclarations: [Formatter.Declaration] = [] formatter.forEachRecursiveDeclaration { declaration in + guard allowlist.contains(declaration.keyword) else { return } + switch formatter.visibility(of: declaration) { case .fileprivate, .private: privateDeclarations.append(declaration) @@ -5182,23 +5189,21 @@ public struct _FormatRules { } } + // Count the usage of each identifier in the file + var usage: [String: Int] = [:] formatter.forEach(.identifier) { _, token in usage[token.string, default: 0] += 1 } + // Remove any private or fileprivate declaration whose name only + // appears a single time in the source file for declaration in privateDeclarations.reversed() { - if let name = declaration.name, - let count = usage[name], - count < 2 - { - switch declaration { - case let .declaration(kind, _, originalRange): - guard allowList.contains(kind) else { break } - formatter.removeTokens(in: originalRange) - case .type, .conditionalCompilation: - break - } - } + guard let name = declaration.name, + let count = usage[name], + count <= 1 + else { continue } + + formatter.removeTokens(in: declaration.originalRange) } } diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index 0076b6058..17f4a9972 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -10618,13 +10618,39 @@ class RedundancyTests: RulesTests { testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) } - func testDoNotRemovePrivateTypealias() { + func testRemovesPrivateTypealias() { let input = """ enum Foo { struct Bar {} private typealias Baz = Bar } """ + let output = """ + enum Foo { + struct Bar {} + } + """ + testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoesntRemoveFileprivateInit() { + let input = """ + struct Foo { + fileprivate init() {} + static let foo = Foo() + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, exclude: ["propertyType"]) + } + + func testCanDisableUnusedPrivateDeclarationRule() { + let input = """ + private enum Foo { + // swiftformat:disable:next unusedPrivateDeclaration + fileprivate static func bar() {} + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) } } From 4f7645495fdd52aa30fe9fed5fb2f20604599859 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 22 Jul 2024 01:19:50 -0700 Subject: [PATCH 17/52] Add docCommentBeforeAttributes rule (#1766) --- Rules.md | 18 ++++++++ Sources/Examples.swift | 9 ++++ Sources/ParsingHelpers.swift | 28 ++++++++++++ Sources/Rules.swift | 35 +++++++++++++++ Tests/RulesTests+Syntax.swift | 84 +++++++++++++++++++++++++++++++++++ 5 files changed, 174 insertions(+) diff --git a/Rules.md b/Rules.md index 7610b8074..78f98f098 100644 --- a/Rules.md +++ b/Rules.md @@ -15,6 +15,7 @@ * [consecutiveBlankLines](#consecutiveBlankLines) * [consecutiveSpaces](#consecutiveSpaces) * [consistentSwitchCaseSpacing](#consistentSwitchCaseSpacing) +* [docCommentBeforeAttributes](#docCommentBeforeAttributes) * [duplicateImports](#duplicateImports) * [elseOnSameLine](#elseOnSameLine) * [emptyBraces](#emptyBraces) @@ -651,6 +652,23 @@ Ensures consistent spacing among all of the cases in a switch statement.

+## docCommentBeforeAttributes + +Place doc comments on declarations before any attributes. + +
+Examples + +```diff ++ /// Doc comment on this function declaration + @MainActor +- /// Doc comment on this function declaration + func foo() {} +``` + +
+
+ ## docComments Use doc comments for API declarations, otherwise use regular comments. diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 1e94e42de..c304cd8a9 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1999,4 +1999,13 @@ private struct Examples { } ``` """ + + let docCommentBeforeAttributes = """ + ```diff + + /// Doc comment on this function declaration + @MainActor + - /// Doc comment on this function declaration + func foo() {} + ``` + """ } diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index 2312a3888..cfba326e6 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -847,6 +847,34 @@ extension Formatter { } } + /// Parses the list of attributes on a declaration, starting at the given index. + func attributes(startingAt index: Int) -> [(startIndex: Int, endIndex: Int, tokens: [Token])] { + assert(tokens[index].isAttribute) + + var attributes: [(startIndex: Int, endIndex: Int, tokens: [Token])] = [] + + var nextAttributeStartIndex = index + while tokens[nextAttributeStartIndex].isAttribute { + guard let endOfAttribute = endOfAttribute(at: nextAttributeStartIndex) else { + return attributes + } + + attributes.append(( + startIndex: nextAttributeStartIndex, + endIndex: endOfAttribute, + tokens: Array(tokens[nextAttributeStartIndex ... endOfAttribute]) + )) + + guard let nextIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfAttribute) else { + return attributes + } + + nextAttributeStartIndex = nextIndex + } + + return attributes + } + /// Whether or not this property at the given introducer index (either `var` or `let`) /// is a stored property or a computed property. func isStoredProperty(atIntroducerIndex introducerIndex: Int) -> Bool { diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 0bc5c41cc..e483ec1f1 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -8324,4 +8324,39 @@ public struct _FormatRules { } } } + + public let docCommentBeforeAttributes = FormatRule( + help: "Place doc comments on declarations before any attributes." + ) { formatter in + formatter.forEachToken(where: \.isDeclarationTypeKeyword) { keywordIndex, _ in + // Parse the attributes on this declaration if present + let startOfAttributes = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) + guard formatter.tokens[startOfAttributes].isAttribute else { return } + + let attributes = formatter.attributes(startingAt: startOfAttributes) + guard !attributes.isEmpty else { return } + + let tokenBeforeAttributes = formatter.lastToken(before: startOfAttributes, where: { !$0.isSpaceOrLinebreak }) + let attributesRange = attributes.first!.startIndex ... attributes.last!.endIndex + + // Make sure there are no comments immediately before, or within, the set of attributes. + guard tokenBeforeAttributes?.isComment != true, + !formatter.tokens[attributesRange].contains(where: \.isComment) + else { return } + + // If there's a comment between the attributes and the rest of the declaration, + // move it above the attributes. + guard let indexAfterAttributes = formatter.index(of: .nonSpaceOrLinebreak, after: attributesRange.upperBound), + let restOfDeclaration = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attributesRange.upperBound), + formatter.tokens[indexAfterAttributes].isComment, + formatter.tokens[indexAfterAttributes ..< restOfDeclaration].allSatisfy(\.isSpaceOrCommentOrLinebreak) + else { return } + + let commentRange = indexAfterAttributes ..< restOfDeclaration + let comment = formatter.tokens[commentRange] + + formatter.removeTokens(in: commentRange) + formatter.insert(comment, at: startOfAttributes) + } + } } diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index c28c0fba1..56ec1567a 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -5498,4 +5498,88 @@ class SyntaxTests: RulesTests { let options = FormatOptions(redundantType: .inferLocalsOnly) testFormatting(for: input, rule: FormatRules.propertyType, options: options) } + + // MARK: - docCommentsBeforeAttributes + + func testDocCommentsBeforeAttributes() { + let input = """ + @MainActor + /// Doc comment on this type declaration + public struct Baaz { + @available(*, deprecated) + /// Doc comment on this property declaration. + /// This comment spans multiple lines. + private var bar: Bar + + @FooBarMacro(arg1: true, arg2: .baaz) + /** + * Doc comment on this function declaration + */ + func foo() {} + } + """ + + let output = """ + /// Doc comment on this type declaration + @MainActor + public struct Baaz { + /// Doc comment on this property declaration. + /// This comment spans multiple lines. + @available(*, deprecated) + private var bar: Bar + + /** + * Doc comment on this function declaration + */ + @FooBarMacro(arg1: true, arg2: .baaz) + func foo() {} + } + """ + + testFormatting(for: input, output, rule: FormatRules.docCommentBeforeAttributes) + } + + func testDocCommentsBeforeMultipleAttributes() { + let input = """ + @MainActor @Macro(argument: true) @available(*, deprecated) + /// Doc comment on this function declaration after several attributes + public func foo() {} + + @MainActor + @Macro(argument: true) + @available(*, deprecated) + // Comment on this function declaration after several attributes + public func bar() {} + """ + + let output = """ + /// Doc comment on this function declaration after several attributes + @MainActor @Macro(argument: true) @available(*, deprecated) + public func foo() {} + + // Comment on this function declaration after several attributes + @MainActor + @Macro(argument: true) + @available(*, deprecated) + public func bar() {} + """ + + testFormatting(for: input, output, rule: FormatRules.docCommentBeforeAttributes, exclude: ["docComments"]) + } + + func testPreserveComplexCommentsBetweenAttributes() { + let input = """ + // Comment before attribute + @MainActor + // Comment after attribute + func foo() {} + + @MainActor + // Comment between attributes + @available(*, deprecated) + func bar() {} + """ + + testFormatting(for: input, rule: FormatRules.docCommentBeforeAttributes, exclude: ["docComments"]) + } } From c26282bd20e604c2a74c43b3a4429728b465167c Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 22 Jul 2024 01:25:17 -0700 Subject: [PATCH 18/52] Improve code organization of organizeDeclarations rule and related helpers (#1765) --- Sources/DeclarationHelpers.swift | 1564 +++++++++++++++++++++++++ Sources/Examples.swift | 12 +- Sources/FormattingHelpers.swift | 1152 ------------------ Sources/OptionDescriptor.swift | 10 +- Sources/ParsingHelpers.swift | 326 ------ Sources/Rules.swift | 6 +- SwiftFormat.xcodeproj/project.pbxproj | 4 + Tests/MetadataTests.swift | 2 +- Tests/RulesTests+Organization.swift | 10 +- Tests/SwiftFormatTests.swift | 6 +- 10 files changed, 1591 insertions(+), 1501 deletions(-) create mode 100644 Sources/DeclarationHelpers.swift diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift new file mode 100644 index 000000000..65d1676b5 --- /dev/null +++ b/Sources/DeclarationHelpers.swift @@ -0,0 +1,1564 @@ +// +// DeclarationHelpers.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/20/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +// MARK: - Declaration + +/// A declaration, like a property, function, or type. +/// https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations/ +/// +/// Forms a tree of declaratons, since `type` declarations have a body +/// that contains child declarations. +enum Declaration: Equatable { + /// A type-like declaration with body of additional declarations (`class`, `struct`, etc) + indirect case type( + kind: String, + open: [Token], + body: [Declaration], + close: [Token], + originalRange: ClosedRange + ) + + /// A simple declaration (like a property or function) + case declaration( + kind: String, + tokens: [Token], + originalRange: ClosedRange + ) + + /// A #if ... #endif conditional compilation block with a body of additional declarations + indirect case conditionalCompilation( + open: [Token], + body: [Declaration], + close: [Token], + originalRange: ClosedRange + ) + + /// The tokens in this declaration + var tokens: [Token] { + switch self { + case let .declaration(_, tokens, _): + return tokens + case let .type(_, openTokens, bodyDeclarations, closeTokens, _), + let .conditionalCompilation(openTokens, bodyDeclarations, closeTokens, _): + return openTokens + bodyDeclarations.flatMap { $0.tokens } + closeTokens + } + } + + /// The opening tokens of the declaration (before the body) + var openTokens: [Token] { + switch self { + case .declaration: + return tokens + case let .type(_, open, _, _, _), + let .conditionalCompilation(open, _, _, _): + return open + } + } + + /// The body of this declaration, if applicable + var body: [Declaration]? { + switch self { + case .declaration: + return nil + case let .type(_, _, body, _, _), + let .conditionalCompilation(_, body, _, _): + return body + } + } + + /// The closing tokens of the declaration (after the body) + var closeTokens: [Token] { + switch self { + case .declaration: + return [] + case let .type(_, _, _, close, _), + let .conditionalCompilation(_, _, close, _): + return close + } + } + + /// The keyword that determines the specific type of declaration that this is + /// (`class`, `func`, `let`, `var`, etc.) + var keyword: String { + switch self { + case let .declaration(kind, _, _), + let .type(kind, _, _, _, _): + return kind + case .conditionalCompilation: + return "#if" + } + } + + /// Whether or not this declaration defines a type (a class, enum, etc, but not an extension) + var definesType: Bool { + var typeKeywords = Token.swiftTypeKeywords + typeKeywords.remove("extension") + return typeKeywords.contains(keyword) + } + + /// The name of this type or variable + var name: String? { + let parser = Formatter(openTokens) + guard let keywordIndex = openTokens.firstIndex(of: .keyword(keyword)), + let nameIndex = parser.index(of: .nonSpaceOrCommentOrLinebreak, after: keywordIndex), + parser.tokens[nameIndex].isIdentifierOrKeyword + else { + return nil + } + + return parser.fullyQualifiedName(startingAt: nameIndex).name + } + + /// The original range of the tokens of this declaration in the original source file + var originalRange: ClosedRange { + switch self { + case let .type(_, _, _, _, originalRange), + let .declaration(_, _, originalRange), + let .conditionalCompilation(_, _, _, originalRange): + return originalRange + } + } +} + +extension Formatter { + /// Parses all of the declarations in the file + func parseDeclarations() -> [Declaration] { + guard !tokens.isEmpty else { return [] } + return parseDeclarations(in: ClosedRange(0 ..< tokens.count)) + } + + /// Parses the declarations in the given range. + func parseDeclarations(in range: ClosedRange) -> [Declaration] { + var declarations = [Declaration]() + var startOfDeclaration = range.lowerBound + forEachToken(onlyWhereEnabled: false) { i, token in + guard range.contains(i), + i >= startOfDeclaration, + token.isDeclarationTypeKeyword || token == .startOfScope("#if") + else { + return + } + + let declarationKeyword = declarationType(at: i) ?? "#if" + let endOfDeclaration = self.endOfDeclaration(atDeclarationKeyword: i, fallBackToEndOfScope: false) + + let declarationRange = startOfDeclaration ... min(endOfDeclaration ?? .max, range.upperBound) + startOfDeclaration = declarationRange.upperBound + 1 + + declarations.append(.declaration( + kind: isEnabled ? declarationKeyword : "", + tokens: Array(tokens[declarationRange]), + originalRange: declarationRange + )) + } + if startOfDeclaration < range.upperBound { + let declarationRange = startOfDeclaration ..< tokens.count + declarations.append(.declaration( + kind: "", + tokens: Array(tokens[declarationRange]), + originalRange: ClosedRange(declarationRange) + )) + } + + return declarations.map { declaration in + // Parses this declaration into a body of declarations separate from the start and end tokens + func parseBody(in bodyRange: Range) -> (start: [Token], body: [Declaration], end: [Token]) { + var startTokens = Array(tokens[declaration.originalRange.lowerBound ..< bodyRange.lowerBound]) + var endTokens = Array(tokens[bodyRange.upperBound ... declaration.originalRange.upperBound]) + + guard !bodyRange.isEmpty else { + return (start: startTokens, body: [], end: endTokens) + } + + var bodyRange = ClosedRange(bodyRange) + + // Move the leading newlines from the `body` into the `start` tokens + // so the first body token is the start of the first declaration + while tokens[bodyRange].first?.isLinebreak == true { + startTokens.append(tokens[bodyRange.lowerBound]) + + if bodyRange.count > 1 { + bodyRange = (bodyRange.lowerBound + 1) ... bodyRange.upperBound + } else { + // If this was the last remaining token in the body, just return now. + // We can't have an empty `bodyRange`. + return (start: startTokens, body: [], end: endTokens) + } + } + + // Move the closing brace's indentation token from the `body` into the `end` tokens + if tokens[bodyRange].last?.isSpace == true { + endTokens.insert(tokens[bodyRange.upperBound], at: endTokens.startIndex) + + if bodyRange.count > 1 { + bodyRange = bodyRange.lowerBound ... (bodyRange.upperBound - 1) + } else { + // If this was the last remaining token in the body, just return now. + // We can't have an empty `bodyRange`. + return (start: startTokens, body: [], end: endTokens) + } + } + + // Parse the inner body declarations of the type + let bodyDeclarations = parseDeclarations(in: bodyRange) + + return (startTokens, bodyDeclarations, endTokens) + } + + // If this declaration represents a type, we need to parse its inner declarations as well. + let typelikeKeywords = ["class", "actor", "struct", "enum", "protocol", "extension"] + + if typelikeKeywords.contains(declaration.keyword), + let declarationTypeKeywordIndex = index( + in: Range(declaration.originalRange), + where: { $0.string == declaration.keyword } + ), + let bodyOpenBrace = index(of: .startOfScope("{"), after: declarationTypeKeywordIndex), + let bodyClosingBrace = endOfScope(at: bodyOpenBrace) + { + let bodyRange = (bodyOpenBrace + 1) ..< bodyClosingBrace + let (startTokens, bodyDeclarations, endTokens) = parseBody(in: bodyRange) + + return .type( + kind: declaration.keyword, + open: startTokens, + body: bodyDeclarations, + close: endTokens, + originalRange: declaration.originalRange + ) + } + + // If this declaration represents a conditional compilation block, + // we also have to parse its inner declarations. + else if declaration.keyword == "#if", + let declarationTypeKeywordIndex = index( + in: Range(declaration.originalRange), + where: { $0.string == "#if" } + ), + let endOfBody = endOfScope(at: declarationTypeKeywordIndex) + { + let startOfBody = endOfLine(at: declarationTypeKeywordIndex) + let (startTokens, bodyDeclarations, endTokens) = parseBody(in: startOfBody ..< endOfBody) + + return .conditionalCompilation( + open: startTokens, + body: bodyDeclarations, + close: endTokens, + originalRange: declaration.originalRange + ) + } else { + return declaration + } + } + } + + /// Returns the end index of the `Declaration` containing `declarationKeywordIndex`. + /// - `declarationKeywordIndex.isDeclarationTypeKeyword` must be `true` + /// (e.g. it must be a keyword like `let`, `var`, `func`, `class`, etc. + /// - Parameter `fallBackToEndOfScope`: whether or not to return the end of the current + /// scope if this is the last declaration in the current scope. If `false`, + /// returns `nil` if this declaration is not followed by some other declaration. + func endOfDeclaration( + atDeclarationKeyword declarationKeywordIndex: Int, + fallBackToEndOfScope: Bool = true + ) -> Int? { + assert(tokens[declarationKeywordIndex].isDeclarationTypeKeyword + || tokens[declarationKeywordIndex] == .startOfScope("#if")) + + // Get declaration keyword + var searchIndex = declarationKeywordIndex + let declarationKeyword = declarationType(at: declarationKeywordIndex) ?? "#if" + switch tokens[declarationKeywordIndex] { + case .startOfScope("#if"): + // For conditional compilation blocks, the `declarationKeyword` _is_ the `startOfScope` + // so we can immediately skip to the corresponding #endif + if let endOfConditionalCompilationScope = endOfScope(at: declarationKeywordIndex) { + searchIndex = endOfConditionalCompilationScope + } + case .keyword("class") where declarationKeyword != "class": + // Most declarations will include exactly one token that `isDeclarationTypeKeyword` in + // - `class func` methods will have two (and the first one will be incorrect!) + searchIndex = index(of: .keyword(declarationKeyword), after: declarationKeywordIndex) ?? searchIndex + case .keyword("import"): + // Symbol imports (like `import class Module.Type`) will have an extra `isDeclarationTypeKeyword` + // immediately following their `declarationKeyword`, so we need to skip them. + if let symbolTypeKeywordIndex = index(of: .nonSpaceOrComment, after: declarationKeywordIndex), + tokens[symbolTypeKeywordIndex].isDeclarationTypeKeyword + { + searchIndex = symbolTypeKeywordIndex + } + case .keyword("protocol"), .keyword("struct"), .keyword("actor"), + .keyword("enum"), .keyword("extension"): + if let scopeStart = index(of: .startOfScope("{"), after: declarationKeywordIndex) { + searchIndex = endOfScope(at: scopeStart) ?? searchIndex + } + default: + break + } + + // Search for the next declaration so we know where this declaration ends. + let nextDeclarationKeywordIndex = index(after: searchIndex, where: { + $0.isDeclarationTypeKeyword || $0 == .startOfScope("#if") + }) + + // Search backward from the next declaration keyword to find where declaration begins. + var endOfDeclaration = nextDeclarationKeywordIndex.flatMap { + index(before: startOfModifiers(at: $0, includingAttributes: true), where: { + !$0.isSpaceOrCommentOrLinebreak + }).map { endOfLine(at: $0) } + } + + // Prefer keeping linebreaks at the end of a declaration's tokens, + // instead of the start of the next delaration's tokens + while let linebreakSearchIndex = endOfDeclaration, + token(at: linebreakSearchIndex + 1)?.isLinebreak == true + { + endOfDeclaration = linebreakSearchIndex + 1 + } + + // If there was another declaration after this one in the same scope, + // then we know this declaration ends before that one starts + if let endOfDeclaration = endOfDeclaration { + return endOfDeclaration + } + + // Otherwise this is the last declaration in the scope. + // To know where this declaration ends we just have to know where + // the parent scope ends. + // - We don't do this inside `parseDeclarations` itself since it handles this cases + if fallBackToEndOfScope, + declarationKeywordIndex != 0, + let endOfParentScope = endOfScope(at: declarationKeywordIndex - 1), + let endOfDeclaration = index(of: .nonSpaceOrLinebreak, before: endOfParentScope) + { + return endOfDeclaration + } + + return nil + } +} + +// MARK: - DeclarationType + +/// The type of a declaration. +enum DeclarationType: String, CaseIterable { + case beforeMarks + case nestedType + case staticProperty + case staticPropertyWithBody + case classPropertyWithBody + case overriddenProperty + case swiftUIPropertyWrapper + case instanceProperty + case instancePropertyWithBody + case instanceLifecycle + case swiftUIProperty + case swiftUIMethod + case overriddenMethod + case staticMethod + case classMethod + case instanceMethod + + var markComment: String { + switch self { + case .beforeMarks: + return "Before Marks" + case .nestedType: + return "Nested Types" + case .staticProperty: + return "Static Properties" + case .staticPropertyWithBody: + return "Static Computed Properties" + case .classPropertyWithBody: + return "Class Properties" + case .overriddenProperty: + return "Overridden Properties" + case .instanceLifecycle: + return "Lifecycle" + case .overriddenMethod: + return "Overridden Functions" + case .swiftUIProperty: + return "Content Properties" + case .swiftUIMethod: + return "Content Methods" + case .swiftUIPropertyWrapper: + return "SwiftUI Properties" + case .instanceProperty: + return "Properties" + case .instancePropertyWithBody: + return "Computed Properties" + case .staticMethod: + return "Static Functions" + case .classMethod: + return "Class Functions" + case .instanceMethod: + return "Functions" + } + } + + static var essentialCases: [DeclarationType] { + [ + .beforeMarks, + .nestedType, + .instanceLifecycle, + .instanceProperty, + .instanceMethod, + ] + } + + static func defaultOrdering(for mode: DeclarationOrganizationMode) -> [DeclarationType] { + switch mode { + case .type: + return allCases + case .visibility: + return allCases.filter { type in + // Exclude beforeMarks and instanceLifecycle, since by default + // these are instead treated as top-level categories + type != .beforeMarks && type != .instanceLifecycle + } + } + } +} + +extension Formatter { + /// The `DeclarationType` of the given `Declaration` + func type( + of declaration: Declaration, + allowlist availableTypes: [DeclarationType] + ) -> DeclarationType { + switch declaration { + case let .type(keyword, _, _, _, _): + return options.beforeMarks.contains(keyword) ? .beforeMarks : .nestedType + + case let .declaration(keyword, tokens, _): + return declarationType(of: keyword, with: tokens, allowlist: availableTypes) + + case let .conditionalCompilation(_, body, _, _): + // Prefer treating conditional compilation blocks as having + // the property type of the first declaration in their body. + guard let firstDeclarationInBlock = body.first else { + // It's unusual to have an empty conditional compilation block. + // Pick an arbitrary declaration type as a fallback. + return .nestedType + } + + return type(of: firstDeclarationInBlock, allowlist: availableTypes) + } + } + + /// The `DeclarationType` of the declaration with the given keyword and tokens + func declarationType( + of keyword: String, + with tokens: [Token], + allowlist availableTypes: [DeclarationType] + ) -> DeclarationType { + guard let declarationTypeTokenIndex = tokens.firstIndex(of: .keyword(keyword)) else { + return .beforeMarks + } + + let declarationParser = Formatter(tokens) + let declarationTypeToken = declarationParser.tokens[declarationTypeTokenIndex] + + if keyword == "case" || options.beforeMarks.contains(keyword) { + return .beforeMarks + } + + for token in declarationParser.tokens { + if options.beforeMarks.contains(token.string) { return .beforeMarks } + } + + let isStaticDeclaration = declarationParser.index( + of: .keyword("static"), + before: declarationTypeTokenIndex + ) != nil + + let isClassDeclaration = declarationParser.index( + of: .keyword("class"), + before: declarationTypeTokenIndex + ) != nil + + let isOverriddenDeclaration = declarationParser.index( + of: .identifier("override"), + before: declarationTypeTokenIndex + ) != nil + + let isDeclarationWithBody: Bool = { + // If there is a code block at the end of the declaration that is _not_ a closure, + // then this declaration has a body. + if let lastClosingBraceIndex = declarationParser.index(of: .endOfScope("}"), before: declarationParser.tokens.count), + let lastOpeningBraceIndex = declarationParser.index(of: .startOfScope("{"), before: lastClosingBraceIndex), + declarationTypeTokenIndex < lastOpeningBraceIndex, + declarationTypeTokenIndex < lastClosingBraceIndex, + !declarationParser.isStartOfClosure(at: lastOpeningBraceIndex) { return true } + + return false + }() + + let isViewDeclaration: Bool = { + guard let someKeywordIndex = declarationParser.index( + of: .identifier("some"), after: declarationTypeTokenIndex + ) else { return false } + + return declarationParser.index(of: .identifier("View"), after: someKeywordIndex) != nil + }() + + let isSwiftUIPropertyWrapper = declarationParser + .modifiersForDeclaration(at: declarationTypeTokenIndex) { _, modifier in + swiftUIPropertyWrappers.contains(modifier) + } + + switch declarationTypeToken { + // Properties and property-like declarations + case .keyword("let"), .keyword("var"), + .keyword("operator"), .keyword("precedencegroup"): + + if isOverriddenDeclaration && availableTypes.contains(.overriddenProperty) { + return .overriddenProperty + } + if isStaticDeclaration && isDeclarationWithBody && availableTypes.contains(.staticPropertyWithBody) { + return .staticPropertyWithBody + } + if isStaticDeclaration && availableTypes.contains(.staticProperty) { + return .staticProperty + } + if isClassDeclaration && availableTypes.contains(.classPropertyWithBody) { + // Interestingly, Swift does not support stored class properties + // so there's no such thing as a class property without a body. + // https://forums.swift.org/t/class-properties/16539/11 + return .classPropertyWithBody + } + if isViewDeclaration && availableTypes.contains(.swiftUIProperty) { + return .swiftUIProperty + } + if !isDeclarationWithBody && isSwiftUIPropertyWrapper && availableTypes.contains(.swiftUIPropertyWrapper) { + return .swiftUIPropertyWrapper + } + if isDeclarationWithBody && availableTypes.contains(.instancePropertyWithBody) { + return .instancePropertyWithBody + } + + return .instanceProperty + + // Functions and function-like declarations + case .keyword("func"), .keyword("subscript"): + // The user can also provide specific instance method names to place in Lifecycle + // - In the function declaration grammar, the function name always + // immediately follows the `func` keyword: + // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_function-name + let methodName = declarationParser.next(.nonSpaceOrCommentOrLinebreak, after: declarationTypeTokenIndex) + if let methodName = methodName, options.lifecycleMethods.contains(methodName.string) { + return .instanceLifecycle + } + if isOverriddenDeclaration && availableTypes.contains(.overriddenMethod) { + return .overriddenMethod + } + if isStaticDeclaration && availableTypes.contains(.staticMethod) { + return .staticMethod + } + if isClassDeclaration && availableTypes.contains(.classMethod) { + return .classMethod + } + if isViewDeclaration && availableTypes.contains(.swiftUIMethod) { + return .swiftUIMethod + } + + return .instanceMethod + + case .keyword("init"), .keyword("deinit"): + return .instanceLifecycle + + // Type-like declarations + case .keyword("typealias"): + return .nestedType + + case .keyword("case"): + return .beforeMarks + + default: + return .beforeMarks + } + } + + /// Represents all the native SwiftUI property wrappers that conform to `DynamicProperty` and cause a SwiftUI view to re-render. + /// Most of these are listed here: https://developer.apple.com/documentation/swiftui/dynamicproperty + private var swiftUIPropertyWrappers: Set { + [ + "@AccessibilityFocusState", + "@AppStorage", + "@Binding", + "@Environment", + "@EnvironmentObject", + "@NSApplicationDelegateAdaptor", + "@FetchRequest", + "@FocusedBinding", + "@FocusedState", + "@FocusedValue", + "@FocusedObject", + "@GestureState", + "@Namespace", + "@ObservedObject", + "@PhysicalMetric", + "@Query", + "@ScaledMetric", + "@SceneStorage", + "@SectionedFetchRequest", + "@State", + "@StateObject", + "@UIApplicationDelegateAdaptor", + "@WKExtensionDelegateAdaptor", + ] + } +} + +// MARK: - Visibility + +/// The visibility of a declaration +enum Visibility: String, CaseIterable, Comparable { + case open + case `public` + case package + case `internal` + case `fileprivate` + case `private` + + static func < (lhs: Visibility, rhs: Visibility) -> Bool { + allCases.firstIndex(of: lhs)! > allCases.firstIndex(of: rhs)! + } +} + +extension Formatter { + /// The `Visibility` of the given `Declaration` + func visibility(of declaration: Declaration) -> Visibility? { + switch declaration { + case let .declaration(keyword, tokens, _), let .type(keyword, open: tokens, _, _, _): + guard let keywordIndex = tokens.firstIndex(of: .keyword(keyword)) else { + return nil + } + + // Search for a visibility keyword in the tokens before the primary keyword, + // making sure we exclude groups like private(set). + var searchIndex = 0 + let parser = Formatter(tokens) + while searchIndex < keywordIndex { + if let visibility = Visibility(rawValue: parser.tokens[searchIndex].string), + parser.next(.nonSpaceOrComment, after: searchIndex) != .startOfScope("(") + { + return visibility + } + + searchIndex += 1 + } + + return nil + case let .conditionalCompilation(_, body, _, _): + // Conditional compilation blocks themselves don't have a category or visbility-level, + // but we still have to assign them a category for the sorting algorithm to function. + // A reasonable heuristic here is to simply use the category of the first declaration + // inside the conditional compilation block. + if let firstDeclarationInBlock = body.first { + return visibility(of: firstDeclarationInBlock) + } else { + return nil + } + } + } +} + +// MARK: - Category + +/// A category of declarations used by the `organizeDeclarations` rule +struct Category: Equatable, Hashable { + var visibility: VisibilityCategory + var type: DeclarationType + var order: Int + var comment: String? = nil + + /// The comment tokens that should precede all declarations in this category + func markComment(from template: String, with mode: DeclarationOrganizationMode) -> String? { + "// " + template + .replacingOccurrences( + of: "%c", + with: comment ?? (mode == .type ? type.markComment : visibility.markComment) + ) + } + + /// Whether or not a mark comment should be added for this category, + /// given the set of existing categories with existing mark comments + func shouldBeMarked(in categoriesWithMarkComment: Set, for mode: DeclarationOrganizationMode) -> Bool { + guard type != .beforeMarks else { + return false + } + + switch mode { + case .type: + return !categoriesWithMarkComment.contains(where: { $0.type == type || $0.visibility == .explicit(type) }) + case .visibility: + return !categoriesWithMarkComment.contains(where: { $0.visibility == visibility }) + } + } +} + +/// The visibility category of a declaration +/// +/// - Note: When adding a new visibility type, remember to also update the list in `Examples.swift`. +enum VisibilityCategory: CaseIterable, Hashable, RawRepresentable { + case visibility(Visibility) + case explicit(DeclarationType) + + init?(rawValue: String) { + if let visibility = Visibility(rawValue: rawValue) { + self = .visibility(visibility) + } else if let type = DeclarationType(rawValue: rawValue) { + self = .explicit(type) + } else { + return nil + } + } + + var rawValue: String { + switch self { + case let .visibility(visibility): + return visibility.rawValue + case let .explicit(declarationType): + return declarationType.rawValue + } + } + + var markComment: String { + switch self { + case let .visibility(type): + return type.rawValue.capitalized + case let .explicit(type): + return type.markComment + } + } + + static var allCases: [VisibilityCategory] { + Visibility.allCases.map { .visibility($0) } + } + + static var essentialCases: [VisibilityCategory] { + Visibility.allCases.map { .visibility($0) } + } + + static func defaultOrdering(for mode: DeclarationOrganizationMode) -> [VisibilityCategory] { + switch mode { + case .type: + return allCases + case .visibility: + return [ + .explicit(.beforeMarks), + .explicit(.instanceLifecycle), + ] + allCases + } + } +} + +extension Formatter { + /// The `Category` of the given `Declaration` + func category( + of declaration: Declaration, + for mode: DeclarationOrganizationMode, + using order: ParsedOrder + ) -> Category { + let visibility = self.visibility(of: declaration) ?? .internal + let type = self.type(of: declaration, allowlist: order.map(\.type)) + + let visibilityCategory: VisibilityCategory + switch mode { + case .visibility: + guard VisibilityCategory.allCases.contains(.explicit(type)) else { + fallthrough + } + + visibilityCategory = .explicit(type) + case .type: + visibilityCategory = .visibility(visibility) + } + + return category(from: order, for: visibilityCategory, with: type) + } + + typealias ParsedOrder = [Category] + + /// The ordering of categories to use for the given `DeclarationOrganizationMode` + func categoryOrder(for mode: DeclarationOrganizationMode) -> ParsedOrder { + typealias ParsedVisibilityMarks = [VisibilityCategory: String] + typealias ParsedTypeMarks = [DeclarationType: String] + + let VisibilityCategorys = options.visibilityOrder?.compactMap { VisibilityCategory(rawValue: $0) } + ?? VisibilityCategory.defaultOrdering(for: mode) + + let declarationTypes = options.typeOrder?.compactMap { DeclarationType(rawValue: $0) } + ?? DeclarationType.defaultOrdering(for: mode) + + // Validate that every essential declaration type is included in either `declarationTypes` or `VisibilityCategorys`. + // Otherwise, we will just crash later when we find a declaration with this type. + for essentialDeclarationType in DeclarationType.essentialCases { + guard declarationTypes.contains(essentialDeclarationType) + || VisibilityCategorys.contains(.explicit(essentialDeclarationType)) + else { + Swift.fatalError("\(essentialDeclarationType.rawValue) must be included in either --typeorder or --visibilityorder") + } + } + + let customVisibilityMarks = options.customVisibilityMarks + let customTypeMarks = options.customTypeMarks + + let parsedVisibilityMarks: ParsedVisibilityMarks = parseMarks(for: customVisibilityMarks) + let parsedTypeMarks: ParsedTypeMarks = parseMarks(for: customTypeMarks) + + switch mode { + case .visibility: + let categoryPairings = VisibilityCategorys.flatMap { VisibilityCategory -> [(VisibilityCategory, DeclarationType)] in + switch VisibilityCategory { + case let .visibility(visibility): + // Each visibility / access control level pairs with all of the declaration types + return declarationTypes.compactMap { declarationType in + (.visibility(visibility), declarationType) + } + + case let .explicit(explicitDeclarationType): + // Each top-level declaration category pairs with all of the visibility types + return VisibilityCategorys.map { VisibilityCategory in + (VisibilityCategory, explicitDeclarationType) + } + } + } + + return categoryPairings.enumerated().map { offset, element in + Category( + visibility: element.0, + type: element.1, + order: offset, + comment: parsedVisibilityMarks[element.0] + ) + } + + case .type: + let categoryPairings = declarationTypes.flatMap { declarationType -> [(VisibilityCategory, DeclarationType)] in + VisibilityCategorys.map { VisibilityCategory in + (VisibilityCategory, declarationType) + } + } + + return categoryPairings.enumerated().map { offset, element in + Category( + visibility: element.0, + type: element.1, + order: offset, + comment: parsedTypeMarks[element.1] + ) + } + } + } + + /// The `Category` of a declaration with the given `VisibilityCategory` and `DeclarationType` + func category( + from order: ParsedOrder, + for visibility: VisibilityCategory, + with type: DeclarationType + ) -> Category { + guard let category = order.first(where: { entry in + entry.visibility == visibility && entry.type == type + || (entry.visibility == .explicit(type) && entry.type == type) + }) + else { + Swift.fatalError("Cannot determine ordering for declaration with visibility=\(visibility.rawValue) and type=\(type.rawValue).") + } + + return category + } + + private func parseMarks( + for options: Set + ) -> [T: String] where T.RawValue == String { + options.map { customMarkEntry -> (T, String)? in + let split = customMarkEntry.split(separator: ":", maxSplits: 1) + + guard split.count == 2, + let rawValue = split.first, + let mark = split.last, + let concreteType = T(rawValue: String(rawValue)) + else { return nil } + + return (concreteType, String(mark)) + } + .compactMap { $0 } + .reduce(into: [:]) { dictionary, option in + dictionary[option.0] = option.1 + } + } +} + +// MARK: - organizeDeclaration + +extension Formatter { + /// A `Declaration` that represents a Swift type + typealias TypeDeclaration = (kind: String, open: [Token], body: [Declaration], close: [Token]) + + /// Organizes the given type declaration into sorted categories + func organizeDeclaration(_ typeDeclaration: TypeDeclaration) -> TypeDeclaration { + guard options.organizeTypes.contains(typeDeclaration.kind), + typeLengthExceedsOrganizationThreshold(typeDeclaration) + else { return typeDeclaration } + + // Parse category order from options + let categoryOrder = self.categoryOrder(for: options.organizationMode) + + // Remove all of the existing category separators, so they can be re-added + // at the correct location after sorting the declarations. + let typeBodyWithoutCategorySeparators = removeExistingCategorySeparators( + from: typeDeclaration.body, + with: options.organizationMode, + using: categoryOrder + ) + + // Categorize each of the declarations into their primary groups + let categorizedDeclarations: [CategorizedDeclaration] = typeBodyWithoutCategorySeparators + .map { declaration in + let declarationCategory = category( + of: declaration, + for: options.organizationMode, + using: categoryOrder + ) + + return (declaration: declaration, category: declarationCategory) + } + + // Sort the declarations based on their category and type + guard let sortedDeclarations = sortCategorizedDeclarations( + categorizedDeclarations, + in: typeDeclaration + ) + else { return typeDeclaration } + + // Add a mark comment for each top-level category + let sortedAndMarkedType = addCategorySeparators( + to: typeDeclaration, + sortedDeclarations: sortedDeclarations + ) + + return sortedAndMarkedType + } + + /// Whether or not the length of this types exceeds the minimum threshold to be organized + private func typeLengthExceedsOrganizationThreshold(_ typeDeclaration: TypeDeclaration) -> Bool { + let organizationThreshold: Int + switch typeDeclaration.kind { + case "class", "actor": + organizationThreshold = options.organizeClassThreshold + case "struct": + organizationThreshold = options.organizeStructThreshold + case "enum": + organizationThreshold = options.organizeEnumThreshold + case "extension": + organizationThreshold = options.organizeExtensionThreshold + default: + organizationThreshold = 0 + } + + guard organizationThreshold != 0 else { + return true + } + + let lineCount = typeDeclaration.body + .flatMap { $0.tokens } + .filter { $0.isLinebreak } + .count + + return lineCount >= organizationThreshold + } + + private typealias CategorizedDeclaration = (declaration: Declaration, category: Category) + + /// Sorts the given categorized declarations based on the defined category ordering + private func sortCategorizedDeclarations( + _ categorizedDeclarations: [CategorizedDeclaration], + in typeDeclaration: TypeDeclaration + ) + -> [CategorizedDeclaration]? + { + let sortAlphabeticallyWithinSubcategories = shouldSortAlphabeticallyWithinSubcategories(in: typeDeclaration) + + var sortedDeclarations = sortDeclarations( + categorizedDeclarations, + sortAlphabeticallyWithinSubcategories: sortAlphabeticallyWithinSubcategories + ) + + // The compiler will synthesize a memberwise init for `struct` + // declarations that don't have an `init` declaration. + // We have to take care to not reorder any properties (but reordering functions etc is ok!) + if !sortAlphabeticallyWithinSubcategories, typeDeclaration.kind == "struct", + !typeDeclaration.body.contains(where: { $0.keyword == "init" }), + !preservesSynthesizedMemberwiseInitializer(categorizedDeclarations, sortedDeclarations) + { + // If sorting by category and by type could cause compilation failures + // by not correctly preserving the synthesized memberwise initializer, + // try to sort _only_ by category (so we can try to preserve the correct category separators) + sortedDeclarations = sortDeclarations(categorizedDeclarations, sortAlphabeticallyWithinSubcategories: false) + + // If sorting _only_ by category still changes the synthesized memberwise initializer, + // then there's nothing we can do to organize this struct. + if !preservesSynthesizedMemberwiseInitializer(categorizedDeclarations, sortedDeclarations) { + return nil + } + } + + return sortedDeclarations + } + + private func sortDeclarations( + _ categorizedDeclarations: [CategorizedDeclaration], + sortAlphabeticallyWithinSubcategories: Bool + ) + -> [CategorizedDeclaration] + { + categorizedDeclarations.enumerated() + .sorted(by: { lhs, rhs in + let (lhsOriginalIndex, lhs) = lhs + let (rhsOriginalIndex, rhs) = rhs + + if lhs.category.order != rhs.category.order { + return lhs.category.order < rhs.category.order + } + + // If this type had a :sort directive, we sort alphabetically + // within the subcategories (where ordering is otherwise undefined) + if sortAlphabeticallyWithinSubcategories, + let lhsName = lhs.declaration.name, + let rhsName = rhs.declaration.name, + lhsName != rhsName + { + return lhsName.localizedCompare(rhsName) == .orderedAscending + } + + // Respect the original declaration ordering when the categories and types are the same + return lhsOriginalIndex < rhsOriginalIndex + }) + .map { $0.element } + } + + /// Whether or not type members should additionally be sorted alphabetically + /// within individual subcategories + private func shouldSortAlphabeticallyWithinSubcategories(in typeDeclaration: TypeDeclaration) -> Bool { + // If this type has a leading :sort directive, we sort alphabetically + // within the subcategories (where ordering is otherwise undefined) + let shouldSortAlphabeticallyBySortingMark = typeDeclaration.open.contains(where: { + $0.isCommentBody && $0.string.contains("swiftformat:sort") && !$0.string.contains(":sort:") + }) + + // If this type declaration name contains pattern — sort as well + let shouldSortAlphabeticallyByDeclarationPattern: Bool = { + let parser = Formatter(typeDeclaration.open) + + guard let kindIndex = parser.index(of: .keyword(typeDeclaration.kind), in: 0 ..< typeDeclaration.open.count), + let identifier = parser.next(.identifier, after: kindIndex) + else { + return false + } + + return options.alphabeticallySortedDeclarationPatterns.contains { + identifier.string.contains($0) + } + }() + + return shouldSortAlphabeticallyBySortingMark + || shouldSortAlphabeticallyByDeclarationPattern + } + + // Whether or not this declaration is an instance property that can affect + // the parameters struct's synthesized memberwise initializer + private func affectsSynthesizedMemberwiseInitializer( + _ declaration: Declaration, + _ category: Category + ) -> Bool { + switch category.type { + case .instanceProperty: + return true + + case .instancePropertyWithBody: + // `instancePropertyWithBody` represents some stored properties, + // but also computed properties. Only stored properties, + // not computed properties, affect the synthesized init. + // + // This is a stored property if and only if + // the declaration body has a `didSet` or `willSet` keyword, + // based on the grammar for a variable declaration: + // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_variable-declaration + let parser = Formatter(declaration.tokens) + + if let bodyOpenBrace = parser.index(of: .startOfScope("{"), after: -1), + let nextToken = parser.next(.nonSpaceOrCommentOrLinebreak, after: bodyOpenBrace), + [.identifier("willSet"), .identifier("didSet")].contains(nextToken) + { + return true + } + + return false + + default: + return false + } + } + + // Whether or not the two given declaration orderings preserve + // the same synthesized memberwise initializer + private func preservesSynthesizedMemberwiseInitializer( + _ lhs: [CategorizedDeclaration], + _ rhs: [CategorizedDeclaration] + ) -> Bool { + let lhsPropertiesOrder = lhs + .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } + .map { $0.declaration } + + let rhsPropertiesOrder = rhs + .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } + .map { $0.declaration } + + return lhsPropertiesOrder == rhsPropertiesOrder + } + + /// Adds MARK category separates to the given type + private func addCategorySeparators( + to typeDeclaration: TypeDeclaration, + sortedDeclarations: [CategorizedDeclaration] + ) + -> TypeDeclaration + { + let numberOfCategories: Int = { + switch options.organizationMode { + case .visibility: + return Set(sortedDeclarations.map(\.category).map(\.visibility)).count + case .type: + return Set(sortedDeclarations.map(\.category).map(\.type)).count + } + }() + + var typeDeclaration = typeDeclaration + var formattedCategories: [Category] = [] + var markedDeclarations: [Declaration] = [] + + for (index, (declaration, category)) in sortedDeclarations.enumerated() { + if options.markCategories, + numberOfCategories > 1, + let markComment = category.markComment(from: options.categoryMarkComment, with: options.organizationMode), + category.shouldBeMarked(in: Set(formattedCategories), for: options.organizationMode) + { + formattedCategories.append(category) + + let declarationParser = Formatter(declaration.tokens) + let indentation = declarationParser.currentIndentForLine(at: 0) + + let endMarkDeclaration = options.lineAfterMarks ? "\n\n" : "\n" + let markDeclaration = tokenize("\(indentation)\(markComment)\(endMarkDeclaration)") + + // If this declaration is the first declaration in the type scope, + // make sure the type's opening sequence of tokens ends with + // at least one blank line so the category separator appears balanced + if markedDeclarations.isEmpty { + typeDeclaration.open = endingWithBlankLine(typeDeclaration.open) + } + + markedDeclarations.append(.declaration( + kind: "comment", + tokens: markDeclaration, + originalRange: 0 ... 1 // placeholder value + )) + } + + if let lastIndexOfSameDeclaration = sortedDeclarations.map(\.category).lastIndex(of: category), + lastIndexOfSameDeclaration == index, + lastIndexOfSameDeclaration != sortedDeclarations.indices.last + { + markedDeclarations.append(mapClosingTokens(in: declaration, with: { endingWithBlankLine($0) })) + } else { + markedDeclarations.append(declaration) + } + } + + typeDeclaration.body = markedDeclarations + return typeDeclaration + } + + /// Removes any existing category separators from the given declarations + private func removeExistingCategorySeparators( + from typeBody: [Declaration], + with mode: DeclarationOrganizationMode, + using order: ParsedOrder + ) -> [Declaration] { + var typeBody = typeBody + + for (declarationIndex, declaration) in typeBody.enumerated() { + let tokensToInspect: [Token] + switch declaration { + case let .declaration(_, tokens, _): + tokensToInspect = tokens + case let .type(_, open, _, _, _), let .conditionalCompilation(open, _, _, _): + // Only inspect the opening tokens of declarations with a body + tokensToInspect = open + } + + // Current amount of variants to pair visibility-type is over 300, + // so we take only categories that could provide typemark that we want to erase + let potentialCategorySeparators = ( + VisibilityCategory.allCases.map { Category(visibility: $0, type: .classMethod, order: 0) } + + DeclarationType.allCases.map { Category(visibility: .visibility(.open), type: $0, order: 0) } + + DeclarationType.allCases.map { Category(visibility: .explicit($0), type: .classMethod, order: 0) } + + order.filter { $0.comment != nil } + ).flatMap { + Array(Set([ + // The user's specific category separator template + $0.markComment(from: options.categoryMarkComment, with: mode), + // Other common variants that we would want to replace with the correct variant + $0.markComment(from: "%c", with: mode), + $0.markComment(from: "// MARK: %c", with: mode), + ])) + }.compactMap { $0 } + + let parser = Formatter(tokensToInspect) + + parser.forEach(.startOfScope("//")) { commentStartIndex, _ in + // Only look at top-level comments inside of the type body + guard parser.currentScope(at: commentStartIndex) == nil else { + return + } + + // Check if this comment matches an expected category separator comment + for potentialSeparatorComment in potentialCategorySeparators { + let potentialCategorySeparator = tokenize(potentialSeparatorComment) + let potentialSeparatorRange = commentStartIndex ..< (commentStartIndex + potentialCategorySeparator.count) + + guard parser.tokens.indices.contains(potentialSeparatorRange.upperBound), + let nextNonwhitespaceIndex = parser.index(of: .nonSpaceOrLinebreak, after: potentialSeparatorRange.upperBound) + else { continue } + + // Check the edit distance of this existing comment with the potential + // valid category separators for this category. If they are similar or identical, + // we'll want to replace the existing comment with the correct comment. + let existingComment = sourceCode(for: Array(parser.tokens[potentialSeparatorRange])) + let minimumEditDistance = Int(0.2 * Float(existingComment.count)) + + guard existingComment.lowercased().editDistance(from: potentialSeparatorComment.lowercased()) + <= minimumEditDistance + else { continue } + + // Makes sure there are only whitespace or other comments before this comment. + // Otherwise, we don't want to remove it. + let tokensBeforeComment = parser.tokens[0 ..< commentStartIndex] + guard !tokensBeforeComment.contains(where: { !$0.isSpaceOrCommentOrLinebreak }) else { + continue + } + + // If we found a matching comment, remove it and all subsequent empty lines + let startOfCommentLine = parser.startOfLine(at: commentStartIndex) + let startOfNextDeclaration = parser.startOfLine(at: nextNonwhitespaceIndex) + parser.removeTokens(in: startOfCommentLine ..< startOfNextDeclaration) + + // Move any tokens from before the category separator into the previous declaration. + // This makes sure that things like comments stay grouped in the same category. + if declarationIndex != 0, startOfCommentLine != 0 { + // Remove the tokens before the category separator from this declaration... + let rangeBeforeComment = 0 ..< startOfCommentLine + let tokensBeforeCommentLine = Array(parser.tokens[rangeBeforeComment]) + parser.removeTokens(in: rangeBeforeComment) + + // ... and append them to the end of the previous declaration + typeBody[declarationIndex - 1] = mapClosingTokens(in: typeBody[declarationIndex - 1]) { + $0 + tokensBeforeCommentLine + } + } + + // Apply the updated tokens back to this declaration + typeBody[declarationIndex] = mapOpeningTokens(in: typeBody[declarationIndex]) { _ in + parser.tokens + } + } + } + } + + return typeBody + } +} + +// MARK: - Helpers + +extension Formatter { + /// Recursively calls the `operation` for every declaration in the source file + func forEachRecursiveDeclaration(_ operation: (Declaration) -> Void) { + forEachRecursiveDeclarations(parseDeclarations(), operation) + } + + /// Applies `operation` to every recursive declaration of the given declarations + func forEachRecursiveDeclarations( + _ declarations: [Declaration], + _ operation: (Declaration) -> Void + ) { + for declaration in declarations { + operation(declaration) + if let body = declaration.body { + forEachRecursiveDeclarations(body, operation) + } + } + } + + /// Applies `mapRecursiveDeclarations` in place + func mapRecursiveDeclarations(with transform: (Declaration) -> Declaration) { + let updatedDeclarations = mapRecursiveDeclarations(parseDeclarations()) { declaration, _ in + transform(declaration) + } + let updatedTokens = updatedDeclarations.flatMap { $0.tokens } + replaceTokens(in: tokens.indices, with: updatedTokens) + } + + /// Applies `transform` to every recursive declaration of the given declarations + func mapRecursiveDeclarations( + _ declarations: [Declaration], in stack: [Declaration] = [], + with transform: (Declaration, _ stack: [Declaration]) -> Declaration + ) -> [Declaration] { + declarations.map { declaration in + let mapped = transform(declaration, stack) + switch mapped { + case let .type(kind, open, body, close, originalRange): + return .type( + kind: kind, + open: open, + body: mapRecursiveDeclarations(body, in: stack + [mapped], with: transform), + close: close, + originalRange: originalRange + ) + + case let .conditionalCompilation(open, body, close, originalRange): + return .conditionalCompilation( + open: open, + body: mapRecursiveDeclarations(body, in: stack + [mapped], with: transform), + close: close, + originalRange: originalRange + ) + + case .declaration: + return declaration + } + } + } + + /// Performs some declaration mapping for each body declaration in this declaration + /// (including any declarations nested in conditional compilation blocks, + /// but not including declarations dested within child types). + func mapBodyDeclarations( + in declaration: Declaration, + with transform: (Declaration) -> Declaration + ) -> Declaration { + switch declaration { + case let .type(kind, open, body, close, originalRange): + return .type( + kind: kind, + open: open, + body: mapBodyDeclarations(body, with: transform), + close: close, + originalRange: originalRange + ) + + case let .conditionalCompilation(open, body, close, originalRange): + return .conditionalCompilation( + open: open, + body: mapBodyDeclarations(body, with: transform), + close: close, + originalRange: originalRange + ) + + case .declaration: + // No work to do, because plain declarations don't have bodies + return declaration + } + } + + private func mapBodyDeclarations( + _ body: [Declaration], + with transform: (Declaration) -> Declaration + ) -> [Declaration] { + body.map { bodyDeclaration in + // Apply `mapBodyDeclaration` to each declaration in the body + switch bodyDeclaration { + case .declaration, .type: + return transform(bodyDeclaration) + + // Recursively step through conditional compilation blocks + // since their body tokens are effectively body tokens of the parent type + case .conditionalCompilation: + return mapBodyDeclarations(in: bodyDeclaration, with: transform) + } + } + } + + /// Performs some generic mapping for each declaration in the given array, + /// stepping through conditional compilation blocks (but not into the body + /// of other nested types) + func mapDeclarations( + _ declarations: [Declaration], + with transform: (Declaration) -> T + ) -> [T] { + declarations.flatMap { declaration -> [T] in + switch declaration { + case .declaration, .type: + return [transform(declaration)] + case let .conditionalCompilation(_, body, _, _): + return mapDeclarations(body, with: transform) + } + } + } + + /// Maps the first group of tokens in this declaration + /// - For declarations with a body, this maps the `open` tokens + /// - For declarations without a body, this maps the entire declaration's tokens + func mapOpeningTokens( + in declaration: Declaration, + with transform: ([Token]) -> [Token] + ) -> Declaration { + switch declaration { + case let .type(kind, open, body, close, originalRange): + return .type( + kind: kind, + open: transform(open), + body: body, + close: close, + originalRange: originalRange + ) + + case let .conditionalCompilation(open, body, close, originalRange): + return .conditionalCompilation( + open: transform(open), + body: body, + close: close, + originalRange: originalRange + ) + + case let .declaration(kind, tokens, originalRange): + return .declaration( + kind: kind, + tokens: transform(tokens), + originalRange: originalRange + ) + } + } + + /// Maps the last group of tokens in this declaration + /// - For declarations with a body, this maps the `close` tokens + /// - For declarations without a body, this maps the entire declaration's tokens + func mapClosingTokens( + in declaration: Declaration, + with transform: ([Token]) -> [Token] + ) -> Declaration { + switch declaration { + case let .type(kind, open, body, close, originalRange): + return .type( + kind: kind, + open: open, + body: body, + close: transform(close), + originalRange: originalRange + ) + + case let .conditionalCompilation(open, body, close, originalRange): + return .conditionalCompilation( + open: open, + body: body, + close: transform(close), + originalRange: originalRange + ) + + case let .declaration(kind, tokens, originalRange): + return .declaration( + kind: kind, + tokens: transform(tokens), + originalRange: originalRange + ) + } + } + + /// Updates the given declaration tokens so it ends with at least one blank like + /// (e.g. so it ends with at least two newlines) + func endingWithBlankLine(_ tokens: [Token]) -> [Token] { + let parser = Formatter(tokens) + + // Determine how many trailing linebreaks there are in this declaration + var numberOfTrailingLinebreaks = 0 + var searchIndex = parser.tokens.count - 1 + + while searchIndex > 0, + let token = parser.token(at: searchIndex), + token.isSpaceOrCommentOrLinebreak + { + if token.isLinebreak { + numberOfTrailingLinebreaks += 1 + } + + searchIndex -= 1 + } + + // Make sure there are at least two newlines, + // so we get a blank line between individual declaration types + while numberOfTrailingLinebreaks < 2 { + parser.insertLinebreak(at: parser.tokens.count) + numberOfTrailingLinebreaks += 1 + } + + return parser.tokens + } + + /// Removes the given visibility keyword from the given declaration + func remove(_ visibilityKeyword: Visibility, from declaration: Declaration) -> Declaration { + mapOpeningTokens(in: declaration) { openTokens in + guard let visibilityKeywordIndex = openTokens + .firstIndex(of: .keyword(visibilityKeyword.rawValue)) + else { + return openTokens + } + + let openTokensFormatter = Formatter(openTokens) + openTokensFormatter.removeToken(at: visibilityKeywordIndex) + + while openTokensFormatter.token(at: visibilityKeywordIndex)?.isSpace == true { + openTokensFormatter.removeToken(at: visibilityKeywordIndex) + } + + return openTokensFormatter.tokens + } + } + + /// Adds the given visibility keyword to the given declaration, + /// replacing any existing visibility keyword. + func add(_ visibilityKeyword: Visibility, to declaration: Declaration) -> Declaration { + var declaration = declaration + + if let existingVisibilityKeyword = visibility(of: declaration) { + declaration = remove(existingVisibilityKeyword, from: declaration) + } + + return mapOpeningTokens(in: declaration) { openTokens in + guard let indexOfKeyword = openTokens + .firstIndex(of: .keyword(declaration.keyword)) + else { + return openTokens + } + + let openTokensFormatter = Formatter(openTokens) + let startOfModifiers = openTokensFormatter + .startOfModifiers(at: indexOfKeyword, includingAttributes: false) + + openTokensFormatter.insert( + tokenize("\(visibilityKeyword.rawValue) "), + at: startOfModifiers + ) + + return openTokensFormatter.tokens + } + } +} diff --git a/Sources/Examples.swift b/Sources/Examples.swift index c304cd8a9..7f48ec283 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -1292,22 +1292,22 @@ private struct Examples { let organizeDeclarations = """ Default value for `--visibilityorder` when using `--organizationmode visibility`: - `\(Formatter.VisibilityType.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` + `\(VisibilityCategory.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` Default value for `--visibilityorder` when using `--organizationmode type`: - `\(Formatter.VisibilityType.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` + `\(VisibilityCategory.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` **NOTE:** When providing custom arguments for `--visibilityorder` the following entries must be included: - `\(Formatter.VisibilityType.essentialCases.map { $0.rawValue }.joined(separator: ", "))` + `\(VisibilityCategory.essentialCases.map { $0.rawValue }.joined(separator: ", "))` Default value for `--typeorder` when using `--organizationmode visibility`: - `\(Formatter.DeclarationType.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` + `\(DeclarationType.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` Default value for `--typeorder` when using `--organizationmode type`: - `\(Formatter.DeclarationType.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` + `\(DeclarationType.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` **NOTE:** The follow declaration types must be included in either `--typeorder` or `--visibilityorder`: - `\(Formatter.DeclarationType.essentialCases.map { $0.rawValue }.joined(separator: ", "))` + `\(DeclarationType.essentialCases.map { $0.rawValue }.joined(separator: ", "))` `--organizationmode visibility` (default) diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 96ebabd62..27ad4fc52 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -1374,36 +1374,6 @@ extension Formatter { return branches } - /// Represents all the native SwiftUI property wrappers that conform to `DynamicProperty` and cause a SwiftUI view to re-render. - /// Most of these are listed here: https://developer.apple.com/documentation/swiftui/dynamicproperty - private var swiftUIPropertyWrappers: Set { - [ - "@AccessibilityFocusState", - "@AppStorage", - "@Binding", - "@Environment", - "@EnvironmentObject", - "@NSApplicationDelegateAdaptor", - "@FetchRequest", - "@FocusedBinding", - "@FocusedState", - "@FocusedValue", - "@FocusedObject", - "@GestureState", - "@Namespace", - "@ObservedObject", - "@PhysicalMetric", - "@Query", - "@ScaledMetric", - "@SceneStorage", - "@SectionedFetchRequest", - "@State", - "@StateObject", - "@UIApplicationDelegateAdaptor", - "@WKExtensionDelegateAdaptor", - ] - } - /// Parses the switch statement case starting at the given index, /// which should be one of: `case`, `default`, or `@unknown`. private func parseSwitchStatementCase(caseOrDefaultIndex: Int) -> (startOfBody: Int, endOfBody: Int)? { @@ -1653,1128 +1623,6 @@ extension Formatter { } } -/// Helpers for recursively traversing the declaration hierarchy -extension Formatter { - /// Recursively calls the `operation` for every declaration in the source file - func forEachRecursiveDeclaration(_ operation: (Declaration) -> Void) { - forEachRecursiveDeclarations(parseDeclarations(), operation) - } - - /// Applies `operation` to every recursive declaration of the given declarations - func forEachRecursiveDeclarations( - _ declarations: [Declaration], - _ operation: (Declaration) -> Void - ) { - for declaration in declarations { - operation(declaration) - if let body = declaration.body { - forEachRecursiveDeclarations(body, operation) - } - } - } - - /// Applies `mapRecursiveDeclarations` in place - func mapRecursiveDeclarations(with transform: (Declaration) -> Declaration) { - let updatedDeclarations = mapRecursiveDeclarations(parseDeclarations()) { declaration, _ in - transform(declaration) - } - let updatedTokens = updatedDeclarations.flatMap { $0.tokens } - replaceTokens(in: tokens.indices, with: updatedTokens) - } - - /// Applies `transform` to every recursive declaration of the given declarations - func mapRecursiveDeclarations( - _ declarations: [Declaration], in stack: [Declaration] = [], - with transform: (Declaration, _ stack: [Declaration]) -> Declaration - ) -> [Declaration] { - declarations.map { declaration in - let mapped = transform(declaration, stack) - switch mapped { - case let .type(kind, open, body, close, originalRange): - return .type( - kind: kind, - open: open, - body: mapRecursiveDeclarations(body, in: stack + [mapped], with: transform), - close: close, - originalRange: originalRange - ) - - case let .conditionalCompilation(open, body, close, originalRange): - return .conditionalCompilation( - open: open, - body: mapRecursiveDeclarations(body, in: stack + [mapped], with: transform), - close: close, - originalRange: originalRange - ) - - case .declaration: - return declaration - } - } - } - - /// Performs some declaration mapping for each body declaration in this declaration - /// (including any declarations nested in conditional compilation blocks, - /// but not including declarations dested within child types). - func mapBodyDeclarations( - in declaration: Declaration, - with transform: (Declaration) -> Declaration - ) -> Declaration { - switch declaration { - case let .type(kind, open, body, close, originalRange): - return .type( - kind: kind, - open: open, - body: mapBodyDeclarations(body, with: transform), - close: close, - originalRange: originalRange - ) - - case let .conditionalCompilation(open, body, close, originalRange): - return .conditionalCompilation( - open: open, - body: mapBodyDeclarations(body, with: transform), - close: close, - originalRange: originalRange - ) - - case .declaration: - // No work to do, because plain declarations don't have bodies - return declaration - } - } - - private func mapBodyDeclarations( - _ body: [Declaration], - with transform: (Declaration) -> Declaration - ) -> [Declaration] { - body.map { bodyDeclaration in - // Apply `mapBodyDeclaration` to each declaration in the body - switch bodyDeclaration { - case .declaration, .type: - return transform(bodyDeclaration) - - // Recursively step through conditional compilation blocks - // since their body tokens are effectively body tokens of the parent type - case .conditionalCompilation: - return mapBodyDeclarations(in: bodyDeclaration, with: transform) - } - } - } - - /// Performs some generic mapping for each declaration in the given array, - /// stepping through conditional compilation blocks (but not into the body - /// of other nested types) - func mapDeclarations( - _ declarations: [Declaration], - with transform: (Declaration) -> T - ) -> [T] { - declarations.flatMap { declaration -> [T] in - switch declaration { - case .declaration, .type: - return [transform(declaration)] - case let .conditionalCompilation(_, body, _, _): - return mapDeclarations(body, with: transform) - } - } - } - - /// Maps the first group of tokens in this declaration - /// - For declarations with a body, this maps the `open` tokens - /// - For declarations without a body, this maps the entire declaration's tokens - func mapOpeningTokens( - in declaration: Declaration, - with transform: ([Token]) -> [Token] - ) -> Declaration { - switch declaration { - case let .type(kind, open, body, close, originalRange): - return .type( - kind: kind, - open: transform(open), - body: body, - close: close, - originalRange: originalRange - ) - - case let .conditionalCompilation(open, body, close, originalRange): - return .conditionalCompilation( - open: transform(open), - body: body, - close: close, - originalRange: originalRange - ) - - case let .declaration(kind, tokens, originalRange): - return .declaration( - kind: kind, - tokens: transform(tokens), - originalRange: originalRange - ) - } - } - - /// Maps the last group of tokens in this declaration - /// - For declarations with a body, this maps the `close` tokens - /// - For declarations without a body, this maps the entire declaration's tokens - func mapClosingTokens( - in declaration: Declaration, - with transform: ([Token]) -> [Token] - ) -> Declaration { - switch declaration { - case let .type(kind, open, body, close, originalRange): - return .type( - kind: kind, - open: open, - body: body, - close: transform(close), - originalRange: originalRange - ) - - case let .conditionalCompilation(open, body, close, originalRange): - return .conditionalCompilation( - open: open, - body: body, - close: transform(close), - originalRange: originalRange - ) - - case let .declaration(kind, tokens, originalRange): - return .declaration( - kind: kind, - tokens: transform(tokens), - originalRange: originalRange - ) - } - } -} - -/// Utility functions used by organizeDeclarations rule -// TODO: find a better place to put this -extension Formatter { - struct Category: Equatable, Hashable { - var visibility: VisibilityType - var type: DeclarationType - var order: Int - var comment: String? = nil - - /// Whether or not a mark comment should be added for this category, - /// given the set of existing categories with existing mark comments - func shouldBeMarked(in categoriesWithMarkComment: Set, for mode: DeclarationOrganizationMode) -> Bool { - guard type != .beforeMarks else { - return false - } - - switch mode { - case .type: - return !categoriesWithMarkComment.contains(where: { $0.type == type || $0.visibility == .explicit(type) }) - case .visibility: - return !categoriesWithMarkComment.contains(where: { $0.visibility == visibility }) - } - } - - /// The comment tokens that should precede all declarations in this category - func markComment(from template: String, with mode: DeclarationOrganizationMode) -> String? { - "// " + template - .replacingOccurrences( - of: "%c", - with: comment ?? (mode == .type ? type.markComment : visibility.markComment) - ) - } - } - - /// The visibility of a declaration - public enum Visibility: String, CaseIterable, Comparable { - case open - case `public` - case package - case `internal` - case `fileprivate` - case `private` - - public static func < (lhs: Visibility, rhs: Visibility) -> Bool { - allCases.firstIndex(of: lhs)! > allCases.firstIndex(of: rhs)! - } - } - - /// The visibility category of a declaration - /// - /// - Note: When adding a new visibility type, remember to also update the list in `Examples.swift`. - public enum VisibilityType: CaseIterable, Hashable, RawRepresentable { - case visibility(Visibility) - case explicit(DeclarationType) - - public init?(rawValue: String) { - if let visibility = Visibility(rawValue: rawValue) { - self = .visibility(visibility) - } else if let type = DeclarationType(rawValue: rawValue) { - self = .explicit(type) - } else { - return nil - } - } - - public var rawValue: String { - switch self { - case let .visibility(visibility): - return visibility.rawValue - case let .explicit(declarationType): - return declarationType.rawValue - } - } - - var markComment: String { - switch self { - case let .visibility(type): - return type.rawValue.capitalized - case let .explicit(type): - return type.markComment - } - } - - public static var allCases: [VisibilityType] { - Visibility.allCases.map { .visibility($0) } - } - - public static var essentialCases: [VisibilityType] { - Visibility.allCases.map { .visibility($0) } - } - - public static func defaultOrdering(for mode: DeclarationOrganizationMode) -> [VisibilityType] { - switch mode { - case .type: - return allCases - case .visibility: - return [ - .explicit(.beforeMarks), - .explicit(.instanceLifecycle), - ] + allCases - } - } - } - - /// The type of a declaration. - /// - /// - Note: When adding a new declaration type, remember to also update the list in `Examples.swift`. - public enum DeclarationType: String, CaseIterable { - case beforeMarks - case nestedType - case staticProperty - case staticPropertyWithBody - case classPropertyWithBody - case overriddenProperty - case swiftUIPropertyWrapper - case instanceProperty - case instancePropertyWithBody - case instanceLifecycle - case swiftUIProperty - case swiftUIMethod - case overriddenMethod - case staticMethod - case classMethod - case instanceMethod - - var markComment: String { - switch self { - case .beforeMarks: - return "Before Marks" - case .nestedType: - return "Nested Types" - case .staticProperty: - return "Static Properties" - case .staticPropertyWithBody: - return "Static Computed Properties" - case .classPropertyWithBody: - return "Class Properties" - case .overriddenProperty: - return "Overridden Properties" - case .instanceLifecycle: - return "Lifecycle" - case .overriddenMethod: - return "Overridden Functions" - case .swiftUIProperty: - return "Content Properties" - case .swiftUIMethod: - return "Content Methods" - case .swiftUIPropertyWrapper: - return "SwiftUI Properties" - case .instanceProperty: - return "Properties" - case .instancePropertyWithBody: - return "Computed Properties" - case .staticMethod: - return "Static Functions" - case .classMethod: - return "Class Functions" - case .instanceMethod: - return "Functions" - } - } - - public static var essentialCases: [DeclarationType] { - [ - .beforeMarks, - .nestedType, - .instanceLifecycle, - .instanceProperty, - .instanceMethod, - ] - } - - public static func defaultOrdering(for mode: DeclarationOrganizationMode) -> [DeclarationType] { - switch mode { - case .type: - return allCases - case .visibility: - return allCases.filter { type in - // Exclude beforeMarks and instanceLifecycle, since by default - // these are instead treated as top-level categories - type != .beforeMarks && type != .instanceLifecycle - } - } - } - } - - func category( - of declaration: Declaration, - for mode: DeclarationOrganizationMode, - using order: ParsedOrder - ) -> Category { - let visibility = self.visibility(of: declaration) ?? .internal - let type = self.type(of: declaration, for: mode, mapping: order.map(\.type)) - - let visibilityType: VisibilityType - switch mode { - case .visibility: - guard VisibilityType.allCases.contains(.explicit(type)) else { - fallthrough - } - - visibilityType = .explicit(type) - case .type: - visibilityType = .visibility(visibility) - } - - return category(from: order, for: visibilityType, with: type) - } - - typealias ParsedOrder = [Category] - func categoryOrder(for mode: DeclarationOrganizationMode) -> ParsedOrder { - typealias ParsedVisibilityMarks = [VisibilityType: String] - typealias ParsedTypeMarks = [DeclarationType: String] - - let visibilityTypes = options.visibilityOrder?.compactMap { VisibilityType(rawValue: $0) } - ?? VisibilityType.defaultOrdering(for: mode) - - let declarationTypes = options.typeOrder?.compactMap { DeclarationType(rawValue: $0) } - ?? DeclarationType.defaultOrdering(for: mode) - - // Validate that every essential declaration type is included in either `declarationTypes` or `visibilityTypes`. - // Otherwise, we will just crash later when we find a declaration with this type. - for essentialDeclarationType in DeclarationType.essentialCases { - guard declarationTypes.contains(essentialDeclarationType) - || visibilityTypes.contains(.explicit(essentialDeclarationType)) - else { - Swift.fatalError("\(essentialDeclarationType.rawValue) must be included in either --typeorder or --visibilityorder") - } - } - - let customVisibilityMarks = options.customVisibilityMarks - let customTypeMarks = options.customTypeMarks - - let parsedVisibilityMarks: ParsedVisibilityMarks = parseMarks(for: customVisibilityMarks) - let parsedTypeMarks: ParsedTypeMarks = parseMarks(for: customTypeMarks) - - switch mode { - case .visibility: - let categoryPairings = visibilityTypes.flatMap { visibilityType -> [(VisibilityType, DeclarationType)] in - switch visibilityType { - case let .visibility(visibility): - // Each visibility / access control level pairs with all of the declaration types - return declarationTypes.compactMap { declarationType in - (.visibility(visibility), declarationType) - } - - case let .explicit(explicitDeclarationType): - // Each top-level declaration category pairs with all of the visibility types - return visibilityTypes.map { visibilityType in - (visibilityType, explicitDeclarationType) - } - } - } - - return categoryPairings.enumerated().map { offset, element in - Category( - visibility: element.0, - type: element.1, - order: offset, - comment: parsedVisibilityMarks[element.0] - ) - } - - case .type: - let categoryPairings = declarationTypes.flatMap { declarationType -> [(VisibilityType, DeclarationType)] in - visibilityTypes.map { visibilityType in - (visibilityType, declarationType) - } - } - - return categoryPairings.enumerated().map { offset, element in - Category( - visibility: element.0, - type: element.1, - order: offset, - comment: parsedTypeMarks[element.1] - ) - } - } - } - - func parseMarks( - for options: Set - ) -> [T: String] where T.RawValue == String { - options.map { customMarkEntry -> (T, String)? in - let split = customMarkEntry.split(separator: ":", maxSplits: 1) - - guard split.count == 2, - let rawValue = split.first, - let mark = split.last, - let concreteType = T(rawValue: String(rawValue)) - else { return nil } - - return (concreteType, String(mark)) - } - .compactMap { $0 } - .reduce(into: [:]) { dictionary, option in - dictionary[option.0] = option.1 - } - } - - func category( - from order: ParsedOrder, - for visibility: VisibilityType, - with type: DeclarationType - ) -> Category { - guard let category = order.first(where: { entry in - entry.visibility == visibility && entry.type == type - || (entry.visibility == .explicit(type) && entry.type == type) - }) - else { - Swift.fatalError("Cannot determine ordering for declaration with visibility=\(visibility.rawValue) and type=\(type.rawValue).") - } - - return category - } - - func visibility(of declaration: Declaration) -> Visibility? { - switch declaration { - case let .declaration(keyword, tokens, _), let .type(keyword, open: tokens, _, _, _): - guard let keywordIndex = tokens.firstIndex(of: .keyword(keyword)) else { - return nil - } - - // Search for a visibility keyword in the tokens before the primary keyword, - // making sure we exclude groups like private(set). - var searchIndex = 0 - let parser = Formatter(tokens) - while searchIndex < keywordIndex { - if let visibility = Visibility(rawValue: parser.tokens[searchIndex].string), - parser.next(.nonSpaceOrComment, after: searchIndex) != .startOfScope("(") - { - return visibility - } - - searchIndex += 1 - } - - return nil - case let .conditionalCompilation(_, body, _, _): - // Conditional compilation blocks themselves don't have a category or visbility-level, - // but we still have to assign them a category for the sorting algorithm to function. - // A reasonable heuristic here is to simply use the category of the first declaration - // inside the conditional compilation block. - if let firstDeclarationInBlock = body.first { - return visibility(of: firstDeclarationInBlock) - } else { - return nil - } - } - } - - func type( - of declaration: Declaration, - for mode: DeclarationOrganizationMode, - mapping availableTypes: [DeclarationType] - ) -> DeclarationType { - switch declaration { - case let .type(keyword, _, _, _, _): - return options.beforeMarks.contains(keyword) ? .beforeMarks : .nestedType - - case let .declaration(keyword, tokens, _): - return type(of: keyword, with: tokens, mapping: availableTypes) - - case let .conditionalCompilation(_, body, _, _): - // Prefer treating conditional compilation blocks as having - // the property type of the first declaration in their body. - guard let firstDeclarationInBlock = body.first else { - // It's unusual to have an empty conditional compilation block. - // Pick an arbitrary declaration type as a fallback. - return .nestedType - } - - return type(of: firstDeclarationInBlock, for: mode, mapping: availableTypes) - } - } - - func type( - of keyword: String, - with tokens: [Token], - mapping availableTypes: [DeclarationType] - ) -> DeclarationType { - guard let declarationTypeTokenIndex = tokens.firstIndex(of: .keyword(keyword)) else { - return .beforeMarks - } - - let declarationParser = Formatter(tokens) - let declarationTypeToken = declarationParser.tokens[declarationTypeTokenIndex] - - if keyword == "case" || options.beforeMarks.contains(keyword) { - return .beforeMarks - } - - for token in declarationParser.tokens { - if options.beforeMarks.contains(token.string) { return .beforeMarks } - } - - let isStaticDeclaration = declarationParser.index( - of: .keyword("static"), - before: declarationTypeTokenIndex - ) != nil - - let isClassDeclaration = declarationParser.index( - of: .keyword("class"), - before: declarationTypeTokenIndex - ) != nil - - let isOverriddenDeclaration = declarationParser.index( - of: .identifier("override"), - before: declarationTypeTokenIndex - ) != nil - - let isDeclarationWithBody: Bool = { - // If there is a code block at the end of the declaration that is _not_ a closure, - // then this declaration has a body. - if let lastClosingBraceIndex = declarationParser.index(of: .endOfScope("}"), before: declarationParser.tokens.count), - let lastOpeningBraceIndex = declarationParser.index(of: .startOfScope("{"), before: lastClosingBraceIndex), - declarationTypeTokenIndex < lastOpeningBraceIndex, - declarationTypeTokenIndex < lastClosingBraceIndex, - !declarationParser.isStartOfClosure(at: lastOpeningBraceIndex) { return true } - - return false - }() - - let isViewDeclaration: Bool = { - guard let someKeywordIndex = declarationParser.index( - of: .identifier("some"), after: declarationTypeTokenIndex - ) else { return false } - - return declarationParser.index(of: .identifier("View"), after: someKeywordIndex) != nil - }() - - let isSwiftUIPropertyWrapper = declarationParser - .modifiersForDeclaration(at: declarationTypeTokenIndex) { _, modifier in - swiftUIPropertyWrappers.contains(modifier) - } - - switch declarationTypeToken { - // Properties and property-like declarations - case .keyword("let"), .keyword("var"), - .keyword("operator"), .keyword("precedencegroup"): - - if isOverriddenDeclaration && availableTypes.contains(.overriddenProperty) { - return .overriddenProperty - } - if isStaticDeclaration && isDeclarationWithBody && availableTypes.contains(.staticPropertyWithBody) { - return .staticPropertyWithBody - } - if isStaticDeclaration && availableTypes.contains(.staticProperty) { - return .staticProperty - } - if isClassDeclaration && availableTypes.contains(.classPropertyWithBody) { - // Interestingly, Swift does not support stored class properties - // so there's no such thing as a class property without a body. - // https://forums.swift.org/t/class-properties/16539/11 - return .classPropertyWithBody - } - if isViewDeclaration && availableTypes.contains(.swiftUIProperty) { - return .swiftUIProperty - } - if !isDeclarationWithBody && isSwiftUIPropertyWrapper && availableTypes.contains(.swiftUIPropertyWrapper) { - return .swiftUIPropertyWrapper - } - if isDeclarationWithBody && availableTypes.contains(.instancePropertyWithBody) { - return .instancePropertyWithBody - } - - return .instanceProperty - - // Functions and function-like declarations - case .keyword("func"), .keyword("subscript"): - // The user can also provide specific instance method names to place in Lifecycle - // - In the function declaration grammar, the function name always - // immediately follows the `func` keyword: - // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_function-name - let methodName = declarationParser.next(.nonSpaceOrCommentOrLinebreak, after: declarationTypeTokenIndex) - if let methodName = methodName, options.lifecycleMethods.contains(methodName.string) { - return .instanceLifecycle - } - if isOverriddenDeclaration && availableTypes.contains(.overriddenMethod) { - return .overriddenMethod - } - if isStaticDeclaration && availableTypes.contains(.staticMethod) { - return .staticMethod - } - if isClassDeclaration && availableTypes.contains(.classMethod) { - return .classMethod - } - if isViewDeclaration && availableTypes.contains(.swiftUIMethod) { - return .swiftUIMethod - } - - return .instanceMethod - - case .keyword("init"), .keyword("deinit"): - return .instanceLifecycle - - // Type-like declarations - case .keyword("typealias"): - return .nestedType - - case .keyword("case"): - return .beforeMarks - - default: - return .beforeMarks - } - } - - /// Updates the given declaration tokens so it ends with at least one blank like - /// (e.g. so it ends with at least two newlines) - func endingWithBlankLine(_ tokens: [Token]) -> [Token] { - let parser = Formatter(tokens) - - // Determine how many trailing linebreaks there are in this declaration - var numberOfTrailingLinebreaks = 0 - var searchIndex = parser.tokens.count - 1 - - while searchIndex > 0, - let token = parser.token(at: searchIndex), - token.isSpaceOrCommentOrLinebreak - { - if token.isLinebreak { - numberOfTrailingLinebreaks += 1 - } - - searchIndex -= 1 - } - - // Make sure there are at least two newlines, - // so we get a blank line between individual declaration types - while numberOfTrailingLinebreaks < 2 { - parser.insertLinebreak(at: parser.tokens.count) - numberOfTrailingLinebreaks += 1 - } - - return parser.tokens - } - - /// Removes any existing category separators from the given declarations - func removeExistingCategorySeparators( - from typeBody: [Declaration], - with mode: DeclarationOrganizationMode, - using order: ParsedOrder - ) -> [Declaration] { - var typeBody = typeBody - - for (declarationIndex, declaration) in typeBody.enumerated() { - let tokensToInspect: [Token] - switch declaration { - case let .declaration(_, tokens, _): - tokensToInspect = tokens - case let .type(_, open, _, _, _), let .conditionalCompilation(open, _, _, _): - // Only inspect the opening tokens of declarations with a body - tokensToInspect = open - } - - // Current amount of variants to pair visibility-type is over 300, - // so we take only categories that could provide typemark that we want to erase - let potentialCategorySeparators = ( - VisibilityType.allCases.map { Category(visibility: $0, type: .classMethod, order: 0) } - + DeclarationType.allCases.map { Category(visibility: .visibility(.open), type: $0, order: 0) } - + DeclarationType.allCases.map { Category(visibility: .explicit($0), type: .classMethod, order: 0) } - + order.filter { $0.comment != nil } - ).flatMap { - Array(Set([ - // The user's specific category separator template - $0.markComment(from: options.categoryMarkComment, with: mode), - // Other common variants that we would want to replace with the correct variant - $0.markComment(from: "%c", with: mode), - $0.markComment(from: "// MARK: %c", with: mode), - ])) - }.compactMap { $0 } - - let parser = Formatter(tokensToInspect) - - parser.forEach(.startOfScope("//")) { commentStartIndex, _ in - // Only look at top-level comments inside of the type body - guard parser.currentScope(at: commentStartIndex) == nil else { - return - } - - // Check if this comment matches an expected category separator comment - for potentialSeparatorComment in potentialCategorySeparators { - let potentialCategorySeparator = tokenize(potentialSeparatorComment) - let potentialSeparatorRange = commentStartIndex ..< (commentStartIndex + potentialCategorySeparator.count) - - guard parser.tokens.indices.contains(potentialSeparatorRange.upperBound), - let nextNonwhitespaceIndex = parser.index(of: .nonSpaceOrLinebreak, after: potentialSeparatorRange.upperBound) - else { continue } - - // Check the edit distance of this existing comment with the potential - // valid category separators for this category. If they are similar or identical, - // we'll want to replace the existing comment with the correct comment. - let existingComment = sourceCode(for: Array(parser.tokens[potentialSeparatorRange])) - let minimumEditDistance = Int(0.2 * Float(existingComment.count)) - - guard existingComment.lowercased().editDistance(from: potentialSeparatorComment.lowercased()) - <= minimumEditDistance - else { continue } - - // Makes sure there are only whitespace or other comments before this comment. - // Otherwise, we don't want to remove it. - let tokensBeforeComment = parser.tokens[0 ..< commentStartIndex] - guard !tokensBeforeComment.contains(where: { !$0.isSpaceOrCommentOrLinebreak }) else { - continue - } - - // If we found a matching comment, remove it and all subsequent empty lines - let startOfCommentLine = parser.startOfLine(at: commentStartIndex) - let startOfNextDeclaration = parser.startOfLine(at: nextNonwhitespaceIndex) - parser.removeTokens(in: startOfCommentLine ..< startOfNextDeclaration) - - // Move any tokens from before the category separator into the previous declaration. - // This makes sure that things like comments stay grouped in the same category. - if declarationIndex != 0, startOfCommentLine != 0 { - // Remove the tokens before the category separator from this declaration... - let rangeBeforeComment = 0 ..< startOfCommentLine - let tokensBeforeCommentLine = Array(parser.tokens[rangeBeforeComment]) - parser.removeTokens(in: rangeBeforeComment) - - // ... and append them to the end of the previous declaration - typeBody[declarationIndex - 1] = mapClosingTokens(in: typeBody[declarationIndex - 1]) { - $0 + tokensBeforeCommentLine - } - } - - // Apply the updated tokens back to this declaration - typeBody[declarationIndex] = mapOpeningTokens(in: typeBody[declarationIndex]) { _ in - parser.tokens - } - } - } - } - - return typeBody - } - - /// Organizes the flat list of declarations based on category and type - func organizeType( - _ typeDeclaration: (kind: String, open: [Token], body: [Declaration], close: [Token]) - ) -> (kind: String, open: [Token], body: [Declaration], close: [Token]) { - guard options.organizeTypes.contains(typeDeclaration.kind) else { - return typeDeclaration - } - - // Make sure this type's body is longer than the organization threshold - let organizationThreshold: Int - switch typeDeclaration.kind { - case "class", "actor": - organizationThreshold = options.organizeClassThreshold - case "struct": - organizationThreshold = options.organizeStructThreshold - case "enum": - organizationThreshold = options.organizeEnumThreshold - case "extension": - organizationThreshold = options.organizeExtensionThreshold - default: - organizationThreshold = 0 - } - - let mode = options.organizationMode - - // Count the number of lines in this declaration - let lineCount = typeDeclaration.body - .flatMap { $0.tokens } - .filter { $0.isLinebreak } - .count - - // Don't organize this type's body if it is shorter than the minimum organization threshold - if lineCount < organizationThreshold { - return typeDeclaration - } - - var typeOpeningTokens = typeDeclaration.open - let typeClosingTokens = typeDeclaration.close - - // Parse category order from options - let categoryOrder = self.categoryOrder(for: mode) - - // Remove all of the existing category separators, so they can be readded - // at the correct location after sorting the declarations. - let bodyWithoutCategorySeparators = removeExistingCategorySeparators( - from: typeDeclaration.body, - with: mode, - using: categoryOrder - ) - - // Categorize each of the declarations into their primary groups - typealias CategorizedDeclarations = [(declaration: Declaration, category: Category)] - - let categorizedDeclarations = bodyWithoutCategorySeparators.map { - (declaration: $0, category: category(of: $0, for: mode, using: categoryOrder)) - } - - // If this type has a leading :sort directive, we sort alphabetically - // within the subcategories (where ordering is otherwise undefined) - let shouldSortAlphabeticallyBySortingMark = typeDeclaration.open.contains(where: { - $0.isCommentBody && $0.string.contains("swiftformat:sort") && !$0.string.contains(":sort:") - }) - // If this type declaration name contains pattern — sort as well - let shouldSortAlphabeticallyByDeclarationPattern: Bool = { - let parser = Formatter(typeDeclaration.open) - - guard let kindIndex = parser.index(of: .keyword(typeDeclaration.kind), in: 0 ..< typeDeclaration.open.count), - let identifier = parser.next(.identifier, after: kindIndex) - else { - return false - } - - return options.alphabeticallySortedDeclarationPatterns.contains { - identifier.string.contains($0) - } - }() - let sortAlphabeticallyWithinSubcategories = shouldSortAlphabeticallyBySortingMark - || shouldSortAlphabeticallyByDeclarationPattern - - // Sorts the given categoried declarations based on their derived metadata - func sortDeclarations(_ declarations: CategorizedDeclarations) -> CategorizedDeclarations { - declarations.enumerated() - .sorted(by: { lhs, rhs in - let (lhsOriginalIndex, lhs) = lhs - let (rhsOriginalIndex, rhs) = rhs - - if lhs.category.order != rhs.category.order { - return lhs.category.order < rhs.category.order - } - - // If this type had a :sort directive, we sort alphabetically - // within the subcategories (where ordering is otherwise undefined) - if sortAlphabeticallyWithinSubcategories, - let lhsName = lhs.declaration.name, - let rhsName = rhs.declaration.name, - lhsName != rhsName - { - return lhsName.localizedCompare(rhsName) == .orderedAscending - } - - // Respect the original declaration ordering when the categories and types are the same - return lhsOriginalIndex < rhsOriginalIndex - }) - .map { $0.element } - } - - // Sort the declarations based on their category and type - var sortedDeclarations = sortDeclarations(categorizedDeclarations) - - // The compiler will synthesize a memberwise init for `struct` - // declarations that don't have an `init` declaration. - // We have to take care to not reorder any properties (but reordering functions etc is ok!) - if !sortAlphabeticallyWithinSubcategories, typeDeclaration.kind == "struct", - !typeDeclaration.body.contains(where: { $0.keyword == "init" }) - { - // Whether or not this declaration is an instance property that can affect - // the parameters struct's synthesized memberwise initializer - func affectsSynthesizedMemberwiseInitializer( - _ declaration: Declaration, - _ category: Category - ) -> Bool { - switch category.type { - case .instanceProperty: - return true - - case .instancePropertyWithBody: - // `instancePropertyWithBody` represents some stored properties, - // but also computed properties. Only stored properties, - // not computed properties, affect the synthesized init. - // - // This is a stored property if and only if - // the declaration body has a `didSet` or `willSet` keyword, - // based on the grammar for a variable declaration: - // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_variable-declaration - let parser = Formatter(declaration.tokens) - - if let bodyOpenBrace = parser.index(of: .startOfScope("{"), after: -1), - let nextToken = parser.next(.nonSpaceOrCommentOrLinebreak, after: bodyOpenBrace), - [.identifier("willSet"), .identifier("didSet")].contains(nextToken) - { - return true - } - - return false - - default: - return false - } - } - - // Whether or not the two given declaration orderings preserve - // the same synthesized memberwise initializer - func preservesSynthesizedMemberwiseInitializer( - _ lhs: CategorizedDeclarations, - _ rhs: CategorizedDeclarations - ) -> Bool { - let lhsPropertiesOrder = lhs - .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } - .map { $0.declaration } - - let rhsPropertiesOrder = rhs - .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } - .map { $0.declaration } - - return lhsPropertiesOrder == rhsPropertiesOrder - } - - if !preservesSynthesizedMemberwiseInitializer(categorizedDeclarations, sortedDeclarations) { - // If sorting by category and by type could cause compilation failures - // by not correctly preserving the synthesized memberwise initializer, - // try to sort _only_ by category (so we can try to preserve the correct category separators) - sortedDeclarations = sortDeclarations(categorizedDeclarations) - - // If sorting _only_ by category still changes the synthesized memberwise initializer, - // then there's nothing we can do to organize this struct. - if !preservesSynthesizedMemberwiseInitializer(categorizedDeclarations, sortedDeclarations) { - return typeDeclaration - } - } - } - - let numberOfCategories: Int = { - switch mode { - case .visibility: - return Set(sortedDeclarations.map(\.category).map(\.visibility)).count - case .type: - return Set(sortedDeclarations.map(\.category).map(\.type)).count - } - }() - - var formattedCategories: [Category] = [] - var markedDeclarations: [Declaration] = [] - - for (index, (declaration, category)) in sortedDeclarations.enumerated() { - if options.markCategories, - numberOfCategories > 1, - let markComment = category.markComment(from: options.categoryMarkComment, with: mode), - category.shouldBeMarked(in: Set(formattedCategories), for: mode) - { - formattedCategories.append(category) - - let declarationParser = Formatter(declaration.tokens) - let indentation = declarationParser.currentIndentForLine(at: 0) - - let endMarkDeclaration = options.lineAfterMarks ? "\n\n" : "\n" - let markDeclaration = tokenize("\(indentation)\(markComment)\(endMarkDeclaration)") - - // If this declaration is the first declaration in the type scope, - // make sure the type's opening sequence of tokens ends with - // at least one blank line so the category separator appears balanced - if markedDeclarations.isEmpty { - typeOpeningTokens = endingWithBlankLine(typeOpeningTokens) - } - - markedDeclarations.append(.declaration( - kind: "comment", - tokens: markDeclaration, - originalRange: 0 ... 1 // placeholder value - )) - } - - if let lastIndexOfSameDeclaration = sortedDeclarations.map(\.category).lastIndex(of: category), - lastIndexOfSameDeclaration == index, - lastIndexOfSameDeclaration != sortedDeclarations.indices.last - { - markedDeclarations.append(mapClosingTokens(in: declaration, with: { endingWithBlankLine($0) })) - } else { - markedDeclarations.append(declaration) - } - } - - return ( - kind: typeDeclaration.kind, - open: typeOpeningTokens, - body: markedDeclarations, - close: typeClosingTokens - ) - } - - /// Removes the given visibility keyword from the given declaration - func remove(_ visibilityKeyword: Visibility, from declaration: Declaration) -> Declaration { - mapOpeningTokens(in: declaration) { openTokens in - guard let visibilityKeywordIndex = openTokens - .firstIndex(of: .keyword(visibilityKeyword.rawValue)) - else { - return openTokens - } - - let openTokensFormatter = Formatter(openTokens) - openTokensFormatter.removeToken(at: visibilityKeywordIndex) - - while openTokensFormatter.token(at: visibilityKeywordIndex)?.isSpace == true { - openTokensFormatter.removeToken(at: visibilityKeywordIndex) - } - - return openTokensFormatter.tokens - } - } - - /// Adds the given visibility keyword to the given declaration, - /// replacing any existing visibility keyword. - func add(_ visibilityKeyword: Visibility, to declaration: Declaration) -> Declaration { - var declaration = declaration - - if let existingVisibilityKeyword = visibility(of: declaration) { - declaration = remove(existingVisibilityKeyword, from: declaration) - } - - return mapOpeningTokens(in: declaration) { openTokens in - guard let indexOfKeyword = openTokens - .firstIndex(of: .keyword(declaration.keyword)) - else { - return openTokens - } - - let openTokensFormatter = Formatter(openTokens) - let startOfModifiers = openTokensFormatter - .startOfModifiers(at: indexOfKeyword, includingAttributes: false) - - openTokensFormatter.insert( - tokenize("\(visibilityKeyword.rawValue) "), - at: startOfModifiers - ) - - return openTokensFormatter.tokens - } - } -} - extension Formatter { // A generic type parameter for a method class GenericType { diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index ccc269bce..ed384c907 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -943,16 +943,16 @@ struct _Descriptors { help: "Order for visibility groups inside declaration", keyPath: \.visibilityOrder, validateArray: { order in - let essentials = Formatter.VisibilityType.essentialCases.map(\.rawValue) + let essentials = VisibilityCategory.essentialCases.map(\.rawValue) for type in essentials { guard order.contains(type) else { throw FormatError.options("--visibilityorder expects \(type) to be included") } } for type in order { - guard let concrete = Formatter.VisibilityType(rawValue: type) else { + guard let concrete = VisibilityCategory(rawValue: type) else { let errorMessage = "'\(type)' is not a valid parameter for --visibilityorder" - guard let match = type.bestMatches(in: Formatter.VisibilityType.allCases.map(\.rawValue)).first else { + guard let match = type.bestMatches(in: VisibilityCategory.allCases.map(\.rawValue)).first else { throw FormatError.options(errorMessage) } throw FormatError.options(errorMessage + ". Did you mean '\(match)?'") @@ -967,9 +967,9 @@ struct _Descriptors { keyPath: \.typeOrder, validateArray: { order in for type in order { - guard let concrete = Formatter.DeclarationType(rawValue: type) else { + guard let concrete = DeclarationType(rawValue: type) else { let errorMessage = "'\(type)' is not a valid parameter for --typeorder" - guard let match = type.bestMatches(in: Formatter.DeclarationType.allCases.map(\.rawValue)).first else { + guard let match = type.bestMatches(in: DeclarationType.allCases.map(\.rawValue)).first else { throw FormatError.options(errorMessage) } throw FormatError.options(errorMessage + ". Did you mean '\(match)?'") diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index cfba326e6..279c4dd2e 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -1909,116 +1909,6 @@ extension Formatter { return (argumentNames: argumentNames, inKeywordIndex: inKeywordIndex) } - enum Declaration: Equatable { - /// A type-like declaration with body of additional declarations (`class`, `struct`, etc) - indirect case type( - kind: String, - open: [Token], - body: [Declaration], - close: [Token], - originalRange: ClosedRange - ) - - /// A simple declaration (like a property or function) - case declaration( - kind: String, - tokens: [Token], - originalRange: ClosedRange - ) - - /// A #if ... #endif conditional compilation block with a body of additional declarations - indirect case conditionalCompilation( - open: [Token], - body: [Declaration], - close: [Token], - originalRange: ClosedRange - ) - - /// The tokens in this declaration - var tokens: [Token] { - switch self { - case let .declaration(_, tokens, _): - return tokens - case let .type(_, openTokens, bodyDeclarations, closeTokens, _), - let .conditionalCompilation(openTokens, bodyDeclarations, closeTokens, _): - return openTokens + bodyDeclarations.flatMap { $0.tokens } + closeTokens - } - } - - /// The opening tokens of the declaration (before the body) - var openTokens: [Token] { - switch self { - case .declaration: - return tokens - case let .type(_, open, _, _, _), - let .conditionalCompilation(open, _, _, _): - return open - } - } - - /// The body of this declaration, if applicable - var body: [Declaration]? { - switch self { - case .declaration: - return nil - case let .type(_, _, body, _, _), - let .conditionalCompilation(_, body, _, _): - return body - } - } - - /// The closing tokens of the declaration (after the body) - var closeTokens: [Token] { - switch self { - case .declaration: - return [] - case let .type(_, _, _, close, _), - let .conditionalCompilation(_, _, close, _): - return close - } - } - - /// The keyword that determines the specific type of declaration that this is - /// (`class`, `func`, `let`, `var`, etc.) - var keyword: String { - switch self { - case let .declaration(kind, _, _), - let .type(kind, _, _, _, _): - return kind - case .conditionalCompilation: - return "#if" - } - } - - /// Whether or not this declaration defines a type (a class, enum, etc, but not an extension) - var definesType: Bool { - ["class", "actor", "struct", "enum", "protocol", "typealias"].contains(keyword) - } - - /// The name of this type or variable - var name: String? { - let parser = Formatter(openTokens) - guard let keywordIndex = openTokens.firstIndex(of: .keyword(keyword)), - let nameIndex = parser.index(of: .nonSpaceOrCommentOrLinebreak, after: keywordIndex), - parser.tokens[nameIndex].isIdentifierOrKeyword - else { - return nil - } - - return parser.fullyQualifiedName(startingAt: nameIndex).name - } - - /// The original range of the tokens of this declaration in the original source file - var originalRange: ClosedRange { - switch self { - case let .type(_, _, _, _, originalRange), - let .declaration(_, _, originalRange), - let .conditionalCompilation(_, _, _, originalRange): - return originalRange - } - } - } - /// The fully qualified name starting at the given index func fullyQualifiedName(startingAt index: Int) -> (name: String, endIndex: Int) { // If the identifier is followed by a dot, it's actually the first @@ -2083,222 +1973,6 @@ extension Formatter { } } - /// Returns the end index of the `Declaration` containing `declarationKeywordIndex`. - /// - `declarationKeywordIndex.isDeclarationTypeKeyword` must be `true` - /// (e.g. it must be a keyword like `let`, `var`, `func`, `class`, etc. - /// - Parameter `fallBackToEndOfScope`: whether or not to return the end of the current - /// scope if this is the last declaration in the current scope. If `false`, - /// returns `nil` if this declaration is not followed by some other declaration. - func endOfDeclaration( - atDeclarationKeyword declarationKeywordIndex: Int, - fallBackToEndOfScope: Bool = true - ) -> Int? { - assert(tokens[declarationKeywordIndex].isDeclarationTypeKeyword - || tokens[declarationKeywordIndex] == .startOfScope("#if")) - - // Get declaration keyword - var searchIndex = declarationKeywordIndex - let declarationKeyword = declarationType(at: declarationKeywordIndex) ?? "#if" - switch tokens[declarationKeywordIndex] { - case .startOfScope("#if"): - // For conditional compilation blocks, the `declarationKeyword` _is_ the `startOfScope` - // so we can immediately skip to the corresponding #endif - if let endOfConditionalCompilationScope = endOfScope(at: declarationKeywordIndex) { - searchIndex = endOfConditionalCompilationScope - } - case .keyword("class") where declarationKeyword != "class": - // Most declarations will include exactly one token that `isDeclarationTypeKeyword` in - // - `class func` methods will have two (and the first one will be incorrect!) - searchIndex = index(of: .keyword(declarationKeyword), after: declarationKeywordIndex) ?? searchIndex - case .keyword("import"): - // Symbol imports (like `import class Module.Type`) will have an extra `isDeclarationTypeKeyword` - // immediately following their `declarationKeyword`, so we need to skip them. - if let symbolTypeKeywordIndex = index(of: .nonSpaceOrComment, after: declarationKeywordIndex), - tokens[symbolTypeKeywordIndex].isDeclarationTypeKeyword - { - searchIndex = symbolTypeKeywordIndex - } - case .keyword("protocol"), .keyword("struct"), .keyword("actor"), - .keyword("enum"), .keyword("extension"): - if let scopeStart = index(of: .startOfScope("{"), after: declarationKeywordIndex) { - searchIndex = endOfScope(at: scopeStart) ?? searchIndex - } - default: - break - } - - // Search for the next declaration so we know where this declaration ends. - let nextDeclarationKeywordIndex = index(after: searchIndex, where: { - $0.isDeclarationTypeKeyword || $0 == .startOfScope("#if") - }) - - // Search backward from the next declaration keyword to find where declaration begins. - var endOfDeclaration = nextDeclarationKeywordIndex.flatMap { - index(before: startOfModifiers(at: $0, includingAttributes: true), where: { - !$0.isSpaceOrCommentOrLinebreak - }).map { endOfLine(at: $0) } - } - - // Prefer keeping linebreaks at the end of a declaration's tokens, - // instead of the start of the next delaration's tokens - while let linebreakSearchIndex = endOfDeclaration, - token(at: linebreakSearchIndex + 1)?.isLinebreak == true - { - endOfDeclaration = linebreakSearchIndex + 1 - } - - // If there was another declaration after this one in the same scope, - // then we know this declaration ends before that one starts - if let endOfDeclaration = endOfDeclaration { - return endOfDeclaration - } - - // Otherwise this is the last declaration in the scope. - // To know where this declaration ends we just have to know where - // the parent scope ends. - // - We don't do this inside `parseDeclarations` itself since it handles this cases - if fallBackToEndOfScope, - declarationKeywordIndex != 0, - let endOfParentScope = endOfScope(at: declarationKeywordIndex - 1), - let endOfDeclaration = index(of: .nonSpaceOrLinebreak, before: endOfParentScope) - { - return endOfDeclaration - } - - return nil - } - - /// Parses all of the declarations in the file - func parseDeclarations() -> [Declaration] { - guard !tokens.isEmpty else { return [] } - return parseDeclarations(in: ClosedRange(0 ..< tokens.count)) - } - - /// Parses the declarations in the given range. - func parseDeclarations(in range: ClosedRange) -> [Declaration] { - var declarations = [Declaration]() - var startOfDeclaration = range.lowerBound - forEachToken(onlyWhereEnabled: false) { i, token in - guard range.contains(i), - i >= startOfDeclaration, - token.isDeclarationTypeKeyword || token == .startOfScope("#if") - else { - return - } - - let declarationKeyword = declarationType(at: i) ?? "#if" - let endOfDeclaration = self.endOfDeclaration(atDeclarationKeyword: i, fallBackToEndOfScope: false) - - let declarationRange = startOfDeclaration ... min(endOfDeclaration ?? .max, range.upperBound) - startOfDeclaration = declarationRange.upperBound + 1 - - declarations.append(.declaration( - kind: isEnabled ? declarationKeyword : "", - tokens: Array(tokens[declarationRange]), - originalRange: declarationRange - )) - } - if startOfDeclaration < range.upperBound { - let declarationRange = startOfDeclaration ..< tokens.count - declarations.append(.declaration( - kind: "", - tokens: Array(tokens[declarationRange]), - originalRange: ClosedRange(declarationRange) - )) - } - - return declarations.map { declaration in - // Parses this declaration into a body of declarations separate from the start and end tokens - func parseBody(in bodyRange: Range) -> (start: [Token], body: [Declaration], end: [Token]) { - var startTokens = Array(tokens[declaration.originalRange.lowerBound ..< bodyRange.lowerBound]) - var endTokens = Array(tokens[bodyRange.upperBound ... declaration.originalRange.upperBound]) - - guard !bodyRange.isEmpty else { - return (start: startTokens, body: [], end: endTokens) - } - - var bodyRange = ClosedRange(bodyRange) - - // Move the leading newlines from the `body` into the `start` tokens - // so the first body token is the start of the first declaration - while tokens[bodyRange].first?.isLinebreak == true { - startTokens.append(tokens[bodyRange.lowerBound]) - - if bodyRange.count > 1 { - bodyRange = (bodyRange.lowerBound + 1) ... bodyRange.upperBound - } else { - // If this was the last remaining token in the body, just return now. - // We can't have an empty `bodyRange`. - return (start: startTokens, body: [], end: endTokens) - } - } - - // Move the closing brace's indentation token from the `body` into the `end` tokens - if tokens[bodyRange].last?.isSpace == true { - endTokens.insert(tokens[bodyRange.upperBound], at: endTokens.startIndex) - - if bodyRange.count > 1 { - bodyRange = bodyRange.lowerBound ... (bodyRange.upperBound - 1) - } else { - // If this was the last remaining token in the body, just return now. - // We can't have an empty `bodyRange`. - return (start: startTokens, body: [], end: endTokens) - } - } - - // Parse the inner body declarations of the type - let bodyDeclarations = parseDeclarations(in: bodyRange) - - return (startTokens, bodyDeclarations, endTokens) - } - - // If this declaration represents a type, we need to parse its inner declarations as well. - let typelikeKeywords = ["class", "actor", "struct", "enum", "protocol", "extension"] - - if typelikeKeywords.contains(declaration.keyword), - let declarationTypeKeywordIndex = index( - in: Range(declaration.originalRange), - where: { $0.string == declaration.keyword } - ), - let bodyOpenBrace = index(of: .startOfScope("{"), after: declarationTypeKeywordIndex), - let bodyClosingBrace = endOfScope(at: bodyOpenBrace) - { - let bodyRange = (bodyOpenBrace + 1) ..< bodyClosingBrace - let (startTokens, bodyDeclarations, endTokens) = parseBody(in: bodyRange) - - return .type( - kind: declaration.keyword, - open: startTokens, - body: bodyDeclarations, - close: endTokens, - originalRange: declaration.originalRange - ) - } - - // If this declaration represents a conditional compilation block, - // we also have to parse its inner declarations. - else if declaration.keyword == "#if", - let declarationTypeKeywordIndex = index( - in: Range(declaration.originalRange), - where: { $0.string == "#if" } - ), - let endOfBody = endOfScope(at: declarationTypeKeywordIndex) - { - let startOfBody = endOfLine(at: declarationTypeKeywordIndex) - let (startTokens, bodyDeclarations, endTokens) = parseBody(in: startOfBody ..< endOfBody) - - return .conditionalCompilation( - open: startTokens, - body: bodyDeclarations, - close: endTokens, - originalRange: declaration.originalRange - ) - } else { - return declaration - } - } - } - /// The type of scope that a declaration is contained within enum DeclarationScope { /// The declaration is a top-level global diff --git a/Sources/Rules.swift b/Sources/Rules.swift index e483ec1f1..1c288625a 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5177,7 +5177,7 @@ public struct _FormatRules { let allowlist = ["let", "var", "func", "typealias"] // Collect all of the `private` or `fileprivate` declarations in the file - var privateDeclarations: [Formatter.Declaration] = [] + var privateDeclarations: [Declaration] = [] formatter.forEachRecursiveDeclaration { declaration in guard allowlist.contains(declaration.keyword) else { return } @@ -5742,7 +5742,7 @@ public struct _FormatRules { switch declaration { // Organize the body of type declarations case let .type(kind, open, body, close, originalRange): - let organizedType = formatter.organizeType((kind, open, body, close)) + let organizedType = formatter.organizeDeclaration((kind, open, body, close)) return .type( kind: organizedType.kind, open: organizedType.open, @@ -5818,7 +5818,7 @@ public struct _FormatRules { } } - let extensionWithUpdatedVisibility: Formatter.Declaration + let extensionWithUpdatedVisibility: Declaration if memberVisibility == extensionVisibility || (memberVisibility == .internal && visibilityKeyword == nil) { diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index bde102c20..b21e5c1a3 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 01F3DF8D1DB9FD3F00454944 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; }; 01F3DF8E1DB9FD3F00454944 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; }; 01F3DF901DBA003E00454944 /* InferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */; }; + 2E230CA22C4C1C0700A16E2E /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; 2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; }; 37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; }; 37D828AC24BF77DA0012FC0A /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -234,6 +235,7 @@ 01F17E841E258A4900DCD359 /* CommandLineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLineTests.swift; sourceTree = ""; }; 01F3DF8B1DB9FD3F00454944 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = ""; }; 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InferenceTests.swift; sourceTree = ""; }; + 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclarationHelpers.swift; sourceTree = ""; }; 2E7D30A32A7940C500C32174 /* Singularize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Singularize.swift; sourceTree = ""; }; 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; 90C4B6CA1DA4B04A009EB000 /* SwiftFormat for Xcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftFormat for Xcode.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -381,6 +383,7 @@ 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */, 01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */, 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */, + 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */, 01ACAE04220CD90F003F3CCF /* Examples.swift */, 2E7D30A32A7940C500C32174 /* Singularize.swift */, 01A0EAA71D5DB4CF00A0A8E3 /* SwiftFormat.h */, @@ -817,6 +820,7 @@ C2FFD1822BD13C9E00774F55 /* XMLReporter.swift in Sources */, 2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */, 01B3987D1D763493009ADE61 /* Formatter.swift in Sources */, + 2E230CA22C4C1C0700A16E2E /* DeclarationHelpers.swift in Sources */, 01F17E821E25870700DCD359 /* CommandLine.swift in Sources */, 01F3DF8C1DB9FD3F00454944 /* Options.swift in Sources */, 01A0EAC21D5DB4F700A0A8E3 /* Tokenizer.swift in Sources */, diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index b9135383f..74af2a9b4 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -219,7 +219,7 @@ class MetadataTests: XCTestCase { { referencedOptions.append(option) } - case .identifier("organizeType"): + case .identifier("organizeDeclaration"): referencedOptions += [ Descriptors.categoryMarkComment, Descriptors.markCategories, diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index 0aeb70cc6..001d9e1e9 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -574,7 +574,7 @@ class OrganizationTests: RulesTests { rule: FormatRules.organizeDeclarations, options: FormatOptions( visibilityOrder: ["private", "internal", "public"], - typeOrder: Formatter.DeclarationType.allCases.map(\.rawValue) + typeOrder: DeclarationType.allCases.map(\.rawValue) ), exclude: ["blankLinesAtStartOfScope"] ) @@ -801,8 +801,8 @@ class OrganizationTests: RulesTests { rule: FormatRules.organizeDeclarations, options: FormatOptions( organizationMode: .visibility, - visibilityOrder: ["instanceMethod"] + Formatter.Visibility.allCases.map(\.rawValue), - typeOrder: Formatter.DeclarationType.allCases.map(\.rawValue).filter { $0 != "instanceMethod" } + visibilityOrder: ["instanceMethod"] + Visibility.allCases.map(\.rawValue), + typeOrder: DeclarationType.allCases.map(\.rawValue).filter { $0 != "instanceMethod" } ), exclude: ["blankLinesAtStartOfScope"] ) @@ -834,8 +834,8 @@ class OrganizationTests: RulesTests { rule: FormatRules.organizeDeclarations, options: FormatOptions( organizationMode: .visibility, - visibilityOrder: Formatter.Visibility.allCases.map(\.rawValue), - typeOrder: Formatter.DeclarationType.allCases.map(\.rawValue) + visibilityOrder: Visibility.allCases.map(\.rawValue), + typeOrder: DeclarationType.allCases.map(\.rawValue) ), exclude: ["blankLinesAtStartOfScope"] ) diff --git a/Tests/SwiftFormatTests.swift b/Tests/SwiftFormatTests.swift index 54ac5b1a8..c3a7c479d 100644 --- a/Tests/SwiftFormatTests.swift +++ b/Tests/SwiftFormatTests.swift @@ -67,7 +67,7 @@ class SwiftFormatTests: XCTestCase { return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 72) + XCTAssertEqual(files.count, 73) } func testInputFilesMatchOutputFilesForSameOutput() { @@ -78,7 +78,7 @@ class SwiftFormatTests: XCTestCase { return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 72) + XCTAssertEqual(files.count, 73) } func testInputFileNotEnumeratedWhenExcluded() { @@ -93,7 +93,7 @@ class SwiftFormatTests: XCTestCase { return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 45) + XCTAssertEqual(files.count, 46) } // MARK: format function From bbe2beebd2fac7d02d90e62a5912ef98d85f4f6b Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 22 Jul 2024 08:47:38 -0700 Subject: [PATCH 19/52] Fix build of CommandLineTool product --- SwiftFormat.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index b21e5c1a3..65bb1279c 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -80,6 +80,9 @@ 01F3DF8D1DB9FD3F00454944 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; }; 01F3DF8E1DB9FD3F00454944 /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; }; 01F3DF901DBA003E00454944 /* InferenceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */; }; + 08180DCF2C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; + 08180DD02C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; + 08180DD12C4EB68000FD60FF /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; 2E230CA22C4C1C0700A16E2E /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; 2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; }; 37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; }; @@ -873,6 +876,7 @@ DD9AD39F2999FCC8001C2C0E /* GithubActionsLogReporter.swift in Sources */, E4E4D3CA2033F17C000D7CB1 /* EnumAssociable.swift in Sources */, 01BBD85A21DAA2A600457380 /* Globs.swift in Sources */, + 08180DCF2C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */, 01045A92211988F100D2BE3D /* Inference.swift in Sources */, 01F3DF8D1DB9FD3F00454944 /* Options.swift in Sources */, E4FABAD6202FEF060065716E /* OptionDescriptor.swift in Sources */, @@ -906,6 +910,7 @@ E4962DE0203F3CD500A02013 /* OptionsStore.swift in Sources */, 01ACAE07220CD915003F3CCF /* Examples.swift in Sources */, E4872113201D3B890014845E /* Formatter.swift in Sources */, + 08180DD02C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */, E4E4D3CB2033F17C000D7CB1 /* EnumAssociable.swift in Sources */, 01BBD85B21DAA2A700457380 /* Globs.swift in Sources */, 01A8320824EC7F7700A9D0EB /* FormattingHelpers.swift in Sources */, @@ -937,6 +942,7 @@ 90F16AFB1DA5ED9A00EB4EA1 /* CommandErrors.swift in Sources */, 01A8320924EC7F7800A9D0EB /* FormattingHelpers.swift in Sources */, 018541CF1DBA0F17000F82E3 /* XCSourceTextBuffer+SwiftFormat.swift in Sources */, + 08180DD12C4EB68000FD60FF /* DeclarationHelpers.swift in Sources */, E4962DE1203F3CD500A02013 /* OptionsStore.swift in Sources */, 9028F7851DA4B435009FE5B4 /* Formatter.swift in Sources */, E487212A201E3DD50014845E /* RulesStore.swift in Sources */, From 435ce82cf496c27b34aac466442a85720aed9df1 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 22 Jul 2024 14:09:58 -0700 Subject: [PATCH 20/52] Improve docCommentsBeforeAttributes rule, fix support for declarations after MARK comments (#1770) --- Rules.md | 36 ++++++------ Sources/Examples.swift | 2 +- Sources/Rules.swift | 15 ++--- Tests/RulesTests+Organization.swift | 2 +- Tests/RulesTests+Syntax.swift | 91 ++++++++++++++++++++++++++--- 5 files changed, 109 insertions(+), 37 deletions(-) diff --git a/Rules.md b/Rules.md index 78f98f098..45433e698 100644 --- a/Rules.md +++ b/Rules.md @@ -15,7 +15,7 @@ * [consecutiveBlankLines](#consecutiveBlankLines) * [consecutiveSpaces](#consecutiveSpaces) * [consistentSwitchCaseSpacing](#consistentSwitchCaseSpacing) -* [docCommentBeforeAttributes](#docCommentBeforeAttributes) +* [docCommentsBeforeAttributes](#docCommentsBeforeAttributes) * [duplicateImports](#duplicateImports) * [elseOnSameLine](#elseOnSameLine) * [emptyBraces](#emptyBraces) @@ -652,23 +652,6 @@ Ensures consistent spacing among all of the cases in a switch statement.

-## docCommentBeforeAttributes - -Place doc comments on declarations before any attributes. - -
-Examples - -```diff -+ /// Doc comment on this function declaration - @MainActor -- /// Doc comment on this function declaration - func foo() {} -``` - -
-
- ## docComments Use doc comments for API declarations, otherwise use regular comments. @@ -696,6 +679,23 @@ Option | Description

+## docCommentsBeforeAttributes + +Place doc comments on declarations before any attributes. + +
+Examples + +```diff ++ /// Doc comment on this function declaration + @MainActor +- /// Doc comment on this function declaration + func foo() {} +``` + +
+
+ ## duplicateImports Remove duplicate import statements. diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 7f48ec283..4c90a8d7b 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -2000,7 +2000,7 @@ private struct Examples { ``` """ - let docCommentBeforeAttributes = """ + let docCommentsBeforeAttributes = """ ```diff + /// Doc comment on this function declaration @MainActor diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 1c288625a..4d33be815 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -8325,7 +8325,7 @@ public struct _FormatRules { } } - public let docCommentBeforeAttributes = FormatRule( + public let docCommentsBeforeAttributes = FormatRule( help: "Place doc comments on declarations before any attributes." ) { formatter in formatter.forEachToken(where: \.isDeclarationTypeKeyword) { keywordIndex, _ in @@ -8336,20 +8336,15 @@ public struct _FormatRules { let attributes = formatter.attributes(startingAt: startOfAttributes) guard !attributes.isEmpty else { return } - let tokenBeforeAttributes = formatter.lastToken(before: startOfAttributes, where: { !$0.isSpaceOrLinebreak }) let attributesRange = attributes.first!.startIndex ... attributes.last!.endIndex - // Make sure there are no comments immediately before, or within, the set of attributes. - guard tokenBeforeAttributes?.isComment != true, - !formatter.tokens[attributesRange].contains(where: \.isComment) - else { return } - // If there's a comment between the attributes and the rest of the declaration, // move it above the attributes. - guard let indexAfterAttributes = formatter.index(of: .nonSpaceOrLinebreak, after: attributesRange.upperBound), + guard let linebreakAfterAttributes = formatter.index(of: .linebreak, after: attributesRange.upperBound), + let indexAfterAttributes = formatter.index(of: .nonSpaceOrLinebreak, after: linebreakAfterAttributes), + indexAfterAttributes < keywordIndex, let restOfDeclaration = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attributesRange.upperBound), - formatter.tokens[indexAfterAttributes].isComment, - formatter.tokens[indexAfterAttributes ..< restOfDeclaration].allSatisfy(\.isSpaceOrCommentOrLinebreak) + formatter.tokens[indexAfterAttributes].isComment else { return } let commentRange = indexAfterAttributes ..< restOfDeclaration diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index 001d9e1e9..4dab22278 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -1643,7 +1643,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, rule: FormatRules.organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) + exclude: ["blankLinesAtStartOfScope", "docCommentsBeforeAttributes"]) } func testHandlesTrailingCommentCorrectly() { diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index 56ec1567a..adf833684 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -5536,7 +5536,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.docCommentBeforeAttributes) + testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes) } func testDocCommentsBeforeMultipleAttributes() { @@ -5564,22 +5564,99 @@ class SyntaxTests: RulesTests { public func bar() {} """ - testFormatting(for: input, output, rule: FormatRules.docCommentBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) } - func testPreserveComplexCommentsBetweenAttributes() { + func testUpdatesCommentsAfterMark() { let input = """ - // Comment before attribute + import FooBarKit + + // MARK: - Foo + @MainActor - // Comment after attribute - func foo() {} + /// Doc comment on this type declaration. + enum Foo { + + // MARK: Public + + @MainActor + /// Doc comment on this function declaration. + public func foo() {} + + // MARK: Private + + // TODO: This function also has a TODO comment. + @MainActor + // Comment on this function declaration. + private func bar() {} + + } + """ + + let output = """ + import FooBarKit + + // MARK: - Foo + + /// Doc comment on this type declaration. + @MainActor + enum Foo { + + // MARK: Public + + /// Doc comment on this function declaration. + @MainActor + public func foo() {} + + // MARK: Private + + // TODO: This function also has a TODO comment. + // Comment on this function declaration. + @MainActor + private func bar() {} + + } + """ + + testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + } + + func testPreservesCommentsBetweenAttributes() { + let input = """ + @MainActor + // Comment between attributes + @available(*, deprecated) + /// Comment before declaration + func bar() {} + + @MainActor // Comment after main actor attribute + @available(*, deprecated) // Comment after deprecation attribute + /// Comment before declaration + func bar() {} + """ + let output = """ + /// Comment before declaration @MainActor // Comment between attributes @available(*, deprecated) func bar() {} + + /// Comment before declaration + @MainActor // Comment after main actor attribute + @available(*, deprecated) // Comment after deprecation attribute + func bar() {} + """ + + testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) + } + + func testPreservesCommentOnSameLineAsAttribute() { + let input = """ + @MainActor // comment trailing attributes + func foo() {} """ - testFormatting(for: input, rule: FormatRules.docCommentBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, rule: FormatRules.docCommentsBeforeAttributes) } } From 5cb234f3b94d3fab658af48bc3df4e096d743b33 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 22 Jul 2024 14:27:25 -0700 Subject: [PATCH 21/52] Fix parseDeclarations bug where incorrect tokens could cause rules to time out (#1769) --- Sources/DeclarationHelpers.swift | 4 +-- Tests/ParsingHelpersTests.swift | 48 +++++++++++++++++++++++++++++ Tests/RulesTests+Organization.swift | 24 +++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 65d1676b5..0feab2727 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -159,11 +159,11 @@ extension Formatter { )) } if startOfDeclaration < range.upperBound { - let declarationRange = startOfDeclaration ..< tokens.count + let declarationRange = startOfDeclaration ... range.upperBound declarations.append(.declaration( kind: "", tokens: Array(tokens[declarationRange]), - originalRange: ClosedRange(declarationRange) + originalRange: declarationRange )) } diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index b87d676b1..4c70d6f1f 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -1629,6 +1629,7 @@ class ParsingHelpersTests: XCTestCase { XCTAssertEqual(declarations.count, 1) XCTAssertEqual(declarations[0].originalRange, 0 ... 24) + XCTAssertEqual(declarations[0].tokens.map(\.string).joined(), input) XCTAssertEqual(declarations[0].body?.count, 2) @@ -1647,6 +1648,53 @@ class ParsingHelpersTests: XCTestCase { ) } + func testParseConditionalCompilationWithNoInnerDeclarations() { + let input = """ + struct Foo { + // This type is empty + } + extension Foo { + // This extension is empty + } + """ + + let formatter = Formatter(tokenize(input)) + let declarations = formatter.parseDeclarations() + XCTAssertEqual(declarations.count, 2) + + XCTAssertEqual( + declarations[0].tokens.map(\.string).joined(), + """ + struct Foo { + // This type is empty + } + + """ + ) + + XCTAssertEqual( + declarations[1].tokens.map(\.string).joined(), + """ + extension Foo { + // This extension is empty + } + """ + ) + } + + func testParseConditionalCompilationWithArgument() { + let input = """ + #if os(Linux) + #error("Linux is currently not supported") + #endif + """ + + let formatter = Formatter(tokenize(input)) + let declarations = formatter.parseDeclarations() + XCTAssertEqual(declarations.count, 1) + XCTAssertEqual(declarations[0].tokens.map(\.string).joined(), input) + } + // MARK: declarationScope func testDeclarationScope_classAndGlobals() { diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index 4dab22278..243be8db6 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -2786,6 +2786,30 @@ class OrganizationTests: RulesTests { testFormatting(for: input, rule: FormatRules.extensionAccessControl) } + func testExtensionAccessControlRuleTerminatesInFileWithConditionalCompilation() { + let input = """ + #if os(Linux) + #error("Linux is currently not supported") + #endif + """ + + testFormatting(for: input, rule: FormatRules.extensionAccessControl) + } + + func testExtensionAccessControlRuleTerminatesInFileWithEmptyType() { + let input = """ + struct Foo { + // This type is empty + } + + extension Foo { + // This extension is empty + } + """ + + testFormatting(for: input, rule: FormatRules.extensionAccessControl) + } + // MARK: markTypes func testAddsMarkBeforeTypes() { From c74333ae9531244f15b811800f6c4075febe471f Mon Sep 17 00:00:00 2001 From: Manny Lopez Date: Mon, 22 Jul 2024 20:37:32 -0700 Subject: [PATCH 22/52] Update unusedPrivateDeclaration and handle false positives (#1771) --- Rules.md | 4 +++ Sources/OptionDescriptor.swift | 6 ++++ Sources/Options.swift | 3 ++ Sources/Rules.swift | 22 +++++++----- Tests/RulesTests+Redundancy.swift | 57 +++++++++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 8 deletions(-) diff --git a/Rules.md b/Rules.md index 45433e698..1164c94dd 100644 --- a/Rules.md +++ b/Rules.md @@ -2767,6 +2767,10 @@ Option | Description Remove unused private and fileprivate declarations. +Option | Description +--- | --- +`--preservedecls` | Comma separated list of declaration names to exclude +
Examples diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index ed384c907..0fa88856b 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -1185,6 +1185,12 @@ struct _Descriptors { help: "\"remove\" (default) redundant nil or \"insert\" missing nil", keyPath: \.nilInit ) + let preservedPrivateDeclarations = OptionDescriptor( + argumentName: "preservedecls", + displayName: "Private Declarations to Exclude", + help: "Comma separated list of declaration names to exclude", + keyPath: \.preservedPrivateDeclarations + ) // MARK: - Internal diff --git a/Sources/Options.swift b/Sources/Options.swift index 5891ed2b2..d1a40456a 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -695,6 +695,7 @@ public struct FormatOptions: CustomStringConvertible { public var dateFormat: DateFormat public var timeZone: FormatTimeZone public var nilInit: NilInitType + public var preservedPrivateDeclarations: Set /// Deprecated public var indentComments: Bool @@ -818,6 +819,7 @@ public struct FormatOptions: CustomStringConvertible { dateFormat: DateFormat = .system, timeZone: FormatTimeZone = .system, nilInit: NilInitType = .remove, + preservedPrivateDeclarations: Set = [], // Doesn't really belong here, but hard to put elsewhere fragment: Bool = false, ignoreConflictMarkers: Bool = false, @@ -931,6 +933,7 @@ public struct FormatOptions: CustomStringConvertible { self.dateFormat = dateFormat self.timeZone = timeZone self.nilInit = nilInit + self.preservedPrivateDeclarations = preservedPrivateDeclarations // 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 4d33be815..3cfeb40b5 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5166,7 +5166,8 @@ public struct _FormatRules { /// Remove unused private and fileprivate declarations public let unusedPrivateDeclaration = FormatRule( help: "Remove unused private and fileprivate declarations.", - disabledByDefault: true + disabledByDefault: true, + options: ["preservedecls"] ) { formatter in guard !formatter.options.fragment else { return } @@ -5179,7 +5180,10 @@ public struct _FormatRules { // Collect all of the `private` or `fileprivate` declarations in the file var privateDeclarations: [Declaration] = [] formatter.forEachRecursiveDeclaration { declaration in - guard allowlist.contains(declaration.keyword) else { return } + guard allowlist.contains(declaration.keyword), + let name = declaration.name, + !(formatter.options.preservedPrivateDeclarations.contains(name)) + else { return } switch formatter.visibility(of: declaration) { case .fileprivate, .private: @@ -5198,12 +5202,14 @@ public struct _FormatRules { // Remove any private or fileprivate declaration whose name only // appears a single time in the source file for declaration in privateDeclarations.reversed() { - guard let name = declaration.name, - let count = usage[name], - count <= 1 - else { continue } - - formatter.removeTokens(in: declaration.originalRange) + // Strip backticks from name for a normalized base name for cases like `default` + guard let name = declaration.name?.trimmingCharacters(in: CharacterSet(charactersIn: "`")) else { continue } + // Check for regular usage, common property wrapper prefixes, and protected names + let variants = [name, "_\(name)", "$\(name)", "`\(name)`"] + let count = variants.compactMap { usage[$0] }.reduce(0, +) + if count <= 1 { + formatter.removeTokens(in: declaration.originalRange) + } } } diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index 17f4a9972..e8e64672b 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -10653,4 +10653,61 @@ class RedundancyTests: RulesTests { testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) } + + func testDoesNotRemovePropertyWrapperPrefixesIfUsed() { + let input = """ + struct ContentView: View { + public init() { + _showButton = .init(initialValue: false) + } + + @State private var showButton: Bool + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoesNotRemoveUnderscoredDeclarationIfUsed() { + let input = """ + struct Foo { + private var _showButton: Bool = true + print(_showButton) + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoesNotRemoveBacktickDeclarationIfUsed() { + let input = """ + struct Foo { + fileprivate static var `default`: Bool = true + func printDefault() { + print(Foo.default) + } + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoesNotRemoveBacktickUsage() { + let input = """ + struct Foo { + fileprivate static var foo = true + func printDefault() { + print(Foo.`foo`) + } + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, exclude: ["redundantBackticks"]) + } + + func testDoNotRemovePreservedPrivateDeclarations() { + let input = """ + enum Foo { + private static let registryAssociation = false + } + """ + let options = FormatOptions(preservedPrivateDeclarations: ["registryAssociation", "hello"]) + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, options: options) + } } From a9507a1b28d5e9d2b9ccc6a216cf53780a311eaf Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Fri, 26 Jul 2024 10:54:24 -0700 Subject: [PATCH 23/52] Update docCommentsBeforeAttributes rule to only apply to doc comments (#1773) --- Sources/ParsingHelpers.swift | 28 ++++++++++++++++ Sources/Rules.swift | 15 +++++---- Tests/RulesTests+Syntax.swift | 62 +++++++++++++++++++++++++---------- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index 279c4dd2e..f0261d3e3 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -1644,6 +1644,34 @@ extension Formatter { return startIndex ... endOfExpression } + /// Whether or not the comment starting at the given index is a doc comment + func isDocComment(startOfComment: Int) -> Bool { + let commentToken = tokens[startOfComment] + guard commentToken == .startOfScope("//") || commentToken == .startOfScope("/*") else { + return false + } + + // Doc comment tokens like `///` and `/**` aren't parsed as a + // single `.startOfScope` token -- they're parsed as: + // `.startOfScope("//"), .commentBody("/ ...")` or + // `.startOfScope("/*"), .commentBody("* ...")` + let startOfDocCommentBody: String + switch commentToken.string { + case "//": + startOfDocCommentBody = "/" + case "/*": + startOfDocCommentBody = "*" + default: + return false + } + + guard let commentBody = token(at: startOfComment + 1), + commentBody.isCommentBody + else { return false } + + return commentBody.string.hasPrefix(startOfDocCommentBody) + } + struct ImportRange: Comparable { var module: String var range: Range diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 3cfeb40b5..471fc8ff8 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -7210,10 +7210,12 @@ public struct _FormatRules { return } - if let commentBody = formatter.token(at: index + 1), - case .commentBody = commentBody + let isDocComment = formatter.isDocComment(startOfComment: index) + + if isDocComment, + let commentBody = formatter.token(at: index + 1), + commentBody.isCommentBody { - let isDocComment = commentBody.string.hasPrefix(startOfDocCommentBody) if useDocComment, !isDocComment, !preserveRegularComments { let updatedCommentBody = "\(startOfDocCommentBody)\(commentBody.string)" formatter.replaceToken(at: index + 1, with: .commentBody(updatedCommentBody)) @@ -8332,7 +8334,8 @@ public struct _FormatRules { } public let docCommentsBeforeAttributes = FormatRule( - help: "Place doc comments on declarations before any attributes." + help: "Place doc comments on declarations before any attributes.", + orderAfter: ["docComments"] ) { formatter in formatter.forEachToken(where: \.isDeclarationTypeKeyword) { keywordIndex, _ in // Parse the attributes on this declaration if present @@ -8344,13 +8347,13 @@ public struct _FormatRules { let attributesRange = attributes.first!.startIndex ... attributes.last!.endIndex - // If there's a comment between the attributes and the rest of the declaration, + // If there's a doc comment between the attributes and the rest of the declaration, // move it above the attributes. guard let linebreakAfterAttributes = formatter.index(of: .linebreak, after: attributesRange.upperBound), let indexAfterAttributes = formatter.index(of: .nonSpaceOrLinebreak, after: linebreakAfterAttributes), indexAfterAttributes < keywordIndex, let restOfDeclaration = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attributesRange.upperBound), - formatter.tokens[indexAfterAttributes].isComment + formatter.isDocComment(startOfComment: indexAfterAttributes) else { return } let commentRange = indexAfterAttributes ..< restOfDeclaration diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index adf833684..98e70fb65 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -5548,7 +5548,7 @@ class SyntaxTests: RulesTests { @MainActor @Macro(argument: true) @available(*, deprecated) - // Comment on this function declaration after several attributes + /// Doc comment on this function declaration after several attributes public func bar() {} """ @@ -5557,14 +5557,14 @@ class SyntaxTests: RulesTests { @MainActor @Macro(argument: true) @available(*, deprecated) public func foo() {} - // Comment on this function declaration after several attributes + /// Doc comment on this function declaration after several attributes @MainActor @Macro(argument: true) @available(*, deprecated) public func bar() {} """ - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes) } func testUpdatesCommentsAfterMark() { @@ -5587,7 +5587,7 @@ class SyntaxTests: RulesTests { // TODO: This function also has a TODO comment. @MainActor - // Comment on this function declaration. + /// Doc comment on this function declaration. private func bar() {} } @@ -5611,40 +5611,40 @@ class SyntaxTests: RulesTests { // MARK: Private // TODO: This function also has a TODO comment. - // Comment on this function declaration. + /// Doc comment on this function declaration. @MainActor private func bar() {} } """ - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) } func testPreservesCommentsBetweenAttributes() { let input = """ @MainActor - // Comment between attributes + /// Doc comment between attributes @available(*, deprecated) - /// Comment before declaration + /// Doc comment before declaration func bar() {} - @MainActor // Comment after main actor attribute - @available(*, deprecated) // Comment after deprecation attribute - /// Comment before declaration + @MainActor /// Doc comment after main actor attribute + @available(*, deprecated) /// Doc comment after deprecation attribute + /// Doc comment before declaration func bar() {} """ let output = """ - /// Comment before declaration + /// Doc comment before declaration @MainActor - // Comment between attributes + /// Doc comment between attributes @available(*, deprecated) func bar() {} - /// Comment before declaration - @MainActor // Comment after main actor attribute - @available(*, deprecated) // Comment after deprecation attribute + /// Doc comment before declaration + @MainActor /// Doc comment after main actor attribute + @available(*, deprecated) /// Doc comment after deprecation attribute func bar() {} """ @@ -5653,10 +5653,36 @@ class SyntaxTests: RulesTests { func testPreservesCommentOnSameLineAsAttribute() { let input = """ - @MainActor // comment trailing attributes + @MainActor /// Doc comment trailing attributes + func foo() {} + """ + + testFormatting(for: input, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) + } + + func testPreservesRegularComments() { + let input = """ + @MainActor + // Comment after attribute + func foo() {} + """ + + testFormatting(for: input, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) + } + + func testCombinesWithDocCommentsRule() { + let input = """ + @MainActor + // Comment after attribute + func foo() {} + """ + + let output = """ + /// Comment after attribute + @MainActor func foo() {} """ - testFormatting(for: input, rule: FormatRules.docCommentsBeforeAttributes) + testFormatting(for: input, [output], rules: [FormatRules.docComments, FormatRules.docCommentsBeforeAttributes]) } } From a3005a7ebf575ddbc83398ea781cfe14c706e777 Mon Sep 17 00:00:00 2001 From: Manny Lopez Date: Sat, 27 Jul 2024 15:12:29 -0700 Subject: [PATCH 24/52] Add exceptions to unusedPrivateDeclaration rule (#1779) --- Sources/DeclarationHelpers.swift | 14 ++++++++ Sources/Rules.swift | 7 +++- Tests/RulesTests+Redundancy.swift | 53 +++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 0feab2727..55bba72b9 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -125,6 +125,20 @@ enum Declaration: Equatable { return originalRange } } + + var modifiers: [String] { + let parser = Formatter(openTokens) + guard let keywordIndex = parser.index(of: .keyword(keyword), after: 0) else { + return [] + } + + var allModifiers = [String]() + _ = parser.modifiersForDeclaration(at: keywordIndex, contains: { _, modifier in + allModifiers.append(modifier) + return false + }) + return allModifiers + } } extension Formatter { diff --git a/Sources/Rules.swift b/Sources/Rules.swift index 471fc8ff8..be72aa9ad 100644 --- a/Sources/Rules.swift +++ b/Sources/Rules.swift @@ -5176,13 +5176,18 @@ public struct _FormatRules { // and it's more difficult to track the usage of other declaration // types like `init`, `subscript`, `operator`, etc. let allowlist = ["let", "var", "func", "typealias"] + let disallowedModifiers = ["override", "@objc", "@IBAction", "@IBSegueAction", "@IBOutlet", "@IBDesignable", "@IBInspectable", "@NSManaged", "@GKInspectable"] // Collect all of the `private` or `fileprivate` declarations in the file var privateDeclarations: [Declaration] = [] formatter.forEachRecursiveDeclaration { declaration in + let declarationModifiers = Set(declaration.modifiers) + let hasDisallowedModifiers = disallowedModifiers.contains(where: { declarationModifiers.contains($0) }) + guard allowlist.contains(declaration.keyword), let name = declaration.name, - !(formatter.options.preservedPrivateDeclarations.contains(name)) + !formatter.options.preservedPrivateDeclarations.contains(name), + !hasDisallowedModifiers else { return } switch formatter.visibility(of: declaration) { diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index e8e64672b..a2de691ce 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -10710,4 +10710,57 @@ class RedundancyTests: RulesTests { let options = FormatOptions(preservedPrivateDeclarations: ["registryAssociation", "hello"]) testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, options: options) } + + func testDoNotRemoveOverridePrivateMethodDeclarations() { + let input = """ + class Poodle: Dog { + override private func makeNoise() { + print("Yip!") + } + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoNotRemoveOverridePrivatePropertyDeclarations() { + let input = """ + class Poodle: Dog { + override private var age: Int { + 7 + } + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoNotRemoveObjcPrivatePropertyDeclaration() { + let input = """ + struct Foo { + @objc + private var bar = "bar" + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoNotRemoveObjcPrivateFunctionDeclaration() { + let input = """ + struct Foo { + @objc + private func doSomething() {} + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } + + func testDoNotRemoveIBActionPrivateFunctionDeclaration() { + let input = """ + class FooViewController: UIViewController { + @IBAction private func buttonPressed(_: UIButton) { + print("Button pressed!") + } + } + """ + testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + } } From 2172c0a958ae048743e8578288b578ce3d0b9861 Mon Sep 17 00:00:00 2001 From: Miguel Jimenez Date: Sun, 28 Jul 2024 11:29:29 -0400 Subject: [PATCH 25/52] Fix FocusState typo (#1785) --- Sources/DeclarationHelpers.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 55bba72b9..e0554011f 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -612,7 +612,7 @@ extension Formatter { "@NSApplicationDelegateAdaptor", "@FetchRequest", "@FocusedBinding", - "@FocusedState", + "@FocusState", "@FocusedValue", "@FocusedObject", "@GestureState", From d79cb436be834fc3a12a350fd5dbbc75dbd185e4 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 29 Jul 2024 09:31:37 -0700 Subject: [PATCH 26/52] Implement each rule in a separate file (#1782) --- PerformanceTests/PerformanceTests.swift | 12 +- Sources/FormatRule.swift | 185 + Sources/FormattingHelpers.swift | 2 +- Sources/RuleRegistry.generated.swift | 122 + Sources/Rules.swift | 8371 ----------------- Sources/Rules/Acronyms.swift | 76 + Sources/Rules/AndOperator.swift | 85 + Sources/Rules/AnyObjectProtocol.swift | 31 + Sources/Rules/ApplicationMain.swift | 32 + Sources/Rules/AssertionFailures.swift | 43 + Sources/Rules/BlankLineAfterImports.swift | 40 + Sources/Rules/BlankLineAfterSwitchCase.swift | 43 + Sources/Rules/BlankLinesAroundMark.swift | 38 + Sources/Rules/BlankLinesAtEndOfScope.swift | 63 + Sources/Rules/BlankLinesAtStartOfScope.swift | 53 + .../BlankLinesBetweenChainedFunctions.swift | 43 + Sources/Rules/BlankLinesBetweenImports.swift | 32 + Sources/Rules/BlankLinesBetweenScopes.swift | 109 + Sources/Rules/BlockComments.swift | 138 + Sources/Rules/Braces.swift | 89 + Sources/Rules/ConditionalAssignment.swift | 251 + Sources/Rules/ConsecutiveBlankLines.swift | 32 + Sources/Rules/ConsecutiveSpaces.swift | 48 + .../Rules/ConsistentSwitchCaseSpacing.swift | 47 + Sources/Rules/DocComments.swift | 177 + .../Rules/DocCommentsBeforeAttributes.swift | 42 + Sources/Rules/DuplicateImports.swift | 35 + Sources/Rules/ElseOnSameLine.swift | 118 + Sources/Rules/EmptyBraces.swift | 41 + Sources/Rules/EnumNamespaces.swift | 124 + Sources/Rules/ExtensionAccessControl.swift | 121 + Sources/Rules/FileHeader.swift | 83 + Sources/Rules/GenericExtensions.swift | 127 + Sources/Rules/HeaderFileName.swift | 34 + Sources/Rules/HoistAwait.swift | 27 + Sources/Rules/HoistPatternLet.swift | 167 + Sources/Rules/HoistTry.swift | 28 + Sources/Rules/Indent.swift | 797 ++ Sources/Rules/InitCoderUnavailable.swift | 60 + Sources/Rules/IsEmpty.swift | 95 + Sources/Rules/LeadingDelimiters.swift | 37 + Sources/Rules/LinebreakAtEndOfFile.swift | 34 + Sources/Rules/Linebreaks.swift | 21 + Sources/Rules/MarkTypes.swift | 254 + Sources/Rules/ModifierOrder.swift | 74 + Sources/Rules/NoExplicitOwnership.swift | 47 + Sources/Rules/NumberFormatting.swift | 105 + Sources/Rules/OpaqueGenericParameters.swift | 273 + Sources/Rules/OrganizeDeclarations.swift | 45 + Sources/Rules/PreferForLoop.swift | 291 + Sources/Rules/PreferKeyPath.swift | 79 + Sources/Rules/PropertyType.swift | 208 + Sources/Rules/RedundantBackticks.swift | 23 + Sources/Rules/RedundantBreak.swift | 31 + Sources/Rules/RedundantClosure.swift | 193 + Sources/Rules/RedundantExtensionACL.swift | 35 + Sources/Rules/RedundantFileprivate.swift | 203 + Sources/Rules/RedundantGet.swift | 34 + Sources/Rules/RedundantInit.swift | 68 + Sources/Rules/RedundantInternal.swift | 42 + Sources/Rules/RedundantLet.swift | 42 + Sources/Rules/RedundantLetError.swift | 28 + Sources/Rules/RedundantNilInit.swift | 76 + Sources/Rules/RedundantObjc.swift | 87 + Sources/Rules/RedundantOptionalBinding.swift | 43 + Sources/Rules/RedundantParens.swift | 228 + Sources/Rules/RedundantPattern.swift | 72 + Sources/Rules/RedundantProperty.swift | 52 + Sources/Rules/RedundantRawValues.swift | 50 + Sources/Rules/RedundantReturn.swift | 218 + Sources/Rules/RedundantSelf.swift | 21 + Sources/Rules/RedundantStaticSelf.swift | 18 + Sources/Rules/RedundantType.swift | 190 + Sources/Rules/RedundantTypedThrows.swift | 41 + Sources/Rules/RedundantVoidReturnType.swift | 46 + Sources/Rules/Semicolons.swift | 52 + Sources/Rules/SortDeclarations.swift | 155 + Sources/Rules/SortImports.swift | 54 + Sources/Rules/SortSwitchCases.swift | 92 + Sources/Rules/SortTypealiases.swift | 135 + Sources/Rules/SortedImports.swift | 23 + Sources/Rules/SortedSwitchCases.swift | 19 + Sources/Rules/SpaceAroundBraces.swift | 39 + Sources/Rules/SpaceAroundBrackets.swift | 71 + Sources/Rules/SpaceAroundComments.swift | 46 + Sources/Rules/SpaceAroundGenerics.swift | 24 + Sources/Rules/SpaceAroundOperators.swift | 142 + Sources/Rules/SpaceAroundParens.swift | 111 + Sources/Rules/SpaceInsideBraces.swift | 35 + Sources/Rules/SpaceInsideBrackets.swift | 31 + Sources/Rules/SpaceInsideComments.swift | 56 + Sources/Rules/SpaceInsideGenerics.swift | 29 + Sources/Rules/SpaceInsideParens.swift | 31 + Sources/Rules/Specifiers.swift | 21 + Sources/Rules/StrongOutlets.swift | 35 + Sources/Rules/StrongifiedSelf.swift | 28 + Sources/Rules/Todos.swift | 69 + Sources/Rules/TrailingClosures.swift | 68 + Sources/Rules/TrailingCommas.swift | 55 + Sources/Rules/TrailingSpace.swift | 27 + Sources/Rules/TypeSugar.swift | 105 + Sources/Rules/UnusedArguments.swift | 307 + Sources/Rules/UnusedPrivateDeclaration.swift | 66 + Sources/Rules/Void.swift | 138 + Sources/Rules/Wrap.swift | 68 + Sources/Rules/WrapArguments.swift | 24 + Sources/Rules/WrapAttributes.swift | 113 + Sources/Rules/WrapConditionalBodies.swift | 24 + Sources/Rules/WrapEnumCases.swift | 70 + Sources/Rules/WrapLoopBodies.swift | 29 + .../WrapMultilineConditionalAssignment.swift | 60 + .../Rules/WrapMultilineStatementBraces.swift | 35 + Sources/Rules/WrapSingleLineComments.swift | 65 + Sources/Rules/WrapSwitchCases.swift | 36 + Sources/Rules/YodaConditions.swift | 139 + Sources/SwiftFormat.swift | 4 +- SwiftFormat.xcodeproj/project.pbxproj | 1146 ++- Tests/CommandLineTests.swift | 4 +- Tests/FormatterTests.swift | 6 +- Tests/GlobsTests.swift | 4 +- Tests/MetadataTests.swift | 341 +- Tests/ReporterTests.swift | 2 +- Tests/RulesTests+Braces.swift | 94 +- Tests/RulesTests+General.swift | 296 +- Tests/RulesTests+Hoisting.swift | 246 +- Tests/RulesTests+Indentation.swift | 672 +- Tests/RulesTests+Linebreaks.swift | 150 +- Tests/RulesTests+Organization.swift | 426 +- Tests/RulesTests+Parens.swift | 362 +- Tests/RulesTests+Redundancy.swift | 1670 ++-- Tests/RulesTests+Spacing.swift | 548 +- Tests/RulesTests+Syntax.swift | 898 +- Tests/RulesTests+Wrapping.swift | 760 +- Tests/RulesTests.swift | 7 + Tests/SwiftFormatTests.swift | 26 +- 135 files changed, 14172 insertions(+), 11587 deletions(-) create mode 100644 Sources/FormatRule.swift create mode 100644 Sources/RuleRegistry.generated.swift delete mode 100644 Sources/Rules.swift create mode 100644 Sources/Rules/Acronyms.swift create mode 100644 Sources/Rules/AndOperator.swift create mode 100644 Sources/Rules/AnyObjectProtocol.swift create mode 100644 Sources/Rules/ApplicationMain.swift create mode 100644 Sources/Rules/AssertionFailures.swift create mode 100644 Sources/Rules/BlankLineAfterImports.swift create mode 100644 Sources/Rules/BlankLineAfterSwitchCase.swift create mode 100644 Sources/Rules/BlankLinesAroundMark.swift create mode 100644 Sources/Rules/BlankLinesAtEndOfScope.swift create mode 100644 Sources/Rules/BlankLinesAtStartOfScope.swift create mode 100644 Sources/Rules/BlankLinesBetweenChainedFunctions.swift create mode 100644 Sources/Rules/BlankLinesBetweenImports.swift create mode 100644 Sources/Rules/BlankLinesBetweenScopes.swift create mode 100644 Sources/Rules/BlockComments.swift create mode 100644 Sources/Rules/Braces.swift create mode 100644 Sources/Rules/ConditionalAssignment.swift create mode 100644 Sources/Rules/ConsecutiveBlankLines.swift create mode 100644 Sources/Rules/ConsecutiveSpaces.swift create mode 100644 Sources/Rules/ConsistentSwitchCaseSpacing.swift create mode 100644 Sources/Rules/DocComments.swift create mode 100644 Sources/Rules/DocCommentsBeforeAttributes.swift create mode 100644 Sources/Rules/DuplicateImports.swift create mode 100644 Sources/Rules/ElseOnSameLine.swift create mode 100644 Sources/Rules/EmptyBraces.swift create mode 100644 Sources/Rules/EnumNamespaces.swift create mode 100644 Sources/Rules/ExtensionAccessControl.swift create mode 100644 Sources/Rules/FileHeader.swift create mode 100644 Sources/Rules/GenericExtensions.swift create mode 100644 Sources/Rules/HeaderFileName.swift create mode 100644 Sources/Rules/HoistAwait.swift create mode 100644 Sources/Rules/HoistPatternLet.swift create mode 100644 Sources/Rules/HoistTry.swift create mode 100644 Sources/Rules/Indent.swift create mode 100644 Sources/Rules/InitCoderUnavailable.swift create mode 100644 Sources/Rules/IsEmpty.swift create mode 100644 Sources/Rules/LeadingDelimiters.swift create mode 100644 Sources/Rules/LinebreakAtEndOfFile.swift create mode 100644 Sources/Rules/Linebreaks.swift create mode 100644 Sources/Rules/MarkTypes.swift create mode 100644 Sources/Rules/ModifierOrder.swift create mode 100644 Sources/Rules/NoExplicitOwnership.swift create mode 100644 Sources/Rules/NumberFormatting.swift create mode 100644 Sources/Rules/OpaqueGenericParameters.swift create mode 100644 Sources/Rules/OrganizeDeclarations.swift create mode 100644 Sources/Rules/PreferForLoop.swift create mode 100644 Sources/Rules/PreferKeyPath.swift create mode 100644 Sources/Rules/PropertyType.swift create mode 100644 Sources/Rules/RedundantBackticks.swift create mode 100644 Sources/Rules/RedundantBreak.swift create mode 100644 Sources/Rules/RedundantClosure.swift create mode 100644 Sources/Rules/RedundantExtensionACL.swift create mode 100644 Sources/Rules/RedundantFileprivate.swift create mode 100644 Sources/Rules/RedundantGet.swift create mode 100644 Sources/Rules/RedundantInit.swift create mode 100644 Sources/Rules/RedundantInternal.swift create mode 100644 Sources/Rules/RedundantLet.swift create mode 100644 Sources/Rules/RedundantLetError.swift create mode 100644 Sources/Rules/RedundantNilInit.swift create mode 100644 Sources/Rules/RedundantObjc.swift create mode 100644 Sources/Rules/RedundantOptionalBinding.swift create mode 100644 Sources/Rules/RedundantParens.swift create mode 100644 Sources/Rules/RedundantPattern.swift create mode 100644 Sources/Rules/RedundantProperty.swift create mode 100644 Sources/Rules/RedundantRawValues.swift create mode 100644 Sources/Rules/RedundantReturn.swift create mode 100644 Sources/Rules/RedundantSelf.swift create mode 100644 Sources/Rules/RedundantStaticSelf.swift create mode 100644 Sources/Rules/RedundantType.swift create mode 100644 Sources/Rules/RedundantTypedThrows.swift create mode 100644 Sources/Rules/RedundantVoidReturnType.swift create mode 100644 Sources/Rules/Semicolons.swift create mode 100644 Sources/Rules/SortDeclarations.swift create mode 100644 Sources/Rules/SortImports.swift create mode 100644 Sources/Rules/SortSwitchCases.swift create mode 100644 Sources/Rules/SortTypealiases.swift create mode 100644 Sources/Rules/SortedImports.swift create mode 100644 Sources/Rules/SortedSwitchCases.swift create mode 100644 Sources/Rules/SpaceAroundBraces.swift create mode 100644 Sources/Rules/SpaceAroundBrackets.swift create mode 100644 Sources/Rules/SpaceAroundComments.swift create mode 100644 Sources/Rules/SpaceAroundGenerics.swift create mode 100644 Sources/Rules/SpaceAroundOperators.swift create mode 100644 Sources/Rules/SpaceAroundParens.swift create mode 100644 Sources/Rules/SpaceInsideBraces.swift create mode 100644 Sources/Rules/SpaceInsideBrackets.swift create mode 100644 Sources/Rules/SpaceInsideComments.swift create mode 100644 Sources/Rules/SpaceInsideGenerics.swift create mode 100644 Sources/Rules/SpaceInsideParens.swift create mode 100644 Sources/Rules/Specifiers.swift create mode 100644 Sources/Rules/StrongOutlets.swift create mode 100644 Sources/Rules/StrongifiedSelf.swift create mode 100644 Sources/Rules/Todos.swift create mode 100644 Sources/Rules/TrailingClosures.swift create mode 100644 Sources/Rules/TrailingCommas.swift create mode 100644 Sources/Rules/TrailingSpace.swift create mode 100644 Sources/Rules/TypeSugar.swift create mode 100644 Sources/Rules/UnusedArguments.swift create mode 100644 Sources/Rules/UnusedPrivateDeclaration.swift create mode 100644 Sources/Rules/Void.swift create mode 100644 Sources/Rules/Wrap.swift create mode 100644 Sources/Rules/WrapArguments.swift create mode 100644 Sources/Rules/WrapAttributes.swift create mode 100644 Sources/Rules/WrapConditionalBodies.swift create mode 100644 Sources/Rules/WrapEnumCases.swift create mode 100644 Sources/Rules/WrapLoopBodies.swift create mode 100644 Sources/Rules/WrapMultilineConditionalAssignment.swift create mode 100644 Sources/Rules/WrapMultilineStatementBraces.swift create mode 100644 Sources/Rules/WrapSingleLineComments.swift create mode 100644 Sources/Rules/WrapSwitchCases.swift create mode 100644 Sources/Rules/YodaConditions.swift diff --git a/PerformanceTests/PerformanceTests.swift b/PerformanceTests/PerformanceTests.swift index 0eac17469..8092abd21 100644 --- a/PerformanceTests/PerformanceTests.swift +++ b/PerformanceTests/PerformanceTests.swift @@ -137,7 +137,7 @@ class PerformanceTests: XCTestCase { let files = PerformanceTests.files let tokens = files.map { tokenize($0) } measure { - _ = tokens.map { try! format($0, rules: [FormatRules.indent]) } + _ = tokens.map { try! format($0, rules: [.indent]) } } } @@ -146,7 +146,7 @@ class PerformanceTests: XCTestCase { let tokens = files.map { tokenize($0) } let options = FormatOptions(indent: "\t", allmanBraces: true) measure { - _ = tokens.map { try! format($0, rules: [FormatRules.indent], options: options) } + _ = tokens.map { try! format($0, rules: [.indent], options: options) } } } @@ -154,7 +154,7 @@ class PerformanceTests: XCTestCase { let files = PerformanceTests.files let tokens = files.map { tokenize($0) } measure { - _ = tokens.map { try! format($0, rules: [FormatRules.redundantSelf]) } + _ = tokens.map { try! format($0, rules: [.redundantSelf]) } } } @@ -163,7 +163,7 @@ class PerformanceTests: XCTestCase { let tokens = files.map { tokenize($0) } let options = FormatOptions(explicitSelf: .insert) measure { - _ = tokens.map { try! format($0, rules: [FormatRules.redundantSelf], options: options) } + _ = tokens.map { try! format($0, rules: [.redundantSelf], options: options) } } } @@ -171,7 +171,7 @@ class PerformanceTests: XCTestCase { let files = PerformanceTests.files let tokens = files.map { tokenize($0) } measure { - _ = tokens.map { try! format($0, rules: [FormatRules.numberFormatting]) } + _ = tokens.map { try! format($0, rules: [.numberFormatting]) } } } @@ -187,7 +187,7 @@ class PerformanceTests: XCTestCase { hexGrouping: .group(1, 1) ) measure { - _ = tokens.map { try! format($0, rules: [FormatRules.numberFormatting], options: options) } + _ = tokens.map { try! format($0, rules: [.numberFormatting], options: options) } } } } diff --git a/Sources/FormatRule.swift b/Sources/FormatRule.swift new file mode 100644 index 000000000..5965a09e0 --- /dev/null +++ b/Sources/FormatRule.swift @@ -0,0 +1,185 @@ +// +// FormatRule.swift +// SwiftFormat +// +// Created by Nick Lockwood on 12/08/2016. +// Copyright 2016 Nick Lockwood +// +// Distributed under the permissive MIT license +// Get the latest version from here: +// +// https://github.com/nicklockwood/SwiftFormat +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + +public final class FormatRule: Equatable, Comparable, CustomStringConvertible { + private let fn: (Formatter) -> Void + fileprivate(set) var name = "[unnamed rule]" + fileprivate(set) var index = 0 + let help: String + let runOnceOnly: Bool + let disabledByDefault: Bool + let orderAfter: [FormatRule] + let options: [String] + let sharedOptions: [String] + let deprecationMessage: String? + + /// Null rule, used for testing + static let none: FormatRule = .init(help: "") { _ in } + + var isDeprecated: Bool { + deprecationMessage != nil + } + + public var description: String { + name + } + + init(help: String, + deprecationMessage: String? = nil, + runOnceOnly: Bool = false, + disabledByDefault: Bool = false, + orderAfter: [FormatRule] = [], + options: [String] = [], + sharedOptions: [String] = [], + _ fn: @escaping (Formatter) -> Void) + { + self.fn = fn + self.help = help + self.runOnceOnly = runOnceOnly + self.disabledByDefault = disabledByDefault || deprecationMessage != nil + self.orderAfter = orderAfter + self.options = options + self.sharedOptions = sharedOptions + self.deprecationMessage = deprecationMessage + } + + public func apply(with formatter: Formatter) { + formatter.currentRule = self + fn(formatter) + formatter.currentRule = nil + } + + public static func == (lhs: FormatRule, rhs: FormatRule) -> Bool { + lhs === rhs + } + + public static func < (lhs: FormatRule, rhs: FormatRule) -> Bool { + lhs.index < rhs.index + } +} + +public let FormatRules = _FormatRules() + +private let rulesByName: [String: FormatRule] = { + var rules = [String: FormatRule]() + for (name, rule) in ruleRegistry { + rule.name = name + rules[name] = rule + } + for rule in rules.values { + assert(rule.name != "[unnamed rule]") + } + let values = rules.values.sorted(by: { $0.name < $1.name }) + for (index, value) in values.enumerated() { + value.index = index * 10 + } + var changedOrder = true + while changedOrder { + changedOrder = false + for value in values { + for rule in value.orderAfter { + if rule.index >= value.index { + value.index = rule.index + 1 + changedOrder = true + } + } + } + } + return rules +}() + +private func allRules(except rules: [String]) -> [FormatRule] { + precondition(!rules.contains(where: { rulesByName[$0] == nil })) + return Array(rulesByName.keys.sorted().compactMap { + rules.contains($0) ? nil : rulesByName[$0] + }) +} + +private let _allRules = allRules(except: []) +private let _deprecatedRules = _allRules.filter { $0.isDeprecated }.map { $0.name } +private let _disabledByDefault = _allRules.filter { $0.disabledByDefault }.map { $0.name } +private let _defaultRules = allRules(except: _disabledByDefault) + +public extension _FormatRules { + /// A Dictionary of rules by name + var byName: [String: FormatRule] { rulesByName } + + /// All rules + var all: [FormatRule] { _allRules } + + /// Default active rules + var `default`: [FormatRule] { _defaultRules } + + /// Rules that are disabled by default + var disabledByDefault: [String] { _disabledByDefault } + + /// Rules that are deprecated + var deprecated: [String] { _deprecatedRules } + + /// Just the specified rules + func named(_ names: [String]) -> [FormatRule] { + Array(names.sorted().compactMap { rulesByName[$0] }) + } + + /// All rules except those specified + func all(except rules: [String]) -> [FormatRule] { + allRules(except: rules) + } +} + +extension _FormatRules { + /// Get all format options used by a given set of rules + func optionsForRules(_ rules: [FormatRule]) -> [String] { + var options = Set() + for rule in rules { + options.formUnion(rule.options + rule.sharedOptions) + } + return options.sorted() + } + + /// Get shared-only options for a given set of rules + func sharedOptionsForRules(_ rules: [FormatRule]) -> [String] { + var options = Set() + var sharedOptions = Set() + for rule in rules { + options.formUnion(rule.options) + sharedOptions.formUnion(rule.sharedOptions) + } + sharedOptions.subtract(options) + return sharedOptions.sorted() + } +} + +public struct _FormatRules { + fileprivate init() {} +} diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index 27ad4fc52..de173b079 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -690,7 +690,7 @@ extension Formatter { } } - if currentRule == FormatRules.wrap { + if currentRule == .wrap { let nextWrapIndex = indexOfNextWrap() ?? endOfLine(at: i) if nextWrapIndex > lastIndex, maxWidth < lineLength(upTo: nextWrapIndex), diff --git a/Sources/RuleRegistry.generated.swift b/Sources/RuleRegistry.generated.swift new file mode 100644 index 000000000..da74a110e --- /dev/null +++ b/Sources/RuleRegistry.generated.swift @@ -0,0 +1,122 @@ +// +// RuleRegistry.generated.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/27/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +/// All of the rules defined in the Rules directory. +/// **Generated automatically when running tests. Do not modify.** +let ruleRegistry: [String: FormatRule] = [ + "acronyms": .acronyms, + "andOperator": .andOperator, + "anyObjectProtocol": .anyObjectProtocol, + "applicationMain": .applicationMain, + "assertionFailures": .assertionFailures, + "blankLineAfterImports": .blankLineAfterImports, + "blankLineAfterSwitchCase": .blankLineAfterSwitchCase, + "blankLinesAroundMark": .blankLinesAroundMark, + "blankLinesAtEndOfScope": .blankLinesAtEndOfScope, + "blankLinesAtStartOfScope": .blankLinesAtStartOfScope, + "blankLinesBetweenChainedFunctions": .blankLinesBetweenChainedFunctions, + "blankLinesBetweenImports": .blankLinesBetweenImports, + "blankLinesBetweenScopes": .blankLinesBetweenScopes, + "blockComments": .blockComments, + "braces": .braces, + "conditionalAssignment": .conditionalAssignment, + "consecutiveBlankLines": .consecutiveBlankLines, + "consecutiveSpaces": .consecutiveSpaces, + "consistentSwitchCaseSpacing": .consistentSwitchCaseSpacing, + "docComments": .docComments, + "docCommentsBeforeAttributes": .docCommentsBeforeAttributes, + "duplicateImports": .duplicateImports, + "elseOnSameLine": .elseOnSameLine, + "emptyBraces": .emptyBraces, + "enumNamespaces": .enumNamespaces, + "extensionAccessControl": .extensionAccessControl, + "fileHeader": .fileHeader, + "genericExtensions": .genericExtensions, + "headerFileName": .headerFileName, + "hoistAwait": .hoistAwait, + "hoistPatternLet": .hoistPatternLet, + "hoistTry": .hoistTry, + "indent": .indent, + "initCoderUnavailable": .initCoderUnavailable, + "isEmpty": .isEmpty, + "leadingDelimiters": .leadingDelimiters, + "linebreakAtEndOfFile": .linebreakAtEndOfFile, + "linebreaks": .linebreaks, + "markTypes": .markTypes, + "modifierOrder": .modifierOrder, + "noExplicitOwnership": .noExplicitOwnership, + "numberFormatting": .numberFormatting, + "opaqueGenericParameters": .opaqueGenericParameters, + "organizeDeclarations": .organizeDeclarations, + "preferForLoop": .preferForLoop, + "preferKeyPath": .preferKeyPath, + "propertyType": .propertyType, + "redundantBackticks": .redundantBackticks, + "redundantBreak": .redundantBreak, + "redundantClosure": .redundantClosure, + "redundantExtensionACL": .redundantExtensionACL, + "redundantFileprivate": .redundantFileprivate, + "redundantGet": .redundantGet, + "redundantInit": .redundantInit, + "redundantInternal": .redundantInternal, + "redundantLet": .redundantLet, + "redundantLetError": .redundantLetError, + "redundantNilInit": .redundantNilInit, + "redundantObjc": .redundantObjc, + "redundantOptionalBinding": .redundantOptionalBinding, + "redundantParens": .redundantParens, + "redundantPattern": .redundantPattern, + "redundantProperty": .redundantProperty, + "redundantRawValues": .redundantRawValues, + "redundantReturn": .redundantReturn, + "redundantSelf": .redundantSelf, + "redundantStaticSelf": .redundantStaticSelf, + "redundantType": .redundantType, + "redundantTypedThrows": .redundantTypedThrows, + "redundantVoidReturnType": .redundantVoidReturnType, + "semicolons": .semicolons, + "sortDeclarations": .sortDeclarations, + "sortImports": .sortImports, + "sortSwitchCases": .sortSwitchCases, + "sortTypealiases": .sortTypealiases, + "sortedImports": .sortedImports, + "sortedSwitchCases": .sortedSwitchCases, + "spaceAroundBraces": .spaceAroundBraces, + "spaceAroundBrackets": .spaceAroundBrackets, + "spaceAroundComments": .spaceAroundComments, + "spaceAroundGenerics": .spaceAroundGenerics, + "spaceAroundOperators": .spaceAroundOperators, + "spaceAroundParens": .spaceAroundParens, + "spaceInsideBraces": .spaceInsideBraces, + "spaceInsideBrackets": .spaceInsideBrackets, + "spaceInsideComments": .spaceInsideComments, + "spaceInsideGenerics": .spaceInsideGenerics, + "spaceInsideParens": .spaceInsideParens, + "specifiers": .specifiers, + "strongOutlets": .strongOutlets, + "strongifiedSelf": .strongifiedSelf, + "todos": .todos, + "trailingClosures": .trailingClosures, + "trailingCommas": .trailingCommas, + "trailingSpace": .trailingSpace, + "typeSugar": .typeSugar, + "unusedArguments": .unusedArguments, + "unusedPrivateDeclaration": .unusedPrivateDeclaration, + "void": .void, + "wrap": .wrap, + "wrapArguments": .wrapArguments, + "wrapAttributes": .wrapAttributes, + "wrapConditionalBodies": .wrapConditionalBodies, + "wrapEnumCases": .wrapEnumCases, + "wrapLoopBodies": .wrapLoopBodies, + "wrapMultilineConditionalAssignment": .wrapMultilineConditionalAssignment, + "wrapMultilineStatementBraces": .wrapMultilineStatementBraces, + "wrapSingleLineComments": .wrapSingleLineComments, + "wrapSwitchCases": .wrapSwitchCases, + "yodaConditions": .yodaConditions, +] diff --git a/Sources/Rules.swift b/Sources/Rules.swift deleted file mode 100644 index be72aa9ad..000000000 --- a/Sources/Rules.swift +++ /dev/null @@ -1,8371 +0,0 @@ -// -// Rules.swift -// SwiftFormat -// -// Created by Nick Lockwood on 12/08/2016. -// Copyright 2016 Nick Lockwood -// -// Distributed under the permissive MIT license -// Get the latest version from here: -// -// https://github.com/nicklockwood/SwiftFormat -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. -// - -import Foundation - -public final class FormatRule: Equatable, Comparable, CustomStringConvertible { - private let fn: (Formatter) -> Void - fileprivate(set) var name = "[unnamed rule]" - fileprivate(set) var index = 0 - let help: String - let runOnceOnly: Bool - let disabledByDefault: Bool - let orderAfter: [String] - let options: [String] - let sharedOptions: [String] - let deprecationMessage: String? - - /// Null rule, used for testing - static let none: FormatRule = .init(help: "") { _ in } - - var isDeprecated: Bool { - deprecationMessage != nil - } - - public var description: String { - name - } - - fileprivate init(help: String, - deprecationMessage: String? = nil, - runOnceOnly: Bool = false, - disabledByDefault: Bool = false, - orderAfter: [String] = [], - options: [String] = [], - sharedOptions: [String] = [], - _ fn: @escaping (Formatter) -> Void) - { - self.fn = fn - self.help = help - self.runOnceOnly = runOnceOnly - self.disabledByDefault = disabledByDefault || deprecationMessage != nil - self.orderAfter = orderAfter - self.options = options - self.sharedOptions = sharedOptions - self.deprecationMessage = deprecationMessage - } - - public func apply(with formatter: Formatter) { - formatter.currentRule = self - fn(formatter) - formatter.currentRule = nil - } - - public static func == (lhs: FormatRule, rhs: FormatRule) -> Bool { - lhs === rhs - } - - public static func < (lhs: FormatRule, rhs: FormatRule) -> Bool { - lhs.index < rhs.index - } -} - -public let FormatRules = _FormatRules() - -private let rulesByName: [String: FormatRule] = { - var rules = [String: FormatRule]() - for (label, value) in Mirror(reflecting: FormatRules).children { - guard let name = label, let rule = value as? FormatRule else { - continue - } - rule.name = name - rules[name] = rule - } - let values = rules.values.sorted(by: { $0.name < $1.name }) - for (index, value) in values.enumerated() { - value.index = index * 10 - } - var changedOrder = true - while changedOrder { - changedOrder = false - for value in values { - for name in value.orderAfter { - guard let rule = rules[name] else { - preconditionFailure(name) - } - if rule.index >= value.index { - value.index = rule.index + 1 - changedOrder = true - } - } - } - } - return rules -}() - -private func allRules(except rules: [String]) -> [FormatRule] { - precondition(!rules.contains(where: { rulesByName[$0] == nil })) - return Array(rulesByName.keys.sorted().compactMap { - rules.contains($0) ? nil : rulesByName[$0] - }) -} - -private let _allRules = allRules(except: []) -private let _deprecatedRules = _allRules.filter { $0.isDeprecated }.map { $0.name } -private let _disabledByDefault = _allRules.filter { $0.disabledByDefault }.map { $0.name } -private let _defaultRules = allRules(except: _disabledByDefault) - -public extension _FormatRules { - /// A Dictionary of rules by name - var byName: [String: FormatRule] { rulesByName } - - /// All rules - var all: [FormatRule] { _allRules } - - /// Default active rules - var `default`: [FormatRule] { _defaultRules } - - /// Rules that are disabled by default - var disabledByDefault: [String] { _disabledByDefault } - - /// Rules that are deprecated - var deprecated: [String] { _deprecatedRules } - - /// Just the specified rules - func named(_ names: [String]) -> [FormatRule] { - Array(names.sorted().compactMap { rulesByName[$0] }) - } - - /// All rules except those specified - func all(except rules: [String]) -> [FormatRule] { - allRules(except: rules) - } -} - -extension _FormatRules { - /// Get all format options used by a given set of rules - func optionsForRules(_ rules: [FormatRule]) -> [String] { - var options = Set() - for rule in rules { - options.formUnion(rule.options + rule.sharedOptions) - } - return options.sorted() - } - - /// Get shared-only options for a given set of rules - func sharedOptionsForRules(_ rules: [FormatRule]) -> [String] { - var options = Set() - var sharedOptions = Set() - for rule in rules { - options.formUnion(rule.options) - sharedOptions.formUnion(rule.sharedOptions) - } - sharedOptions.subtract(options) - return sharedOptions.sorted() - } -} - -public struct _FormatRules { - fileprivate init() {} - - /// Replace the obsolete `@UIApplicationMain` and `@NSApplicationMain` - /// attributes with `@main` in Swift 5.3 and above, per SE-0383 - public let applicationMain = FormatRule( - help: """ - Replace obsolete @UIApplicationMain and @NSApplicationMain attributes - with @main for Swift 5.3 and above. - """ - ) { formatter in - guard formatter.options.swiftVersion >= "5.3" else { - return - } - formatter.forEachToken(where: { - [ - .keyword("@UIApplicationMain"), - .keyword("@NSApplicationMain"), - ].contains($0) - }) { i, _ in - formatter.replaceToken(at: i, with: .keyword("@main")) - } - } - - /// Implement the following rules with respect to the spacing around parens: - /// * There is no space between an opening paren and the preceding identifier, - /// unless the identifier is one of the specified keywords - /// * There is no space between an opening paren and the preceding closing brace - /// * There is no space between an opening paren and the preceding closing square bracket - /// * There is space between a closing paren and following identifier - /// * There is space between a closing paren and following opening brace - /// * There is no space between a closing paren and following opening square bracket - public let spaceAroundParens = FormatRule( - help: "Add or remove space around parentheses." - ) { formatter in - func spaceAfter(_ keywordOrAttribute: String, index: Int) -> Bool { - switch keywordOrAttribute { - case "@autoclosure": - if formatter.options.swiftVersion < "3", - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: index), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("escaping") - { - assert(formatter.tokens[nextIndex] == .startOfScope("(")) - return false - } - return true - case "@escaping", "@noescape", "@Sendable": - return true - case _ where keywordOrAttribute.hasPrefix("@"): - if let i = formatter.index(of: .startOfScope("("), after: index) { - return formatter.isParameterList(at: i) - } - return false - case "private", "fileprivate", "internal", - "init", "subscript", "throws": - return false - case "await": - return formatter.options.swiftVersion >= "5.5" || - formatter.options.swiftVersion == .undefined - default: - return keywordOrAttribute.first.map { !"@#".contains($0) } ?? true - } - } - - formatter.forEach(.startOfScope("(")) { i, _ in - let index = i - 1 - guard let prevToken = formatter.token(at: index) else { - return - } - switch prevToken { - case let .keyword(string) where spaceAfter(string, index: index): - fallthrough - case .endOfScope("]") where formatter.isInClosureArguments(at: index), - .endOfScope(")") where formatter.isAttribute(at: index), - .identifier("some") where formatter.isTypePosition(at: index), - .identifier("any") where formatter.isTypePosition(at: index), - .identifier("borrowing") where formatter.isTypePosition(at: index), - .identifier("consuming") where formatter.isTypePosition(at: index), - .identifier("isolated") where formatter.isTypePosition(at: index), - .identifier("sending") where formatter.isTypePosition(at: index): - formatter.insert(.space(" "), at: i) - case .space: - let index = i - 2 - guard let token = formatter.token(at: index) else { - return - } - switch token { - case .identifier("some") where formatter.isTypePosition(at: index), - .identifier("any") where formatter.isTypePosition(at: index), - .identifier("borrowing") where formatter.isTypePosition(at: index), - .identifier("consuming") where formatter.isTypePosition(at: index), - .identifier("isolated") where formatter.isTypePosition(at: index), - .identifier("sending") where formatter.isTypePosition(at: index): - break - case let .keyword(string) where !spaceAfter(string, index: index): - fallthrough - case .number, .identifier: - fallthrough - case .endOfScope("}"), .endOfScope(">"), - .endOfScope("]") where !formatter.isInClosureArguments(at: index), - .endOfScope(")") where !formatter.isAttribute(at: index): - formatter.removeToken(at: i - 1) - default: - break - } - default: - break - } - } - formatter.forEach(.endOfScope(")")) { i, _ in - guard let nextToken = formatter.token(at: i + 1) else { - return - } - switch nextToken { - case .identifier, .keyword, .startOfScope("{"): - formatter.insert(.space(" "), at: i + 1) - case .space where formatter.token(at: i + 2) == .startOfScope("["): - formatter.removeToken(at: i + 1) - default: - break - } - } - } - - /// Remove space immediately inside parens - public let spaceInsideParens = FormatRule( - help: "Remove space inside parentheses." - ) { formatter in - formatter.forEach(.startOfScope("(")) { i, _ in - if formatter.token(at: i + 1)?.isSpace == true, - formatter.token(at: i + 2)?.isComment == false - { - formatter.removeToken(at: i + 1) - } - } - formatter.forEach(.endOfScope(")")) { i, _ in - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isCommentOrLinebreak == false - { - formatter.removeToken(at: i - 1) - } - } - } - - /// Implement the following rules with respect to the spacing around square brackets: - /// * There is no space between an opening bracket and the preceding identifier, - /// unless the identifier is one of the specified keywords - /// * There is no space between an opening bracket and the preceding closing brace - /// * There is no space between an opening bracket and the preceding closing square bracket - /// * There is space between a closing bracket and following identifier - /// * There is space between a closing bracket and following opening brace - public let spaceAroundBrackets = FormatRule( - help: "Add or remove space around square brackets." - ) { formatter in - formatter.forEach(.startOfScope("[")) { i, _ in - let index = i - 1 - guard let prevToken = formatter.token(at: index) else { - return - } - switch prevToken { - case .keyword, - .identifier("borrowing") where formatter.isTypePosition(at: index), - .identifier("consuming") where formatter.isTypePosition(at: index), - .identifier("sending") where formatter.isTypePosition(at: index): - formatter.insert(.space(" "), at: i) - case .space: - let index = i - 2 - if let token = formatter.token(at: index) { - switch token { - case .identifier("borrowing") where formatter.isTypePosition(at: index), - .identifier("consuming") where formatter.isTypePosition(at: index), - .identifier("sending") where formatter.isTypePosition(at: index): - break - case .identifier, .number, .endOfScope("]"), .endOfScope("}"), .endOfScope(")"): - formatter.removeToken(at: i - 1) - default: - break - } - } - default: - break - } - } - formatter.forEach(.endOfScope("]")) { i, _ in - guard let nextToken = formatter.token(at: i + 1) else { - return - } - switch nextToken { - case .identifier, .keyword, .startOfScope("{"), - .startOfScope("(") where formatter.isInClosureArguments(at: i): - formatter.insert(.space(" "), at: i + 1) - case .space: - switch formatter.token(at: i + 2) { - case .startOfScope("(")? where !formatter.isInClosureArguments(at: i + 2), .startOfScope("[")?: - formatter.removeToken(at: i + 1) - default: - break - } - default: - break - } - } - } - - /// Remove space immediately inside square brackets - public let spaceInsideBrackets = FormatRule( - help: "Remove space inside square brackets." - ) { formatter in - formatter.forEach(.startOfScope("[")) { i, _ in - if formatter.token(at: i + 1)?.isSpace == true, - formatter.token(at: i + 2)?.isComment == false - { - formatter.removeToken(at: i + 1) - } - } - formatter.forEach(.endOfScope("]")) { i, _ in - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isCommentOrLinebreak == false - { - formatter.removeToken(at: i - 1) - } - } - } - - /// Ensure that there is space between an opening brace and the preceding - /// identifier, and between a closing brace and the following identifier. - public let spaceAroundBraces = FormatRule( - help: "Add or remove space around curly braces." - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - if let prevToken = formatter.token(at: i - 1) { - switch prevToken { - case .space, .linebreak, .operator(_, .prefix), .operator(_, .infix), - .startOfScope where !prevToken.isStringDelimiter: - break - default: - formatter.insert(.space(" "), at: i) - } - } - } - formatter.forEach(.endOfScope("}")) { i, _ in - if let nextToken = formatter.token(at: i + 1) { - switch nextToken { - case .identifier, .keyword: - formatter.insert(.space(" "), at: i + 1) - default: - break - } - } - } - } - - /// Ensure that there is space immediately inside braces - public let spaceInsideBraces = FormatRule( - help: "Add space inside curly braces." - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - if let nextToken = formatter.token(at: i + 1) { - if !nextToken.isSpaceOrLinebreak, - ![.endOfScope("}"), .startOfScope("{")].contains(nextToken) - { - formatter.insert(.space(" "), at: i + 1) - } - } - } - formatter.forEach(.endOfScope("}")) { i, _ in - if let prevToken = formatter.token(at: i - 1) { - if !prevToken.isSpaceOrLinebreak, - ![.endOfScope("}"), .startOfScope("{")].contains(prevToken) - { - formatter.insert(.space(" "), at: i) - } - } - } - } - - /// Ensure there is no space between an opening chevron and the preceding identifier - public let spaceAroundGenerics = FormatRule( - help: "Remove space around angle brackets." - ) { formatter in - formatter.forEach(.startOfScope("<")) { i, _ in - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isIdentifierOrKeyword == true - { - formatter.removeToken(at: i - 1) - } - } - } - - /// Remove space immediately inside chevrons - public let spaceInsideGenerics = FormatRule( - help: "Remove space inside angle brackets." - ) { formatter in - formatter.forEach(.startOfScope("<")) { i, _ in - if formatter.token(at: i + 1)?.isSpace == true { - formatter.removeToken(at: i + 1) - } - } - formatter.forEach(.endOfScope(">")) { i, _ in - if formatter.token(at: i - 1)?.isSpace == true, - formatter.token(at: i - 2)?.isLinebreak == false - { - formatter.removeToken(at: i - 1) - } - } - } - - /// Implement the following rules with respect to the spacing around operators: - /// * Infix operators are separated from their operands by a space on either - /// side. Does not affect prefix/postfix operators, as required by syntax. - /// * Delimiters, such as commas and colons, are consistently followed by a - /// single space, unless it appears at the end of a line, and is not - /// 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", "typedelimiter"] - ) { formatter in - formatter.forEachToken { i, token in - switch token { - case .operator(_, .none): - switch formatter.token(at: i + 1) { - case nil, .linebreak?, .endOfScope?, .operator?, .delimiter?, - .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: - break - case .space?: - switch formatter.next(.nonSpaceOrLinebreak, after: i) { - case nil, .linebreak?, .endOfScope?, .delimiter?, - .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: - formatter.removeToken(at: i + 1) - default: - break - } - default: - formatter.insert(.space(" "), at: i + 1) - } - case .operator("?", .postfix), .operator("!", .postfix): - if let prevToken = formatter.token(at: i - 1), - formatter.token(at: i + 1)?.isSpaceOrLinebreak == false, - [.keyword("as"), .keyword("try")].contains(prevToken) - { - formatter.insert(.space(" "), at: i + 1) - } - case .operator(".", _): - if formatter.token(at: i + 1)?.isSpace == true { - formatter.removeToken(at: i + 1) - } - guard let prevIndex = formatter.index(of: .nonSpace, before: i) else { - formatter.removeTokens(in: 0 ..< i) - break - } - let spaceRequired: Bool - switch formatter.tokens[prevIndex] { - case .operator(_, .infix), .startOfScope: - return - case let token where token.isUnwrapOperator: - if let prevToken = formatter.last(.nonSpace, before: prevIndex), - [.keyword("as"), .keyword("try")].contains(prevToken) - { - spaceRequired = true - } else { - spaceRequired = false - } - case .operator(_, .prefix): - spaceRequired = false - case let token: - spaceRequired = !token.isAttribute && !token.isLvalue - } - if formatter.token(at: i - 1)?.isSpaceOrLinebreak == true { - if !spaceRequired { - formatter.removeToken(at: i - 1) - } - } else if spaceRequired { - formatter.insertSpace(" ", at: i) - } - case .operator("?", .infix): - break // Spacing around ternary ? is not optional - case let .operator(name, .infix) where formatter.options.noSpaceOperators.contains(name) || - (!formatter.options.spaceAroundRangeOperators && token.isRangeOperator): - if formatter.token(at: i + 1)?.isSpace == true, - formatter.token(at: i - 1)?.isSpace == true, - let nextToken = formatter.next(.nonSpace, after: i), - !nextToken.isCommentOrLinebreak, !nextToken.isOperator, - let prevToken = formatter.last(.nonSpace, before: i), - !prevToken.isCommentOrLinebreak, !prevToken.isOperator || prevToken.isUnwrapOperator - { - formatter.removeToken(at: i + 1) - formatter.removeToken(at: i - 1) - } - case .operator(_, .infix): - if formatter.token(at: i + 1)?.isSpaceOrLinebreak == false { - formatter.insert(.space(" "), at: i + 1) - } - if formatter.token(at: i - 1)?.isSpaceOrLinebreak == false { - formatter.insert(.space(" "), at: i) - } - case .operator(_, .prefix): - if let prevIndex = formatter.index(of: .nonSpace, before: i, if: { - [.startOfScope("["), .startOfScope("("), .startOfScope("<")].contains($0) - }) { - formatter.removeTokens(in: prevIndex + 1 ..< i) - } else if let prevToken = formatter.token(at: i - 1), - !prevToken.isSpaceOrLinebreak, !prevToken.isOperator - { - formatter.insert(.space(" "), at: i) - } - case .delimiter(":"): - // TODO: make this check more robust, and remove redundant space - if formatter.token(at: i + 1)?.isIdentifier == true, - formatter.token(at: i + 2) == .delimiter(":") - { - // It's a selector - break - } - fallthrough - case .operator(_, .postfix), .delimiter(","), .delimiter(";"), .startOfScope(":"): - switch formatter.token(at: i + 1) { - case nil, .space?, .linebreak?, .endOfScope?, .operator?, .delimiter?: - break - default: - // Ensure there is a space after the token - formatter.insert(.space(" "), at: i + 1) - } - - let spaceBeforeToken = formatter.token(at: i - 1)?.isSpace == true - && formatter.token(at: i - 2)?.isLinebreak == false - - if spaceBeforeToken, formatter.options.typeDelimiterSpacing == .spaceAfter { - // Remove space before the token - formatter.removeToken(at: i - 1) - } else if !spaceBeforeToken, formatter.options.typeDelimiterSpacing == .spaced { - formatter.insertSpace(" ", at: i) - } - default: - break - } - } - } - - /// Add space around comments, except at the start or end of a line - public let spaceAroundComments = FormatRule( - help: "Add space before and/or after comments." - ) { formatter in - formatter.forEach(.startOfScope("//")) { i, _ in - if let prevToken = formatter.token(at: i - 1), !prevToken.isSpaceOrLinebreak { - formatter.insert(.space(" "), at: i) - } - } - formatter.forEach(.endOfScope("*/")) { i, _ in - guard let startIndex = formatter.index(of: .startOfScope("/*"), before: i), - case let .commentBody(commentStart)? = formatter.next(.nonSpaceOrLinebreak, after: startIndex), - case let .commentBody(commentEnd)? = formatter.last(.nonSpaceOrLinebreak, before: i), - !commentStart.hasPrefix("@"), !commentEnd.hasSuffix("@") - else { - return - } - if let nextToken = formatter.token(at: i + 1) { - if !nextToken.isSpaceOrLinebreak { - if nextToken != .delimiter(",") { - formatter.insert(.space(" "), at: i + 1) - } - } else if formatter.next(.nonSpace, after: i + 1) == .delimiter(",") { - formatter.removeToken(at: i + 1) - } - } - if let prevToken = formatter.token(at: startIndex - 1), !prevToken.isSpaceOrLinebreak { - if case let .commentBody(text) = prevToken, text.last?.unicodeScalars.last?.isSpace == true { - return - } - formatter.insert(.space(" "), at: startIndex) - } - } - } - - /// Add space inside comments, taking care not to mangle headerdoc or - /// carefully preformatted comments, such as star boxes, etc. - public let spaceInsideComments = FormatRule( - help: "Add leading and/or trailing space inside comments." - ) { formatter in - formatter.forEach(.startOfScope("//")) { i, _ in - guard case let .commentBody(string)? = formatter.token(at: i + 1), - let first = string.first else { return } - if "/!:".contains(first) { - let nextIndex = string.index(after: string.startIndex) - if nextIndex < string.endIndex, case let next = string[nextIndex], !" \t/".contains(next) { - let string = String(string.first!) + " " + String(string.dropFirst()) - formatter.replaceToken(at: i + 1, with: .commentBody(string)) - } - } else if !" \t".contains(first), !string.hasPrefix("===") { // Special-case check for swift stdlib codebase - formatter.insert(.space(" "), at: i + 1) - } - } - formatter.forEach(.startOfScope("/*")) { i, _ in - guard case let .commentBody(string)? = formatter.token(at: i + 1), - !string.hasPrefix("---"), !string.hasPrefix("@"), !string.hasSuffix("---"), !string.hasSuffix("@") - else { - return - } - if let first = string.first, "*!:".contains(first) { - let nextIndex = string.index(after: string.startIndex) - if nextIndex < string.endIndex, case let next = string[nextIndex], - !" /t".contains(next), !string.hasPrefix("**"), !string.hasPrefix("*/") - { - let string = String(string.first!) + " " + String(string.dropFirst()) - formatter.replaceToken(at: i + 1, with: .commentBody(string)) - } - } else { - formatter.insert(.space(" "), at: i + 1) - } - if let i = formatter.index(of: .endOfScope("*/"), after: i), let prevToken = formatter.token(at: i - 1) { - if !prevToken.isSpaceOrLinebreak, !prevToken.string.hasSuffix("*"), - !prevToken.string.trimmingCharacters(in: .whitespaces).isEmpty - { - formatter.insert(.space(" "), at: i) - } - } - } - } - - /// Removes explicit type declarations from initialization declarations - public let redundantType = FormatRule( - help: "Remove redundant type from variable declarations.", - options: ["redundanttype"] - ) { formatter in - formatter.forEach(.operator("=", .infix)) { i, _ in - guard let keyword = formatter.lastSignificantKeyword(at: i), - ["var", "let"].contains(keyword) - else { - return - } - - let equalsIndex = i - guard let colonIndex = formatter.index(before: i, where: { - [.delimiter(":"), .operator("=", .infix)].contains($0) - }), formatter.tokens[colonIndex] == .delimiter(":"), - let typeEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) - else { return } - - // Compares whether or not two types are equivalent - func compare(typeStartingAfter j: Int, withTypeStartingAfter i: Int) - -> (matches: Bool, i: Int, j: Int, wasValue: Bool) - { - var i = i, j = j, wasValue = false - - while let typeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - typeIndex <= typeEndIndex, - let valueIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: j) - { - let typeToken = formatter.tokens[typeIndex] - let valueToken = formatter.tokens[valueIndex] - if !wasValue { - switch valueToken { - case _ where valueToken.isStringDelimiter, .number, - .identifier("true"), .identifier("false"): - if formatter.options.redundantType == .explicit { - // We never remove the value in this case, so exit early - return (false, i, j, wasValue) - } - wasValue = true - default: - break - } - } - guard typeToken == formatter.typeToken(forValueToken: valueToken) else { - return (false, i, j, wasValue) - } - // Avoid introducing "inferred to have type 'Void'" warning - if formatter.options.redundantType == .inferred, typeToken == .identifier("Void") || - typeToken == .endOfScope(")") && formatter.tokens[i] == .startOfScope("(") - { - return (false, i, j, wasValue) - } - i = typeIndex - j = valueIndex - if formatter.tokens[j].isStringDelimiter, let next = formatter.endOfScope(at: j) { - j = next - } - } - guard i == typeEndIndex else { - return (false, i, j, wasValue) - } - - // Check for ternary - if let endOfExpression = formatter.endOfExpression(at: j, upTo: [.operator("?", .infix)]), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfExpression) == .operator("?", .infix) - { - return (false, i, j, wasValue) - } - - return (true, i, j, wasValue) - } - - // The implementation of RedundantType uses inferred or explicit, - // potentially depending on the context. - let isInferred: Bool - let declarationKeywordIndex: Int? - switch formatter.options.redundantType { - case .inferred: - isInferred = true - declarationKeywordIndex = nil - case .explicit: - isInferred = false - declarationKeywordIndex = formatter.declarationIndexAndScope(at: equalsIndex).index - case .inferLocalsOnly: - let (index, scope) = formatter.declarationIndexAndScope(at: equalsIndex) - switch scope { - case .global, .type: - isInferred = false - declarationKeywordIndex = index - case .local: - isInferred = true - declarationKeywordIndex = nil - } - } - - // Explicit type can't be safely removed from @Model classes - // https://github.com/nicklockwood/SwiftFormat/issues/1649 - if !isInferred, - let declarationKeywordIndex = declarationKeywordIndex, - formatter.modifiersForDeclaration(at: declarationKeywordIndex, contains: "@Model") - { - return - } - - // Removes a type already processed by `compare(typeStartingAfter:withTypeStartingAfter:)` - func removeType(after indexBeforeStartOfType: Int, i: Int, j: Int, wasValue: Bool) { - if isInferred { - formatter.removeTokens(in: colonIndex ... typeEndIndex) - if formatter.tokens[colonIndex - 1].isSpace { - formatter.removeToken(at: colonIndex - 1) - } - } else if !wasValue, let valueStartIndex = formatter - .index(of: .nonSpaceOrCommentOrLinebreak, after: indexBeforeStartOfType), - !formatter.isConditionalStatement(at: i), - let endIndex = formatter.endOfExpression(at: j, upTo: []), - endIndex > j - { - let allowChains = formatter.options.swiftVersion >= "5.4" - if formatter.next(.nonSpaceOrComment, after: j) == .startOfScope("(") { - if allowChains || formatter.index( - of: .operator(".", .infix), - in: j ..< endIndex - ) == nil { - formatter.replaceTokens(in: valueStartIndex ... j, with: [ - .operator(".", .infix), .identifier("init"), - ]) - } - } else if let nextIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - after: j, - if: { $0 == .operator(".", .infix) } - ), allowChains || formatter.index( - of: .operator(".", .infix), - in: (nextIndex + 1) ..< endIndex - ) == nil { - formatter.removeTokens(in: valueStartIndex ... j) - } - } - } - - // In Swift 5.9+ (SE-0380) we need to handle if / switch expressions by checking each branch - if formatter.options.swiftVersion >= "5.9", - let tokenAfterEquals = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex), - let conditionalBranches = formatter.conditionalBranches(at: tokenAfterEquals), - formatter.allRecursiveConditionalBranches( - in: conditionalBranches, - satisfy: { branch in - compare(typeStartingAfter: branch.startOfBranch, withTypeStartingAfter: colonIndex).matches - } - ) - { - if isInferred { - formatter.removeTokens(in: colonIndex ... typeEndIndex) - if formatter.tokens[colonIndex - 1].isSpace { - formatter.removeToken(at: colonIndex - 1) - } - } else { - formatter.forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in - let (_, i, j, wasValue) = compare( - typeStartingAfter: branch.startOfBranch, - withTypeStartingAfter: colonIndex - ) - - removeType(after: branch.startOfBranch, i: i, j: j, wasValue: wasValue) - } - } - } - - // Otherwise this is just a simple assignment expression where the RHS is a single value - else { - let (matches, i, j, wasValue) = compare(typeStartingAfter: equalsIndex, withTypeStartingAfter: colonIndex) - if matches { - removeType(after: equalsIndex, i: i, j: j, wasValue: wasValue) - } - } - } - } - - /// Collapse all consecutive space characters to a single space, except at - /// the start of a line or inside a comment or string, as these have no semantic - /// meaning and lead to noise in commits. - public let consecutiveSpaces = FormatRule( - help: "Replace consecutive spaces with a single space." - ) { formatter in - formatter.forEach(.space) { i, token in - switch token { - case .space(""): - formatter.removeToken(at: i) - case .space(" "): - break - default: - guard let prevToken = formatter.token(at: i - 1), - let nextToken = formatter.token(at: i + 1) - else { - return - } - switch prevToken { - case .linebreak, .startOfScope("/*"), .startOfScope("//"), .commentBody: - return - case .endOfScope("*/") where nextToken == .startOfScope("/*") && - formatter.currentScope(at: i) == .startOfScope("/*"): - return - default: - break - } - switch nextToken { - case .linebreak, .endOfScope("*/"), .commentBody: - return - default: - formatter.replaceToken(at: i, with: .space(" ")) - } - } - } - } - - /// Converts types used for hosting only static members into enums to avoid instantiation. - public let enumNamespaces = FormatRule( - help: """ - Convert types used for hosting only static members into enums (an empty enum is - the canonical way to create a namespace in Swift as it can't be instantiated). - """, - options: ["enumnamespaces"] - ) { formatter in - func rangeHostsOnlyStaticMembersAtTopLevel(_ range: Range) -> Bool { - // exit for empty declarations - guard formatter.next(.nonSpaceOrCommentOrLinebreak, in: range) != nil else { - return false - } - - var j = range.startIndex - while j < range.endIndex, let token = formatter.token(at: j) { - if token == .startOfScope("{"), - let skip = formatter.index(of: .endOfScope("}"), after: j) - { - j = skip - continue - } - // exit if there's a explicit init - if token == .keyword("init") { - return false - } else if [.keyword("let"), - .keyword("var"), - .keyword("func")].contains(token), - !formatter.modifiersForDeclaration(at: j, contains: "static") - { - return false - } - j += 1 - } - return true - } - - func rangeContainsTypeInit(_ type: String, in range: Range) -> Bool { - for i in range { - guard case let .identifier(name) = formatter.tokens[i], - [type, "Self", "self"].contains(name) - else { - continue - } - if let nextIndex = formatter.index(of: .nonSpaceOrComment, after: i), - let nextToken = formatter.token(at: nextIndex), nextToken == .startOfScope("(") || - (nextToken == .operator(".", .infix) && [.identifier("init"), .identifier("self")] - .contains(formatter.next(.nonSpaceOrComment, after: nextIndex) ?? .space(""))) - { - return true - } - } - return false - } - - func rangeContainsSelfAssignment(_ range: Range) -> Bool { - for i in range { - guard case .identifier("self") = formatter.tokens[i] else { - continue - } - if let token = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - [.operator("=", .infix), .delimiter(":"), .startOfScope("(")].contains(token) - { - return true - } - } - return false - } - - formatter.forEachToken(where: { [.keyword("class"), .keyword("struct")].contains($0) }) { i, token in - if token == .keyword("class") { - guard let next = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i), - // exit if structs only - formatter.options.enumNamespaces != .structsOnly, - // exit if class is a type modifier - !(next.isKeywordOrAttribute || next.isModifierKeyword), - // exit for class as protocol conformance - formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .delimiter(":"), - // exit if not closed for extension - formatter.modifiersForDeclaration(at: i, contains: "final") - else { - return - } - } - guard let braceIndex = formatter.index(of: .startOfScope("{"), after: i), - // exit if import statement - formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import"), - // exit if has attribute(s) - !formatter.modifiersForDeclaration(at: i, contains: { $1.hasPrefix("@") }), - // exit if type is conforming any other types - !formatter.tokens[i ... braceIndex].contains(.delimiter(":")), - let endIndex = formatter.index(of: .endOfScope("}"), after: braceIndex), - case let .identifier(name)? = formatter.next(.identifier, after: i + 1) - else { - return - } - let range = braceIndex + 1 ..< endIndex - if rangeHostsOnlyStaticMembersAtTopLevel(range), - !rangeContainsTypeInit(name, in: range), !rangeContainsSelfAssignment(range) - { - formatter.replaceToken(at: i, with: .keyword("enum")) - - if let finalIndex = formatter.indexOfModifier("final", forDeclarationAt: i), - let nextIndex = formatter.index(of: .nonSpace, after: finalIndex) - { - formatter.removeTokens(in: finalIndex ..< nextIndex) - } - } - } - } - - /// Remove trailing space from the end of lines, as it has no semantic - /// meaning and leads to noise in commits. - public let trailingSpace = FormatRule( - help: "Remove trailing space at end of a line.", - orderAfter: ["wrap", "wrapArguments"], - options: ["trimwhitespace"] - ) { formatter in - formatter.forEach(.space) { i, _ in - if formatter.token(at: i + 1)?.isLinebreak ?? true, - formatter.options.truncateBlankLines || formatter.token(at: i - 1)?.isLinebreak == false - { - formatter.removeToken(at: i) - } - } - } - - /// Collapse all consecutive blank lines into a single blank line - public let consecutiveBlankLines = FormatRule( - help: "Replace consecutive blank lines with a single blank line." - ) { formatter in - formatter.forEach(.linebreak) { i, _ in - guard let prevIndex = formatter.index(of: .nonSpace, before: i, if: { $0.isLinebreak }) else { - return - } - if let scope = formatter.currentScope(at: i), scope.isMultilineStringDelimiter { - return - } - if let nextIndex = formatter.index(of: .nonSpace, after: i) { - if formatter.tokens[nextIndex].isLinebreak { - formatter.removeTokens(in: i + 1 ... nextIndex) - } - } else if !formatter.options.fragment { - formatter.removeTokens(in: i ..< formatter.tokens.count) - } - } - } - - /// Remove blank lines immediately after an opening brace, bracket, paren or chevron - public let blankLinesAtStartOfScope = FormatRule( - help: "Remove leading blank line at the start of a scope.", - orderAfter: ["organizeDeclarations"], - options: ["typeblanklines"] - ) { formatter in - formatter.forEach(.startOfScope) { i, token in - guard ["{", "(", "[", "<"].contains(token.string), - let indexOfFirstLineBreak = formatter.index(of: .nonSpaceOrComment, after: i), - // If there is extra code on the same line, ignore it - formatter.tokens[indexOfFirstLineBreak].isLinebreak - else { return } - - // Consumers can choose whether or not this rule should apply to type bodies - if !formatter.options.removeStartOrEndBlankLinesFromTypes, - ["class", "actor", "struct", "enum", "protocol", "extension"].contains( - formatter.lastSignificantKeyword(at: i, excluding: ["where"])) - { - return - } - - // Find next non-space token - var index = indexOfFirstLineBreak + 1 - var indexOfLastLineBreak = indexOfFirstLineBreak - loop: while let token = formatter.token(at: index) { - switch token { - case .linebreak: - indexOfLastLineBreak = index - case .space: - break - default: - break loop - } - index += 1 - } - if formatter.options.removeBlankLines, indexOfFirstLineBreak != indexOfLastLineBreak { - formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak) - return - } - } - } - - /// Remove blank lines immediately before a closing brace, bracket, paren or chevron - /// unless it's followed by more code on the same line (e.g. } else { ) - public let blankLinesAtEndOfScope = FormatRule( - help: "Remove trailing blank line at the end of a scope.", - orderAfter: ["organizeDeclarations"], - sharedOptions: ["typeblanklines"] - ) { formatter in - formatter.forEach(.startOfScope) { startOfScopeIndex, _ in - guard let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex) else { return } - let endOfScope = formatter.tokens[endOfScopeIndex] - - guard ["}", ")", "]", ">"].contains(endOfScope.string), - // If there is extra code after the closing scope on the same line, ignore it - (formatter.next(.nonSpaceOrComment, after: endOfScopeIndex).map { $0.isLinebreak }) ?? true - else { return } - - // Consumers can choose whether or not this rule should apply to type bodies - if !formatter.options.removeStartOrEndBlankLinesFromTypes, - ["class", "actor", "struct", "enum", "protocol", "extension"].contains( - formatter.lastSignificantKeyword(at: startOfScopeIndex, excluding: ["where"])) - { - return - } - - // Find previous non-space token - var index = endOfScopeIndex - 1 - var indexOfFirstLineBreak: Int? - var indexOfLastLineBreak: Int? - loop: while let token = formatter.token(at: index) { - switch token { - case .linebreak: - indexOfFirstLineBreak = index - if indexOfLastLineBreak == nil { - indexOfLastLineBreak = index - } - case .space: - break - default: - break loop - } - index -= 1 - } - if formatter.options.removeBlankLines, - let indexOfFirstLineBreak = indexOfFirstLineBreak, - indexOfFirstLineBreak != indexOfLastLineBreak - { - formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak!) - return - } - } - } - - /// Remove blank lines between import statements - public let blankLinesBetweenImports = FormatRule( - help: """ - Remove blank lines between import statements. - """, - disabledByDefault: true, - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.keyword("import")) { currentImportIndex, _ in - guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex), - let nextImportIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine, if: { - $0 == .keyword("@testable") || $0 == .keyword("import") - }) - else { - return - } - - formatter.replaceTokens(in: endOfLine ..< nextImportIndex, with: formatter.linebreakToken(for: currentImportIndex + 1)) - } - } - - /// Remove blank lines between chained functions but keep the linebreaks - public let blankLinesBetweenChainedFunctions = FormatRule( - help: """ - Remove blank lines between chained functions but keep the linebreaks. - """ - ) { formatter in - formatter.forEach(.operator(".", .infix)) { i, _ in - let endOfLine = formatter.endOfLine(at: i) - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfLine), - formatter.tokens[nextIndex] == .operator(".", .infix), - // Make sure to preserve any code comment between the two lines - let nextTokenOrComment = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine) - { - if formatter.tokens[nextTokenOrComment].isComment { - if formatter.options.enabledRules.contains(FormatRules.blankLinesAroundMark.name), - case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenOrComment), - body.hasPrefix("MARK:") - { - return - } - if let endOfComment = formatter.index(of: .comment, before: nextIndex) { - let endOfLine = formatter.endOfLine(at: endOfComment) - let startOfLine = formatter.startOfLine(at: nextIndex) - formatter.removeTokens(in: endOfLine + 1 ..< startOfLine) - } - } - let startOfLine = formatter.startOfLine(at: nextTokenOrComment) - formatter.removeTokens(in: endOfLine + 1 ..< startOfLine) - } - } - } - - /// Insert blank line after import statements - public let blankLineAfterImports = FormatRule( - help: """ - Insert blank line after import statements. - """, - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.keyword("import")) { currentImportIndex, _ in - guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex), - var nextIndex = formatter.index(of: .nonSpace, after: endOfLine) - else { - return - } - var keyword: Token = formatter.tokens[nextIndex] - while keyword == .startOfScope("#if") || keyword.isModifierKeyword || keyword.isAttribute, - let index = formatter.index(of: .keyword, after: nextIndex) - { - nextIndex = index - keyword = formatter.tokens[nextIndex] - } - switch formatter.tokens[nextIndex] { - case .linebreak, .keyword("import"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"): - break - default: - formatter.insertLinebreak(at: endOfLine) - } - } - } - - /// Adds a blank line immediately after a closing brace, unless followed by another closing brace - public let blankLinesBetweenScopes = FormatRule( - help: """ - Insert blank line before class, struct, enum, extension, protocol or function - declarations. - """, - sharedOptions: ["linebreaks"] - ) { formatter in - var spaceableScopeStack = [true] - var isSpaceableScopeType = false - formatter.forEachToken(onlyWhereEnabled: false) { i, token in - outer: switch token { - case .keyword("class"), - .keyword("actor"), - .keyword("struct"), - .keyword("extension"), - .keyword("enum"): - isSpaceableScopeType = - (formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import")) - case .keyword("func"), .keyword("var"): - isSpaceableScopeType = false - case .startOfScope("{"): - spaceableScopeStack.append(isSpaceableScopeType) - isSpaceableScopeType = false - case .endOfScope("}"): - spaceableScopeStack.removeLast() - guard spaceableScopeStack.last == true, - let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: i), - formatter.lastIndex(of: .linebreak, in: openingBraceIndex + 1 ..< i) != nil - else { - // Inline braces - break - } - var i = i - if let nextTokenIndex = formatter.index(of: .nonSpace, after: i, if: { - $0 == .startOfScope("(") - }), let closingParenIndex = formatter.index(of: - .endOfScope(")"), after: nextTokenIndex) - { - i = closingParenIndex - } - guard let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i), - formatter.isEnabled, formatter.options.insertBlankLines, - let firstLinebreakIndex = formatter.index(of: .linebreak, in: i + 1 ..< nextTokenIndex), - formatter.index(of: .linebreak, in: firstLinebreakIndex + 1 ..< nextTokenIndex) == nil - else { - break - } - if var nextNonCommentIndex = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) - { - while formatter.tokens[nextNonCommentIndex] == .startOfScope("#if"), - let nextIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - after: formatter.endOfLine(at: nextNonCommentIndex) - ) - { - nextNonCommentIndex = nextIndex - } - switch formatter.tokens[nextNonCommentIndex] { - case .error, .endOfScope, - .operator(".", _), .delimiter(","), .delimiter(":"), - .keyword("else"), .keyword("catch"), .keyword("#else"): - break outer - case .keyword("while"): - if let previousBraceIndex = formatter.index(of: .startOfScope("{"), before: i), - formatter.last(.nonSpaceOrCommentOrLinebreak, before: previousBraceIndex) - == .keyword("repeat") - { - break outer - } - default: - if formatter.isLabel(at: nextNonCommentIndex), let colonIndex - = formatter.index(of: .delimiter(":"), after: nextNonCommentIndex), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex) - == .startOfScope("{") - { - break outer - } - } - } - switch formatter.tokens[nextTokenIndex] { - case .startOfScope("//"): - if case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenIndex), - body.trimmingCharacters(in: .whitespaces).lowercased().hasPrefix("sourcery:") - { - break - } - formatter.insertLinebreak(at: firstLinebreakIndex) - default: - formatter.insertLinebreak(at: firstLinebreakIndex) - } - default: - break - } - } - } - - /// Adds a blank line around MARK: comments - public let blankLinesAroundMark = FormatRule( - help: "Insert blank line before and after `MARK:` comments.", - options: ["lineaftermarks"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEachToken { i, token in - guard case let .commentBody(comment) = token, comment.hasPrefix("MARK:"), - let startIndex = formatter.index(of: .nonSpace, before: i), - formatter.tokens[startIndex] == .startOfScope("//") else { return } - if let nextIndex = formatter.index(of: .linebreak, after: i), - let nextToken = formatter.next(.nonSpace, after: nextIndex), - !nextToken.isLinebreak, nextToken != .endOfScope("}"), - formatter.options.lineAfterMarks - { - formatter.insertLinebreak(at: nextIndex) - } - if formatter.options.insertBlankLines, - let lastIndex = formatter.index(of: .linebreak, before: startIndex), - let lastToken = formatter.last(.nonSpace, before: lastIndex), - !lastToken.isLinebreak, lastToken != .startOfScope("{") - { - formatter.insertLinebreak(at: lastIndex) - } - } - } - - /// Always end file with a linebreak, to avoid incompatibility with certain unix tools: - /// http://stackoverflow.com/questions/2287967/why-is-it-recommended-to-have-empty-line-in-the-end-of-file - public let linebreakAtEndOfFile = FormatRule( - help: "Add empty blank line at end of file.", - sharedOptions: ["linebreaks"] - ) { formatter in - guard !formatter.options.fragment else { return } - var wasLinebreak = true - formatter.forEachToken(onlyWhereEnabled: false) { _, token in - switch token { - case .linebreak: - wasLinebreak = true - case .space: - break - default: - wasLinebreak = false - } - } - if formatter.isEnabled, !wasLinebreak { - formatter.insertLinebreak(at: formatter.tokens.count) - } - } - - /// Indent code according to standard scope indenting rules. - /// The type (tab or space) and level (2 spaces, 4 spaces, etc.) of the - /// indenting can be configured with the `options` parameter of the formatter. - public let indent = FormatRule( - help: "Indent code in accordance with the scope level.", - orderAfter: ["trailingSpace", "wrap", "wrapArguments"], - options: ["indent", "tabwidth", "smarttabs", "indentcase", "ifdef", "xcodeindentation", "indentstrings"], - sharedOptions: ["trimwhitespace", "allman", "wrapconditions", "wrapternary"] - ) { formatter in - var scopeStack: [Token] = [] - var scopeStartLineIndexes: [Int] = [] - var lastNonSpaceOrLinebreakIndex = -1 - var lastNonSpaceIndex = -1 - var indentStack = [""] - var stringBodyIndentStack = [""] - var indentCounts = [1] - var linewrapStack = [false] - var lineIndex = 0 - - func inFunctionDeclarationWhereReturnTypeIsWrappedToStartOfLine(at i: Int) -> Bool { - guard let returnOperatorIndex = formatter.startOfReturnType(at: i) else { - return false - } - return formatter.last(.nonSpaceOrComment, before: returnOperatorIndex)?.isLinebreak == true - } - - func isFirstStackedClosureArgument(at i: Int) -> Bool { - assert(formatter.tokens[i] == .startOfScope("{")) - if let prevIndex = formatter.index(of: .nonSpace, before: i), - let prevToken = formatter.token(at: prevIndex), prevToken == .startOfScope("(") || - (prevToken == .delimiter(":") && formatter.token(at: prevIndex - 1)?.isIdentifier == true - && formatter.last(.nonSpace, before: prevIndex - 1) == .startOfScope("(")), - let endIndex = formatter.endOfScope(at: i), - let commaIndex = formatter.index(of: .nonSpace, after: endIndex, if: { - $0 == .delimiter(",") - }), - formatter.next(.nonSpaceOrComment, after: commaIndex)?.isLinebreak == true - { - return true - } - return false - } - - if formatter.options.fragment, - let firstIndex = formatter.index(of: .nonSpaceOrLinebreak, after: -1), - let indentToken = formatter.token(at: firstIndex - 1), case let .space(string) = indentToken - { - indentStack[0] = string - } - formatter.forEachToken(onlyWhereEnabled: false) { i, token in - func popScope() { - if linewrapStack.last == true { - indentStack.removeLast() - stringBodyIndentStack.removeLast() - } - indentStack.removeLast() - stringBodyIndentStack.removeLast() - indentCounts.removeLast() - linewrapStack.removeLast() - scopeStartLineIndexes.removeLast() - scopeStack.removeLast() - } - - func stringBodyIndent(at i: Int) -> String { - var space = "" - let start = formatter.startOfLine(at: i) - if let index = formatter.index(of: .nonSpace, in: start ..< i), - case let .stringBody(string) = formatter.tokens[index], - string.unicodeScalars.first?.isSpace == true - { - var index = string.startIndex - while index < string.endIndex, string[index].unicodeScalars.first!.isSpace { - space.append(string[index]) - index = string.index(after: index) - } - } - return space - } - - var i = i - switch token { - case let .startOfScope(string): - switch string { - case ":" where scopeStack.last == .endOfScope("case"): - popScope() - case "{" where !formatter.isStartOfClosure(at: i, in: scopeStack.last) && - linewrapStack.last == true: - indentStack.removeLast() - linewrapStack[linewrapStack.count - 1] = false - default: - break - } - // Handle start of scope - scopeStack.append(token) - var indentCount: Int - if lineIndex > scopeStartLineIndexes.last ?? -1 { - indentCount = 1 - } else if token.isMultilineStringDelimiter, let endIndex = formatter.endOfScope(at: i), - let closingIndex = formatter.index(of: .endOfScope(")"), after: endIndex), - formatter.next(.linebreak, in: endIndex + 1 ..< closingIndex) != nil - { - indentCount = 1 - } else if scopeStack.count > 1, scopeStack[scopeStack.count - 2] == .startOfScope(":") { - indentCount = 1 - } else { - indentCount = indentCounts.last! + 1 - } - var indent = indentStack[indentStack.count - indentCount] - - switch string { - case "/*": - if scopeStack.count < 2 || scopeStack[scopeStack.count - 2] != .startOfScope("/*") { - // Comments only indent one space - indent += " " - } - case ":": - indent += formatter.options.indent - if formatter.options.indentCase, - scopeStack.count < 2 || scopeStack[scopeStack.count - 2] != .startOfScope("#if") - { - indent += formatter.options.indent - } - case "#if": - if let lineIndex = formatter.index(of: .linebreak, after: i), - let nextKeyword = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lineIndex), [ - .endOfScope("case"), .endOfScope("default"), .keyword("@unknown"), - ].contains(nextKeyword) - { - indent = indentStack[indentStack.count - indentCount - 1] - if formatter.options.indentCase { - indent += formatter.options.indent - } - } - switch formatter.options.ifdefIndent { - case .indent: - i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) - indent += formatter.options.indent - case .noIndent: - i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) - case .outdent: - i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) - } - case "{" where isFirstStackedClosureArgument(at: i): - guard var prevIndex = formatter.index(of: .nonSpace, before: i) else { - assertionFailure() - break - } - if formatter.tokens[prevIndex] == .delimiter(":") { - guard formatter.token(at: prevIndex - 1)?.isIdentifier == true, - let parenIndex = formatter.index(of: .nonSpace, before: prevIndex - 1, if: { - $0 == .startOfScope("(") - }) - else { - let stringIndent = stringBodyIndent(at: i) - stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent - indent += stringIndent + formatter.options.indent - break - } - prevIndex = parenIndex - } - let startIndex = formatter.startOfLine(at: i) - indent = formatter.spaceEquivalentToTokens(from: startIndex, upTo: prevIndex + 1) - indentStack[indentStack.count - 1] = indent - indent += formatter.options.indent - indentCount -= 1 - case "{" where formatter.isStartOfClosure(at: i): - // When a trailing closure starts on the same line as the end of a multi-line - // method call the trailing closure body should be double-indented - if let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i), - formatter.tokens[prevIndex] == .endOfScope(")"), - case let prevIndent = formatter.currentIndentForLine(at: prevIndex), - prevIndent == indent + formatter.options.indent - { - if linewrapStack.last == false { - linewrapStack[linewrapStack.count - 1] = true - indentStack.append(prevIndent) - stringBodyIndentStack.append("") - } - indent = prevIndent - } - let stringIndent = stringBodyIndent(at: i) - stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent - indent += stringIndent + formatter.options.indent - case _ where token.isStringDelimiter, "//": - break - case "[", "(": - guard let linebreakIndex = formatter.index(of: .linebreak, after: i), - let nextIndex = formatter.index(of: .nonSpace, after: i), - nextIndex != linebreakIndex - else { - fallthrough - } - if formatter.last(.nonSpaceOrComment, before: linebreakIndex) != .delimiter(","), - formatter.next(.nonSpaceOrComment, after: linebreakIndex) != .delimiter(",") - { - fallthrough - } - let start = formatter.startOfLine(at: i) - // Align indent with previous value - let lastIndentCount = indentCounts.last ?? 0 - if indentCount > lastIndentCount { - indentCount = lastIndentCount - indentCounts[indentCounts.count - 1] = 1 - } - indent = formatter.spaceEquivalentToTokens(from: start, upTo: nextIndex) - default: - let stringIndent = stringBodyIndent(at: i) - stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent - indent += stringIndent + formatter.options.indent - } - indentStack.append(indent) - stringBodyIndentStack.append("") - indentCounts.append(indentCount) - scopeStartLineIndexes.append(lineIndex) - linewrapStack.append(false) - case .space: - if i == 0, !formatter.options.fragment, - formatter.token(at: i + 1)?.isLinebreak != true - { - formatter.removeToken(at: i) - } - case .error("}"), .error("]"), .error(")"), .error(">"): - // Handled over-terminated fragment - if let prevToken = formatter.token(at: i - 1) { - if case let .space(string) = prevToken { - let prevButOneToken = formatter.token(at: i - 2) - if prevButOneToken == nil || prevButOneToken!.isLinebreak { - indentStack[0] = string - } - } else if prevToken.isLinebreak { - indentStack[0] = "" - } - } - return - case .keyword("#else"), .keyword("#elseif"): - var indent = indentStack[indentStack.count - 2] - if scopeStack.last == .startOfScope(":") { - indent = indentStack[indentStack.count - 4] - if formatter.options.indentCase { - indent += formatter.options.indent - } - } - let start = formatter.startOfLine(at: i) - switch formatter.options.ifdefIndent { - case .indent, .noIndent: - i += formatter.insertSpaceIfEnabled(indent, at: start) - case .outdent: - i += formatter.insertSpaceIfEnabled("", at: start) - } - case .keyword("@unknown") where scopeStack.last != .startOfScope("#if"): - var indent = indentStack[indentStack.count - 2] - if formatter.options.indentCase { - indent += formatter.options.indent - } - let start = formatter.startOfLine(at: i) - let stringIndent = stringBodyIndentStack.last! - i += formatter.insertSpaceIfEnabled(stringIndent + indent, at: start) - case .keyword("in") where scopeStack.last == .startOfScope("{"): - if let startIndex = formatter.index(of: .startOfScope("{"), before: i), - formatter.index(of: .keyword("for"), in: startIndex + 1 ..< i) == nil, - let paramsIndex = formatter.index(of: .startOfScope, in: startIndex + 1 ..< i), - !formatter.tokens[startIndex + 1 ..< paramsIndex].contains(where: { - $0.isLinebreak - }), formatter.tokens[paramsIndex + 1 ..< i].contains(where: { - $0.isLinebreak - }) - { - indentStack[indentStack.count - 1] += formatter.options.indent - } - case .operator("=", .infix): - // If/switch expressions on their own line following an `=` assignment should always be indented - guard let nextKeyword = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - ["if", "switch"].contains(formatter.tokens[nextKeyword].string), - !formatter.onSameLine(i, nextKeyword) - else { fallthrough } - - let indent = (indentStack.last ?? "") + formatter.options.indent - indentStack.append(indent) - stringBodyIndentStack.append("") - indentCounts.append(1) - scopeStartLineIndexes.append(lineIndex) - linewrapStack.append(false) - scopeStack.append(.operator("=", .infix)) - scopeStartLineIndexes.append(lineIndex) - default: - // If this is the final `endOfScope` in a conditional assignment, - // we have to end the scope introduced by that assignment operator. - defer { - if token == .endOfScope("}"), let startOfScope = formatter.startOfScope(at: i) { - // Find the `=` before this start of scope, which isn't itself part of the conditional statement - var previousAssignmentIndex = formatter.index(of: .operator("=", .infix), before: startOfScope) - while let currentPreviousAssignmentIndex = previousAssignmentIndex, - formatter.isConditionalStatement(at: currentPreviousAssignmentIndex) - { - previousAssignmentIndex = formatter.index(of: .operator("=", .infix), before: currentPreviousAssignmentIndex) - } - - // Make sure the `=` actually created a new scope - if scopeStack.last == .operator("=", .infix), - // Parse the conditional branches following the `=` assignment operator - let previousAssignmentIndex = previousAssignmentIndex, - let nextTokenAfterAssignment = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: previousAssignmentIndex), - let conditionalBranches = formatter.conditionalBranches(at: nextTokenAfterAssignment), - // If this is the very end of the conditional assignment following the `=`, - // then we can end the scope. - conditionalBranches.last?.endOfBranch == i - { - popScope() - } - } - } - - // Handle end of scope - if let scope = scopeStack.last, token.isEndOfScope(scope) { - let indentCount = indentCounts.last! - 1 - popScope() - guard !token.isLinebreak, lineIndex > scopeStartLineIndexes.last ?? -1 else { - break - } - // If indentCount > 0, drop back to previous indent level - if indentCount > 0 { - indentStack.removeLast(indentCount) - stringBodyIndentStack.removeLast(indentCount) - for _ in 0 ..< indentCount { - indentStack.append(indentStack.last ?? "") - stringBodyIndentStack.append(stringBodyIndentStack.last ?? "") - } - } - - // Don't reduce indent if line doesn't start with end of scope - let start = formatter.startOfLine(at: i) - guard let firstIndex = formatter.index(of: .nonSpaceOrComment, after: start - 1) else { - break - } - if firstIndex != i { - break - } - func isInIfdef() -> Bool { - guard scopeStack.last == .startOfScope("#if") else { - return false - } - var index = i - 1 - while index > 0 { - switch formatter.tokens[index] { - case .keyword("switch"): - return false - case .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): - return true - default: - index -= 1 - } - } - return false - } - if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .outdent { - i += formatter.insertSpaceIfEnabled("", at: start) - } else { - var indent = indentStack.last ?? "" - if token.isSwitchCaseOrDefault, - formatter.options.indentCase, !isInIfdef() - { - indent += formatter.options.indent - } - let stringIndent = stringBodyIndentStack.last! - i += formatter.insertSpaceIfEnabled(stringIndent + indent, at: start) - } - } else if token == .endOfScope("#endif"), indentStack.count > 1 { - var indent = indentStack[indentStack.count - 2] - if scopeStack.last == .startOfScope(":"), indentStack.count > 1 { - indent = indentStack[indentStack.count - 4] - if formatter.options.indentCase { - indent += formatter.options.indent - } - popScope() - } - switch formatter.options.ifdefIndent { - case .indent, .noIndent: - i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) - case .outdent: - i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) - } - if scopeStack.last == .startOfScope("#if") { - popScope() - } - } - } - switch token { - case .endOfScope("case"): - scopeStack.append(token) - var indent = (indentStack.last ?? "") - if formatter.next(.nonSpaceOrComment, after: i)?.isLinebreak == true { - indent += formatter.options.indent - } else { - if formatter.options.indentCase { - indent += formatter.options.indent - } - // Align indent with previous case value - indent += formatter.spaceEquivalentToWidth(5) - } - indentStack.append(indent) - stringBodyIndentStack.append("") - indentCounts.append(1) - scopeStartLineIndexes.append(lineIndex) - linewrapStack.append(false) - fallthrough - case .endOfScope("default"), .keyword("@unknown"), - .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): - var index = formatter.startOfLine(at: i) - if index == i || index == i - 1 { - let indent: String - if case let .space(space) = formatter.tokens[index] { - indent = space - } else { - indent = "" - } - index -= 1 - while let prevToken = formatter.token(at: index - 1), prevToken.isComment, - let startIndex = formatter.index(of: .nonSpaceOrComment, before: index), - formatter.tokens[startIndex].isLinebreak - { - // Set indent for comment immediately before this line to match this line - if !formatter.isCommentedCode(at: startIndex + 1) { - formatter.insertSpaceIfEnabled(indent, at: startIndex + 1) - } - if case .endOfScope("*/") = prevToken, - var index = formatter.index(of: .startOfScope("/*"), after: startIndex) - { - while let linebreakIndex = formatter.index(of: .linebreak, after: index) { - formatter.insertSpaceIfEnabled(indent + " ", at: linebreakIndex + 1) - index = linebreakIndex - } - } - index = startIndex - } - } - case .linebreak: - // Detect linewrap - let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) - let linewrapped = lastNonSpaceOrLinebreakIndex > -1 && ( - !formatter.isEndOfStatement(at: lastNonSpaceOrLinebreakIndex, in: scopeStack.last) || - (nextTokenIndex.map { formatter.isTrailingClosureLabel(at: $0) } == true) || - !(nextTokenIndex == nil || [ - .endOfScope("}"), .endOfScope("]"), .endOfScope(")"), - ].contains(formatter.tokens[nextTokenIndex!]) || - formatter.isStartOfStatement(at: nextTokenIndex!, in: scopeStack.last) || ( - ((formatter.tokens[nextTokenIndex!].isIdentifier && !(formatter.tokens[nextTokenIndex!] == .identifier("async") && formatter.currentScope(at: nextTokenIndex!) != .startOfScope("("))) || [ - .keyword("try"), .keyword("await"), - ].contains(formatter.tokens[nextTokenIndex!])) && - formatter.last(.nonSpaceOrCommentOrLinebreak, before: nextTokenIndex!).map { - $0 != .keyword("return") && !$0.isOperator(ofType: .infix) - } ?? false) || ( - formatter.tokens[nextTokenIndex!] == .delimiter(",") && [ - "<", "[", "(", "case", - ].contains(formatter.currentScope(at: nextTokenIndex!)?.string ?? "") - ) - ) - ) - - // Determine current indent - var indent = indentStack.last ?? "" - if linewrapped, lineIndex == scopeStartLineIndexes.last { - indent = indentStack.count > 1 ? indentStack[indentStack.count - 2] : "" - } - lineIndex += 1 - - func shouldIndentNextLine(at i: Int) -> Bool { - // If there is a linebreak after certain symbols, we should add - // an additional indentation to the lines at the same indention scope - // after this line. - let endOfLine = formatter.endOfLine(at: i) - switch formatter.token(at: endOfLine - 1) { - case .keyword("return")?, .operator("=", .infix)?: - let endOfNextLine = formatter.endOfLine(at: endOfLine + 1) - switch formatter.last(.nonSpaceOrCommentOrLinebreak, before: endOfNextLine) { - case .operator(_, .infix)?, .delimiter(",")?: - return false - case .endOfScope(")")?: - return !formatter.options.xcodeIndentation - default: - return formatter.lastIndex(of: .startOfScope, - in: i ..< endOfNextLine) == nil - } - default: - return false - } - } - - guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: i), - let nextToken = formatter.token(at: nextNonSpaceIndex) - else { - break - } - - // Begin wrap scope - if linewrapStack.last == true { - if !linewrapped { - indentStack.removeLast() - linewrapStack[linewrapStack.count - 1] = false - indent = indentStack.last! - } else { - let shouldIndentLeadingDotStatement: Bool - if formatter.options.xcodeIndentation { - if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i), - formatter.token(at: formatter.startOfLine( - at: prevIndex, excludingIndent: true - )) == .endOfScope("}"), - formatter.index(of: .linebreak, in: prevIndex + 1 ..< i) != nil - { - shouldIndentLeadingDotStatement = false - } else { - shouldIndentLeadingDotStatement = true - } - } else { - shouldIndentLeadingDotStatement = ( - formatter.startOfConditionalStatement(at: i) != nil - && formatter.options.wrapConditions == .beforeFirst - ) - } - if shouldIndentLeadingDotStatement, - formatter.next(.nonSpace, after: i) == .operator(".", .infix), - let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i), - case let lineStart = formatter.index(of: .linebreak, before: prevIndex + 1) ?? - formatter.startOfLine(at: prevIndex), - let startIndex = formatter.index(of: .nonSpace, after: lineStart), - formatter.isStartOfStatement(at: startIndex) || ( - (formatter.tokens[startIndex].isIdentifier || [ - .keyword("try"), .keyword("await"), - ].contains(formatter.tokens[startIndex]) || - formatter.isTrailingClosureLabel(at: startIndex)) && - formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex).map { - $0 != .keyword("return") && !$0.isOperator(ofType: .infix) - } ?? false) - { - indent += formatter.options.indent - indentStack[indentStack.count - 1] = indent - } - - // When inside conditionals, unindent after any commas (which separate conditions) - // that were indented by the block above - if !formatter.options.xcodeIndentation, - formatter.options.wrapConditions == .beforeFirst, - formatter.isConditionalStatement(at: i), - formatter.lastToken(before: i, where: { - $0.is(.nonSpaceOrCommentOrLinebreak) - }) == .delimiter(","), - let conditionBeginIndex = formatter.index(before: i, where: { - ["if", "guard", "while", "for"].contains($0.string) - }), - formatter.currentIndentForLine(at: conditionBeginIndex) - .count < indent.count + formatter.options.indent.count - { - indent = formatter.currentIndentForLine(at: conditionBeginIndex) + formatter.options.indent - indentStack[indentStack.count - 1] = indent - } - - let startOfLineIndex = formatter.startOfLine(at: i, excludingIndent: true) - let startOfLine = formatter.tokens[startOfLineIndex] - - if formatter.options.wrapTernaryOperators == .beforeOperators, - startOfLine == .operator(":", .infix) || startOfLine == .operator("?", .infix) - { - // Push a ? scope onto the stack so we can easily know - // that the next : is the closing operator of this ternary - if startOfLine.string == "?" { - // We smuggle the index of this operator in the scope stack - // so we can recover it trivially when handling the - // corresponding : operator. - scopeStack.append(.operator("?-\(startOfLineIndex)", .infix)) - } - - // Indent any operator-leading lines following a compomnent operator - // of a wrapped ternary operator expression, except for the : - // following a ? - if let nextToken = formatter.next(.nonSpace, after: i), - nextToken.isOperator(ofType: .infix), - nextToken != .operator(":", .infix) - { - indent += formatter.options.indent - indentStack[indentStack.count - 1] = indent - } - } - - // Make sure the indentation for this : operator matches - // the indentation of the previous ? operator - if formatter.options.wrapTernaryOperators == .beforeOperators, - formatter.next(.nonSpace, after: i) == .operator(":", .infix), - let scope = scopeStack.last, - scope.string.hasPrefix("?"), - scope.isOperator(ofType: .infix), - let previousOperatorIndex = scope.string.components(separatedBy: "-").last.flatMap({ Int($0) }) - { - scopeStack.removeLast() - indent = formatter.currentIndentForLine(at: previousOperatorIndex) - indentStack[indentStack.count - 1] = indent - } - } - } else if linewrapped { - func isWrappedDeclaration() -> Bool { - guard let keywordIndex = formatter - .indexOfLastSignificantKeyword(at: i, excluding: [ - "where", "throws", "rethrows", - ]), !formatter.tokens[keywordIndex ..< i].contains(.endOfScope("}")), - case let .keyword(keyword) = formatter.tokens[keywordIndex], - ["class", "actor", "struct", "enum", "protocol", "extension", - "func"].contains(keyword) - else { - return false - } - - let end = formatter.endOfLine(at: i + 1) - guard let lastToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: end + 1), - [.startOfScope("{"), .endOfScope("}")].contains(lastToken) else { return false } - - return true - } - - // Don't indent line starting with dot if previous line was just a closing brace - var lastToken = formatter.tokens[lastNonSpaceOrLinebreakIndex] - if formatter.options.allmanBraces, nextToken == .startOfScope("{"), - formatter.isStartOfClosure(at: nextNonSpaceIndex) - { - // Don't indent further - } else if formatter.token(at: nextTokenIndex ?? -1) == .operator(".", .infix) || - formatter.isLabel(at: nextTokenIndex ?? -1) - { - var lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) - let startToken = formatter.token(at: lineStart) - if let startToken = startToken, [ - .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif") - ].contains(startToken) { - if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lineStart) { - lastNonSpaceOrLinebreakIndex = index - lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) - } - } - if formatter.token(at: lineStart) == .operator(".", .infix), - [.keyword("#else"), .keyword("#elseif"), .endOfScope("#endif")].contains(startToken) - { - indent = formatter.currentIndentForLine(at: lineStart) - } else if formatter.tokens[lineStart ..< lastNonSpaceOrLinebreakIndex].allSatisfy({ - $0.isEndOfScope || $0.isSpaceOrComment - }) { - if lastToken.isEndOfScope { - indent = formatter.currentIndentForLine(at: lastNonSpaceOrLinebreakIndex) - } - if !lastToken.isEndOfScope || lastToken == .endOfScope("case") || - formatter.options.xcodeIndentation, ![ - .endOfScope("}"), .endOfScope(")") - ].contains(lastToken) - { - indent += formatter.options.indent - } - } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { - indent += formatter.linewrapIndent(at: i) - } - } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { - indent += formatter.linewrapIndent(at: i) - } - - linewrapStack[linewrapStack.count - 1] = true - indentStack.append(indent) - stringBodyIndentStack.append("") - } - // Avoid indenting commented code - guard !formatter.isCommentedCode(at: nextNonSpaceIndex) else { - break - } - // Apply indent - switch nextToken { - case .linebreak: - if formatter.options.truncateBlankLines { - formatter.insertSpaceIfEnabled("", at: i + 1) - } else if scopeStack.last?.isStringDelimiter == true, - formatter.token(at: i + 1)?.isSpace == true - { - formatter.insertSpaceIfEnabled(indent, at: i + 1) - } - case .error, .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"), - .startOfScope("#if") where formatter.options.ifdefIndent != .indent: - break - case .startOfScope("/*"), .commentBody, .endOfScope("*/"): - nextNonSpaceIndex = formatter.endOfScope(at: nextNonSpaceIndex) ?? nextNonSpaceIndex - fallthrough - case .startOfScope("//"): - nextNonSpaceIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, - after: nextNonSpaceIndex) ?? nextNonSpaceIndex - nextNonSpaceIndex = formatter.index(of: .nonSpaceOrLinebreak, - before: nextNonSpaceIndex) ?? nextNonSpaceIndex - if let lineIndex = formatter.index(of: .linebreak, after: nextNonSpaceIndex), - let nextToken = formatter.next(.nonSpace, after: lineIndex), - [.startOfScope("#if"), .keyword("#else"), .keyword("#elseif")].contains(nextToken) - { - break - } - fallthrough - case .startOfScope("#if"): - if let lineIndex = formatter.index(of: .linebreak, after: nextNonSpaceIndex), - let nextKeyword = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lineIndex), [ - .endOfScope("case"), .endOfScope("default"), .keyword("@unknown"), - ].contains(nextKeyword) - { - break - } - formatter.insertSpaceIfEnabled(indent, at: i + 1) - case .endOfScope, .keyword("@unknown"): - if let scope = scopeStack.last { - switch scope { - case .startOfScope("/*"), .startOfScope("#if"), - .keyword("#else"), .keyword("#elseif"), - .startOfScope where scope.isStringDelimiter: - formatter.insertSpaceIfEnabled(indent, at: i + 1) - default: - break - } - } - default: - var lastIndex = lastNonSpaceOrLinebreakIndex > -1 ? lastNonSpaceOrLinebreakIndex : i - while formatter.token(at: lastIndex) == .endOfScope("#endif"), - let index = formatter.index(of: .startOfScope, before: lastIndex, if: { - $0 == .startOfScope("#if") - }) - { - lastIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - before: index - ) ?? index - } - let lastToken = formatter.tokens[lastIndex] - if [.endOfScope("}"), .endOfScope(")")].contains(lastToken), - lastIndex == formatter.startOfLine(at: lastIndex, excludingIndent: true), - formatter.token(at: nextNonSpaceIndex) == .operator(".", .infix) || - (lastToken == .endOfScope("}") && formatter.isLabel(at: nextNonSpaceIndex)) - { - indent = formatter.currentIndentForLine(at: lastIndex) - } - if formatter.options.fragment, lastToken == .delimiter(",") { - break // Can't reliably indent - } - formatter.insertSpaceIfEnabled(indent, at: i + 1) - } - - if linewrapped, shouldIndentNextLine(at: i) { - indentStack[indentStack.count - 1] += formatter.options.indent - } - default: - break - } - // Track token for line wraps - if !token.isSpaceOrComment { - lastNonSpaceIndex = i - if !token.isLinebreak { - lastNonSpaceOrLinebreakIndex = i - } - } - } - - if formatter.options.indentStrings { - formatter.forEach(.startOfScope("\"\"\"")) { stringStartIndex, _ in - let baseIndent = formatter.currentIndentForLine(at: stringStartIndex) - let expectedIndent = baseIndent + formatter.options.indent - - guard let stringEndIndex = formatter.endOfScope(at: stringStartIndex), - // Preserve the default indentation if the opening """ is on a line by itself - formatter.startOfLine(at: stringStartIndex, excludingIndent: true) != stringStartIndex - else { return } - - for linebreakIndex in (stringStartIndex ..< stringEndIndex).reversed() - where formatter.tokens[linebreakIndex].isLinebreak - { - // If this line is completely blank, do nothing - // - This prevents conflicts with the trailingSpace rule - if formatter.nextToken(after: linebreakIndex)?.isLinebreak == true { - continue - } - - let indentIndex = linebreakIndex + 1 - if formatter.tokens[indentIndex].is(.space) { - formatter.replaceToken(at: indentIndex, with: .space(expectedIndent)) - } else { - formatter.insert(.space(expectedIndent), at: indentIndex) - } - } - } - } - } - - /// Add @available(*, unavailable) to init?(coder aDecoder: NSCoder) - public let initCoderUnavailable = FormatRule( - help: """ - Add `@available(*, unavailable)` attribute to required `init(coder:)` when - it hasn't been implemented. - """, - options: ["initcodernil"], - sharedOptions: ["linebreaks"] - ) { formatter in - let unavailableTokens = tokenize("@available(*, unavailable)") - formatter.forEach(.identifier("required")) { i, _ in - // look for required init?(coder - guard var initIndex = formatter.index(of: .keyword("init"), after: i) else { return } - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: initIndex, if: { - $0 == .operator("?", .postfix) - }) { - initIndex = nextIndex - } - - guard let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: initIndex, if: { - $0 == .startOfScope("(") - }), let coderIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: parenIndex, if: { - $0 == .identifier("coder") - }), let endParenIndex = formatter.index(of: .endOfScope(")"), after: coderIndex), - let braceIndex = formatter.index(of: .startOfScope("{"), after: endParenIndex) - else { return } - - // make sure the implementation is empty or fatalError - guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: braceIndex, if: { - [.endOfScope("}"), .identifier("fatalError")].contains($0) - }) else { return } - - if formatter.options.initCoderNil, - formatter.token(at: firstTokenIndex) == .identifier("fatalError"), - let fatalParenEndOfScope = formatter.index(of: .endOfScope, after: firstTokenIndex + 1) - { - formatter.replaceTokens(in: firstTokenIndex ... fatalParenEndOfScope, with: [.identifier("nil")]) - } - - // avoid adding attribute if it's already there - if formatter.modifiersForDeclaration(at: i, contains: "@available") { return } - - let startIndex = formatter.startOfModifiers(at: i, includingAttributes: true) - formatter.insert(.space(formatter.currentIndentForLine(at: startIndex)), at: startIndex) - formatter.insertLinebreak(at: startIndex) - formatter.insert(unavailableTokens, at: startIndex) - } - } - - /// Implement brace-wrapping rules - public let braces = FormatRule( - help: "Wrap braces in accordance with selected style (K&R or Allman).", - options: ["allman"], - sharedOptions: ["linebreaks", "maxwidth", "indent", "tabwidth", "assetliterals"] - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - guard let closingBraceIndex = formatter.endOfScope(at: i), - // Check this isn't an inline block - formatter.index(of: .linebreak, in: i + 1 ..< closingBraceIndex) != nil, - let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - ![.delimiter(","), .keyword("in")].contains(prevToken), - !prevToken.is(.startOfScope) - else { - return - } - if let penultimateToken = formatter.last(.nonSpaceOrComment, before: closingBraceIndex), - !penultimateToken.isLinebreak - { - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: closingBraceIndex) - formatter.insertLinebreak(at: closingBraceIndex) - if formatter.token(at: closingBraceIndex - 1)?.isSpace == true { - formatter.removeToken(at: closingBraceIndex - 1) - } - } - if formatter.options.allmanBraces { - // Implement Allman-style braces, where opening brace appears on the next line - switch formatter.last(.nonSpace, before: i) ?? .space("") { - case .identifier, .keyword, .endOfScope, .number, - .operator("?", .postfix), .operator("!", .postfix): - formatter.insertLinebreak(at: i) - if let breakIndex = formatter.index(of: .linebreak, after: i + 1), - let nextIndex = formatter.index(of: .nonSpace, after: breakIndex, if: { $0.isLinebreak }) - { - formatter.removeTokens(in: breakIndex ..< nextIndex) - } - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: i + 1) - if formatter.tokens[i - 1].isSpace { - formatter.removeToken(at: i - 1) - } - default: - break - } - } else { - // Implement K&R-style braces, where opening brace appears on the same line - guard let prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), - formatter.tokens[prevIndex ..< i].contains(where: { $0.isLinebreak }), - !formatter.tokens[prevIndex].isComment - else { - return - } - - var maxWidth = formatter.options.maxWidth - if maxWidth == 0 { - maxWidth = .max - } - - // Check that unwrapping wouldn't exceed line length - let endOfLine = formatter.endOfLine(at: i) - let length = formatter.lineLength(from: i, upTo: endOfLine) - let prevLineLength = formatter.lineLength(at: prevIndex) - guard prevLineLength + length + 1 <= maxWidth else { - return - } - - // Avoid conflicts with wrapMultilineStatementBraces - let ruleName = FormatRules.wrapMultilineStatementBraces.name - if formatter.options.enabledRules.contains(ruleName), - formatter.shouldWrapMultilineStatementBrace(at: i) - { - return - } - formatter.replaceTokens(in: prevIndex + 1 ..< i, with: .space(" ")) - } - } - } - - /// Ensure that an `else` statement following `if { ... }` appears on the same line - /// as the closing brace. This has no effect on the `else` part of a `guard` statement. - /// Also applies to `catch` after `try` and `while` after `repeat`. - public let elseOnSameLine = FormatRule( - help: """ - Place `else`, `catch` or `while` keyword in accordance with current style (same or - next line). - """, - orderAfter: ["wrapMultilineStatementBraces"], - options: ["elseposition", "guardelse"], - sharedOptions: ["allman", "linebreaks"] - ) { formatter in - func bracesContainLinebreak(_ endIndex: Int) -> Bool { - guard let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { - return false - } - return (startIndex ..< endIndex).contains(where: { formatter.tokens[$0].isLinebreak }) - } - formatter.forEachToken { i, token in - switch token { - case .keyword("while"): - if let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - $0 == .endOfScope("}") - }), let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex), - formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex) == .keyword("repeat") { - fallthrough - } - case .keyword("else"): - guard var prevIndex = formatter.index(of: .nonSpace, before: i), - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - !$0.isComment - }) - else { - return - } - let isOnNewLine = formatter.tokens[prevIndex].isLinebreak - if isOnNewLine { - prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) ?? prevIndex - } - if formatter.tokens[prevIndex] == .endOfScope("}") { - fallthrough - } - guard let guardIndex = formatter.indexOfLastSignificantKeyword(at: prevIndex + 1, excluding: [ - "var", "let", "case", - ]), formatter.tokens[guardIndex] == .keyword("guard") else { - return - } - let shouldWrap: Bool - switch formatter.options.guardElsePosition { - case .auto: - // Only wrap if else or following brace is on next line - shouldWrap = isOnNewLine || - formatter.tokens[i + 1 ..< nextIndex].contains { $0.isLinebreak } - case .nextLine: - // Only wrap if guard statement spans multiple lines - shouldWrap = isOnNewLine || - formatter.tokens[guardIndex + 1 ..< nextIndex].contains { $0.isLinebreak } - case .sameLine: - shouldWrap = false - } - if shouldWrap { - if !formatter.options.allmanBraces { - formatter.replaceTokens(in: i + 1 ..< nextIndex, with: .space(" ")) - } - if !isOnNewLine { - formatter.replaceTokens(in: prevIndex + 1 ..< i, with: - formatter.linebreakToken(for: prevIndex + 1)) - formatter.insertSpace(formatter.currentIndentForLine(at: guardIndex), at: prevIndex + 2) - } - } else if isOnNewLine { - formatter.replaceTokens(in: prevIndex + 1 ..< i, with: .space(" ")) - } - case .keyword("catch"): - guard let prevIndex = formatter.index(of: .nonSpace, before: i) else { - return - } - - let precededByBlankLine = formatter.tokens[prevIndex].isLinebreak - && formatter.lastToken(before: prevIndex, where: { !$0.isSpaceOrComment })?.isLinebreak == true - - if precededByBlankLine { - return - } - - let shouldWrap = formatter.options.allmanBraces || formatter.options.elseOnNextLine - if !shouldWrap, formatter.tokens[prevIndex].isLinebreak { - if let prevBraceIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex, if: { - $0 == .endOfScope("}") - }), bracesContainLinebreak(prevBraceIndex) { - formatter.replaceTokens(in: prevBraceIndex + 1 ..< i, with: .space(" ")) - } - } else if shouldWrap, let token = formatter.token(at: prevIndex), !token.isLinebreak, - let prevBraceIndex = (token == .endOfScope("}")) ? prevIndex : - formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex, if: { - $0 == .endOfScope("}") - }), bracesContainLinebreak(prevBraceIndex) - { - formatter.replaceTokens(in: prevIndex + 1 ..< i, with: - formatter.linebreakToken(for: prevIndex + 1)) - formatter.insertSpace(formatter.currentIndentForLine(at: prevIndex + 1), at: prevIndex + 2) - } - default: - break - } - } - } - - public let wrapConditionalBodies = FormatRule( - help: "Wrap the bodies of inline conditional statements onto a new line.", - disabledByDefault: true, - sharedOptions: ["linebreaks", "indent"] - ) { formatter in - formatter.forEachToken(where: { [.keyword("if"), .keyword("else")].contains($0) }) { i, _ in - guard let startIndex = formatter.index(of: .startOfScope("{"), after: i) else { - return formatter.fatalError("Expected {", at: i) - } - formatter.wrapStatementBody(at: startIndex) - } - } - - public let wrapLoopBodies = FormatRule( - help: "Wrap the bodies of inline loop statements onto a new line.", - orderAfter: ["preferForLoop"], - sharedOptions: ["linebreaks", "indent"] - ) { formatter in - formatter.forEachToken(where: { [ - .keyword("for"), - .keyword("while"), - .keyword("repeat"), - ].contains($0) }) { i, token in - if let startIndex = formatter.index(of: .startOfScope("{"), after: i) { - formatter.wrapStatementBody(at: startIndex) - } else if token == .keyword("for") { - return formatter.fatalError("Expected {", at: i) - } - } - } - - /// Ensure that the last item in a multi-line array literal is followed by a comma. - /// This is useful for preventing noise in commits when items are added to end of array. - public let trailingCommas = FormatRule( - help: "Add or remove trailing comma from the last item in a collection literal.", - options: ["commas"] - ) { formatter in - formatter.forEach(.endOfScope("]")) { i, _ in - guard let prevTokenIndex = formatter.index(of: .nonSpaceOrComment, before: i), - let scopeType = formatter.scopeType(at: i) - else { - return - } - switch scopeType { - case .array, .dictionary: - switch formatter.tokens[prevTokenIndex] { - case .linebreak: - guard let prevTokenIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, before: prevTokenIndex + 1 - ) else { - break - } - switch formatter.tokens[prevTokenIndex] { - case .startOfScope("["), .delimiter(":"): - break // do nothing - case .delimiter(","): - if !formatter.options.trailingCommas { - formatter.removeToken(at: prevTokenIndex) - } - default: - if formatter.options.trailingCommas { - formatter.insert(.delimiter(","), at: prevTokenIndex + 1) - } - } - case .delimiter(","): - formatter.removeToken(at: prevTokenIndex) - default: - break - } - default: - return - } - } - } - - /// Ensure that TODO, MARK and FIXME comments are followed by a : as required - public let todos = FormatRule( - help: "Use correct formatting for `TODO:`, `MARK:` or `FIXME:` comments." - ) { formatter in - formatter.forEachToken { i, token in - guard case var .commentBody(string) = token else { - return - } - var removedSpace = false - if string.hasPrefix("/"), let scopeStart = formatter.index(of: .startOfScope, before: i, if: { - $0 == .startOfScope("//") - }) { - if let prevLinebreak = formatter.index(of: .linebreak, before: scopeStart), - case .commentBody? = formatter.last(.nonSpace, before: prevLinebreak) - { - return - } - if let nextLinebreak = formatter.index(of: .linebreak, after: i), - case .startOfScope("//")? = formatter.next(.nonSpace, after: nextLinebreak) - { - return - } - removedSpace = true - string = string.replacingOccurrences(of: "^/(\\s+)", with: "", options: .regularExpression) - } - for pair in [ - "todo:": "TODO:", - "todo :": "TODO:", - "fixme:": "FIXME:", - "fixme :": "FIXME:", - "mark:": "MARK:", - "mark :": "MARK:", - "mark-": "MARK: -", - "mark -": "MARK: -", - ] where string.lowercased().hasPrefix(pair.0) { - string = pair.1 + string.dropFirst(pair.0.count) - } - guard let tag = ["TODO", "MARK", "FIXME"].first(where: { string.hasPrefix($0) }) else { - return - } - var suffix = String(string[tag.endIndex ..< string.endIndex]) - if let first = suffix.unicodeScalars.first, !" :".unicodeScalars.contains(first) { - // If not followed by a space or :, don't mess with it as it may be a custom format - return - } - while let first = suffix.unicodeScalars.first, " :".unicodeScalars.contains(first) { - suffix = String(suffix.dropFirst()) - } - if tag == "MARK", suffix.hasPrefix("-"), suffix != "-", !suffix.hasPrefix("- ") { - suffix = "- " + suffix.dropFirst() - } - formatter.replaceToken(at: i, with: .commentBody(tag + ":" + (suffix.isEmpty ? "" : " \(suffix)"))) - if removedSpace { - formatter.insertSpace(" ", at: i) - } - } - } - - /// Remove semicolons, except where doing so would change the meaning of the code - public let semicolons = FormatRule( - help: "Remove semicolons.", - options: ["semicolons"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.delimiter(";")) { i, _ in - if let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { - let prevTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) - let prevToken = prevTokenIndex.map { formatter.tokens[$0] } - if prevToken == nil || nextToken == .endOfScope("}") { - // Safe to remove - formatter.removeToken(at: i) - } else if prevToken == .keyword("return") || ( - formatter.options.swiftVersion < "3" && - // Might be a traditional for loop (not supported in Swift 3 and above) - formatter.currentScope(at: i) == .startOfScope("(") - ) { - // Not safe to remove or replace - } else if case .identifier? = prevToken, formatter.last( - .nonSpaceOrCommentOrLinebreak, before: prevTokenIndex! - ) == .keyword("var") { - // Not safe to remove or replace - } else if formatter.next(.nonSpaceOrComment, after: i)?.isLinebreak == true { - // Safe to remove - formatter.removeToken(at: i) - } else if !formatter.options.allowInlineSemicolons { - // Replace with a linebreak - if formatter.token(at: i + 1)?.isSpace == true { - formatter.removeToken(at: i + 1) - } - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: i + 1) - formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i)) - } - } else { - // Safe to remove - formatter.removeToken(at: i) - } - } - } - - /// Standardise linebreak characters as whatever is specified in the options (\n by default) - public let linebreaks = FormatRule( - help: "Use specified linebreak character for all linebreaks (CR, LF or CRLF).", - options: ["linebreaks"] - ) { formatter in - formatter.forEach(.linebreak) { i, _ in - formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i)) - } - } - - /// Deprecated - public let specifiers = FormatRule( - help: "Use consistent ordering for member modifiers.", - deprecationMessage: "Use modifierOrder instead.", - options: ["modifierorder"] - ) { formatter in - _ = formatter.options.modifierOrder - FormatRules.modifierOrder.apply(with: formatter) - } - - /// Standardise the order of property modifiers - public let modifierOrder = FormatRule( - help: "Use consistent ordering for member modifiers.", - options: ["modifierorder"] - ) { formatter in - formatter.forEach(.keyword) { i, token in - switch token.string { - case "let", "func", "var", "class", "actor", "extension", "init", "enum", - "struct", "typealias", "subscript", "associatedtype", "protocol": - break - default: - return - } - var modifiers = [String: [Token]]() - var lastModifier: (name: String, tokens: [Token])? - func pushModifier() { - lastModifier.map { modifiers[$0.name] = $0.tokens } - } - var lastIndex = i - var previousIndex = lastIndex - loop: while let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lastIndex) { - switch formatter.tokens[index] { - case .operator(_, .prefix), .operator(_, .infix), .keyword("case"): - // Last modifier was invalid - lastModifier = nil - lastIndex = previousIndex - break loop - case let token where token.isModifierKeyword: - pushModifier() - lastModifier = (token.string, [Token](formatter.tokens[index ..< lastIndex])) - previousIndex = lastIndex - lastIndex = index - case .endOfScope(")"): - if case let .identifier(param)? = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index), - let openParenIndex = formatter.index(of: .startOfScope("("), before: index), - let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: openParenIndex), - let token = formatter.token(at: index), token.isModifierKeyword - { - pushModifier() - let modifier = token.string + (param == "set" ? "(set)" : "") - lastModifier = (modifier, [Token](formatter.tokens[index ..< lastIndex])) - previousIndex = lastIndex - lastIndex = index - } else { - break loop - } - default: - // Not a modifier - break loop - } - } - pushModifier() - guard !modifiers.isEmpty else { return } - var sortedModifiers = [Token]() - for modifier in formatter.modifierOrder { - if let tokens = modifiers[modifier] { - sortedModifiers += tokens - } - } - formatter.replaceTokens(in: lastIndex ..< i, with: sortedModifiers) - } - } - - /// Convert closure arguments to trailing closure syntax where possible - public let trailingClosures = FormatRule( - help: "Use trailing closure syntax where applicable.", - options: ["trailingclosures", "nevertrailing"] - ) { formatter in - let useTrailing = Set([ - "async", "asyncAfter", "sync", "autoreleasepool", - ] + formatter.options.trailingClosures) - - let nonTrailing = Set([ - "performBatchUpdates", - "expect", // Special case to support autoclosure arguments in the Nimble framework - ] + formatter.options.neverTrailing) - - formatter.forEach(.startOfScope("(")) { i, _ in - guard let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - case let .identifier(name) = prevToken, // TODO: are trailing closures allowed in other cases? - !nonTrailing.contains(name), !formatter.isConditionalStatement(at: i) - else { - return - } - guard let closingIndex = formatter.index(of: .endOfScope(")"), after: i), let closingBraceIndex = - formatter.index(of: .nonSpaceOrComment, before: closingIndex, if: { $0 == .endOfScope("}") }), - let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: closingBraceIndex), - formatter.index(of: .endOfScope("}"), before: openingBraceIndex) == nil - else { - return - } - guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) != .startOfScope("{"), - var startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: openingBraceIndex) - else { - return - } - switch formatter.tokens[startIndex] { - case .delimiter(","), .startOfScope("("): - break - case .delimiter(":"): - guard useTrailing.contains(name) else { - return - } - if let commaIndex = formatter.index(of: .delimiter(","), before: openingBraceIndex) { - startIndex = commaIndex - } else if formatter.index(of: .startOfScope("("), before: openingBraceIndex) == i { - startIndex = i - } else { - return - } - default: - return - } - let wasParen = (startIndex == i) - formatter.removeParen(at: closingIndex) - formatter.replaceTokens(in: startIndex ..< openingBraceIndex, with: - wasParen ? [.space(" ")] : [.endOfScope(")"), .space(" ")]) - } - } - - /// Remove redundant parens around the arguments for loops, if statements, closures, etc. - public let redundantParens = FormatRule( - help: "Remove redundant parentheses." - ) { formatter in - func nestedParens(in range: ClosedRange) -> ClosedRange? { - guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: range.lowerBound, if: { - $0 == .startOfScope("(") - }), let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: range.upperBound, if: { - $0 == .endOfScope(")") - }), formatter.index(of: .endOfScope(")"), after: startIndex) == endIndex else { - return nil - } - return startIndex ... endIndex - } - - // TODO: unify with conditionals logic in trailingClosures - let conditionals = Set(["in", "while", "if", "case", "switch", "where", "for", "guard"]) - - formatter.forEach(.startOfScope("(")) { i, _ in - guard var closingIndex = formatter.index(of: .endOfScope(")"), after: i), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .keyword("repeat") - else { - return - } - var innerParens = nestedParens(in: i ... closingIndex) - while let range = innerParens, nestedParens(in: range) != nil { - // TODO: this could be a lot more efficient if we kept track of the - // removed token indices instead of recalculating paren positions every time - formatter.removeParen(at: range.upperBound) - formatter.removeParen(at: range.lowerBound) - closingIndex = formatter.index(of: .endOfScope(")"), after: i)! - innerParens = nestedParens(in: i ... closingIndex) - } - var isClosure = false - let previousIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) ?? -1 - let prevToken = formatter.token(at: previousIndex) ?? .space("") - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) ?? .space("") - switch nextToken { - case .operator("->", .infix), .keyword("throws"), .keyword("rethrows"), - .identifier("async"), .keyword("in"): - if prevToken != .keyword("throws"), - formatter.index(before: i, where: { - [.endOfScope(")"), .operator("->", .infix), .keyword("for")].contains($0) - }) == nil, - let scopeIndex = formatter.startOfScope(at: i) - { - isClosure = formatter.isStartOfClosure(at: scopeIndex) && formatter.isInClosureArguments(at: i) - } - if !isClosure, nextToken != .keyword("in") { - return // It's a closure type, function declaration or for loop - } - case .operator: - if case let .operator(inner, _)? = formatter.last(.nonSpace, before: closingIndex), - !["?", "!"].contains(inner) - { - return - } - default: - break - } - switch prevToken { - case .stringBody, .operator("?", .postfix), .operator("!", .postfix), .operator("->", .infix): - return - case .identifier: // TODO: are trailing closures allowed in other cases? - // Parens before closure - guard closingIndex == formatter.index(of: .nonSpace, after: i), - let openingIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closingIndex, if: { - $0 == .startOfScope("{") - }), - formatter.isStartOfClosure(at: openingIndex) - else { - return - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - case _ where isClosure: - if formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) == closingIndex || - formatter.index(of: .delimiter(":"), in: i + 1 ..< closingIndex) != nil || - formatter.tokens[i + 1 ..< closingIndex].contains(.identifier("self")) - { - return - } - if let index = formatter.tokens[i + 1 ..< closingIndex].firstIndex(of: .identifier("_")), - formatter.next(.nonSpaceOrComment, after: index)?.isIdentifier == true - { - return - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - case let .keyword(name) where !conditionals.contains(name) && !["let", "var", "return"].contains(name): - return - case .endOfScope("}"), .endOfScope(")"), .endOfScope("]"), .endOfScope(">"): - if formatter.tokens[previousIndex + 1 ..< i].contains(where: { $0.isLinebreak }) { - fallthrough - } - return // Probably a method invocation - case .delimiter(","), .endOfScope, .keyword: - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) ?? .space("") - guard formatter.index(of: .endOfScope("}"), before: closingIndex) == nil, - ![.endOfScope("}"), .endOfScope(">")].contains(prevToken) || - ![.startOfScope("{"), .delimiter(",")].contains(nextToken) - else { - return - } - let string = prevToken.string - if ![.startOfScope("{"), .delimiter(","), .startOfScope(":")].contains(nextToken), - !(string == "for" && nextToken == .keyword("in")), - !(string == "guard" && nextToken == .keyword("else")) - { - // TODO: this is confusing - refactor to move fallthrough to end of case - fallthrough - } - if formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< closingIndex) == nil || - formatter.index(of: .delimiter(","), in: i + 1 ..< closingIndex) != nil - { - // Might be a tuple, so we won't remove the parens - // TODO: improve the logic here so we don't misidentify function calls as tuples - return - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - case .operator(_, .infix): - guard let nextIndex = formatter.index(of: .nonSpaceOrComment, after: i, if: { - $0 == .startOfScope("{") - }), let lastIndex = formatter.index(of: .endOfScope("}"), after: nextIndex), - formatter.index(of: .nonSpaceOrComment, before: closingIndex) == lastIndex else { - fallthrough - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - default: - if let range = innerParens { - formatter.removeParen(at: range.upperBound) - formatter.removeParen(at: range.lowerBound) - closingIndex = formatter.index(of: .endOfScope(")"), after: i)! - innerParens = nil - } - if prevToken == .startOfScope("("), - formatter.last(.nonSpaceOrComment, before: previousIndex) == .identifier("Selector") - { - return - } - if case .operator = formatter.tokens[closingIndex - 1], - case .operator(_, .infix)? = formatter.token(at: closingIndex + 1) - { - return - } - let nextNonLinebreak = formatter.next(.nonSpaceOrComment, after: closingIndex) - if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - case .operator = formatter.tokens[index] - { - if nextToken.isOperator(".") || (index == i + 1 && - formatter.token(at: i - 1)?.isSpaceOrCommentOrLinebreak == false) - { - return - } - switch nextNonLinebreak { - case .startOfScope("[")?, .startOfScope("(")?, .operator(_, .postfix)?: - return - default: - break - } - } - guard formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) != closingIndex, - formatter.index(in: i + 1 ..< closingIndex, where: { - switch $0 { - case .operator(_, .infix), .identifier("any"), .identifier("some"), .identifier("each"), - .keyword("as"), .keyword("is"), .keyword("try"), .keyword("await"): - switch prevToken { - // TODO: add option to always strip parens in this case (or only for boolean operators?) - case .operator("=", .infix) where $0 == .operator("->", .infix): - break - case .operator(_, .prefix), .operator(_, .infix), .keyword("as"), .keyword("is"): - return true - default: - break - } - switch nextToken { - case .operator(_, .postfix), .operator(_, .infix), .keyword("as"), .keyword("is"): - return true - default: - break - } - switch nextNonLinebreak { - case .startOfScope("[")?, .startOfScope("(")?, .operator(_, .postfix)?: - return true - default: - return false - } - case .operator(_, .postfix): - switch prevToken { - case .operator(_, .prefix), .keyword("as"), .keyword("is"): - return true - default: - return false - } - case .delimiter(","), .delimiter(":"), .delimiter(";"), - .operator(_, .none), .startOfScope("{"): - return true - default: - return false - } - }) == nil, - formatter.index(in: i + 1 ..< closingIndex, where: { $0.isUnwrapOperator }) ?? closingIndex >= - formatter.index(of: .nonSpace, before: closingIndex) ?? closingIndex - 1 - else { - return - } - if formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) == .keyword("#file") { - return - } - formatter.removeParen(at: closingIndex) - formatter.removeParen(at: i) - } - } - } - - /// Remove redundant `get {}` clause inside read-only computed property - public let redundantGet = FormatRule( - help: "Remove unneeded `get` clause inside computed properties." - ) { formatter in - formatter.forEach(.identifier("get")) { i, _ in - if formatter.isAccessorKeyword(at: i, checkKeyword: false), - let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - $0 == .startOfScope("{") - }), let openIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0 == .startOfScope("{") - }), - let closeIndex = formatter.index(of: .endOfScope("}"), after: openIndex), - let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closeIndex, if: { - $0 == .endOfScope("}") - }) - { - formatter.removeTokens(in: closeIndex ..< nextIndex) - formatter.removeTokens(in: prevIndex + 1 ... openIndex) - // TODO: fix-up indenting of lines in between removed braces - } - } - } - - /// Remove or insert redundant `= nil` initialization for Optional properties - public let redundantNilInit = FormatRule( - help: "Remove/insert redundant `nil` default value (Optional vars are nil by default).", - options: ["nilinit"] - ) { formatter in - func search(from index: Int, isStoredProperty: Bool) { - if let optionalIndex = formatter.index(of: .unwrapOperator, after: index) { - if formatter.index(of: .endOfStatement, in: index + 1 ..< optionalIndex) != nil { - return - } - let previousToken = formatter.tokens[optionalIndex - 1] - if !previousToken.isSpaceOrCommentOrLinebreak && previousToken != .keyword("as") { - let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: optionalIndex, if: { - $0 == .operator("=", .infix) - }) - switch formatter.options.nilInit { - case .remove: - if let equalsIndex = equalsIndex, let nilIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { - $0 == .identifier("nil") - }) { - formatter.removeTokens(in: optionalIndex + 1 ... nilIndex) - } - case .insert: - if isStoredProperty && equalsIndex == nil { - let tokens: [Token] = [.space(" "), .operator("=", .infix), .space(" "), .identifier("nil")] - formatter.insert(tokens, at: optionalIndex + 1) - } - } - } - search(from: optionalIndex, isStoredProperty: isStoredProperty) - } - } - - // Check modifiers don't include `lazy` - formatter.forEach(.keyword("var")) { i, _ in - if formatter.modifiersForDeclaration(at: i, contains: { - $1 == "lazy" || ($1 != "@objc" && $1.hasPrefix("@")) - }) || formatter.isInResultBuilder(at: i) { - return // Can't remove the init - } - // Check this isn't a Codable - if let scopeIndex = formatter.index(of: .startOfScope("{"), before: i) { - var prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: scopeIndex) - loop: while let index = prevIndex { - switch formatter.tokens[index] { - case .identifier("Codable"), .identifier("Decodable"): - return // Can't safely remove the default value - case .keyword("struct") where formatter.options.swiftVersion < "5.2": - if formatter.index(of: .keyword("init"), after: scopeIndex) == nil { - return // Can't safely remove the default value - } - break loop - case .keyword: - break loop - default: - break - } - prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index) - } - } - // Find the nil - search(from: i, isStoredProperty: formatter.isStoredProperty(atIntroducerIndex: i)) - } - } - - /// Remove redundant let/var for unnamed variables - public let redundantLet = FormatRule( - help: "Remove redundant `let`/`var` from ignored variables." - ) { formatter in - formatter.forEach(.identifier("_")) { i, _ in - guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":"), - let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - [.keyword("let"), .keyword("var")].contains($0) - }), - let nextNonSpaceIndex = formatter.index(of: .nonSpaceOrLinebreak, after: prevIndex) - else { - return - } - if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: prevIndex) { - switch prevToken { - case .keyword("if"), .keyword("guard"), .keyword("while"), .identifier("async"), - .keyword where prevToken.isAttribute, - .delimiter(",") where formatter.currentScope(at: i) != .startOfScope("("): - return - default: - break - } - } - // Crude check for Result Builder - if formatter.isInResultBuilder(at: i) { - return - } - formatter.removeTokens(in: prevIndex ..< nextNonSpaceIndex) - } - } - - /// Remove redundant pattern in case statements - public let redundantPattern = FormatRule( - help: "Remove redundant pattern matching parameter syntax." - ) { formatter in - func redundantBindings(in range: Range) -> Bool { - var isEmpty = true - for token in formatter.tokens[range.lowerBound ..< range.upperBound] { - switch token { - case .identifier("_"): - isEmpty = false - case .space, .linebreak, .delimiter(","), .keyword("let"), .keyword("var"): - break - default: - return false - } - } - return !isEmpty - } - - formatter.forEach(.startOfScope("(")) { i, _ in - let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i) - if let prevIndex = prevIndex, let prevToken = formatter.token(at: prevIndex), - [.keyword("case"), .endOfScope("case")].contains(prevToken) - { - // Not safe to remove - return - } - guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex), - [.startOfScope(":"), .operator("=", .infix)].contains(nextToken), - redundantBindings(in: i + 1 ..< endIndex) - else { - return - } - formatter.removeTokens(in: i ... endIndex) - if let prevIndex = prevIndex, formatter.tokens[prevIndex].isIdentifier, - formatter.last(.nonSpaceOrComment, before: prevIndex)?.string == "." - { - if let endOfScopeIndex = formatter.index( - before: prevIndex, - where: { tkn in tkn == .endOfScope("case") || tkn == .keyword("case") } - ), - let varOrLetIndex = formatter.index(after: endOfScopeIndex, where: { tkn in - tkn == .keyword("let") || tkn == .keyword("var") - }), - let operatorIndex = formatter.index(of: .operator, before: prevIndex), - varOrLetIndex < operatorIndex - { - formatter.removeTokens(in: varOrLetIndex ..< operatorIndex) - } - return - } - - // Was an assignment - formatter.insert(.identifier("_"), at: i) - if formatter.token(at: i - 1).map({ $0.isSpaceOrLinebreak }) != true { - formatter.insert(.space(" "), at: i) - } - } - } - - /// Remove redundant raw string values for case statements - public let redundantRawValues = FormatRule( - help: "Remove redundant raw string values for enum cases." - ) { formatter in - formatter.forEach(.keyword("enum")) { i, _ in - guard let nameIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, after: i, if: { $0.isIdentifier } - ), let colonIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: { $0 == .delimiter(":") } - ), formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex) == .identifier("String"), - let braceIndex = formatter.index(of: .startOfScope("{"), after: colonIndex) else { - return - } - var lastIndex = formatter.index(of: .keyword("case"), after: braceIndex) - while var index = lastIndex { - guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { - $0.isIdentifier - }) else { break } - if let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: nameIndex, if: { - $0 == .operator("=", .infix) - }), let quoteIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { - $0 == .startOfScope("\"") - }), formatter.token(at: quoteIndex + 2) == .endOfScope("\"") { - if formatter.tokens[nameIndex].unescaped() == formatter.token(at: quoteIndex + 1)?.string { - formatter.removeTokens(in: nameIndex + 1 ... quoteIndex + 2) - index = nameIndex - } else { - index = quoteIndex + 2 - } - } else { - index = nameIndex - } - lastIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { - $0 == .delimiter(",") - }) ?? formatter.index(of: .keyword("case"), after: index) - } - } - } - - /// Remove redundant void return values for function and closure declarations - public let redundantVoidReturnType = FormatRule( - help: "Remove explicit `Void` return type.", - options: ["closurevoid"] - ) { formatter in - formatter.forEach(.operator("->", .infix)) { i, _ in - guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - let endIndex = formatter.endOfVoidType(at: startIndex) - else { - return - } - - // If this is the explicit return type of a closure, it should - // always be safe to remove - if formatter.options.closureVoidReturn == .remove, - formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) == .keyword("in") - { - formatter.removeTokens(in: i ..< formatter.index(of: .nonSpace, after: endIndex)!) - return - } - - guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) == .startOfScope("{") - else { return } - - guard let prevIndex = formatter.index(of: .endOfScope(")"), before: i), - let parenIndex = formatter.index(of: .startOfScope("("), before: prevIndex), - let startToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: parenIndex), - startToken.isIdentifier || [.startOfScope("{"), .endOfScope("]")].contains(startToken) - else { - return - } - formatter.removeTokens(in: i ..< formatter.index(of: .nonSpace, after: endIndex)!) - } - } - - /// Remove redundant return keyword - public let redundantReturn = FormatRule( - help: "Remove unneeded `return` keyword." - ) { formatter in - // indices of returns that are safe to remove - var returnIndices = [Int]() - - // Also handle redundant void returns in void functions, which can always be removed. - // - The following code is the original implementation of the `redundantReturn` rule - // and is partially redundant with the below code so could be simplified in the future. - formatter.forEach(.keyword("return")) { i, _ in - guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) else { - return - } - defer { - // Check return wasn't removed already - if formatter.token(at: i) == .keyword("return") { - returnIndices.append(i) - } - } - switch formatter.tokens[startIndex] { - case .keyword("in"): - break - case .startOfScope("{"): - guard var prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex) else { - break - } - if formatter.options.swiftVersion < "5.1", formatter.isAccessorKeyword(at: prevIndex) { - return - } - if formatter.tokens[prevIndex] == .endOfScope(")"), - let j = formatter.index(of: .startOfScope("("), before: prevIndex) - { - prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: j) ?? j - if formatter.tokens[prevIndex] == .operator("?", .postfix) { - prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex) ?? prevIndex - } - let prevToken = formatter.tokens[prevIndex] - guard prevToken.isIdentifier || prevToken == .keyword("init") else { - return - } - } - let prevToken = formatter.tokens[prevIndex] - guard ![.delimiter(":"), .startOfScope("(")].contains(prevToken), - var prevKeywordIndex = formatter.indexOfLastSignificantKeyword( - at: startIndex, excluding: ["where"] - ) - else { - break - } - switch formatter.tokens[prevKeywordIndex].string { - case "let", "var": - guard formatter.options.swiftVersion >= "5.1" || prevToken == .operator("=", .infix) || - formatter.lastIndex(of: .operator("=", .infix), in: prevKeywordIndex + 1 ..< prevIndex) != nil, - !formatter.isConditionalStatement(at: prevKeywordIndex) - else { - return - } - case "func", "throws", "rethrows", "init", "subscript": - if formatter.options.swiftVersion < "5.1", - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .endOfScope("}") - { - return - } - default: - return - } - default: - guard let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0 == .endOfScope("}") - }), let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { - return - } - if !formatter.isStartOfClosure(at: startIndex), !["func", "throws", "rethrows"] - .contains(formatter.lastSignificantKeyword(at: startIndex, excluding: ["where"]) ?? "") - { - return - } - } - // Don't remove return if it's followed by more code - guard let endIndex = formatter.endOfScope(at: i), - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) == endIndex - else { - return - } - if formatter.index(of: .nonSpaceOrLinebreak, after: i) == endIndex, - let startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) - { - formatter.removeTokens(in: startIndex + 1 ... i) - return - } - formatter.removeToken(at: i) - if var nextIndex = formatter.index(of: .nonSpace, after: i - 1, if: { $0.isLinebreak }) { - if let i = formatter.index(of: .nonSpaceOrLinebreak, after: nextIndex) { - nextIndex = i - 1 - } - formatter.removeTokens(in: i ... nextIndex) - } else if formatter.token(at: i)?.isSpace == true { - formatter.removeToken(at: i) - } - } - - // Explicit returns are redundant in closures, functions, etc with a single statement body - formatter.forEach(.startOfScope("{")) { startOfScopeIndex, _ in - // Closures always supported implicit returns, but other types of scopes - // only support implicit return in Swift 5.1+ (SE-0255) - let isClosure = formatter.isStartOfClosure(at: startOfScopeIndex) - if formatter.options.swiftVersion < "5.1", !isClosure { - return - } - - // Make sure this is a type of scope that supports implicit returns - if !isClosure, formatter.isConditionalStatement(at: startOfScopeIndex, excluding: ["where"]) || - ["do", "else", "catch"].contains(formatter.lastSignificantKeyword(at: startOfScopeIndex, excluding: ["throws"])) - { - return - } - - // Only strip return from conditional block if conditionalAssignment rule is enabled - let stripConditionalReturn = formatter.options.enabledRules.contains("conditionalAssignment") - - // Make sure the body only has a single statement - guard formatter.blockBodyHasSingleStatement( - atStartOfScope: startOfScopeIndex, - includingConditionalStatements: true, - includingReturnStatements: true, - includingReturnInConditionalStatements: stripConditionalReturn - ) else { - return - } - - // Make sure we aren't in a failable `init?`, where explicit return is required unless it's the only statement - if !isClosure, let lastSignificantKeywordIndex = formatter.indexOfLastSignificantKeyword(at: startOfScopeIndex), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: startOfScopeIndex) != .keyword("return"), - formatter.tokens[lastSignificantKeywordIndex] == .keyword("init"), - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lastSignificantKeywordIndex), - nextToken == .operator("?", .postfix) - { - return - } - - // Find all of the return keywords to remove before we remove any of them, - // so we can apply additional validation first. - var returnKeywordRangesToRemove = [Range]() - var hasReturnThatCantBeRemoved = false - - /// Finds the return keywords to remove and stores them in `returnKeywordRangesToRemove` - func removeReturn(atStartOfScope startOfScopeIndex: Int) { - // If this scope is a single-statement if or switch statement then we have to recursively - // remove the return from each branch of the if statement - let startOfBody = formatter.startOfBody(atStartOfScope: startOfScopeIndex) - - if let firstTokenInBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfBody), - let conditionalBranches = formatter.conditionalBranches(at: firstTokenInBody) - { - for branch in conditionalBranches.reversed() { - // In Swift 5.9, there's a bug that prevents you from writing an - // if or switch expression using an `as?` on one of the branches: - // https://github.com/apple/swift/issues/68764 - // - // if condition { - // foo as? String - // } else { - // "bar" - // } - // - if formatter.conditionalBranchHasUnsupportedCastOperator( - startOfScopeIndex: branch.startOfBranch) - { - hasReturnThatCantBeRemoved = true - return - } - - removeReturn(atStartOfScope: branch.startOfBranch) - } - } - - // Otherwise this is a simple case with a single return at the start of the scope - else if let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex), - let returnIndex = formatter.index(of: .keyword("return"), after: startOfScopeIndex), - returnIndices.contains(returnIndex), - returnIndex < endOfScopeIndex, - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: returnIndex), - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: returnIndex)! < endOfScopeIndex - { - let range = returnIndex ..< nextIndex - for (i, index) in returnIndices.enumerated().reversed() { - if range.contains(index) { - returnIndices.remove(at: i) - } else if index > returnIndex { - returnIndices[i] -= range.count - } - } - returnKeywordRangesToRemove.append(range) - } - } - - removeReturn(atStartOfScope: startOfScopeIndex) - - guard !hasReturnThatCantBeRemoved else { return } - - for returnKeywordRangeToRemove in returnKeywordRangesToRemove.sorted(by: { $0.startIndex > $1.startIndex }) { - formatter.removeTokens(in: returnKeywordRangeToRemove) - } - } - } - - /// Remove redundant backticks around non-keywords, or in places where keywords don't need escaping - public let redundantBackticks = FormatRule( - help: "Remove redundant backticks around identifiers." - ) { formatter in - formatter.forEach(.identifier) { i, token in - guard token.string.first == "`", !formatter.backticksRequired(at: i) else { - return - } - formatter.replaceToken(at: i, with: .identifier(token.unescaped())) - } - } - - /// Remove redundant Self keyword - public let redundantStaticSelf = FormatRule( - help: "Remove explicit `Self` where applicable." - ) { formatter in - formatter.addOrRemoveSelf(static: true) - } - - /// Insert or remove redundant self keyword - public let redundantSelf = FormatRule( - help: "Insert/remove explicit `self` where applicable.", - options: ["self", "selfrequired"] - ) { formatter in - _ = formatter.options.selfRequired - _ = formatter.options.explicitSelf - formatter.addOrRemoveSelf(static: false) - } - - /// Replace unused arguments with an underscore - public let unusedArguments = FormatRule( - help: "Mark unused function arguments with `_`.", - options: ["stripunusedargs"] - ) { formatter in - guard !formatter.options.fragment else { return } - - func removeUsed(from argNames: inout [String], with associatedData: inout [T], - locals: Set = [], in range: CountableRange) - { - var isDeclaration = false - var wasDeclaration = false - var isConditional = false - var isGuard = false - var locals = locals - var tempLocals = Set() - func pushLocals() { - if isDeclaration, isConditional { - for name in tempLocals { - if let index = argNames.firstIndex(of: name), - !locals.contains(name) - { - argNames.remove(at: index) - associatedData.remove(at: index) - } - } - } - wasDeclaration = isDeclaration - isDeclaration = false - locals.formUnion(tempLocals) - tempLocals.removeAll() - } - var i = range.lowerBound - while i < range.upperBound { - if formatter.isStartOfStatement(at: i, treatingCollectionKeysAsStart: false), - // Immediately following an `=` operator, if or switch keywords - // are expressions rather than statements. - formatter.lastToken(before: i, where: { !$0.isSpaceOrCommentOrLinebreak })?.isOperator("=") != true - { - pushLocals() - wasDeclaration = false - } - let token = formatter.tokens[i] - outer: switch token { - case .keyword("guard"): - isGuard = true - case .keyword("let"), .keyword("var"), .keyword("func"), .keyword("for"): - isDeclaration = true - var i = i - while let scopeStart = formatter.index(of: .startOfScope("("), before: i) { - i = scopeStart - } - isConditional = formatter.isConditionalStatement(at: i) - case .identifier: - let name = token.unescaped() - guard let index = argNames.firstIndex(of: name), !locals.contains(name) else { - break - } - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i)?.isOperator(".") == false, - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":") || - [.startOfScope("("), .startOfScope("[")].contains(formatter.currentScope(at: i) ?? .space("")) - { - if isDeclaration { - switch formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { - case .endOfScope(")")?, .operator("=", .infix)?, - .delimiter(",")? where !isConditional: - tempLocals.insert(name) - break outer - default: - break - } - } - argNames.remove(at: index) - associatedData.remove(at: index) - if argNames.isEmpty { - return - } - } - case .startOfScope("{"): - guard let endIndex = formatter.endOfScope(at: i) else { - return formatter.fatalError("Expected }", at: i) - } - if formatter.isStartOfClosure(at: i) { - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - } else if isGuard { - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - pushLocals() - } else { - let prevLocals = locals - pushLocals() - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - locals = prevLocals - } - - isGuard = false - i = endIndex - case .endOfScope("case"), .endOfScope("default"): - pushLocals() - guard let colonIndex = formatter.index(of: .startOfScope(":"), after: i) else { - return formatter.fatalError("Expected :", at: i) - } - guard let endIndex = formatter.endOfScope(at: colonIndex) else { - return formatter.fatalError("Expected end of case statement", - at: colonIndex) - } - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - i = endIndex - 1 - case .operator("=", .infix), .delimiter(":"), .startOfScope(":"), - .keyword("in"), .keyword("where"): - wasDeclaration = isDeclaration - isDeclaration = false - case .delimiter(","): - if let scope = formatter.currentScope(at: i), [ - .startOfScope("("), .startOfScope("["), .startOfScope("<"), - ].contains(scope) { - break - } - if isConditional { - if isGuard, wasDeclaration { - pushLocals() - } - wasDeclaration = false - } else { - let _wasDeclaration = wasDeclaration - pushLocals() - isDeclaration = _wasDeclaration - } - case .delimiter(";"): - pushLocals() - wasDeclaration = false - default: - break - } - i += 1 - } - } - // Closure arguments - formatter.forEach(.keyword("in")) { i, _ in - var argNames = [String]() - var nameIndexPairs = [(Int, Int)]() - guard let start = formatter.index(of: .startOfScope("{"), before: i) else { - return - } - var index = i - 1 - var argCountStack = [0] - while index > start { - let token = formatter.tokens[index] - switch token { - case .endOfScope("}"): - return - case .endOfScope("]"): - // TODO: handle unused capture list arguments - index = formatter.index(of: .startOfScope("["), before: index) ?? index - case .endOfScope(")"): - argCountStack.append(argNames.count) - case .startOfScope("("): - argCountStack.removeLast() - case .delimiter(","): - argCountStack[argCountStack.count - 1] = argNames.count - case .identifier("async") where - formatter.last(.nonSpaceOrLinebreak, before: index)?.isIdentifier == true: - fallthrough - case .operator("->", .infix), .keyword("throws"): - // Everything after this was part of return value - let count = argCountStack.last ?? 0 - argNames.removeSubrange(count ..< argNames.count) - nameIndexPairs.removeSubrange(count ..< nameIndexPairs.count) - case let .keyword(name) where - !token.isAttribute && !name.hasPrefix("#") && name != "inout": - return - case .identifier: - guard argCountStack.count < 3, - let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index), [ - .delimiter(","), .startOfScope("("), .startOfScope("{"), .endOfScope("]"), - ].contains(prevToken), let scopeStart = formatter.index(of: .startOfScope, before: index), - ![.startOfScope("["), .startOfScope("<")].contains(formatter.tokens[scopeStart]) - else { - break - } - let name = token.unescaped() - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index), - let nextToken = formatter.token(at: nextIndex), case .identifier = nextToken, - formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .delimiter(":") - { - let internalName = nextToken.unescaped() - if internalName != "_" { - argNames.append(internalName) - nameIndexPairs.append((index, nextIndex)) - } - } else if name != "_" { - argNames.append(name) - nameIndexPairs.append((index, index)) - } - default: - break - } - index -= 1 - } - guard !argNames.isEmpty, let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: i) else { - return - } - removeUsed(from: &argNames, with: &nameIndexPairs, in: i + 1 ..< bodyEndIndex) - for pair in nameIndexPairs { - if case .identifier("_") = formatter.tokens[pair.0], pair.0 != pair.1 { - formatter.removeToken(at: pair.1) - if formatter.tokens[pair.1 - 1] == .space(" ") { - formatter.removeToken(at: pair.1 - 1) - } - } else { - formatter.replaceToken(at: pair.1, with: .identifier("_")) - } - } - } - // Function arguments - formatter.forEachToken { i, token in - guard formatter.options.stripUnusedArguments != .closureOnly, - case let .keyword(keyword) = token, ["func", "init", "subscript"].contains(keyword), - let startIndex = formatter.index(of: .startOfScope("("), after: i), - let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) else { return } - let isOperator = (keyword == "subscript") || - (keyword == "func" && formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isOperator == true) - var index = startIndex - var argNames = [String]() - var nameIndexPairs = [(Int, Int)]() - while index < endIndex { - guard let externalNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { - if case let .identifier(name) = $0 { - return formatter.options.stripUnusedArguments != .unnamedOnly || name == "_" - } - // Probably an empty argument list - return false - }) else { return } - guard let nextIndex = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: externalNameIndex) else { return } - let nextToken = formatter.tokens[nextIndex] - switch nextToken { - case let .identifier(name): - if name != "_" { - argNames.append(nextToken.unescaped()) - nameIndexPairs.append((externalNameIndex, nextIndex)) - } - case .delimiter(":"): - let externalNameToken = formatter.tokens[externalNameIndex] - if case let .identifier(name) = externalNameToken, name != "_" { - argNames.append(externalNameToken.unescaped()) - nameIndexPairs.append((externalNameIndex, externalNameIndex)) - } - default: - return - } - index = formatter.index(of: .delimiter(","), after: index) ?? endIndex - } - guard !argNames.isEmpty, let bodyStartIndex = formatter.index(after: endIndex, where: { - switch $0 { - case .startOfScope("{"): // What we're looking for - return true - case .keyword("throws"), - .keyword("rethrows"), - .identifier("async"), - .keyword("where"), - .keyword("is"): - return false // Keep looking - case .keyword: - return true // Not valid between end of arguments and start of body - default: - return false // Keep looking - } - }), formatter.tokens[bodyStartIndex] == .startOfScope("{"), - let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: bodyStartIndex) else { - return - } - removeUsed(from: &argNames, with: &nameIndexPairs, in: bodyStartIndex + 1 ..< bodyEndIndex) - for pair in nameIndexPairs.reversed() { - if pair.0 == pair.1 { - if isOperator { - formatter.replaceToken(at: pair.0, with: .identifier("_")) - } else { - formatter.insert(.identifier("_"), at: pair.0 + 1) - formatter.insert(.space(" "), at: pair.0 + 1) - } - } else if case .identifier("_") = formatter.tokens[pair.0] { - formatter.removeToken(at: pair.1) - if formatter.tokens[pair.1 - 1] == .space(" ") { - formatter.removeToken(at: pair.1 - 1) - } - } else { - formatter.replaceToken(at: pair.1, with: .identifier("_")) - } - } - } - } - - public let hoistTry = FormatRule( - help: "Move inline `try` keyword(s) to start of expression.", - options: ["throwcapturing"] - ) { formatter in - let names = formatter.options.throwCapturing.union(["expect"]) - formatter.forEachToken(where: { - $0 == .startOfScope("(") || $0 == .startOfScope("[") - }) { i, _ in - formatter.hoistEffectKeyword("try", inScopeAt: i) { prevIndex in - guard case let .identifier(name) = formatter.tokens[prevIndex] else { - return false - } - return name.hasPrefix("XCTAssert") || formatter.isSymbol(at: prevIndex, in: names) - } - } - } - - /// Reposition `await` keyword outside of the current scope. - public let hoistAwait = FormatRule( - help: "Move inline `await` keyword(s) to start of expression.", - options: ["asynccapturing"] - ) { formatter in - guard formatter.options.swiftVersion >= "5.5" else { return } - - formatter.forEachToken(where: { - $0 == .startOfScope("(") || $0 == .startOfScope("[") - }) { i, _ in - formatter.hoistEffectKeyword("await", inScopeAt: i) { prevIndex in - formatter.isSymbol(at: prevIndex, in: formatter.options.asyncCapturing) - } - } - } - - /// Move `let` and `var` inside patterns to the beginning - public let hoistPatternLet = FormatRule( - help: "Reposition `let` or `var` bindings within pattern.", - options: ["patternlet"] - ) { formatter in - func indicesOf(_ keyword: String, in range: CountableRange) -> [Int]? { - var indices = [Int]() - var keywordFound = false, identifierFound = false - var count = 0 - for index in range { - switch formatter.tokens[index] { - case .keyword(keyword): - indices.append(index) - keywordFound = true - case .identifier("_"): - break - case .identifier where formatter.last(.nonSpaceOrComment, before: index) != .operator(".", .prefix): - identifierFound = true - if keywordFound { - count += 1 - } - case .delimiter(","): - guard keywordFound || !identifierFound else { - return nil - } - keywordFound = false - identifierFound = false - case .startOfScope("{"): - return nil - case .startOfScope("<"): - // See: https://github.com/nicklockwood/SwiftFormat/issues/768 - return nil - default: - break - } - } - return (keywordFound || !identifierFound) && count > 0 ? indices : nil - } - - formatter.forEach(.startOfScope("(")) { i, _ in - let hoist = formatter.options.hoistPatternLet - // Check if pattern already starts with let/var - guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), - let prevIndex = formatter.index(before: i, where: { - switch $0 { - case .operator(".", _), .keyword("let"), .keyword("var"), - .endOfScope("*/"): - return false - case .endOfScope, .delimiter, .operator, .keyword: - return true - default: - return false - } - }) - else { - return - } - switch formatter.tokens[prevIndex] { - case .endOfScope("case"), .keyword("case"), .keyword("catch"): - break - case .delimiter(","): - loop: for token in formatter.tokens[0 ..< prevIndex].reversed() { - switch token { - case .endOfScope("case"), .keyword("catch"): - break loop - case .keyword("var"), .keyword("let"): - break - case .keyword: - // Tuple assignment - return - default: - break - } - } - default: - return - } - let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: prevIndex) - ?? (prevIndex + 1) - if case let .keyword(keyword) = formatter.tokens[startIndex], - ["let", "var"].contains(keyword) - { - if hoist { - // No changes needed - return - } - // Find variable indices - var indices = [Int]() - var index = i + 1 - var wasParenOrCommaOrLabel = true - while index < endIndex { - let token = formatter.tokens[index] - switch token { - case .delimiter(","), .startOfScope("("), .delimiter(":"): - wasParenOrCommaOrLabel = true - case .identifier("_"), .identifier("true"), .identifier("false"), .identifier("nil"): - wasParenOrCommaOrLabel = false - case let .identifier(name) where wasParenOrCommaOrLabel: - wasParenOrCommaOrLabel = false - let next = formatter.next(.nonSpaceOrComment, after: index) - if next != .operator(".", .infix), next != .delimiter(":") { - indices.append(index) - } - case _ where token.isSpaceOrCommentOrLinebreak: - break - case .startOfScope("["): - guard let next = formatter.endOfScope(at: index) else { - return formatter.fatalError("Expected ]", at: index) - } - index = next - default: - wasParenOrCommaOrLabel = false - } - index += 1 - } - // Insert keyword at indices - for index in indices.reversed() { - formatter.insert([.keyword(keyword), .space(" ")], at: index) - } - // Remove keyword - let range = ((formatter.index(of: .nonSpace, before: startIndex) ?? - (prevIndex - 1)) + 1) ... startIndex - formatter.removeTokens(in: range) - } else if hoist { - // Find let/var keyword indices - var keyword = "let" - guard let indices: [Int] = { - guard let indices = indicesOf(keyword, in: i + 1 ..< endIndex) else { - keyword = "var" - return indicesOf(keyword, in: i + 1 ..< endIndex) - } - return indices - }() else { - return - } - // Remove keywords inside parens - for index in indices.reversed() { - if formatter.tokens[index + 1].isSpace { - formatter.removeToken(at: index + 1) - } - formatter.removeToken(at: index) - } - // Insert keyword before parens - formatter.insert(.keyword(keyword), at: startIndex) - if let nextToken = formatter.token(at: startIndex + 1), !nextToken.isSpaceOrLinebreak { - formatter.insert(.space(" "), at: startIndex + 1) - } - if let prevToken = formatter.token(at: startIndex - 1), - !prevToken.isSpaceOrCommentOrLinebreak, !prevToken.isStartOfScope - { - formatter.insert(.space(" "), at: startIndex) - } - } - } - } - - public let wrap = FormatRule( - help: "Wrap lines that exceed the specified maximum width.", - options: ["maxwidth", "nowrapoperators", "assetliterals", "wrapternary"], - sharedOptions: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", "indent", - "trimwhitespace", "linebreaks", "tabwidth", "maxwidth", "smarttabs", "wrapreturntype", - "wrapconditions", "wraptypealiases", "wrapternary", "wrapeffects", "conditionswrap"] - ) { formatter in - let maxWidth = formatter.options.maxWidth - guard maxWidth > 0 else { return } - - // Wrap collections first to avoid conflict - formatter.wrapCollectionsAndArguments(completePartialWrapping: false, - wrapSingleArguments: false) - - // Wrap other line types - var currentIndex = 0 - var indent = "" - var alreadyLinewrapped = false - - func isLinewrapToken(_ token: Token?) -> Bool { - switch token { - case .delimiter?, .operator(_, .infix)?: - return true - default: - return false - } - } - - formatter.forEachToken(onlyWhereEnabled: false) { i, token in - if i < currentIndex { - return - } - if token.isLinebreak { - indent = formatter.currentIndentForLine(at: i + 1) - alreadyLinewrapped = isLinewrapToken(formatter.last(.nonSpaceOrComment, before: i)) - currentIndex = i + 1 - } else if let breakPoint = formatter.indexWhereLineShouldWrapInLine(at: i) { - if !alreadyLinewrapped { - indent += formatter.linewrapIndent(at: breakPoint) - } - alreadyLinewrapped = true - if formatter.isEnabled { - let spaceAdded = formatter.insertSpace(indent, at: breakPoint + 1) - formatter.insertLinebreak(at: breakPoint + 1) - currentIndex = breakPoint + spaceAdded + 2 - } else { - currentIndex = breakPoint + 1 - } - } else { - currentIndex = formatter.endOfLine(at: i) - } - } - - formatter.wrapCollectionsAndArguments(completePartialWrapping: true, - wrapSingleArguments: true) - } - - /// Normalize argument wrapping style - public let wrapArguments = FormatRule( - help: "Align wrapped function arguments or collection elements.", - orderAfter: ["wrap"], - options: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", - "wrapreturntype", "wrapconditions", "wraptypealiases", "wrapeffects", "conditionswrap"], - sharedOptions: ["indent", "trimwhitespace", "linebreaks", - "tabwidth", "maxwidth", "smarttabs", "assetliterals", "wrapternary"] - ) { formatter in - formatter.wrapCollectionsAndArguments(completePartialWrapping: true, - wrapSingleArguments: false) - } - - public let wrapMultilineStatementBraces = FormatRule( - help: "Wrap the opening brace of multiline statements.", - orderAfter: ["braces", "indent", "wrapArguments"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - guard formatter.last(.nonSpaceOrComment, before: i)?.isLinebreak == false, - formatter.shouldWrapMultilineStatementBrace(at: i), - let endIndex = formatter.endOfScope(at: i) - else { - return - } - let indent = formatter.currentIndentForLine(at: endIndex) - // Insert linebreak - formatter.insertLinebreak(at: i) - // Align the opening brace with closing brace - formatter.insertSpace(indent, at: i + 1) - // Clean up trailing space on the previous line - if case .space? = formatter.token(at: i - 1) { - formatter.removeToken(at: i - 1) - } - } - } - - /// Formats enum cases declaration into one case per line - public let wrapEnumCases = FormatRule( - help: "Rewrite comma-delimited enum cases to one case per line.", - disabledByDefault: true, - options: ["wrapenumcases"], - sharedOptions: ["linebreaks"] - ) { formatter in - - func shouldWrapCaseRangeGroup(_ caseRangeGroup: [Formatter.EnumCaseRange]) -> Bool { - guard let firstIndex = caseRangeGroup.first?.value.lowerBound, - let scopeStart = formatter.startOfScope(at: firstIndex), - formatter.tokens[scopeStart ..< firstIndex].contains(where: { $0.isLinebreak }) - else { - // Don't wrap if first case is on same line as opening `{` - return false - } - return formatter.options.wrapEnumCases == .always || caseRangeGroup.contains(where: { - formatter.tokens[$0.value].contains(where: { - [.startOfScope("("), .operator("=", .infix)].contains($0) - }) - }) - } - - formatter.parseEnumCaseRanges() - .filter(shouldWrapCaseRangeGroup) - .flatMap { $0 } - .filter { $0.endOfCaseRangeToken == .delimiter(",") } - .reversed() - .forEach { enumCase in - guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: enumCase.value.upperBound) else { - return - } - let caseIndex = formatter.lastIndex(of: .keyword("case"), in: 0 ..< enumCase.value.lowerBound) - let indent = formatter.currentIndentForLine(at: caseIndex ?? enumCase.value.lowerBound) - - if formatter.tokens[nextNonSpaceIndex] == .startOfScope("//") { - formatter.removeToken(at: enumCase.value.upperBound) - if formatter.token(at: enumCase.value.upperBound)?.isSpace == true, - formatter.token(at: enumCase.value.upperBound - 1)?.isSpace == true - { - formatter.removeToken(at: enumCase.value.upperBound - 1) - } - nextNonSpaceIndex = formatter.index(of: .linebreak, after: enumCase.value.upperBound) ?? nextNonSpaceIndex - } else { - formatter.removeTokens(in: enumCase.value.upperBound ..< nextNonSpaceIndex) - nextNonSpaceIndex = enumCase.value.upperBound - } - - if !formatter.tokens[nextNonSpaceIndex].isLinebreak { - formatter.insertLinebreak(at: nextNonSpaceIndex) - } - - let offset = indent.isEmpty ? 0 : 1 - formatter.insertSpace(indent, at: nextNonSpaceIndex + 1) - formatter.insert([.keyword("case")], at: nextNonSpaceIndex + 1 + offset) - formatter.insertSpace(" ", at: nextNonSpaceIndex + 2 + offset) - } - } - - /// Wrap single-line comments that exceed given `FormatOptions.maxWidth` setting. - public let wrapSingleLineComments = FormatRule( - help: "Wrap single line `//` comments that exceed the specified `--maxwidth`.", - sharedOptions: ["maxwidth", "indent", "tabwidth", "assetliterals", "linebreaks"] - ) { formatter in - let delimiterLength = "//".count - var maxWidth = formatter.options.maxWidth - guard maxWidth > 3 else { - return - } - - formatter.forEach(.startOfScope("//")) { i, _ in - let startOfLine = formatter.startOfLine(at: i) - let endOfLine = formatter.endOfLine(at: i) - guard formatter.lineLength(from: startOfLine, upTo: endOfLine) > maxWidth else { - return - } - - guard let startIndex = formatter.index(of: .nonSpace, after: i), - case var .commentBody(comment) = formatter.tokens[startIndex], - !comment.isCommentDirective - else { - return - } - - var words = comment.components(separatedBy: " ") - comment = words.removeFirst() - let commentPrefix = comment == "/" ? "/ " : comment.hasPrefix("/") ? "/" : "" - let prefixLength = formatter.lineLength(upTo: startIndex) - var length = prefixLength + comment.count - while length <= maxWidth, let next = words.first, - length + next.count < maxWidth || - // Don't wrap if next word won't fit on a line by itself anyway - prefixLength + commentPrefix.count + next.count > maxWidth - { - comment += " \(next)" - length += next.count + 1 - words.removeFirst() - } - if words.isEmpty || comment == commentPrefix { - return - } - var prefix = formatter.tokens[i ..< startIndex] - if let token = formatter.token(at: startOfLine), case .space = token { - prefix.insert(token, at: prefix.startIndex) - } - formatter.replaceTokens(in: startIndex ..< endOfLine, with: [ - .commentBody(comment), formatter.linebreakToken(for: startIndex), - ] + prefix + [ - .commentBody(commentPrefix + words.joined(separator: " ")), - ]) - } - } - - /// Writes one switch case per line - public let wrapSwitchCases = FormatRule( - help: "Wrap comma-delimited switch cases onto multiple lines.", - disabledByDefault: true, - sharedOptions: ["linebreaks", "tabwidth", "indent", "smarttabs"] - ) { formatter in - formatter.forEach(.endOfScope("case")) { i, _ in - guard var endIndex = formatter.index(of: .startOfScope(":"), after: i) else { return } - let lineStart = formatter.startOfLine(at: i) - let indent = formatter.spaceEquivalentToTokens(from: lineStart, upTo: i + 2) - - var startIndex = i - while let commaIndex = formatter.index(of: .delimiter(","), in: startIndex + 1 ..< endIndex), - let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: commaIndex) - { - if formatter.index(of: .linebreak, in: commaIndex ..< nextIndex) == nil { - formatter.insertLinebreak(at: commaIndex + 1) - let delta = formatter.insertSpace(indent, at: commaIndex + 2) - endIndex += 1 + delta - } - startIndex = commaIndex - } - } - } - - /// Normalize the use of void in closure arguments and return values - public let void = FormatRule( - help: "Use `Void` for type declarations and `()` for values.", - options: ["voidtype"] - ) { formatter in - func isArgumentToken(at index: Int) -> Bool { - guard let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: index) else { - return false - } - switch nextToken { - case .operator("->", .infix), .keyword("throws"), .keyword("rethrows"), .identifier("async"): - return true - case .startOfScope("{"): - if formatter.tokens[index] == .endOfScope(")"), - let index = formatter.index(of: .startOfScope("("), before: index), - let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index, if: { - $0.isIdentifier - }), formatter.last(.nonSpaceOrCommentOrLinebreak, before: nameIndex) == .keyword("func") - { - return true - } - return false - case .keyword("in"): - if formatter.tokens[index] == .endOfScope(")"), - let index = formatter.index(of: .startOfScope("("), before: index) - { - return formatter.last(.nonSpaceOrCommentOrLinebreak, before: index) == .startOfScope("{") - } - return false - default: - return false - } - } - - let hasLocalVoid: Bool = { - for (i, token) in formatter.tokens.enumerated() where token == .identifier("Void") { - if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) { - switch prevToken { - case .keyword("typealias"), .keyword("struct"), .keyword("class"), .keyword("enum"): - return true - default: - break - } - } - } - return false - }() - - formatter.forEach(.identifier("Void")) { i, _ in - if let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0 == .endOfScope(")") - }), var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), { - let token = formatter.tokens[prevIndex] - if token == .delimiter(":"), - let prevPrevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex), - formatter.tokens[prevPrevIndex] == .identifier("_"), - let startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevPrevIndex), - formatter.tokens[startIndex] == .startOfScope("(") - { - prevIndex = startIndex - return true - } - return token == .startOfScope("(") - }() { - if isArgumentToken(at: nextIndex) || formatter.last( - .nonSpaceOrLinebreak, - before: prevIndex - )?.isIdentifier == true { - if !formatter.options.useVoid, !hasLocalVoid { - // Convert to parens - formatter.replaceToken(at: i, with: .endOfScope(")")) - formatter.insert(.startOfScope("("), at: i) - } - } else if formatter.options.useVoid { - // Strip parens - formatter.removeTokens(in: i + 1 ... nextIndex) - formatter.removeTokens(in: prevIndex ..< i) - } else { - // Remove Void - formatter.removeTokens(in: prevIndex + 1 ..< nextIndex) - } - } else if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - [.operator(".", .prefix), .operator(".", .infix), - .keyword("typealias")].contains(prevToken) - { - return - } else if formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) == - .operator(".", .infix) - { - return - } else if formatter.next(.nonSpace, after: i) == .startOfScope("(") { - if !hasLocalVoid { - formatter.removeToken(at: i) - } - } else if !formatter.options.useVoid || isArgumentToken(at: i), !hasLocalVoid { - // Convert to parens - formatter.replaceToken(at: i, with: [.startOfScope("("), .endOfScope(")")]) - } - } - formatter.forEach(.startOfScope("(")) { i, _ in - guard formatter.options.useVoid else { - return - } - guard let endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0 == .endOfScope(")") - }), let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), - !isArgumentToken(at: endIndex) else { - return - } - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator("->", .infix) { - if !hasLocalVoid { - formatter.replaceTokens(in: i ... endIndex, with: .identifier("Void")) - } - } else if prevToken == .startOfScope("<") || - (prevToken == .delimiter(",") && formatter.currentScope(at: i) == .startOfScope("<")), - !hasLocalVoid - { - formatter.replaceTokens(in: i ... endIndex, with: .identifier("Void")) - } - // TODO: other cases - } - } - - /// Standardize formatting of numeric literals - public let numberFormatting = FormatRule( - help: """ - Use consistent grouping for numeric literals. Groups will be separated by `_` - delimiters to improve readability. For each numeric type you can specify a group - size (the number of digits in each group) and a threshold (the minimum number of - digits in a number before grouping is applied). - """, - options: ["decimalgrouping", "binarygrouping", "octalgrouping", "hexgrouping", - "fractiongrouping", "exponentgrouping", "hexliteralcase", "exponentcase"] - ) { formatter in - func applyGrouping(_ grouping: Grouping, to number: inout String) { - switch grouping { - case .none, .group: - number = number.replacingOccurrences(of: "_", with: "") - case .ignore: - return - } - guard case let .group(group, threshold) = grouping, group > 0, number.count >= threshold else { - return - } - var output = Substring() - var index = number.endIndex - var count = 0 - repeat { - index = number.index(before: index) - if count > 0, count % group == 0 { - output.insert("_", at: output.startIndex) - } - count += 1 - output.insert(number[index], at: output.startIndex) - } while index != number.startIndex - number = String(output) - } - formatter.forEachToken { i, token in - guard case let .number(number, type) = token else { - return - } - let grouping: Grouping - let prefix: String, exponentSeparator: String, parts: [String] - switch type { - case .integer, .decimal: - grouping = formatter.options.decimalGrouping - prefix = "" - exponentSeparator = formatter.options.uppercaseExponent ? "E" : "e" - parts = number.components(separatedBy: CharacterSet(charactersIn: ".eE")) - case .binary: - grouping = formatter.options.binaryGrouping - prefix = "0b" - exponentSeparator = "" - parts = [String(number[prefix.endIndex...])] - case .octal: - grouping = formatter.options.octalGrouping - prefix = "0o" - exponentSeparator = "" - parts = [String(number[prefix.endIndex...])] - case .hex: - grouping = formatter.options.hexGrouping - prefix = "0x" - exponentSeparator = formatter.options.uppercaseExponent ? "P" : "p" - parts = number[prefix.endIndex...].components(separatedBy: CharacterSet(charactersIn: ".pP")).map { - formatter.options.uppercaseHex ? $0.uppercased() : $0.lowercased() - } - } - var main = parts[0], fraction = "", exponent = "" - switch parts.count { - case 2 where number.contains("."): - fraction = parts[1] - case 2: - exponent = parts[1] - case 3: - fraction = parts[1] - exponent = parts[2] - default: - break - } - applyGrouping(grouping, to: &main) - if formatter.options.fractionGrouping { - applyGrouping(grouping, to: &fraction) - } - if formatter.options.exponentGrouping { - applyGrouping(grouping, to: &exponent) - } - var result = prefix + main - if !fraction.isEmpty { - result += "." + fraction - } - if !exponent.isEmpty { - result += exponentSeparator + exponent - } - formatter.replaceToken(at: i, with: .number(result, type)) - } - } - - /// Strip header comments from the file - public let fileHeader = FormatRule( - help: "Use specified source file header template for all files.", - runOnceOnly: true, - options: ["header", "dateformat", "timezone"], - sharedOptions: ["linebreaks"] - ) { formatter in - var headerTokens = [Token]() - var directives = [String]() - switch formatter.options.fileHeader { - case .ignore: - return - case var .replace(string): - let file = formatter.options.fileInfo - let options = ReplacementOptions( - dateFormat: formatter.options.dateFormat, - timeZone: formatter.options.timeZone - ) - - for (key, replacement) in formatter.options.fileInfo.replacements { - if let replacementStr = replacement.resolve(file, options) { - while let range = string.range(of: "{\(key.rawValue)}") { - string.replaceSubrange(range, with: replacementStr) - } - } - } - headerTokens = tokenize(string) - directives = headerTokens.compactMap { - guard case let .commentBody(body) = $0 else { - return nil - } - return body.commentDirective - } - } - - guard let headerRange = formatter.headerCommentTokenRange(includingDirectives: directives) else { - return - } - - if headerTokens.isEmpty { - formatter.removeTokens(in: headerRange) - return - } - - var lastHeaderTokenIndex = headerRange.endIndex - 1 - let endIndex = lastHeaderTokenIndex + headerTokens.count - if formatter.tokens.endIndex > endIndex, headerTokens == Array(formatter.tokens[ - lastHeaderTokenIndex + 1 ... endIndex - ]) { - lastHeaderTokenIndex += headerTokens.count - } - let headerLinebreaks = headerTokens.reduce(0) { result, token -> Int in - result + (token.isLinebreak ? 1 : 0) - } - if lastHeaderTokenIndex < formatter.tokens.count - 1 { - headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 1)) - if lastHeaderTokenIndex < formatter.tokens.count - 2, - !formatter.tokens[lastHeaderTokenIndex + 1 ... lastHeaderTokenIndex + 2].allSatisfy({ - $0.isLinebreak - }) - { - headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 2)) - } - } - if let index = formatter.index(of: .nonSpace, after: lastHeaderTokenIndex, if: { - $0.isLinebreak - }) { - lastHeaderTokenIndex = index - } - formatter.replaceTokens(in: headerRange.startIndex ..< lastHeaderTokenIndex + 1, with: headerTokens) - } - - /// Ensure file name reference in header matches actual file name - public let headerFileName = FormatRule( - help: "Ensure file name in header comment matches the actual file name.", - runOnceOnly: true, - orderAfter: ["fileHeader"] - ) { formatter in - guard let fileName = formatter.options.fileInfo.fileName, - let headerRange = formatter.headerCommentTokenRange(includingDirectives: ["*"]), - fileName.hasSuffix(".swift") - else { - return - } - - for i in headerRange { - guard case let .commentBody(body) = formatter.tokens[i] else { - continue - } - if body.hasSuffix(".swift"), body != fileName, !body.contains(where: { " /".contains($0) }) { - formatter.replaceToken(at: i, with: .commentBody(fileName)) - } - } - } - - /// Strip redundant `.init` from type instantiations - public let redundantInit = FormatRule( - help: "Remove explicit `init` if not required.", - orderAfter: ["propertyType"] - ) { formatter in - formatter.forEach(.identifier("init")) { initIndex, _ in - guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: initIndex, if: { - $0.isOperator(".") - }), let openParenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: initIndex, if: { - $0 == .startOfScope("(") - }), let closeParenIndex = formatter.index(of: .endOfScope(")"), after: openParenIndex), - formatter.last(.nonSpaceOrCommentOrLinebreak, before: closeParenIndex) != .delimiter(":"), - let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: dotIndex), - let prevToken = formatter.token(at: prevIndex), - formatter.isValidEndOfType(at: prevIndex), - // Find and parse the type that comes before the .init call - let startOfTypeIndex = Array(0 ..< dotIndex).reversed().last(where: { typeIndex in - guard let type = formatter.parseType(at: typeIndex) else { return false } - return (formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: type.range.upperBound) == dotIndex - // Since `Foo.init` is potentially a valid type, the `.init` may be parsed as part of the type name - || type.range.upperBound == initIndex) - // If this is actually a method call like `type(of: foo).init()`, the token before the "type" - // (which in this case looks like a tuple) will be an identifier. - && !(formatter.last(.nonSpaceOrComment, before: typeIndex)?.isIdentifier ?? false) - }), - let type = formatter.parseType(at: startOfTypeIndex), - // Filter out values that start with a lowercase letter. - // This covers edge cases like `super.init()`, where the `init` is not redundant. - let firstChar = type.name.components(separatedBy: ".").last?.first, - firstChar != "$", - String(firstChar).uppercased() == String(firstChar) - else { return } - - let lineStart = formatter.startOfLine(at: prevIndex, excludingIndent: true) - if [.startOfScope("#if"), .keyword("#elseif")].contains(formatter.tokens[lineStart]) { - return - } - var j = dotIndex - while let prevIndex = formatter.index( - of: prevToken, before: j - ) ?? formatter.index( - of: .startOfScope, before: j - ) { - j = prevIndex - if prevToken == formatter.tokens[prevIndex], - let prevPrevToken = formatter.last( - .nonSpaceOrCommentOrLinebreak, before: prevIndex - ), [.keyword("let"), .keyword("var")].contains(prevPrevToken) - { - return - } - } - formatter.removeTokens(in: initIndex + 1 ..< openParenIndex) - formatter.removeTokens(in: dotIndex ... initIndex) - } - } - - /// Deprecated - public let sortedSwitchCases = FormatRule( - help: "Sort switch cases alphabetically.", - deprecationMessage: "Use sortSwitchCases instead." - ) { formatter in - FormatRules.sortSwitchCases.apply(with: formatter) - } - - /// Sorts switch cases alphabetically - public let sortSwitchCases = FormatRule( - help: "Sort switch cases alphabetically.", - disabledByDefault: true - ) { formatter in - formatter.parseSwitchCaseRanges() - .reversed() // don't mess with indexes - .forEach { switchCaseRanges in - guard switchCaseRanges.count > 1, // nothing to sort - let firstCaseIndex = switchCaseRanges.first?.beforeDelimiterRange.lowerBound else { return } - - let indentCounts = switchCaseRanges.map { formatter.currentIndentForLine(at: $0.beforeDelimiterRange.lowerBound).count } - let maxIndentCount = indentCounts.max() ?? 0 - - func sortableValue(for token: Token) -> String? { - switch token { - case let .identifier(name): - return name - case let .stringBody(body): - return body - case let .number(value, .hex): - return Int(value.dropFirst(2), radix: 16) - .map(String.init) ?? value - case let .number(value, .octal): - return Int(value.dropFirst(2), radix: 8) - .map(String.init) ?? value - case let .number(value, .binary): - return Int(value.dropFirst(2), radix: 2) - .map(String.init) ?? value - case let .number(value, _): - return value - default: - return nil - } - } - - let sorted = switchCaseRanges.sorted { case1, case2 -> Bool in - let lhs = formatter.tokens[case1.beforeDelimiterRange] - .compactMap(sortableValue) - let rhs = formatter.tokens[case2.beforeDelimiterRange] - .compactMap(sortableValue) - for (lhs, rhs) in zip(lhs, rhs) { - switch lhs.localizedStandardCompare(rhs) { - case .orderedAscending: - return true - case .orderedDescending: - return false - case .orderedSame: - continue - } - } - return lhs.count < rhs.count - } - - let sortedTokens = sorted.map { formatter.tokens[$0.beforeDelimiterRange] } - let sortedComments = sorted.map { formatter.tokens[$0.afterDelimiterRange] } - - // ignore if there's a where keyword and it is not in the last place. - let firstWhereIndex = sortedTokens.firstIndex(where: { slice in slice.contains(.keyword("where")) }) - guard firstWhereIndex == nil || firstWhereIndex == sortedTokens.count - 1 else { return } - - for switchCase in switchCaseRanges.enumerated().reversed() { - let newTokens = Array(sortedTokens[switchCase.offset]) - var newComments = Array(sortedComments[switchCase.offset]) - let oldComments = formatter.tokens[switchCaseRanges[switchCase.offset].afterDelimiterRange] - - if newComments.last?.isLinebreak == oldComments.last?.isLinebreak { - formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].afterDelimiterRange, with: newComments) - } else if newComments.count > 1, - newComments.last?.isLinebreak == true, oldComments.last?.isLinebreak == false - { - // indent the new content - newComments.append(.space(String(repeating: " ", count: maxIndentCount))) - formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].afterDelimiterRange, with: newComments) - } - - formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].beforeDelimiterRange, with: newTokens) - } - } - } - - /// Deprecated - public let sortedImports = FormatRule( - help: "Sort import statements alphabetically.", - deprecationMessage: "Use sortImports instead.", - options: ["importgrouping"], - sharedOptions: ["linebreaks"] - ) { formatter in - _ = formatter.options.importGrouping - _ = formatter.options.linebreak - FormatRules.sortImports.apply(with: formatter) - } - - /// Sort import statements - public let sortImports = FormatRule( - help: "Sort import statements alphabetically.", - options: ["importgrouping"], - sharedOptions: ["linebreaks"] - ) { formatter in - func sortRanges(_ ranges: [Formatter.ImportRange]) -> [Formatter.ImportRange] { - if case .alpha = formatter.options.importGrouping { - return ranges.sorted(by: <) - } else if case .length = formatter.options.importGrouping { - return ranges.sorted { $0.module.count < $1.module.count } - } - // Group @testable imports at the top or bottom - // TODO: need more general solution for handling other import attributes - return ranges.sorted { - // If both have a @testable keyword, or neither has one, just sort alphabetically - guard $0.isTestable != $1.isTestable else { - return $0 < $1 - } - return formatter.options.importGrouping == .testableFirst ? $0.isTestable : $1.isTestable - } - } - - for var importRanges in formatter.parseImports().reversed() { - guard importRanges.count > 1 else { continue } - let range: Range = importRanges.first!.range.lowerBound ..< importRanges.last!.range.upperBound - let sortedRanges = sortRanges(importRanges) - var insertedLinebreak = false - var sortedTokens = sortedRanges.flatMap { inputRange -> [Token] in - var tokens = Array(formatter.tokens[inputRange.range]) - if tokens.first?.isLinebreak == false { - insertedLinebreak = true - tokens.insert(formatter.linebreakToken(for: tokens.startIndex), at: tokens.startIndex) - } - return tokens - } - if insertedLinebreak { - sortedTokens.removeFirst() - } - formatter.replaceTokens(in: range, with: sortedTokens) - } - } - - /// Remove duplicate import statements - public let duplicateImports = FormatRule( - help: "Remove duplicate import statements." - ) { formatter in - for var importRanges in formatter.parseImports().reversed() { - for i in importRanges.indices.reversed() { - let range = importRanges.remove(at: i) - guard let j = importRanges.firstIndex(where: { $0.module == range.module }) else { - continue - } - let range2 = importRanges[j] - if Set(range.attributes).isSubset(of: range2.attributes) { - formatter.removeTokens(in: range.range) - continue - } - if j >= i { - formatter.removeTokens(in: range2.range) - importRanges.remove(at: j) - } - importRanges.append(range) - } - } - } - - /// Strip unnecessary `weak` from @IBOutlet properties (except delegates and datasources) - public let strongOutlets = FormatRule( - help: "Remove `weak` modifier from `@IBOutlet` properties." - ) { formatter in - formatter.forEach(.keyword("@IBOutlet")) { i, _ in - guard let varIndex = formatter.index(of: .keyword("var"), after: i), - let weakIndex = (i ..< varIndex).first(where: { formatter.tokens[$0] == .identifier("weak") }), - case let .identifier(name)? = formatter.next(.identifier, after: varIndex) - else { - return - } - let lowercased = name.lowercased() - if lowercased.hasSuffix("delegate") || lowercased.hasSuffix("datasource") { - return - } - if formatter.tokens[weakIndex + 1].isSpace { - formatter.removeToken(at: weakIndex + 1) - } else if formatter.tokens[weakIndex - 1].isSpace { - formatter.removeToken(at: weakIndex - 1) - } - formatter.removeToken(at: weakIndex) - } - } - - /// Remove white-space between empty braces - public let emptyBraces = FormatRule( - help: "Remove whitespace inside empty braces.", - options: ["emptybraces"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - guard let closingIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0 == .endOfScope("}") - }) else { - return - } - if let token = formatter.next(.nonSpaceOrComment, after: closingIndex), - [.keyword("else"), .keyword("catch")].contains(token) - { - return - } - let range = i + 1 ..< closingIndex - switch formatter.options.emptyBracesSpacing { - case .noSpace: - formatter.removeTokens(in: range) - case .spaced: - formatter.replaceTokens(in: range, with: .space(" ")) - case .linebreak: - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: range.endIndex) - formatter.replaceTokens(in: range, with: formatter.linebreakToken(for: i + 1)) - } - } - } - - /// Replace the `&&` operator with `,` where applicable - public let andOperator = FormatRule( - help: "Prefer comma over `&&` in `if`, `guard` or `while` conditions." - ) { formatter in - formatter.forEachToken { i, token in - switch token { - case .keyword("if"), .keyword("guard"), - .keyword("while") where formatter.last(.keyword, before: i) != .keyword("repeat"): - break - default: - return - } - guard var endIndex = formatter.index(of: .startOfScope("{"), after: i) else { - return - } - if formatter.options.swiftVersion < "5.3", formatter.isInResultBuilder(at: i) { - return - } - var index = i + 1 - var chevronIndex: Int? - outer: while index < endIndex { - switch formatter.tokens[index] { - case .operator("&&", .infix): - let endOfGroup = formatter.index(of: .delimiter(","), after: index) ?? endIndex - var nextOpIndex = index - while let next = formatter.index(of: .operator, after: nextOpIndex) { - if formatter.tokens[next] == .operator("||", .infix) { - index = endOfGroup - continue outer - } - nextOpIndex = next - } - if let chevronIndex = chevronIndex, - formatter.index(of: .operator(">", .infix), in: index ..< endIndex) != nil - { - // Check if this would cause ambiguity for chevrons - var tokens = Array(formatter.tokens[i ... endIndex]) - tokens[index - i] = .delimiter(",") - tokens.append(.endOfScope("}")) - let reparsed = tokenize(sourceCode(for: tokens)) - if reparsed[chevronIndex - i] == .startOfScope("<") { - return - } - } - formatter.replaceToken(at: index, with: .delimiter(",")) - if formatter.tokens[index - 1] == .space(" ") { - formatter.removeToken(at: index - 1) - endIndex -= 1 - index -= 1 - } else if let prevIndex = formatter.index(of: .nonSpace, before: index), - formatter.tokens[prevIndex].isLinebreak, let nonLinbreak = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex) - { - formatter.removeToken(at: index) - formatter.insert(.delimiter(","), at: nonLinbreak + 1) - if formatter.tokens[index + 1] == .space(" ") { - formatter.removeToken(at: index + 1) - endIndex -= 1 - } - } - case .operator("<", .infix): - chevronIndex = index - case .operator("||", .infix), .operator("=", .infix), .keyword("try"): - index = formatter.index(of: .delimiter(","), after: index) ?? endIndex - case .startOfScope: - index = formatter.endOfScope(at: index) ?? endIndex - default: - break - } - index += 1 - } - } - } - - /// Replace count == 0 with isEmpty - public let isEmpty = FormatRule( - help: "Prefer `isEmpty` over comparing `count` against zero.", - disabledByDefault: true - ) { formatter in - formatter.forEach(.identifier("count")) { i, _ in - guard let dotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i, if: { - $0.isOperator(".") - }), let opIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0.isOperator - }), let endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: opIndex, if: { - $0 == .number("0", .integer) - }) else { - return - } - var isOptional = false - var index = dotIndex - var wasIdentifier = false - loop: while true { - guard let prev = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index) else { - break - } - switch formatter.tokens[prev] { - case .operator("!", _), .operator(".", _): - break // Ignored - case .operator("?", _): - if formatter.tokens[prev - 1].isSpace { - break loop - } - isOptional = true - case let .operator(op, .infix): - guard ["||", "&&", ":"].contains(op) else { - return - } - break loop - case .keyword, .delimiter, .startOfScope: - break loop - case .identifier: - if wasIdentifier { - break loop - } - wasIdentifier = true - index = prev - continue - case .endOfScope: - guard !wasIdentifier, let start = formatter.index(of: .startOfScope, before: prev) else { - break loop - } - wasIdentifier = false - index = start - continue - default: - break - } - wasIdentifier = false - index = prev - } - let isEmpty: Bool - switch formatter.tokens[opIndex] { - case .operator("==", .infix): isEmpty = true - case .operator("!=", .infix), .operator(">", .infix): isEmpty = false - default: return - } - if isEmpty { - if isOptional { - formatter.replaceTokens(in: i ... endIndex, with: [ - .identifier("isEmpty"), .space(" "), .operator("==", .infix), .space(" "), .identifier("true"), - ]) - } else { - formatter.replaceTokens(in: i ... endIndex, with: .identifier("isEmpty")) - } - } else { - if isOptional { - formatter.replaceTokens(in: i ... endIndex, with: [ - .identifier("isEmpty"), .space(" "), .operator("!=", .infix), .space(" "), .identifier("true"), - ]) - } else { - formatter.replaceTokens(in: i ... endIndex, with: .identifier("isEmpty")) - formatter.insert(.operator("!", .prefix), at: index) - } - } - } - } - - /// Remove redundant `let error` from `catch` statements - public let redundantLetError = FormatRule( - help: "Remove redundant `let error` from `catch` clause." - ) { formatter in - formatter.forEach(.keyword("catch")) { i, _ in - if let letIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0 == .keyword("let") - }), let errorIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: letIndex, if: { - $0 == .identifier("error") - }), let scopeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: errorIndex, if: { - $0 == .startOfScope("{") - }) { - formatter.removeTokens(in: letIndex ..< scopeIndex) - } - } - } - - /// Prefer `AnyObject` over `class` for class-based protocols - public let anyObjectProtocol = FormatRule( - help: "Prefer `AnyObject` over `class` in protocol definitions." - ) { formatter in - formatter.forEach(.keyword("protocol")) { i, _ in - guard formatter.options.swiftVersion >= "4.1", - let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0.isIdentifier - }), let colonIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: { - $0 == .delimiter(":") - }), let classIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex, if: { - $0 == .keyword("class") - }) - else { - return - } - formatter.replaceToken(at: classIndex, with: .identifier("AnyObject")) - } - } - - /// Remove redundant `break` keyword from switch cases - public let redundantBreak = FormatRule( - help: "Remove redundant `break` in switch case." - ) { formatter in - formatter.forEach(.keyword("break")) { i, _ in - guard formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .startOfScope(":"), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isEndOfScope == true, - var startIndex = formatter.index(of: .nonSpace, before: i), - let endIndex = formatter.index(of: .nonSpace, after: i), - formatter.currentScope(at: i) == .startOfScope(":") - else { - return - } - if !formatter.tokens[startIndex].isLinebreak || !formatter.tokens[endIndex].isLinebreak { - startIndex += 1 - } - formatter.removeTokens(in: startIndex ..< endIndex) - } - } - - /// Removed backticks from `self` when strongifying - public let strongifiedSelf = FormatRule( - help: "Remove backticks around `self` in Optional unwrap expressions." - ) { formatter in - formatter.forEach(.identifier("`self`")) { i, _ in - guard formatter.options.swiftVersion >= "4.2", - let equalIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { - $0 == .operator("=", .infix) - }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: equalIndex) == .identifier("self"), - formatter.isConditionalStatement(at: i) - else { - return - } - formatter.replaceToken(at: i, with: .identifier("self")) - } - } - - /// Remove redundant @objc annotation - public let redundantObjc = FormatRule( - help: "Remove redundant `@objc` annotations." - ) { formatter in - let objcAttributes = [ - "@IBOutlet", "@IBAction", "@IBSegueAction", - "@IBDesignable", "@IBInspectable", "@GKInspectable", - "@NSManaged", - ] - - formatter.forEach(.keyword("@objc")) { i, _ in - guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .startOfScope("(") else { - return - } - var index = i - loop: while var nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index) { - switch formatter.tokens[nextIndex] { - case .keyword("class"), .keyword("actor"), .keyword("enum"), - // Not actually allowed currently, but: future-proofing! - .keyword("protocol"), .keyword("struct"): - return - case .keyword("private"), .keyword("fileprivate"): - if formatter.next(.nonSpaceOrComment, after: nextIndex) == .startOfScope("(") { - break - } - // Can't safely remove objc from private members - return - case let token where token.isAttribute: - if let startIndex = formatter.index(of: .startOfScope("("), after: nextIndex), - let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) - { - nextIndex = endIndex - } - case let token: - guard token.isModifierKeyword else { - break loop - } - } - index = nextIndex - } - func removeAttribute() { - formatter.removeToken(at: i) - if formatter.token(at: i)?.isSpace == true { - formatter.removeToken(at: i) - } else if formatter.token(at: i - 1)?.isSpace == true { - formatter.removeToken(at: i - 1) - } - } - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i, if: { - $0.isAttribute && objcAttributes.contains($0.string) - }) != nil || formatter.next(.nonSpaceOrCommentOrLinebreak, after: i, if: { - $0.isAttribute && objcAttributes.contains($0.string) - }) != nil { - removeAttribute() - return - } - guard let scopeStart = formatter.index(of: .startOfScope("{"), before: i), - let keywordIndex = formatter.index(of: .keyword, before: scopeStart) - else { - return - } - switch formatter.tokens[keywordIndex] { - case .keyword("class"), .keyword("actor"): - if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objcMembers") { - removeAttribute() - } - case .keyword("extension"): - if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objc") { - removeAttribute() - } - default: - break - } - } - } - - /// Replace Array, Dictionary and Optional with [T], [T: U] and T? - public let typeSugar = FormatRule( - help: "Prefer shorthand syntax for Arrays, Dictionaries and Optionals.", - options: ["shortoptionals"] - ) { formatter in - formatter.forEach(.startOfScope("<")) { i, _ in - guard let typeIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), - case let .identifier(identifier) = formatter.tokens[typeIndex], - let endIndex = formatter.index(of: .endOfScope(">"), after: i), - let typeStart = formatter.index(of: .nonSpaceOrLinebreak, in: i + 1 ..< endIndex), - let typeEnd = formatter.lastIndex(of: .nonSpaceOrLinebreak, in: i + 1 ..< endIndex) - else { - return - } - let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex, if: { - $0.isOperator(".") - }) - if let dotIndex = dotIndex, formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: dotIndex, if: { - ![.identifier("self"), .identifier("Type")].contains($0) - }) != nil, identifier != "Optional" { - return - } - // Workaround for https://bugs.swift.org/browse/SR-12856 - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeIndex) != .delimiter(":") || - formatter.currentScope(at: i) == .startOfScope("[") - { - var startIndex = i - if formatter.tokens[typeIndex] == .identifier("Dictionary") { - startIndex = formatter.index(of: .delimiter(","), in: i + 1 ..< endIndex) ?? startIndex - } - if let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startIndex, if: { - $0 == .startOfScope("(") - }), let underscoreIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: parenIndex, if: { - $0 == .identifier("_") - }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: underscoreIndex)?.isIdentifier == true { - return - } - } - if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeIndex) { - switch prevToken { - case .keyword("struct"), .keyword("class"), .keyword("actor"), - .keyword("enum"), .keyword("protocol"), .keyword("typealias"): - return - default: - break - } - } - switch formatter.tokens[typeIndex] { - case .identifier("Array"): - formatter.replaceTokens(in: typeIndex ... endIndex, with: - [.startOfScope("[")] + formatter.tokens[typeStart ... typeEnd] + [.endOfScope("]")]) - case .identifier("Dictionary"): - guard let commaIndex = formatter.index(of: .delimiter(","), in: typeStart ..< typeEnd) else { - return - } - formatter.replaceToken(at: commaIndex, with: .delimiter(":")) - formatter.replaceTokens(in: typeIndex ... endIndex, with: - [.startOfScope("[")] + formatter.tokens[typeStart ... typeEnd] + [.endOfScope("]")]) - case .identifier("Optional"): - if formatter.options.shortOptionals == .exceptProperties, - let lastKeyword = formatter.lastSignificantKeyword(at: i), - ["var", "let"].contains(lastKeyword) - { - return - } - if formatter.lastSignificantKeyword(at: i) == "case" || - formatter.last(.endOfScope, before: i) == .endOfScope("case") - { - // https://bugs.swift.org/browse/SR-13838 - return - } - var typeTokens = formatter.tokens[typeStart ... typeEnd] - if [.operator("&", .infix), .operator("->", .infix), - .identifier("some"), .identifier("any")].contains(where: typeTokens.contains) - { - typeTokens.insert(.startOfScope("("), at: typeTokens.startIndex) - typeTokens.append(.endOfScope(")")) - } - typeTokens.append(.operator("?", .postfix)) - formatter.replaceTokens(in: typeIndex ... endIndex, with: typeTokens) - default: - return - } - // Drop leading Swift. namespace - if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: typeIndex, if: { - $0.isOperator(".") - }), let swiftTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: dotIndex, if: { - $0 == .identifier("Swift") - }) { - formatter.removeTokens(in: swiftTokenIndex ..< typeIndex) - } - } - } - - /// Remove redundant access control level modifiers in extensions - public let redundantExtensionACL = FormatRule( - help: "Remove redundant access control modifiers." - ) { formatter in - formatter.forEach(.keyword("extension")) { i, _ in - var acl = "" - guard formatter.modifiersForDeclaration(at: i, contains: { - acl = $1 - return aclModifiers.contains(acl) - }), let startIndex = formatter.index(of: .startOfScope("{"), after: i), - var endIndex = formatter.index(of: .endOfScope("}"), after: startIndex) else { - return - } - if acl == "private" { acl = "fileprivate" } - while let aclIndex = formatter.lastIndex(of: .keyword(acl), in: startIndex + 1 ..< endIndex) { - formatter.removeToken(at: aclIndex) - if formatter.token(at: aclIndex)?.isSpace == true { - formatter.removeToken(at: aclIndex) - } - endIndex = aclIndex - } - } - } - - /// Remove unused private and fileprivate declarations - public let unusedPrivateDeclaration = FormatRule( - help: "Remove unused private and fileprivate declarations.", - disabledByDefault: true, - options: ["preservedecls"] - ) { formatter in - guard !formatter.options.fragment else { return } - - // Only remove unused properties, functions, or typealiases. - // - This rule doesn't currently support removing unused types, - // and it's more difficult to track the usage of other declaration - // types like `init`, `subscript`, `operator`, etc. - let allowlist = ["let", "var", "func", "typealias"] - let disallowedModifiers = ["override", "@objc", "@IBAction", "@IBSegueAction", "@IBOutlet", "@IBDesignable", "@IBInspectable", "@NSManaged", "@GKInspectable"] - - // Collect all of the `private` or `fileprivate` declarations in the file - var privateDeclarations: [Declaration] = [] - formatter.forEachRecursiveDeclaration { declaration in - let declarationModifiers = Set(declaration.modifiers) - let hasDisallowedModifiers = disallowedModifiers.contains(where: { declarationModifiers.contains($0) }) - - guard allowlist.contains(declaration.keyword), - let name = declaration.name, - !formatter.options.preservedPrivateDeclarations.contains(name), - !hasDisallowedModifiers - else { return } - - switch formatter.visibility(of: declaration) { - case .fileprivate, .private: - privateDeclarations.append(declaration) - case .none, .open, .public, .package, .internal: - break - } - } - - // Count the usage of each identifier in the file - var usage: [String: Int] = [:] - formatter.forEach(.identifier) { _, token in - usage[token.string, default: 0] += 1 - } - - // Remove any private or fileprivate declaration whose name only - // appears a single time in the source file - for declaration in privateDeclarations.reversed() { - // Strip backticks from name for a normalized base name for cases like `default` - guard let name = declaration.name?.trimmingCharacters(in: CharacterSet(charactersIn: "`")) else { continue } - // Check for regular usage, common property wrapper prefixes, and protected names - let variants = [name, "_\(name)", "$\(name)", "`\(name)`"] - let count = variants.compactMap { usage[$0] }.reduce(0, +) - if count <= 1 { - formatter.removeTokens(in: declaration.originalRange) - } - } - } - - /// Replace `fileprivate` with `private` where possible - public let redundantFileprivate = FormatRule( - help: "Prefer `private` over `fileprivate` where equivalent." - ) { formatter in - guard !formatter.options.fragment else { return } - - var hasUnreplacedFileprivates = false - formatter.forEach(.keyword("fileprivate")) { i, _ in - // check if definition is at file-scope - if formatter.index(of: .startOfScope, before: i) == nil { - formatter.replaceToken(at: i, with: .keyword("private")) - } else { - hasUnreplacedFileprivates = true - } - } - guard hasUnreplacedFileprivates else { - return - } - let importRanges = formatter.parseImports() - var fileJustContainsOneType: Bool? - func ifCodeInRange(_ range: CountableRange) -> Bool { - var index = range.lowerBound - while index < range.upperBound, let nextIndex = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: index ..< range.upperBound) - { - guard let importRange = importRanges.first(where: { - $0.contains(where: { $0.range.contains(nextIndex) }) - }) else { - return true - } - index = importRange.last!.range.upperBound + 1 - } - return false - } - func isTypeInitialized(_ name: String, in range: CountableRange) -> Bool { - for i in range { - switch formatter.tokens[i] { - case .identifier(name): - guard let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) else { - break - } - switch formatter.tokens[nextIndex] { - case .operator(".", .infix): - if formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("init") { - return true - } - case .startOfScope("("): - return true - case .startOfScope("{"): - if formatter.isStartOfClosure(at: nextIndex) { - return true - } - default: - break - } - case .identifier("init"): - // TODO: this will return true if *any* type is initialized using type inference. - // Is there a way to narrow this down a bit? - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator(".", .prefix) { - return true - } - default: - break - } - } - return false - } - // TODO: improve this logic to handle shadowing - func areMembers(_ names: [String], of type: String, - referencedIn range: CountableRange) -> Bool - { - var i = range.lowerBound - while i < range.upperBound { - switch formatter.tokens[i] { - case .keyword("struct"), .keyword("extension"), .keyword("enum"), .keyword("actor"), - .keyword("class") where formatter.declarationType(at: i) == "class": - guard let startIndex = formatter.index(of: .startOfScope("{"), after: i), - let endIndex = formatter.endOfScope(at: startIndex) - else { - break - } - guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - formatter.tokens[nameIndex] != .identifier(type) - else { - i = endIndex - break - } - for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] - where names.contains(name) - { - return true - } - i = endIndex - case let .identifier(name) where names.contains(name): - if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - $0 == .operator(".", .infix) - }), formatter.last(.nonSpaceOrCommentOrLinebreak, before: dotIndex) - != .identifier("self") - { - return true - } - default: - break - } - i += 1 - } - return false - } - func isInitOverridden(for type: String, in range: CountableRange) -> Bool { - for i in range { - if case .keyword("init") = formatter.tokens[i], - let scopeStart = formatter.index(of: .startOfScope("{"), after: i), - formatter.index(of: .identifier("super"), after: scopeStart) != nil, - let scopeIndex = formatter.index(of: .startOfScope("{"), before: i), - let colonIndex = formatter.index(of: .delimiter(":"), before: scopeIndex), - formatter.next( - .nonSpaceOrCommentOrLinebreak, - in: colonIndex + 1 ..< scopeIndex - ) == .identifier(type) - { - return true - } - } - return false - } - formatter.forEach(.keyword("fileprivate")) { i, _ in - // Check if definition is a member of a file-scope type - guard formatter.options.swiftVersion >= "4", - let scopeIndex = formatter.index(of: .startOfScope, before: i, if: { - $0 == .startOfScope("{") - }), let typeIndex = formatter.index(of: .keyword, before: scopeIndex, if: { - ["class", "actor", "struct", "enum", "extension"].contains($0.string) - }), let nameIndex = formatter.index(of: .identifier, in: typeIndex ..< scopeIndex), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: nameIndex)?.isOperator(".") == false, - case let .identifier(typeName) = formatter.tokens[nameIndex], - let endIndex = formatter.index(of: .endOfScope, after: scopeIndex), - formatter.currentScope(at: typeIndex) == nil - else { - return - } - // Get member type - guard let keywordIndex = formatter.index(of: .keyword, in: i + 1 ..< endIndex), - let memberType = formatter.declarationType(at: keywordIndex), - // TODO: check if member types are exposed in the interface, otherwise convert them too - ["let", "var", "func", "init"].contains(memberType) - else { - return - } - // Check that type doesn't (potentially) conform to a protocol - // TODO: use a whitelist of known protocols to make this check less blunt - guard !formatter.tokens[typeIndex ..< scopeIndex].contains(.delimiter(":")) else { - return - } - // Check for code outside of main type definition - let startIndex = formatter.startOfModifiers(at: typeIndex, includingAttributes: true) - if fileJustContainsOneType == nil { - fileJustContainsOneType = !ifCodeInRange(0 ..< startIndex) && - !ifCodeInRange(endIndex + 1 ..< formatter.tokens.count) - } - if fileJustContainsOneType == true { - formatter.replaceToken(at: i, with: .keyword("private")) - return - } - // Check if type name is initialized outside type, and if so don't - // change any fileprivate members in case we break memberwise initializer - // TODO: check if struct contains an overridden init; if so we can skip this check - if formatter.tokens[typeIndex] == .keyword("struct"), - isTypeInitialized(typeName, in: 0 ..< startIndex) || - isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count) - { - return - } - // Check if member is referenced outside type - if memberType == "init" { - // Make initializer private if it's not called anywhere - if !isTypeInitialized(typeName, in: 0 ..< startIndex), - !isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count), - !isInitOverridden(for: typeName, in: 0 ..< startIndex), - !isInitOverridden(for: typeName, in: endIndex + 1 ..< formatter.tokens.count) - { - formatter.replaceToken(at: i, with: .keyword("private")) - } - } else if let _names = formatter.namesInDeclaration(at: keywordIndex), - case let names = _names + _names.map({ "$\($0)" }), - !areMembers(names, of: typeName, referencedIn: 0 ..< startIndex), - !areMembers(names, of: typeName, referencedIn: endIndex + 1 ..< formatter.tokens.count) - { - formatter.replaceToken(at: i, with: .keyword("private")) - } - } - } - - /// Reorders "yoda conditions" where constant is placed on lhs of a comparison - public let yodaConditions = FormatRule( - help: "Prefer constant values to be on the right-hand-side of expressions.", - options: ["yodaswap"] - ) { formatter in - func valuesInRangeAreConstant(_ range: CountableRange) -> Bool { - var index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: range) - while var i = index { - switch formatter.tokens[i] { - case .startOfScope where isConstant(at: i): - guard let endIndex = formatter.index(of: .endOfScope, after: i) else { - return false - } - i = endIndex - fallthrough - case _ where isConstant(at: i), .delimiter(","), .delimiter(":"): - index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< range.upperBound) - case .identifier: - guard let nextIndex = - formatter.index(of: .nonSpaceOrComment, in: i + 1 ..< range.upperBound), - formatter.tokens[nextIndex] == .delimiter(":") - else { - return false - } - // Identifier is a label - index = nextIndex - default: - return false - } - } - return true - } - func isConstant(at index: Int) -> Bool { - var index = index - while case .operator(_, .postfix) = formatter.tokens[index] { - index -= 1 - } - guard let token = formatter.token(at: index) else { - return false - } - switch token { - case .number, .identifier("true"), .identifier("false"), .identifier("nil"): - return true - case .endOfScope("]"), .endOfScope(")"): - guard let startIndex = formatter.index(of: .startOfScope, before: index), - !formatter.isSubscriptOrFunctionCall(at: startIndex) - else { - return false - } - return valuesInRangeAreConstant(startIndex + 1 ..< index) - case .startOfScope("["), .startOfScope("("): - guard !formatter.isSubscriptOrFunctionCall(at: index), - let endIndex = formatter.index(of: .endOfScope, after: index) - else { - return false - } - return valuesInRangeAreConstant(index + 1 ..< endIndex) - case .startOfScope, .endOfScope: - // TODO: what if string contains interpolation? - return token.isStringDelimiter - case _ where formatter.options.yodaSwap == .literalsOnly: - // Don't treat .members as constant - return false - case .operator(".", .prefix) where formatter.token(at: index + 1)?.isIdentifier == true, - .identifier where formatter.token(at: index - 1) == .operator(".", .prefix) && - formatter.token(at: index - 2) != .operator("\\", .prefix): - return true - default: - return false - } - } - func isOperator(at index: Int?) -> Bool { - guard let index = index else { - return false - } - switch formatter.tokens[index] { - // Discount operators with higher precedence than == - case .operator("=", .infix), - .operator("&&", .infix), .operator("||", .infix), - .operator("?", .infix), .operator(":", .infix): - return false - case .operator(_, .infix), .keyword("as"), .keyword("is"): - return true - default: - return false - } - } - func startOfValue(at index: Int) -> Int? { - var index = index - while case .operator(_, .postfix)? = formatter.token(at: index) { - index -= 1 - } - if case .endOfScope? = formatter.token(at: index) { - guard let i = formatter.index(of: .startOfScope, before: index) else { - return nil - } - index = i - } - while case .operator(_, .prefix)? = formatter.token(at: index - 1) { - index -= 1 - } - return index - } - - formatter.forEachToken { i, token in - guard case let .operator(op, .infix) = token, - let opIndex = ["==", "!=", "<", "<=", ">", ">="].firstIndex(of: op), - let prevIndex = formatter.index(of: .nonSpace, before: i), - isConstant(at: prevIndex), let startIndex = startOfValue(at: prevIndex), - !isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex)), - let nextIndex = formatter.index(of: .nonSpace, after: i), !isConstant(at: nextIndex) || - isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nextIndex)), - let endIndex = formatter.endOfExpression(at: nextIndex, upTo: [ - .operator("&&", .infix), .operator("||", .infix), - .operator("?", .infix), .operator(":", .infix), - ]) - else { - return - } - let inverseOp = ["==", "!=", ">", ">=", "<", "<="][opIndex] - let expression = Array(formatter.tokens[nextIndex ... endIndex]) - let constant = Array(formatter.tokens[startIndex ... prevIndex]) - formatter.replaceTokens(in: nextIndex ... endIndex, with: constant) - formatter.replaceToken(at: i, with: .operator(inverseOp, .infix)) - formatter.replaceTokens(in: startIndex ... prevIndex, with: expression) - } - } - - public let leadingDelimiters = FormatRule( - help: "Move leading delimiters to the end of the previous line.", - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.delimiter) { i, _ in - guard let endOfLine = formatter.index(of: .nonSpace, before: i, if: { - $0.isLinebreak - }) else { - return - } - let nextIndex = formatter.index(of: .nonSpace, after: i) ?? (i + 1) - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex) - formatter.insertLinebreak(at: nextIndex) - formatter.removeTokens(in: i + 1 ..< nextIndex) - guard case .commentBody? = formatter.last(.nonSpace, before: endOfLine) else { - formatter.removeTokens(in: endOfLine ..< i) - return - } - let startIndex = formatter.index(of: .nonSpaceOrComment, before: endOfLine) ?? -1 - formatter.removeTokens(in: endOfLine ..< i) - let comment = Array(formatter.tokens[startIndex + 1 ..< endOfLine]) - formatter.insert(comment, at: endOfLine + 1) - formatter.removeTokens(in: startIndex + 1 ..< endOfLine) - } - } - - public let wrapAttributes = FormatRule( - help: "Wrap @attributes onto a separate line, or keep them on the same line.", - options: ["funcattributes", "typeattributes", "varattributes", "storedvarattrs", "computedvarattrs", "complexattrs", "noncomplexattrs"], - sharedOptions: ["linebreaks", "maxwidth"] - ) { formatter in - formatter.forEach(.attribute) { i, _ in - // Ignore sequential attributes - guard let endIndex = formatter.endOfAttribute(at: i), - var keywordIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - after: endIndex, if: { $0.isKeyword || $0.isModifierKeyword } - ) - else { - return - } - - // Skip modifiers - while formatter.isModifier(at: keywordIndex), - let nextIndex = formatter.index(of: .keyword, after: keywordIndex) - { - keywordIndex = nextIndex - } - - // Check which `AttributeMode` option to use - var attributeMode: AttributeMode - switch formatter.tokens[keywordIndex].string { - case "func", "init", "subscript": - attributeMode = formatter.options.funcAttributes - case "class", "actor", "struct", "enum", "protocol", "extension": - attributeMode = formatter.options.typeAttributes - case "var", "let": - let storedOrComputedAttributeMode: AttributeMode - if formatter.isStoredProperty(atIntroducerIndex: keywordIndex) { - storedOrComputedAttributeMode = formatter.options.storedVarAttributes - } else { - storedOrComputedAttributeMode = formatter.options.computedVarAttributes - } - - // If the relevant `storedvarattrs` or `computedvarattrs` option hasn't been configured, - // fall back to the previous (now deprecated) `varattributes` option. - if storedOrComputedAttributeMode == .preserve { - attributeMode = formatter.options.varAttributes - } else { - attributeMode = storedOrComputedAttributeMode - } - default: - return - } - - // If the complexAttributes option is configured, it takes precedence over other options - // if this is a complex attributes with arguments. - let attributeName = formatter.tokens[i].string - let isComplexAttribute = formatter.isComplexAttribute(at: i) - && !formatter.options.complexAttributesExceptions.contains(attributeName) - - if isComplexAttribute, formatter.options.complexAttributes != .preserve { - attributeMode = formatter.options.complexAttributes - } - - // Apply the `AttributeMode` - switch attributeMode { - case .preserve: - return - case .prevLine: - // Make sure there's a newline immediately following the attribute - if let nextIndex = formatter.index(of: .nonSpaceOrComment, after: endIndex), - formatter.token(at: nextIndex)?.isLinebreak != true - { - formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex) - formatter.insertLinebreak(at: nextIndex) - // Remove any trailing whitespace left on the line with the attributes - if let prevToken = formatter.token(at: nextIndex - 1), prevToken.isSpace { - formatter.removeToken(at: nextIndex - 1) - } - } - case .sameLine: - // Make sure there isn't a newline immediately following the attribute - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), - formatter.tokens[(endIndex + 1) ..< nextIndex].contains(where: { $0.isLinebreak }) - { - // If unwrapping the attribute causes the line to exceed the max width, - // leave it as-is. The existing formatting is likely better than how - // this would be re-unwrapped by the wrap rule. - let startOfLine = formatter.startOfLine(at: i) - let endOfLine = formatter.endOfLine(at: i) - let startOfNextLine = formatter.startOfLine(at: nextIndex, excludingIndent: true) - let endOfNextLine = formatter.endOfLine(at: nextIndex) - let combinedLine = formatter.tokens[startOfLine ... endOfLine].map { $0.string }.joined() - + formatter.tokens[startOfNextLine ..< endOfNextLine].map { $0.string }.joined() - - if formatter.options.maxWidth > 0, combinedLine.count > formatter.options.maxWidth { - return - } - - // Replace the newline with a space so the attribute doesn't - // merge with the next token. - formatter.replaceTokens(in: (endIndex + 1) ..< nextIndex, with: .space(" ")) - } - } - } - } - - public let preferKeyPath = FormatRule( - help: "Convert trivial `map { $0.foo }` closures to keyPath-based syntax." - ) { formatter in - formatter.forEach(.startOfScope("{")) { i, _ in - guard formatter.options.swiftVersion >= "5.2", - var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) - else { - return - } - var prevToken = formatter.tokens[prevIndex] - var label: String? - if prevToken == .delimiter(":"), - let labelIndex = formatter.index(of: .nonSpace, before: prevIndex), - case let .identifier(name) = formatter.tokens[labelIndex], - let prevIndex2 = formatter.index(of: .nonSpaceOrLinebreak, before: labelIndex) - { - label = name - prevToken = formatter.tokens[prevIndex2] - prevIndex = prevIndex2 - } - let parenthesized = prevToken == .startOfScope("(") - if parenthesized { - prevToken = formatter.last(.nonSpaceOrLinebreak, before: prevIndex) ?? prevToken - } - guard case let .identifier(name) = prevToken, - ["map", "flatMap", "compactMap", "allSatisfy", "filter", "contains"].contains(name), - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { - $0 == .identifier("$0") - }), - let endIndex = formatter.endOfScope(at: i), - let lastIndex = formatter.index(of: .nonSpaceOrLinebreak, before: endIndex) - else { - return - } - if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), - formatter.isLabel(at: nextIndex) - { - return - } - if name == "contains" { - if label != "where" { - return - } - } else if label != nil { - return - } - var replacementTokens: [Token] - if nextIndex == lastIndex { - // TODO: add this when https://bugs.swift.org/browse/SR-12897 is fixed - // replacementTokens = tokenize("\\.self") - return - } else { - let tokens = formatter.tokens[nextIndex + 1 ... lastIndex] - guard tokens.allSatisfy({ $0.isSpace || $0.isIdentifier || $0.isOperator(".") }) else { - return - } - replacementTokens = [.operator("\\", .prefix)] + tokens - } - if let label = label { - replacementTokens = [.identifier(label), .delimiter(":"), .space(" ")] + replacementTokens - } - if !parenthesized { - replacementTokens = [.startOfScope("(")] + replacementTokens + [.endOfScope(")")] - } - formatter.replaceTokens(in: prevIndex + 1 ... endIndex, with: replacementTokens) - } - } - - public let organizeDeclarations = FormatRule( - help: "Organize declarations within class, struct, enum, actor, and extension bodies.", - runOnceOnly: true, - disabledByDefault: true, - orderAfter: ["extensionAccessControl", "redundantFileprivate"], - options: [ - "categorymark", "markcategories", "beforemarks", - "lifecycle", "organizetypes", "structthreshold", "classthreshold", - "enumthreshold", "extensionlength", "organizationmode", - "visibilityorder", "typeorder", "visibilitymarks", "typemarks", - ], - sharedOptions: ["sortedpatterns", "lineaftermarks"] - ) { formatter in - guard !formatter.options.fragment else { return } - - formatter.mapRecursiveDeclarations { declaration in - switch declaration { - // Organize the body of type declarations - case let .type(kind, open, body, close, originalRange): - let organizedType = formatter.organizeDeclaration((kind, open, body, close)) - return .type( - kind: organizedType.kind, - open: organizedType.open, - body: organizedType.body, - close: organizedType.close, - originalRange: originalRange - ) - - case .conditionalCompilation, .declaration: - return declaration - } - } - } - - public let extensionAccessControl = FormatRule( - help: "Configure the placement of an extension's access control keyword.", - options: ["extensionacl"] - ) { formatter in - guard !formatter.options.fragment else { return } - - let declarations = formatter.parseDeclarations() - let updatedDeclarations = formatter.mapRecursiveDeclarations(declarations) { declaration, _ in - guard case let .type("extension", open, body, close, _) = declaration else { - return declaration - } - - let visibilityKeyword = formatter.visibility(of: declaration) - // `private` visibility at top level of file is equivalent to `fileprivate` - let extensionVisibility = (visibilityKeyword == .private) ? .fileprivate : visibilityKeyword - - switch formatter.options.extensionACLPlacement { - // If all declarations in the extension have the same visibility, - // remove the keyword from the individual declarations and - // place it on the extension itself. - case .onExtension: - if extensionVisibility == nil, - let delimiterIndex = declaration.openTokens.firstIndex(of: .delimiter(":")), - declaration.openTokens.firstIndex(of: .keyword("where")).map({ $0 > delimiterIndex }) ?? true - { - // Extension adds protocol conformance so can't have visibility modifier - return declaration - } - - let visibilityOfBodyDeclarations = formatter - .mapDeclarations(body) { - formatter.visibility(of: $0) ?? extensionVisibility ?? .internal - } - .compactMap { $0 } - - let counts = Set(visibilityOfBodyDeclarations).sorted().map { visibility in - (visibility, count: visibilityOfBodyDeclarations.filter { $0 == visibility }.count) - } - - guard let memberVisibility = counts.max(by: { $0.count < $1.count })?.0, - memberVisibility <= extensionVisibility ?? .public, - // Check that most common level is also most visible - memberVisibility == visibilityOfBodyDeclarations.max(), - // `private` can't be hoisted without changing code behavior - // (private applied at extension level is equivalent to `fileprivate`) - memberVisibility > .private - else { return declaration } - - if memberVisibility > extensionVisibility ?? .internal { - // Check type being extended does not have lower visibility - for d in declarations where d.name == declaration.name { - if case let .type(kind, _, _, _, _) = d { - if kind != "extension", formatter.visibility(of: d) ?? .internal < memberVisibility { - // Cannot make extension with greater visibility than type being extended - return declaration - } - break - } - } - } - - let extensionWithUpdatedVisibility: Declaration - if memberVisibility == extensionVisibility || - (memberVisibility == .internal && visibilityKeyword == nil) - { - extensionWithUpdatedVisibility = declaration - } else { - extensionWithUpdatedVisibility = formatter.add(memberVisibility, to: declaration) - } - - return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in - let visibility = formatter.visibility(of: bodyDeclaration) - if memberVisibility > visibility ?? extensionVisibility ?? .internal { - if visibility == nil { - return formatter.add(.internal, to: bodyDeclaration) - } - return bodyDeclaration - } - return formatter.remove(memberVisibility, from: bodyDeclaration) - } - - // Move the extension's visibility keyword to each individual declaration - case .onDeclarations: - // If the extension visibility is unspecified then there isn't any work to do - guard let extensionVisibility = extensionVisibility else { - return declaration - } - - // Remove the visibility keyword from the extension declaration itself - let extensionWithUpdatedVisibility = formatter.remove(visibilityKeyword!, from: declaration) - - // And apply the extension's visibility to each of its child declarations - // that don't have an explicit visibility keyword - return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in - if formatter.visibility(of: bodyDeclaration) == nil { - // If there was no explicit visibility keyword, then this declaration - // was using the visibility of the extension itself. - return formatter.add(extensionVisibility, to: bodyDeclaration) - } else { - // Keep the existing visibility - return bodyDeclaration - } - } - } - } - - let updatedTokens = updatedDeclarations.flatMap { $0.tokens } - formatter.replaceTokens(in: formatter.tokens.indices, with: updatedTokens) - } - - public let markTypes = FormatRule( - help: "Add a MARK comment before top-level types and extensions.", - runOnceOnly: true, - disabledByDefault: true, - options: ["marktypes", "typemark", "markextensions", "extensionmark", "groupedextension"], - sharedOptions: ["lineaftermarks"] - ) { formatter in - var declarations = formatter.parseDeclarations() - - // Do nothing if there is only one top-level declaration in the file (excluding imports) - let declarationsWithoutImports = declarations.filter { $0.keyword != "import" } - guard declarationsWithoutImports.count > 1 else { - return - } - - for (index, declaration) in declarations.enumerated() { - guard case let .type(kind, open, body, close, _) = declaration else { continue } - - guard var typeName = declaration.name else { - continue - } - - let markMode: MarkMode - let commentTemplate: String - let isGroupedExtension: Bool - switch declaration.keyword { - case "extension": - // TODO: this should be stored in declaration at parse time - markMode = formatter.options.markExtensions - - // We provide separate mark comment customization points for - // extensions that are "grouped" with (e.g. following) their extending type, - // vs extensions that are completely separate. - // - // struct Foo { } - // extension Foo { } // This extension is "grouped" with its extending type - // extension String { } // This extension is standalone (not grouped with any type) - // - let isGroupedWithExtendingType: Bool - if let indexOfExtendingType = declarations[.. [Token] in - var openingFormatter = Formatter(openingTokens) - - guard let keywordIndex = openingFormatter.index(after: -1, where: { - $0.string == declaration.keyword - }) else { return openingTokens } - - // If this declaration is extension, check if it has any conformances - var conformanceNames: String? - if declaration.keyword == "extension", - var conformanceSearchIndex = openingFormatter.index(of: .delimiter(":"), after: keywordIndex) - { - var conformances = [String]() - - let endOfConformances = openingFormatter.index(of: .keyword("where"), after: keywordIndex) - ?? openingFormatter.index(of: .startOfScope("{"), after: keywordIndex) - ?? openingFormatter.tokens.count - - while let token = openingFormatter.token(at: conformanceSearchIndex), - conformanceSearchIndex < endOfConformances - { - if token.isIdentifier { - let (fullyQualifiedName, next) = openingFormatter.fullyQualifiedName(startingAt: conformanceSearchIndex) - conformances.append(fullyQualifiedName) - conformanceSearchIndex = next - } - - conformanceSearchIndex += 1 - } - - if !conformances.isEmpty { - conformanceNames = conformances.joined(separator: ", ") - } - } - - // Build the types expected mark comment by replacing `%t`s with the type name - // and `%c`s with the list of conformances added in the extension (if applicable) - var markForType: String? - - if !commentTemplate.contains("%c") { - markForType = commentTemplate.replacingOccurrences(of: "%t", with: typeName) - } else if commentTemplate.contains("%c"), let conformanceNames = conformanceNames { - markForType = commentTemplate - .replacingOccurrences(of: "%t", with: typeName) - .replacingOccurrences(of: "%c", with: conformanceNames) - } - - // If this is an extension without any conformances, but contains exactly - // one body declaration (a type), we can mark the extension with the nested type's name - // (e.g. `// MARK: Foo.Bar`). - if declaration.keyword == "extension", - conformanceNames == nil - { - // Find all of the nested extensions, so we can form the fully qualified - // name of the inner-most type (e.g. `Foo.Bar.Baaz.Quux`). - var extensions = [declaration] - - while let innerExtension = extensions.last, - let extensionBody = innerExtension.body, - extensionBody.count == 1, - extensionBody[0].keyword == "extension" - { - extensions.append(extensionBody[0]) - } - - let innermostExtension = extensions.last! - let extensionNames = extensions.compactMap { $0.name }.joined(separator: ".") - - if let extensionBody = innermostExtension.body, - extensionBody.count == 1, - let nestedType = extensionBody.first, - nestedType.definesType, - let nestedTypeName = nestedType.name - { - let fullyQualifiedName = "\(extensionNames).\(nestedTypeName)" - - if isGroupedExtension { - markForType = "// \(formatter.options.groupedExtensionMarkComment)" - .replacingOccurrences(of: "%c", with: fullyQualifiedName) - } else { - markForType = "// \(formatter.options.typeMarkComment)" - .replacingOccurrences(of: "%t", with: fullyQualifiedName) - } - } - } - - guard let expectedComment = markForType else { - return openingFormatter.tokens - } - - // Remove any lines that have the same prefix as the comment template - // - We can't really do exact matches here like we do for `organizeDeclaration` - // category separators, because there's a much wider variety of options - // that a user could use for the type name (orphaned renames, etc.) - var commentPrefixes = Set(["// MARK: ", "// MARK: - "]) - - if let typeNameSymbolIndex = commentTemplate.firstIndex(of: "%") { - commentPrefixes.insert(String(commentTemplate.prefix(upTo: typeNameSymbolIndex))) - } - - openingFormatter.forEach(.startOfScope("//")) { index, _ in - let startOfLine = openingFormatter.startOfLine(at: index) - let endOfLine = openingFormatter.endOfLine(at: index) - - let commentLine = sourceCode(for: Array(openingFormatter.tokens[index ... endOfLine])) - - for commentPrefix in commentPrefixes { - if commentLine.lowercased().hasPrefix(commentPrefix.lowercased()) { - // If we found a line that matched the comment prefix, - // remove it and any linebreak immediately after it. - if openingFormatter.token(at: endOfLine + 1)?.isLinebreak == true { - openingFormatter.removeToken(at: endOfLine + 1) - } - - openingFormatter.removeTokens(in: startOfLine ... endOfLine) - break - } - } - } - - // When inserting a mark before the first declaration, - // we should make sure we place it _after_ the file header. - var markInsertIndex = 0 - if index == 0 { - // Search for the end of the file header, which ends when we hit a - // blank line or any non-space/comment/lintbreak - var endOfFileHeader = 0 - - while openingFormatter.token(at: endOfFileHeader)?.isSpaceOrCommentOrLinebreak == true { - endOfFileHeader += 1 - - if openingFormatter.token(at: endOfFileHeader)?.isLinebreak == true, - openingFormatter.next(.nonSpace, after: endOfFileHeader)?.isLinebreak == true - { - markInsertIndex = endOfFileHeader + 2 - break - } - } - } - - // Insert the expected comment at the start of the declaration - let endMarkDeclaration = formatter.options.lineAfterMarks ? "\n\n" : "\n" - openingFormatter.insert(tokenize("\(expectedComment)\(endMarkDeclaration)"), at: markInsertIndex) - - // If the previous declaration doesn't end in a blank line, - // add an additional linebreak to balance the mark. - if index != 0 { - declarations[index - 1] = formatter.mapClosingTokens(in: declarations[index - 1]) { - formatter.endingWithBlankLine($0) - } - } - - return openingFormatter.tokens - } - } - - let updatedTokens = declarations.flatMap { $0.tokens } - formatter.replaceTokens(in: 0 ..< formatter.tokens.count, with: updatedTokens) - } - - public let sortDeclarations = FormatRule( - help: """ - Sorts the body of declarations with // swiftformat:sort - and declarations between // swiftformat:sort:begin and - // swiftformat:sort:end comments. - """, - options: ["sortedpatterns"], - sharedOptions: ["organizetypes"] - ) { formatter in - formatter.forEachToken( - where: { - $0.isCommentBody && $0.string.contains("swiftformat:sort") - || $0.isDeclarationTypeKeyword(including: Array(Token.swiftTypeKeywords)) - } - ) { index, token in - - let rangeToSort: ClosedRange - let numberOfLeadingLinebreaks: Int - - // For `:sort:begin`, directives, we sort the declarations - // between the `:begin` and and `:end` comments - let shouldBePartiallySorted = token.string.contains("swiftformat:sort:begin") - - let identifier = formatter.next(.identifier, after: index) - let shouldBeSortedByNamePattern = formatter.options.alphabeticallySortedDeclarationPatterns.contains { - identifier?.string.contains($0) ?? false - } - let shouldBeSortedByMarkComment = token.isCommentBody && !token.string.contains(":sort:") - // For `:sort` directives and types with matching name pattern, we sort the declarations - // between the open and close brace of the following type - let shouldBeFullySorted = shouldBeSortedByNamePattern || shouldBeSortedByMarkComment - - if shouldBePartiallySorted { - guard let endCommentIndex = formatter.tokens[index...].firstIndex(where: { - $0.isComment && $0.string.contains("swiftformat:sort:end") - }), - let sortRangeStart = formatter.index(of: .nonSpaceOrComment, after: index), - let firstRangeToken = formatter.index(of: .nonLinebreak, after: sortRangeStart), - let lastRangeToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: endCommentIndex - 2) - else { return } - - rangeToSort = sortRangeStart ... lastRangeToken - numberOfLeadingLinebreaks = firstRangeToken - sortRangeStart - } else if shouldBeFullySorted { - guard let typeOpenBrace = formatter.index(of: .startOfScope("{"), after: index), - let typeCloseBrace = formatter.endOfScope(at: typeOpenBrace), - let firstTypeBodyToken = formatter.index(of: .nonLinebreak, after: typeOpenBrace), - let lastTypeBodyToken = formatter.index(of: .nonLinebreak, before: typeCloseBrace), - let declarationKeyword = formatter.lastSignificantKeyword(at: typeOpenBrace), - lastTypeBodyToken > typeOpenBrace - else { return } - - // Sorting the body of a type conflicts with the `organizeDeclarations` - // keyword if enabled for this type of declaration. In that case, - // defer to the sorting implementation in `organizeDeclarations`. - if formatter.options.enabledRules.contains(FormatRules.organizeDeclarations.name), - formatter.options.organizeTypes.contains(declarationKeyword) - { - return - } - - rangeToSort = firstTypeBodyToken ... lastTypeBodyToken - // We don't include any leading linebreaks in the range to sort, - // since `firstTypeBodyToken` is the first `nonLinebreak` in the body - numberOfLeadingLinebreaks = 0 - } else { - return - } - - var declarations = Formatter(Array(formatter.tokens[rangeToSort])) - .parseDeclarations() - .enumerated() - .sorted(by: { lhs, rhs -> Bool in - let (lhsIndex, lhsDeclaration) = lhs - let (rhsIndex, rhsDeclaration) = rhs - - // Primarily sort by name, to alphabetize - if let lhsName = lhsDeclaration.name, - let rhsName = rhsDeclaration.name, - lhsName != rhsName - { - return lhsName.localizedCompare(rhsName) == .orderedAscending - } - - // Otherwise preserve the existing order - else { - return lhsIndex < rhsIndex - } - - }) - .map { $0.element } - - // Make sure there's at least one newline between each declaration - for i in 0 ..< max(0, declarations.count - 1) { - let declaration = declarations[i] - let nextDeclaration = declarations[i + 1] - - if declaration.tokens.last?.isLinebreak == false, - nextDeclaration.tokens.first?.isLinebreak == false - { - declarations[i + 1] = formatter.mapOpeningTokens(in: nextDeclaration) { openTokens in - let openFormatter = Formatter(openTokens) - openFormatter.insertLinebreak(at: 0) - return openFormatter.tokens - } - } - } - - var sortedFormatter = Formatter(declarations.flatMap { $0.tokens }) - - // Make sure the type has the same number of leading line breaks - // as it did before sorting - if let currentLeadingLinebreakCount = sortedFormatter.tokens.firstIndex(where: { !$0.isLinebreak }) { - if numberOfLeadingLinebreaks != currentLeadingLinebreakCount { - sortedFormatter.removeTokens(in: 0 ..< currentLeadingLinebreakCount) - - for _ in 0 ..< numberOfLeadingLinebreaks { - sortedFormatter.insertLinebreak(at: 0) - } - } - - } else { - for _ in 0 ..< numberOfLeadingLinebreaks { - sortedFormatter.insertLinebreak(at: 0) - } - } - - // There are always expected to be zero trailing line breaks, - // so we remove any trailing line breaks - // (this is because `typeBodyRange` specifically ends before the first - // trailing linebreak) - while sortedFormatter.tokens.last?.isLinebreak == true { - sortedFormatter.removeLastToken() - } - - if Array(formatter.tokens[rangeToSort]) != sortedFormatter.tokens { - formatter.replaceTokens( - in: rangeToSort, - with: sortedFormatter.tokens - ) - } - } - } - - public let assertionFailures = FormatRule( - help: """ - Changes all instances of assert(false, ...) to assertionFailure(...) - and precondition(false, ...) to preconditionFailure(...). - """ - ) { formatter in - formatter.forEachToken { i, token in - switch token { - case .identifier("assert"), .identifier("precondition"): - guard let scopeStart = formatter.index(of: .nonSpace, after: i, if: { - $0 == .startOfScope("(") - }), let identifierIndex = formatter.index(of: .nonSpaceOrLinebreak, after: scopeStart, if: { - $0 == .identifier("false") - }), var endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: identifierIndex) else { - return - } - - // if there are more arguments, replace the comma and space as well - if formatter.tokens[endIndex] == .delimiter(",") { - endIndex = formatter.index(of: .nonSpace, after: endIndex) ?? endIndex - } - - let replacements = ["assert": "assertionFailure", "precondition": "preconditionFailure"] - formatter.replaceTokens(in: i ..< endIndex, with: [ - .identifier(replacements[token.string]!), .startOfScope("("), - ]) - default: - break - } - } - } - - public let acronyms = FormatRule( - help: "Capitalize acronyms when the first character is capitalized.", - disabledByDefault: true, - options: ["acronyms"] - ) { formatter in - formatter.forEachToken { index, token in - guard token.is(.identifier) || token.isComment else { return } - - var updatedText = token.string - - for acronym in formatter.options.acronyms { - let find = acronym.capitalized - let replace = acronym.uppercased() - - for replaceCandidateRange in token.string.ranges(of: find) { - let acronymShouldBeCapitalized: Bool - - if replaceCandidateRange.upperBound < token.string.indices.last! { - let indexAfterMatch = replaceCandidateRange.upperBound - let characterAfterMatch = token.string[indexAfterMatch] - - // Only treat this as an acronym if the next character is uppercased, - // to prevent "Id" from matching strings like "Identifier". - if characterAfterMatch.isUppercase || characterAfterMatch.isWhitespace { - acronymShouldBeCapitalized = true - } - - // But if the next character is 's', and then the character after the 's' is uppercase, - // allow the acronym to be capitalized (to handle the plural case, `Ids` to `IDs`) - else if characterAfterMatch == Character("s") { - if indexAfterMatch < token.string.indices.last! { - let characterAfterNext = token.string[token.string.index(after: indexAfterMatch)] - acronymShouldBeCapitalized = (characterAfterNext.isUppercase || characterAfterNext.isWhitespace) - } else { - acronymShouldBeCapitalized = true - } - } else { - acronymShouldBeCapitalized = false - } - } else { - acronymShouldBeCapitalized = true - } - - if acronymShouldBeCapitalized { - updatedText.replaceSubrange(replaceCandidateRange, with: replace) - } - } - } - - if token.string != updatedText { - let updatedToken: Token - switch token { - case .identifier: - updatedToken = .identifier(updatedText) - case .commentBody: - updatedToken = .commentBody(updatedText) - default: - return - } - - formatter.replaceToken(at: index, with: updatedToken) - } - } - } - - public let blockComments = FormatRule( - help: "Convert block comments to consecutive single line comments.", - disabledByDefault: true - ) { formatter in - formatter.forEachToken { i, token in - switch token { - case .startOfScope("/*"): - guard var endIndex = formatter.endOfScope(at: i) else { - return formatter.fatalError("Expected */", at: i) - } - - // We can only convert block comments to single-line comments - // if there are no non-comment tokens on the same line. - // - For example, we can't convert `if foo { /* code */ }` - // to a line comment because it would comment out the closing brace. - // - // To guard against this, we verify that there is only - // comment or whitespace tokens on the remainder of this line - guard formatter.next(.nonSpace, after: endIndex)?.isLinebreak != false else { - return - } - - var isDocComment = false - var stripLeadingStars = true - func replaceCommentBody(at index: Int) -> Int { - var delta = 0 - var space = "" - if case let .space(s) = formatter.tokens[index] { - formatter.removeToken(at: index) - space = s - delta -= 1 - } - if case let .commentBody(body)? = formatter.token(at: index) { - var body = Substring(body) - if stripLeadingStars { - if body.hasPrefix("*") { - body = body.drop(while: { $0 == "*" }) - } else { - stripLeadingStars = false - } - } - let prefix = isDocComment ? "/" : "" - if !prefix.isEmpty || !body.isEmpty, !body.hasPrefix(" ") { - space += " " - } - formatter.replaceToken( - at: index, - with: .commentBody(prefix + space + body) - ) - } else if isDocComment { - formatter.insert(.commentBody("/"), at: index) - delta += 1 - } - return delta - } - - // Replace opening delimiter - var startIndex = i - let indent = formatter.currentIndentForLine(at: i) - if case let .commentBody(body) = formatter.tokens[i + 1] { - isDocComment = body.hasPrefix("*") - let commentBody = body.drop(while: { $0 == "*" }) - formatter.replaceToken(at: i + 1, with: .commentBody("/" + commentBody)) - } - formatter.replaceToken(at: i, with: .startOfScope("//")) - if let nextToken = formatter.token(at: i + 1), - nextToken.isSpaceOrLinebreak || nextToken.string == (isDocComment ? "/" : ""), - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i + 1), - nextIndex > i + 2 - { - let range = i + 1 ..< nextIndex - formatter.removeTokens(in: range) - endIndex -= range.count - startIndex = i + 1 - endIndex += replaceCommentBody(at: startIndex) - } - - // Replace ending delimiter - if let i = formatter.index(of: .nonSpace, before: endIndex, if: { - $0.isLinebreak - }) { - let range = i ... endIndex - formatter.removeTokens(in: range) - endIndex -= range.count - } - - // remove /* and */ - var index = i - while index <= endIndex { - switch formatter.tokens[index] { - case .startOfScope("/*"): - formatter.removeToken(at: index) - endIndex -= 1 - if formatter.tokens[index - 1].isSpace { - formatter.removeToken(at: index - 1) - index -= 1 - endIndex -= 1 - } - case .endOfScope("*/"): - formatter.removeToken(at: index) - endIndex -= 1 - if formatter.tokens[index - 1].isSpace { - formatter.removeToken(at: index - 1) - index -= 1 - endIndex -= 1 - } - case .linebreak: - endIndex += formatter.insertSpace(indent, at: index + 1) - guard let i = formatter.index(of: .nonSpace, after: index) else { - index += 1 - continue - } - index = i - formatter.insert(.startOfScope("//"), at: index) - var delta = 1 + replaceCommentBody(at: index + 1) - index += delta - endIndex += delta - default: - index += 1 - } - } - default: - break - } - } - } - - public let redundantClosure = FormatRule( - help: """ - Removes redundant closures bodies, containing a single statement, - which are called immediately. - """, - disabledByDefault: false, - orderAfter: ["redundantReturn"] - ) { formatter in - formatter.forEach(.startOfScope("{")) { closureStartIndex, _ in - var startIndex = closureStartIndex - if formatter.isStartOfClosure(at: closureStartIndex), - var closureEndIndex = formatter.endOfScope(at: closureStartIndex), - // Closures that are called immediately are redundant - // (as long as there's exactly one statement inside them) - var closureCallOpenParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureEndIndex), - var closureCallCloseParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureCallOpenParenIndex), - formatter.token(at: closureCallOpenParenIndex) == .startOfScope("("), - formatter.token(at: closureCallCloseParenIndex) == .endOfScope(")"), - // Make sure to exclude closures that are completely empty, - // because removing them could break the build. - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureStartIndex) != closureEndIndex - { - /// Whether or not this closure has a single, simple expression in its body. - /// These closures can always be simplified / removed regardless of the context. - let hasSingleSimpleExpression = formatter.blockBodyHasSingleStatement( - atStartOfScope: closureStartIndex, - includingConditionalStatements: false, - includingReturnStatements: true - ) - - /// Whether or not this closure has a single if/switch expression in its body. - /// Since if/switch expressions are only valid in the `return` position or as an `=` assignment, - /// these closures can only sometimes be simplified / removed. - let hasSingleConditionalExpression = !hasSingleSimpleExpression && - formatter.blockBodyHasSingleStatement( - atStartOfScope: closureStartIndex, - includingConditionalStatements: true, - includingReturnStatements: true, - includingReturnInConditionalStatements: false - ) - - guard hasSingleSimpleExpression || hasSingleConditionalExpression else { - return - } - - // This rule also doesn't support closures with an `in` token. - // - We can't just remove this, because it could have important type information. - // For example, `let double = { () -> Double in 100 }()` and `let double = 100` have different types. - // - We could theoretically support more sophisticated checks / transforms here, - // but this seems like an edge case so we choose not to handle it. - for inIndex in closureStartIndex ... closureEndIndex - where formatter.token(at: inIndex) == .keyword("in") - { - if !formatter.indexIsWithinNestedClosure(inIndex, startOfScopeIndex: closureStartIndex) { - return - } - } - - // If the closure calls a single function, which throws or returns `Never`, - // then removing the closure will cause a compilation failure. - // - We maintain a list of known functions that return `Never`. - // We could expand this to be user-provided if necessary. - for i in closureStartIndex ... closureEndIndex { - switch formatter.tokens[i] { - case .identifier("fatalError"), .identifier("preconditionFailure"), .keyword("throw"): - if !formatter.indexIsWithinNestedClosure(i, startOfScopeIndex: closureStartIndex) { - return - } - default: - break - } - } - - // If closure is preceded by try and/or await then remove those too - if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex, if: { - $0 == .keyword("await") - }) { - startIndex = prevIndex - } - if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex, if: { - $0 == .keyword("try") - }) { - startIndex = prevIndex - } - - // Since if/switch expressions are only valid in the `return` position or as an `=` assignment, - // these closures can only sometimes be simplified / removed. - if hasSingleConditionalExpression { - // Find the `{` start of scope or `=` and verify that the entire following expression consists of just this closure. - var startOfScopeContainingClosure = formatter.startOfScope(at: startIndex) - var assignmentBeforeClosure = formatter.index(of: .operator("=", .infix), before: startIndex) - - if let assignmentBeforeClosure = assignmentBeforeClosure, formatter.isConditionalStatement(at: assignmentBeforeClosure) { - // Not valid to use conditional expression directly in condition body - return - } - - let potentialStartOfExpressionContainingClosure: Int? - switch (startOfScopeContainingClosure, assignmentBeforeClosure) { - case (nil, nil): - potentialStartOfExpressionContainingClosure = nil - case (.some(let startOfScope), nil): - guard formatter.tokens[startOfScope] == .startOfScope("{") else { return } - potentialStartOfExpressionContainingClosure = startOfScope - case (nil, let .some(assignmentBeforeClosure)): - potentialStartOfExpressionContainingClosure = assignmentBeforeClosure - case let (.some(startOfScope), .some(assignmentBeforeClosure)): - potentialStartOfExpressionContainingClosure = max(startOfScope, assignmentBeforeClosure) - } - - if let potentialStartOfExpressionContainingClosure = potentialStartOfExpressionContainingClosure { - guard var startOfExpressionIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: potentialStartOfExpressionContainingClosure) - else { return } - - // Skip over any return token that may be present - if formatter.tokens[startOfExpressionIndex] == .keyword("return"), - let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfExpressionIndex) - { - startOfExpressionIndex = nextTokenIndex - } - - // Parse the expression and require that entire expression is simply just this closure. - guard let expressionRange = formatter.parseExpressionRange(startingAt: startOfExpressionIndex), - expressionRange == startIndex ... closureCallCloseParenIndex - else { return } - } - } - - // If the closure is a property with an explicit `Void` type, - // we can't remove the closure since the build would break - // if the method is `@discardableResult` - // https://github.com/nicklockwood/SwiftFormat/issues/1236 - if let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex), - formatter.token(at: equalsIndex) == .operator("=", .infix), - let colonIndex = formatter.index(of: .delimiter(":"), before: equalsIndex), - let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex), - formatter.endOfVoidType(at: nextIndex) != nil - { - return - } - - // First we remove the spaces and linebreaks between the { } and the remainder of the closure body - // - This requires a bit of bookkeeping, but makes sure we don't remove any - // whitespace characters outside of the closure itself - while formatter.token(at: closureStartIndex + 1)?.isSpaceOrLinebreak == true { - formatter.removeToken(at: closureStartIndex + 1) - - closureCallOpenParenIndex -= 1 - closureCallCloseParenIndex -= 1 - closureEndIndex -= 1 - } - - while formatter.token(at: closureEndIndex - 1)?.isSpaceOrLinebreak == true { - formatter.removeToken(at: closureEndIndex - 1) - - closureCallOpenParenIndex -= 1 - closureCallCloseParenIndex -= 1 - closureEndIndex -= 1 - } - - // remove the trailing }() tokens, working backwards to not invalidate any indices - formatter.removeToken(at: closureCallCloseParenIndex) - formatter.removeToken(at: closureCallOpenParenIndex) - formatter.removeToken(at: closureEndIndex) - - // Remove the initial return token, and any trailing space, if present. - if let returnIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureStartIndex), - formatter.token(at: returnIndex)?.string == "return" - { - while formatter.token(at: returnIndex + 1)?.isSpaceOrLinebreak == true { - formatter.removeToken(at: returnIndex + 1) - } - - formatter.removeToken(at: returnIndex) - } - - // Finally, remove then open `{` token - formatter.removeTokens(in: startIndex ... closureStartIndex) - } - } - } - - public let redundantOptionalBinding = FormatRule( - help: "Remove redundant identifiers in optional binding conditions.", - // We can convert `if let foo = self.foo` to just `if let foo`, - // but only if `redundantSelf` can first remove the `self.`. - orderAfter: ["redundantSelf"] - ) { formatter in - formatter.forEachToken { i, token in - // `if let foo` conditions were added in Swift 5.7 (SE-0345) - if formatter.options.swiftVersion >= "5.7", - - [.keyword("let"), .keyword("var")].contains(token), - formatter.isConditionalStatement(at: i), - - let identiferIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - let identifier = formatter.token(at: identiferIndex), - - let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: identiferIndex, if: { - $0 == .operator("=", .infix) - }), - - let nextIdentifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex, if: { - $0 == identifier - }), - - let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIdentifierIndex), - [.startOfScope("{"), .delimiter(","), .keyword("else")].contains(nextToken) - { - formatter.removeTokens(in: identiferIndex + 1 ... nextIdentifierIndex) - } - } - } - - public let opaqueGenericParameters = FormatRule( - help: """ - Use opaque generic parameters (`some Protocol`) instead of generic parameters - with constraints (`T where T: Protocol`, etc) where equivalent. Also supports - primary associated types for common standard library types, so definitions like - `T where T: Collection, T.Element == Foo` are updated to `some Collection`. - """, - options: ["someany"] - ) { formatter in - formatter.forEach(.keyword) { keywordIndex, keyword in - guard // Opaque generic parameter syntax is only supported in Swift 5.7+ - formatter.options.swiftVersion >= "5.7", - // Apply this rule to any function-like declaration - [.keyword("func"), .keyword("init"), .keyword("subscript")].contains(keyword), - // Validate that this is a generic method using angle bracket syntax, - // and find the indices for all of the key tokens - let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), - let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), - let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), - let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), - genericSignatureStartIndex < paramListStartIndex, - genericSignatureEndIndex < paramListStartIndex, - let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), - let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) - else { return } - - var genericTypes = [Formatter.GenericType]() - - // Parse the generics in the angle brackets (e.g. ``) - formatter.parseGenericTypes( - from: genericSignatureStartIndex, - to: genericSignatureEndIndex, - into: &genericTypes - ) - - // Parse additional conformances and constraints after the `where` keyword if present - // (e.g. `where Foo: Fooable, Foo.Bar: Barable, Foo.Baaz == Baazable`) - var whereTokenIndex: Int? - if let whereIndex = formatter.index(of: .keyword("where"), after: paramListEndIndex), - whereIndex < openBraceIndex - { - whereTokenIndex = whereIndex - formatter.parseGenericTypes(from: whereIndex, to: openBraceIndex, into: &genericTypes) - } - - // Parse the return type if present - var returnTypeTokens: [Token]? - if let returnIndex = formatter.index(of: .operator("->", .infix), after: paramListEndIndex), - returnIndex < openBraceIndex, returnIndex < whereTokenIndex ?? openBraceIndex - { - let returnTypeRange = (returnIndex + 1) ..< (whereTokenIndex ?? openBraceIndex) - returnTypeTokens = Array(formatter.tokens[returnTypeRange]) - } - - let genericParameterListRange = (genericSignatureStartIndex + 1) ..< genericSignatureEndIndex - let genericParameterListTokens = formatter.tokens[genericParameterListRange] - - let parameterListRange = (paramListStartIndex + 1) ..< paramListEndIndex - let parameterListTokens = formatter.tokens[parameterListRange] - - let bodyRange = (openBraceIndex + 1) ..< closeBraceIndex - let bodyTokens = formatter.tokens[bodyRange] - - for genericType in genericTypes { - // If the generic type doesn't occur in the generic parameter list (<...>), - // then we inherited it from the generic context and can't replace the type - // with an opaque parameter. - if !genericParameterListTokens.contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - continue - } - - // We can only remove the generic type if it appears exactly once in the parameter list. - // - If the generic type occurs _multiple_ times in the parameter list, - // it isn't eligible to be removed. For example `(T, T) where T: Foo` - // requires the two params to be the same underlying type, but - // `(some Foo, some Foo)` does not. - // - If the generic type occurs _zero_ times in the parameter list - // then removing the generic parameter would also remove any - // potentially-important constraints (for example, if the type isn't - // used in the function parameters / body and is only constrained relative - // to generic types in the parent type scope). If this generic parameter - // is truly unused and redundant then the compiler would emit an error. - let countInParameterList = parameterListTokens.filter { $0.string == genericType.name }.count - if countInParameterList != 1 { - genericType.eligibleToRemove = false - continue - } - - // If the generic type occurs in the body of the function, then it can't be removed - if bodyTokens.contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - continue - } - - // If the generic type is referenced in any attributes, then it can't be removed - let startOfModifiers = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) - let modifierTokens = formatter.tokens[startOfModifiers ..< keywordIndex] - if modifierTokens.contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - continue - } - - // If the generic type is used in a constraint of any other generic type, then the type - // can't be removed without breaking that other type - let otherGenericTypes = genericTypes.filter { $0.name != genericType.name } - let otherTypeConformances = otherGenericTypes.flatMap { $0.conformances } - for otherTypeConformance in otherTypeConformances { - let conformanceTokens = formatter.tokens[otherTypeConformance.sourceRange] - if conformanceTokens.contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - } - } - - // In some weird cases you can also have a generic constraint that references a generic - // type from the parent context with the same name. We can't change these, since it - // can cause the build to break - for conformance in genericType.conformances { - if tokenize(conformance.name).contains(where: { $0.string == genericType.name }) { - genericType.eligibleToRemove = false - } - } - - // A generic used as a return type is different from an opaque result type (SE-244). - // For example in `-> T where T: Fooable`, the generic type is caller-specified, - // but with `-> some Fooable` the generic type is specified by the function implementation. - // Because those represent different concepts, we can't convert between them, - // so have to mark the generic type as ineligible if it appears in the return type. - if let returnTypeTokens = returnTypeTokens, - returnTypeTokens.contains(where: { $0.string == genericType.name }) - { - genericType.eligibleToRemove = false - continue - } - - // If the method that generates the opaque parameter syntax doesn't succeed, - // then this type is ineligible (because it used a generic constraint that - // can't be represented using this syntax). - // TODO: this option probably needs to be captured earlier to support comment directives - if genericType.asOpaqueParameter(useSomeAny: formatter.options.useSomeAny) == nil { - genericType.eligibleToRemove = false - continue - } - - // If the generic type is used as a closure type parameter, it can't be removed or the compiler - // will emit a "'some' cannot appear in parameter position in parameter type " error - for tokenIndex in keywordIndex ... closeBraceIndex { - // Check if this is the start of a closure - if formatter.tokens[tokenIndex] == .startOfScope("("), - tokenIndex != paramListStartIndex, - let endOfScope = formatter.endOfScope(at: tokenIndex), - let tokenAfterParen = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfScope), - [.operator("->", .infix), .keyword("throws"), .identifier("async")].contains(tokenAfterParen), - // Check if the closure type parameters contains this generic type - formatter.tokens[tokenIndex ... endOfScope].contains(where: { $0.string == genericType.name }) - { - genericType.eligibleToRemove = false - } - } - - // Extract the comma-separated list of function parameters, - // so we can check conditions on the individual parameters - let parameterListTokenIndices = (paramListStartIndex + 1) ..< paramListEndIndex - - // Split the parameter list at each comma that's directly within the paren list scope - let parameters = parameterListTokenIndices - .split(whereSeparator: { index in - let token = formatter.tokens[index] - return token == .delimiter(",") - && formatter.endOfScope(at: index) == paramListEndIndex - }) - .map { parameterIndices in - parameterIndices.map { index in - formatter.tokens[index] - } - } - - for parameterTokens in parameters { - // Variadic parameters don't support opaque generic syntax, so we have to check - // if any use cases of this type in the parameter list are variadic - if parameterTokens.contains(.operator("...", .postfix)), - parameterTokens.contains(.identifier(genericType.name)) - { - genericType.eligibleToRemove = false - } - } - } - - let genericsEligibleToRemove = genericTypes.filter { $0.eligibleToRemove } - let sourceRangesToRemove = Set(genericsEligibleToRemove.flatMap { type in - [type.definitionSourceRange] + type.conformances.map { $0.sourceRange } - }) - - // We perform modifications to the function signature in reverse order - // so we don't invalidate any of the indices we've recorded. So first - // we remove components of the where clause. - if let whereIndex = formatter.index(of: .keyword("where"), after: paramListEndIndex), - whereIndex < openBraceIndex - { - let whereClauseSourceRanges = sourceRangesToRemove.filter { $0.lowerBound > whereIndex } - formatter.removeTokens(in: Array(whereClauseSourceRanges)) - - if let newOpenBraceIndex = formatter.index(of: .startOfScope("{"), after: whereIndex) { - // if where clause is completely empty, we need to remove the where token as well - if formatter.index(of: .nonSpaceOrLinebreak, after: whereIndex) == newOpenBraceIndex { - formatter.removeTokens(in: whereIndex ..< newOpenBraceIndex) - } - // remove trailing comma - else if let commaIndex = formatter.index( - of: .nonSpaceOrCommentOrLinebreak, - before: newOpenBraceIndex, if: { $0 == .delimiter(",") } - ) { - formatter.removeToken(at: commaIndex) - if formatter.tokens[commaIndex - 1].isSpace, - formatter.tokens[commaIndex].isSpaceOrLinebreak - { - formatter.removeToken(at: commaIndex - 1) - } - } - } - } - - // Replace all of the uses of generic types that are eligible to remove - // with the corresponding opaque parameter declaration - for index in parameterListRange.reversed() { - if let matchingGenericType = genericsEligibleToRemove.first(where: { $0.name == formatter.tokens[index].string }), - var opaqueParameter = matchingGenericType.asOpaqueParameter(useSomeAny: formatter.options.useSomeAny) - { - // If this instance of the type is followed by a `.` or `?` then we have to wrap the new type in parens - // (e.g. changing `Foo.Type` to `some Any.Type` breaks the build, it needs to be `(some Any).Type`) - if let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: index), - [.operator(".", .infix), .operator("?", .postfix)].contains(nextToken) - { - opaqueParameter.insert(.startOfScope("("), at: 0) - opaqueParameter.append(.endOfScope(")")) - } - - formatter.replaceToken(at: index, with: opaqueParameter) - } - } - - // Remove types from the generic parameter list - let genericParameterListSourceRanges = sourceRangesToRemove.filter { $0.lowerBound < genericSignatureEndIndex } - formatter.removeTokens(in: Array(genericParameterListSourceRanges)) - - // If we left a dangling comma at the end of the generic parameter list, we need to clean it up - if let newGenericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), - let trailingCommaIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: newGenericSignatureEndIndex), - formatter.tokens[trailingCommaIndex] == .delimiter(",") - { - formatter.removeTokens(in: trailingCommaIndex ..< newGenericSignatureEndIndex) - } - - // If we removed all of the generic types, we also have to remove the angle brackets - if let newGenericSignatureEndIndex = formatter.index(of: .nonSpaceOrLinebreak, after: genericSignatureStartIndex), - formatter.token(at: newGenericSignatureEndIndex) == .endOfScope(">") - { - formatter.removeTokens(in: genericSignatureStartIndex ... newGenericSignatureEndIndex) - } - } - } - - public let genericExtensions = FormatRule( - help: """ - Use angle brackets (`extension Array`) for generic type extensions - instead of type constraints (`extension Array where Element == Foo`). - """, - options: ["generictypes"] - ) { formatter in - formatter.forEach(.keyword("extension")) { extensionIndex, _ in - guard // Angle brackets syntax in extensions is only supported in Swift 5.7+ - formatter.options.swiftVersion >= "5.7", - let typeNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: extensionIndex), - let extendedType = formatter.token(at: typeNameIndex)?.string, - // If there's already an open angle bracket after the generic type name - // then the extension is already using the target syntax, so there's - // no work to do - formatter.next(.nonSpaceOrCommentOrLinebreak, after: typeNameIndex) != .startOfScope("<"), - let openBraceIndex = formatter.index(of: .startOfScope("{"), after: typeNameIndex), - let whereIndex = formatter.index(of: .keyword("where"), after: typeNameIndex), - whereIndex < openBraceIndex - else { return } - - // Prepopulate a `Self` generic type, which is implicitly present in extension definitions - let selfType = Formatter.GenericType( - name: "Self", - definitionSourceRange: typeNameIndex ... typeNameIndex, - conformances: [ - Formatter.GenericType.GenericConformance( - name: extendedType, - typeName: "Self", - type: .concreteType, - sourceRange: typeNameIndex ... typeNameIndex - ), - ] - ) - - var genericTypes = [selfType] - - // Parse the generic constraints in the where clause - formatter.parseGenericTypes( - from: whereIndex, - to: openBraceIndex, - into: &genericTypes, - qualifyGenericTypeName: { genericTypeName in - // In an extension all types implicitly refer to `Self`. - // For example, `Element == Foo` is actually fully-qualified as - // `Self.Element == Foo`. Using the fully-qualified `Self.Element` name - // here makes it so the generic constraint is populated as a child - // of `selfType`. - if !genericTypeName.hasPrefix("Self.") { - return "Self." + genericTypeName - } else { - return genericTypeName - } - } - ) - - var knownGenericTypes: [(name: String, genericTypes: [String])] = [ - (name: "Collection", genericTypes: ["Element"]), - (name: "Sequence", genericTypes: ["Element"]), - (name: "Array", genericTypes: ["Element"]), - (name: "Set", genericTypes: ["Element"]), - (name: "Dictionary", genericTypes: ["Key", "Value"]), - (name: "Optional", genericTypes: ["Wrapped"]), - ] - - // Users can provide additional generic types via the `generictypes` option - for userProvidedType in formatter.options.genericTypes.components(separatedBy: ";") { - guard let openAngleBracket = userProvidedType.firstIndex(of: "<"), - let closeAngleBracket = userProvidedType.firstIndex(of: ">") - else { continue } - - let typeName = String(userProvidedType[.. Bool { - // Check if this is a special type of comment that isn't documentation - if case let .commentBody(body)? = formatter.next(.nonSpace, after: index), body.isCommentDirective { - return false - } - - // Check if this token defines a declaration that supports doc comments - var declarationToken = formatter.tokens[nextDeclarationIndex] - if declarationToken.isAttribute || declarationToken.isModifierKeyword, - let index = formatter.index(after: nextDeclarationIndex, where: { $0.isDeclarationTypeKeyword }) - { - declarationToken = formatter.tokens[index] - } - guard declarationToken.isDeclarationTypeKeyword(excluding: ["import"]) else { - return false - } - - // Only use doc comments on declarations in type bodies, or top-level declarations - if let startOfEnclosingScope = formatter.index(of: .startOfScope, before: index) { - switch formatter.tokens[startOfEnclosingScope] { - case .startOfScope("#if"): - break - case .startOfScope("{"): - guard let scope = formatter.lastSignificantKeyword(at: startOfEnclosingScope, excluding: ["where"]), - ["class", "actor", "struct", "enum", "protocol", "extension"].contains(scope) - else { - return false - } - default: - return false - } - } - - // If there are blank lines between comment and declaration, comment is not treated as doc comment - let trailingTokens = formatter.tokens[(endOfComment - 1) ... nextDeclarationIndex] - let lines = trailingTokens.split(omittingEmptySubsequences: false, whereSeparator: \.isLinebreak) - if lines.contains(where: { $0.allSatisfy(\.isSpace) }) { - return false - } - - // Only comments at the start of a line can be doc comments - if let previousToken = formatter.index(of: .nonSpaceOrLinebreak, before: index) { - let commentLine = formatter.startOfLine(at: index) - let previousTokenLine = formatter.startOfLine(at: previousToken) - - if commentLine == previousTokenLine { - return false - } - } - - // Comments inside conditional statements are not doc comments - return !formatter.isConditionalStatement(at: index) - } - - var commentIndices = [index] - if token == .startOfScope("//") { - var i = index - while let prevLineIndex = formatter.index(of: .linebreak, before: i), - case let lineStartIndex = formatter.startOfLine(at: prevLineIndex, excludingIndent: true), - formatter.token(at: lineStartIndex) == .startOfScope("//") - { - commentIndices.append(lineStartIndex) - i = lineStartIndex - } - i = index - while let nextLineIndex = formatter.index(of: .linebreak, after: i), - let lineStartIndex = formatter.index(of: .nonSpace, after: nextLineIndex), - formatter.token(at: lineStartIndex) == .startOfScope("//") - { - commentIndices.append(lineStartIndex) - i = lineStartIndex - } - } - - let useDocComment = shouldBeDocComment(at: index, endOfComment: endOfComment) - guard commentIndices.allSatisfy({ - shouldBeDocComment(at: $0, endOfComment: endOfComment) == useDocComment - }) else { - return - } - - // Determine whether or not this is the start of a list of sequential declarations, like: - // - // // The placeholder names we use in test cases - // case foo - // case bar - // case baaz - // - // In these cases it's not obvious whether or not the comment refers to the property or - // the entire group, so we preserve the existing formatting. - var preserveRegularComments = false - if useDocComment, - let declarationKeyword = formatter.index(after: endOfComment, where: \.isDeclarationTypeKeyword), - let endOfDeclaration = formatter.endOfDeclaration(atDeclarationKeyword: declarationKeyword, fallBackToEndOfScope: false), - let nextDeclarationKeyword = formatter.index(after: endOfDeclaration, where: \.isDeclarationTypeKeyword) - { - let linebreaksBetweenDeclarations = formatter.tokens[declarationKeyword ... nextDeclarationKeyword] - .filter { $0.isLinebreak }.count - - // If there is only a single line break between the start of this declaration and the subsequent declaration, - // then they are written sequentially in a block. In this case, don't convert regular comments to doc comments. - if linebreaksBetweenDeclarations == 1 { - preserveRegularComments = true - } - } - - // Doc comment tokens like `///` and `/**` aren't parsed as a - // single `.startOfScope` token -- they're parsed as: - // `.startOfScope("//"), .commentBody("/ ...")` or - // `.startOfScope("/*"), .commentBody("* ...")` - let startOfDocCommentBody: String - switch token.string { - case "//": - startOfDocCommentBody = "/" - case "/*": - startOfDocCommentBody = "*" - default: - return - } - - let isDocComment = formatter.isDocComment(startOfComment: index) - - if isDocComment, - let commentBody = formatter.token(at: index + 1), - commentBody.isCommentBody - { - if useDocComment, !isDocComment, !preserveRegularComments { - let updatedCommentBody = "\(startOfDocCommentBody)\(commentBody.string)" - formatter.replaceToken(at: index + 1, with: .commentBody(updatedCommentBody)) - } else if !useDocComment, isDocComment, !formatter.options.preserveDocComments { - let prefix = commentBody.string.prefix(while: { String($0) == startOfDocCommentBody }) - - // Do nothing if this is a unusual comment like `//////////////////` - // or `/****************`. We can't just remove one of the tokens, because - // that would make this rule have a different output each time, but we - // shouldn't remove all of them since that would be unexpected. - if prefix.count > 1 { - return - } - - formatter.replaceToken( - at: index + 1, - with: .commentBody(String(commentBody.string.dropFirst())) - ) - } - - } else if useDocComment, !preserveRegularComments { - formatter.insert(.commentBody(startOfDocCommentBody), at: index + 1) - } - } - } - - public let conditionalAssignment = FormatRule( - help: "Assign properties using if / switch expressions.", - orderAfter: ["redundantReturn"], - options: ["condassignment"] - ) { formatter in - // If / switch expressions were added in Swift 5.9 (SE-0380) - guard formatter.options.swiftVersion >= "5.9" else { - return - } - - formatter.forEach(.keyword) { startOfConditional, keywordToken in - // Look for an if/switch expression where the first branch starts with `identifier =` - guard ["if", "switch"].contains(keywordToken.string), - let conditionalBranches = formatter.conditionalBranches(at: startOfConditional), - var startOfFirstBranch = conditionalBranches.first?.startOfBranch - else { return } - - // Traverse any nested if/switch branches until we find the first code branch - while let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfFirstBranch), - ["if", "switch"].contains(formatter.tokens[firstTokenInBranch].string), - let nestedConditionalBranches = formatter.conditionalBranches(at: firstTokenInBranch), - let startOfNestedBranch = nestedConditionalBranches.first?.startOfBranch - { - startOfFirstBranch = startOfNestedBranch - } - - // Check if the first branch starts with the pattern `lvalue =`. - guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfFirstBranch), - let lvalueRange = formatter.parseExpressionRange(startingAt: firstTokenIndex), - let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: lvalueRange.upperBound), - formatter.tokens[equalsIndex] == .operator("=", .infix) - else { return } - - // Whether or not the conditional statement that starts at the given index - // has branches that are exhaustive - func conditionalBranchesAreExhaustive( - conditionKeywordIndex: Int, - branches: [Formatter.ConditionalBranch] - ) - -> Bool - { - // Switch statements are compiler-guaranteed to be exhaustive - if formatter.tokens[conditionKeywordIndex] == .keyword("switch") { - return true - } - - // If statements are only exhaustive if the last branch - // is `else` (not `else if`). - else if formatter.tokens[conditionKeywordIndex] == .keyword("if"), - let lastCondition = branches.last, - let tokenBeforeLastCondition = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lastCondition.startOfBranch) - { - return formatter.tokens[tokenBeforeLastCondition] == .keyword("else") - } - - return false - } - - // Whether or not the given conditional branch body qualifies as a single statement - // that assigns a value to `identifier`. This is either: - // 1. a single assignment to `lvalue =` - // 2. a single `if` or `switch` statement where each of the branches also qualify, - // and the statement is exhaustive. - func isExhaustiveSingleStatementAssignment(_ branch: Formatter.ConditionalBranch) -> Bool { - guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return false } - - // If this is an if/switch statement, verify that all of the branches are also - // single-statement assignments and that the statement is exhaustive. - if let conditionalBranches = formatter.conditionalBranches(at: firstTokenIndex), - let lastConditionalStatement = conditionalBranches.last - { - let allBranchesAreExhaustiveSingleStatement = conditionalBranches.allSatisfy { branch in - isExhaustiveSingleStatementAssignment(branch) - } - - let isOnlyStatementInScope = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lastConditionalStatement.endOfBranch)?.isEndOfScope == true - - let isExhaustive = conditionalBranchesAreExhaustive( - conditionKeywordIndex: firstTokenIndex, - branches: conditionalBranches - ) - - return allBranchesAreExhaustiveSingleStatement - && isOnlyStatementInScope - && isExhaustive - } - - // Otherwise we expect this to be of the pattern `lvalue = (statement)` - else if let firstExpressionRange = formatter.parseExpressionRange(startingAt: firstTokenIndex), - formatter.tokens[firstExpressionRange] == formatter.tokens[lvalueRange], - let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: firstExpressionRange.upperBound), - formatter.tokens[equalsIndex] == .operator("=", .infix), - let valueStartIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex) - { - // We know this branch starts with `identifier =`, but have to check that the - // remaining code in the branch is a single statement. To do that we can - // create a temporary formatter with the branch body _excluding_ `identifier =`. - let assignmentStatementRange = valueStartIndex ..< branch.endOfBranch - var tempScopeTokens = [Token]() - tempScopeTokens.append(.startOfScope("{")) - tempScopeTokens.append(contentsOf: formatter.tokens[assignmentStatementRange]) - tempScopeTokens.append(.endOfScope("}")) - - let tempFormatter = Formatter(tempScopeTokens, options: formatter.options) - guard tempFormatter.blockBodyHasSingleStatement( - atStartOfScope: 0, - includingConditionalStatements: true, - includingReturnStatements: false - ) else { - return false - } - - // In Swift 5.9, there's a bug that prevents you from writing an - // if or switch expression using an `as?` on one of the branches: - // https://github.com/apple/swift/issues/68764 - // - // let result = if condition { - // foo as? String - // } else { - // "bar" - // } - // - if tempFormatter.conditionalBranchHasUnsupportedCastOperator(startOfScopeIndex: 0) { - return false - } - - return true - } - - return false - } - - guard conditionalBranches.allSatisfy(isExhaustiveSingleStatementAssignment), - conditionalBranchesAreExhaustive(conditionKeywordIndex: startOfConditional, branches: conditionalBranches) - else { - return - } - - // Removes the `identifier =` from each conditional branch - func removeAssignmentFromAllBranches() { - formatter.forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in - guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch), - let firstExpressionRange = formatter.parseExpressionRange(startingAt: firstTokenIndex), - let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: firstExpressionRange.upperBound), - formatter.tokens[equalsIndex] == .operator("=", .infix), - let valueStartIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex) - else { return } - - formatter.removeTokens(in: firstTokenIndex ..< valueStartIndex) - } - } - - // If this expression follows a property like `let identifier: Type`, we just - // have to insert an `=` between property and the conditional. - // - Find the introducer (let/var), parse the property, and verify that the identifier - // matches the identifier assigned on each conditional branch. - if let introducerIndex = formatter.indexOfLastSignificantKeyword(at: startOfConditional, excluding: ["if", "switch"]), - ["let", "var"].contains(formatter.tokens[introducerIndex].string), - let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), - formatter.tokens[lvalueRange.lowerBound].string == property.identifier, - property.value == nil, - let typeRange = property.type?.range, - let nextTokenAfterProperty = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: typeRange.upperBound), - nextTokenAfterProperty == startOfConditional - { - removeAssignmentFromAllBranches() - - let rangeBetweenTypeAndConditional = (typeRange.upperBound + 1) ..< startOfConditional - - // If there are no comments between the type and conditional, - // we reformat it from: - // - // let foo: Foo\n - // if condition { - // - // to: - // - // let foo: Foo = if condition { - // - if formatter.tokens[rangeBetweenTypeAndConditional].allSatisfy(\.isSpaceOrLinebreak) { - formatter.replaceTokens(in: rangeBetweenTypeAndConditional, with: [ - .space(" "), - .operator("=", .infix), - .space(" "), - ]) - } - - // But if there are comments, then we shouldn't just delete them. - // Instead we just insert `= ` after the type. - else { - formatter.insert([.operator("=", .infix), .space(" ")], at: startOfConditional) - } - } - - // Otherwise we insert an `identifier =` before the if/switch expression - else if !formatter.options.conditionalAssignmentOnlyAfterNewProperties { - // In this case we should only apply the conversion if this is a top-level condition, - // and not nested in some parent condition. In large complex if/switch conditions - // with multiple layers of nesting, for example, this prevents us from making any - // changes unless the entire set of nested conditions can be converted as a unit. - // - First attempt to find and parse a parent if / switch condition. - var startOfParentScope = formatter.startOfScope(at: startOfConditional) - - // If we're inside a switch case, expand to look at the whole switch statement - while let currentStartOfParentScope = startOfParentScope, - formatter.tokens[currentStartOfParentScope] == .startOfScope(":"), - let caseToken = formatter.index(of: .endOfScope("case"), before: currentStartOfParentScope) - { - startOfParentScope = formatter.startOfScope(at: caseToken) - } - - if let startOfParentScope = startOfParentScope, - let mostRecentIfOrSwitch = formatter.index(of: .keyword, before: startOfParentScope, if: { ["if", "switch"].contains($0.string) }), - let conditionalBranches = formatter.conditionalBranches(at: mostRecentIfOrSwitch), - let startOfFirstParentBranch = conditionalBranches.first?.startOfBranch, - let endOfLastParentBranch = conditionalBranches.last?.endOfBranch, - // If this condition is contained within a parent condition, do nothing. - // We should only convert the entire set of nested conditions together as a unit. - (startOfFirstParentBranch ... endOfLastParentBranch).contains(startOfConditional) - { return } - - let lvalueTokens = formatter.tokens[lvalueRange] - - // Now we can remove the `identifier =` from each branch, - // and instead add it before the if / switch expression. - removeAssignmentFromAllBranches() - - let identifierEqualsTokens = lvalueTokens + [ - .space(" "), - .operator("=", .infix), - .space(" "), - ] - - formatter.insert(identifierEqualsTokens, at: startOfConditional) - } - } - } - - public let sortTypealiases = FormatRule( - help: "Sort protocol composition typealiases alphabetically." - ) { formatter in - formatter.forEach(.keyword("typealias")) { typealiasIndex, _ in - guard let (equalsIndex, andTokenIndices, endIndex) = formatter.parseProtocolCompositionTypealias(at: typealiasIndex), - let typealiasNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) - else { - return - } - - var seenTypes = Set() - - // Split the typealias into individual elements. - // Any comments on their own line are grouped with the following element. - let delimiters = [equalsIndex] + andTokenIndices - var parsedElements: [(startIndex: Int, delimiterIndex: Int, endIndex: Int, type: String, allTokens: [Token], isDuplicate: Bool)] = [] - - for delimiter in delimiters.indices { - let endOfPreviousElement = parsedElements.last?.endIndex ?? typealiasNameIndex - let elementStartIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfPreviousElement) ?? delimiters[delimiter] - - // Start with the end index just being the end of the type name - var elementEndIndex: Int - let nextElementIsOnSameLine: Bool - if delimiter == delimiters.indices.last { - elementEndIndex = endIndex - nextElementIsOnSameLine = false - } else { - let nextDelimiterIndex = delimiters[delimiter + 1] - elementEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: nextDelimiterIndex) ?? (nextDelimiterIndex - 1) - - let endOfLine = formatter.endOfLine(at: elementEndIndex) - nextElementIsOnSameLine = formatter.endOfLine(at: nextDelimiterIndex) == endOfLine - } - - // Handle comments in multiline typealiases - if !nextElementIsOnSameLine { - // Any comments on the same line as the type name should be considered part of this element. - // Any comments after the linebreak are consisidered part of the next element. - // To do that we just extend this element to the end of the current line. - elementEndIndex = formatter.endOfLine(at: elementEndIndex) - 1 - } - - let tokens = Array(formatter.tokens[elementStartIndex ... elementEndIndex]) - let typeName = tokens - .filter { !$0.isSpaceOrCommentOrLinebreak && !$0.isOperator } - .map { $0.string }.joined() - - // While we're here, also filter out any duplicates. - // Since we're sorting, duplicates would sit right next to each other - // which makes them especially obvious. - let isDuplicate = seenTypes.contains(typeName) - seenTypes.insert(typeName) - - parsedElements.append(( - startIndex: elementStartIndex, - delimiterIndex: delimiters[delimiter], - endIndex: elementEndIndex, - type: typeName, - allTokens: tokens, - isDuplicate: isDuplicate - )) - } - - // Sort each element by type name - var sortedElements = parsedElements.sorted(by: { lhsElement, rhsElement in - lhsElement.type.lexicographicallyPrecedes(rhsElement.type) - }) - - // Don't modify the file if the typealias is already sorted - if parsedElements.map(\.startIndex) == sortedElements.map(\.startIndex) { - return - } - - let firstNonDuplicateIndex = sortedElements.firstIndex(where: { !$0.isDuplicate }) - - for elementIndex in sortedElements.indices { - // Revalidate all of the delimiters after sorting - // (the first delimiter should be `=` and all others should be `&` - let delimiterIndexInTokens = sortedElements[elementIndex].delimiterIndex - sortedElements[elementIndex].startIndex - - if elementIndex == firstNonDuplicateIndex { - sortedElements[elementIndex].allTokens[delimiterIndexInTokens] = .operator("=", .infix) - } else { - sortedElements[elementIndex].allTokens[delimiterIndexInTokens] = .operator("&", .infix) - } - - // Make sure there's always a linebreak after any comments, to prevent - // them from accidentially commenting out following elements of the typealias - if elementIndex != sortedElements.indices.last, - sortedElements[elementIndex].allTokens.last?.isComment == true, - let nextToken = formatter.nextToken(after: parsedElements[elementIndex].endIndex), - !nextToken.isLinebreak - { - sortedElements[elementIndex].allTokens.append(.linebreak("\n", 0)) - } - - // If this element starts with a comment, that's because the comment - // was originally on a line all by itself. To preserve this, make sure - // there's a linebreak before the comment. - if elementIndex != sortedElements.indices.first, - sortedElements[elementIndex].allTokens.first?.isComment == true, - let previousToken = formatter.lastToken(before: parsedElements[elementIndex].startIndex, where: { !$0.isSpace }), - !previousToken.isLinebreak - { - sortedElements[elementIndex].allTokens.insert(.linebreak("\n", 0), at: 0) - } - } - - // Replace each index in the parsed list with the corresponding index in the sorted list, - // working backwards to not invalidate any existing indices - for (originalElement, newElement) in zip(parsedElements, sortedElements).reversed() { - if newElement.isDuplicate, let tokenBeforeElement = formatter.index(of: .nonSpaceOrLinebreak, before: originalElement.startIndex) { - formatter.removeTokens(in: (tokenBeforeElement + 1) ... originalElement.endIndex) - } else { - formatter.replaceTokens( - in: originalElement.startIndex ... originalElement.endIndex, - with: newElement.allTokens - ) - } - } - } - } - - public let redundantInternal = FormatRule( - help: "Remove redundant internal access control." - ) { formatter in - formatter.forEach(.keyword("internal")) { internalKeywordIndex, _ in - // Don't remove import acl - if formatter.next(.nonSpaceOrComment, after: internalKeywordIndex) == .keyword("import") { - return - } - - // If we're inside an extension, then `internal` is only redundant if the extension itself is `internal`. - if let startOfScope = formatter.startOfScope(at: internalKeywordIndex), - let typeKeywordIndex = formatter.indexOfLastSignificantKeyword(at: startOfScope, excluding: ["where"]), - formatter.tokens[typeKeywordIndex] == .keyword("extension"), - // In the language grammar, the ACL level always directly precedes the - // `extension` keyword if present. - let previousToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeKeywordIndex), - ["public", "package", "internal", "private", "fileprivate"].contains(previousToken.string), - previousToken.string != "internal" - { - // The extension has an explicit ACL other than `internal`, so is not internal. - // We can't remove the `internal` keyword since the declaration would change - // to the ACL of the extension. - return - } - - guard formatter.token(at: internalKeywordIndex + 1)?.isSpace == true else { return } - - formatter.removeTokens(in: internalKeywordIndex ... (internalKeywordIndex + 1)) - } - } - - public let preferForLoop = FormatRule( - help: "Convert functional `forEach` calls to for loops.", - options: ["anonymousforeach", "onelineforeach"] - ) { formatter in - formatter.forEach(.identifier("forEach")) { forEachIndex, _ in - // Make sure this is a function call preceded by a `.` - guard let functionCallDotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: forEachIndex), - formatter.tokens[functionCallDotIndex] == .operator(".", .infix), - let indexAfterForEach = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: forEachIndex), - let indexBeforeFunctionCallDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: functionCallDotIndex) - else { return } - - // Parse either `{ ... }` or `({ ... })` - let forEachCallOpenParenIndex: Int? - let closureOpenBraceIndex: Int - let closureCloseBraceIndex: Int - let forEachCallCloseParenIndex: Int? - - switch formatter.tokens[indexAfterForEach] { - case .startOfScope("{"): - guard let endOfClosureScope = formatter.endOfScope(at: indexAfterForEach) else { return } - - forEachCallOpenParenIndex = nil - closureOpenBraceIndex = indexAfterForEach - closureCloseBraceIndex = endOfClosureScope - forEachCallCloseParenIndex = nil - - case .startOfScope("("): - guard let endOfFunctionCall = formatter.endOfScope(at: indexAfterForEach), - let indexAfterOpenParen = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterForEach), - formatter.tokens[indexAfterOpenParen] == .startOfScope("{"), - let endOfClosureScope = formatter.endOfScope(at: indexAfterOpenParen) - else { return } - - forEachCallOpenParenIndex = indexAfterForEach - closureOpenBraceIndex = indexAfterOpenParen - closureCloseBraceIndex = endOfClosureScope - forEachCallCloseParenIndex = endOfFunctionCall - - default: - return - } - - // Abort early for single-line loops - guard !formatter.options.preserveSingleLineForEach || formatter - .tokens[closureOpenBraceIndex ..< closureCloseBraceIndex].contains(where: { $0.isLinebreak }) - else { return } - - // Ignore closures with capture lists for now since they're rare - // in this context and add complexity - guard let firstIndexInClosureBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureOpenBraceIndex), - formatter.tokens[firstIndexInClosureBody] != .startOfScope("[") - else { return } - - // Parse the value that `forEach` is being called on - let forLoopSubjectRange: ClosedRange - var forLoopSubjectIdentifier: String? - - // Parse a functional chain backwards from the `forEach` token - var currentIndex = forEachIndex - - // Returns the start index of the chain component ending at the given index - func startOfChainComponent(at index: Int) -> Int? { - // The previous item in a dot chain can either be: - // 1. an identifier like `foo.` - // 2. a function call like `foo(...).` - // 3. a subscript like `foo[...]. - // 4. a trailing closure like `map { ... }` - // 5. Some other combination of parens / subscript like `(foo).` - // or even `foo["bar"]()()`. - // And any of these can be preceeded by one of the others - switch formatter.tokens[index] { - case let .identifier(identifierName): - // Allowlist certain dot chain elements that should be ignored. - // For example, in `foos.reversed().forEach { ... }` we want - // `forLoopSubjectIdentifier` to be `foos` rather than `reversed`. - let chainElementsToIgnore = Set([ - "reversed", "sorted", "shuffled", "enumerated", "dropFirst", "dropLast", - "map", "flatMap", "compactMap", "filter", "reduce", "lazy", - ]) - - if forLoopSubjectIdentifier == nil || chainElementsToIgnore.contains(forLoopSubjectIdentifier ?? "") { - // Since we have to pick a single identifier to represent the subject of the for loop, - // just use the last identifier in the chain - forLoopSubjectIdentifier = identifierName - } - - return index - - case .endOfScope(")"), .endOfScope("]"): - let closingParenIndex = index - guard let startOfScopeIndex = formatter.startOfScope(at: closingParenIndex), - let previousNonSpaceNonCommentIndex = formatter.index(of: .nonSpaceOrComment, before: startOfScopeIndex) - else { return nil } - - // When we find parens for a function call or braces for a subscript, - // continue parsing at the previous non-space non-comment token. - // - If the previous token is a newline then this isn't a function call - // and we'd stop parsing. `foo ()` is a function call but `foo\n()` isn't. - return startOfChainComponent(at: previousNonSpaceNonCommentIndex) ?? startOfScopeIndex - - case .endOfScope("}"): - // Stop parsing if we reach a trailing closure. - // Converting this to a for loop would result in unusual looking syntax like - // `for string in strings.map { $0.uppercased() } { print(string) }` - // which causes a warning to be emitted: "trailing closure in this context is - // confusable with the body of the statement; pass as a parenthesized argument - // to silence this warning". - return nil - - default: - return nil - } - } - - while let previousDotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: currentIndex), - formatter.tokens[previousDotIndex] == .operator(".", .infix), - let tokenBeforeDotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: previousDotIndex) - { - guard let startOfChainComponent = startOfChainComponent(at: tokenBeforeDotIndex) else { - // If we parse a dot we expect to parse at least one additional component in the chain. - // Otherwise we'd have a malformed chain that starts with a dot, so abort. - return - } - - currentIndex = startOfChainComponent - } - - guard currentIndex != forEachIndex else { return } - forLoopSubjectRange = currentIndex ... indexBeforeFunctionCallDot - - // If there is a `try` before the `forEach` we cannot know if the subject is async/throwing or the body, - // which makes it impossible to know if we should move it or *remove* it, so we must abort (same for await). - if let tokenIndexBeforeForLoop = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: currentIndex), - var prevToken = formatter.token(at: tokenIndexBeforeForLoop) - { - if prevToken.isUnwrapOperator { - prevToken = formatter.last(.nonSpaceOrComment, before: tokenIndexBeforeForLoop) ?? .space("") - } - if [.keyword("try"), .keyword("await")].contains(prevToken) { - return - } - } - - // If the chain includes linebreaks, don't convert it to a for loop. - // - // In this case converting something like: - // - // placeholderStrings - // .filter { $0.style == .fooBar } - // .map { $0.uppercased() } - // .forEach { print($0) } - // - // to: - // - // for placeholderString in placeholderStrings - // .filter { $0.style == .fooBar } - // .map { $0.uppercased() } { print($0) } - // - // would be a pretty obvious downgrade. - if formatter.tokens[forLoopSubjectRange].contains(where: \.isLinebreak) { - return - } - - /// The names of the argument to the `forEach` closure. - /// e.g. `["foo"]` in `forEach { foo in ... }` - /// or `["foo, bar"]` in `forEach { (foo: Foo, bar: Bar) in ... }` - let forEachValueNames: [String] - let inKeywordIndex: Int? - let isAnonymousClosure: Bool - - if let argumentList = formatter.parseClosureArgumentList(at: closureOpenBraceIndex) { - isAnonymousClosure = false - forEachValueNames = argumentList.argumentNames - inKeywordIndex = argumentList.inKeywordIndex - } else { - isAnonymousClosure = true - inKeywordIndex = nil - - if formatter.options.preserveAnonymousForEach { - return - } - - // We can't introduce an identifier that matches a keyword or already exists in - // the loop body so choose the first eligible option from a set of potential names - var eligibleValueNames = ["item", "element", "value"] - if var identifier = forLoopSubjectIdentifier?.singularized(), !identifier.isSwiftKeyword { - eligibleValueNames = [identifier] + eligibleValueNames - } - - // The chosen name shouldn't already exist in the closure body - guard let chosenValueName = eligibleValueNames.first(where: { name in - !formatter.tokens[closureOpenBraceIndex ... closureCloseBraceIndex].contains(where: { $0.string == name }) - }) else { return } - - forEachValueNames = [chosenValueName] - } - - // Validate that the closure body is eligible to be converted to a for loop - for closureBodyIndex in closureOpenBraceIndex ... closureCloseBraceIndex { - guard !formatter.indexIsWithinNestedClosure(closureBodyIndex, startOfScopeIndex: closureOpenBraceIndex) else { continue } - - // We can only handle anonymous closures that just use $0, since we don't have good names to - // use for other arguments like $1, $2, etc. If the closure has an anonymous argument - // other than just $0 then we have to ignore it. - if formatter.tokens[closureBodyIndex].string.hasPrefix("$"), - let intValue = Int(formatter.tokens[closureBodyIndex].string.dropFirst()), - intValue != 0 - { - return - } - - // We can convert `return`s to `continue`, but only when `return` is on its own line. - // It's legal to write something like `return print("foo")` in a `forEach` as long as - // you're still returning a `Void` value. Since `continue print("foo")` isn't legal, - // we should just ignore this closure. - if formatter.tokens[closureBodyIndex] == .keyword("return"), - let tokenAfterReturnKeyword = formatter.next(.nonSpaceOrComment, after: closureBodyIndex), - !tokenAfterReturnKeyword.isLinebreak - { - return - } - } - - // Start updating the `forEach` call to a `for .. in .. {` loop - for closureBodyIndex in closureOpenBraceIndex ... closureCloseBraceIndex { - guard !formatter.indexIsWithinNestedClosure(closureBodyIndex, startOfScopeIndex: closureOpenBraceIndex) else { continue } - - // The for loop won't have any `$0` identifiers anymore, so we have to - // update those to the value at the current loop index - if isAnonymousClosure, formatter.tokens[closureBodyIndex].string == "$0" { - formatter.replaceToken(at: closureBodyIndex, with: .identifier(forEachValueNames[0])) - } - - // In a `forEach` closure, `return` continues to the next loop iteration. - // To get the same behavior in a for loop we convert `return`s to `continue`s. - if formatter.tokens[closureBodyIndex] == .keyword("return") { - formatter.replaceToken(at: closureBodyIndex, with: .keyword("continue")) - } - } - - if let forEachCallCloseParenIndex = forEachCallCloseParenIndex { - formatter.removeToken(at: forEachCallCloseParenIndex) - } - - // Construct the new for loop - var newTokens: [Token] = [ - .keyword("for"), - .space(" "), - ] - - let forEachValueNameTokens: [Token] - if forEachValueNames.count == 1 { - newTokens.append(.identifier(forEachValueNames[0])) - } else { - newTokens.append(contentsOf: tokenize("(\(forEachValueNames.joined(separator: ", ")))")) - } - - newTokens.append(contentsOf: [ - .space(" "), - .keyword("in"), - .space(" "), - ]) - - newTokens.append(contentsOf: formatter.tokens[forLoopSubjectRange]) - - newTokens.append(contentsOf: [ - .space(" "), - .startOfScope("{"), - ]) - - formatter.replaceTokens( - in: (forLoopSubjectRange.lowerBound) ... (inKeywordIndex ?? closureOpenBraceIndex), - with: newTokens - ) - } - } - - public let noExplicitOwnership = FormatRule( - help: "Don't use explicit ownership modifiers (borrowing / consuming).", - disabledByDefault: true - ) { formatter in - formatter.forEachToken { keywordIndex, token in - guard [.identifier("borrowing"), .identifier("consuming")].contains(token), - let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: keywordIndex) - else { return } - - // Use of `borrowing` and `consuming` as ownership modifiers - // immediately precede a valid type, or the `func` keyword. - // You could also simply use these names as a property, - // like `let borrowing = foo` or `func myFunc(borrowing foo: Foo)`. - // As a simple heuristic to detect the difference, attempt to parse the - // following tokens as a type, and require that it doesn't start with lower-case letter. - let isValidOwnershipModifier: Bool - if formatter.tokens[nextTokenIndex] == .keyword("func") { - isValidOwnershipModifier = true - } - - else if let type = formatter.parseType(at: nextTokenIndex), - type.name.first?.isLowercase == false - { - isValidOwnershipModifier = true - } - - else { - isValidOwnershipModifier = false - } - - if isValidOwnershipModifier { - formatter.removeTokens(in: keywordIndex ..< nextTokenIndex) - } - } - } - - public let wrapMultilineConditionalAssignment = FormatRule( - help: "Wrap multiline conditional assignment expressions after the assignment operator.", - disabledByDefault: true, - orderAfter: ["conditionalAssignment"], - sharedOptions: ["linebreaks"] - ) { formatter in - formatter.forEach(.keyword) { startOfCondition, keywordToken in - guard [.keyword("if"), .keyword("switch")].contains(keywordToken), - let assignmentIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfCondition), - formatter.tokens[assignmentIndex] == .operator("=", .infix), - let endOfPropertyDefinition = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: assignmentIndex) - else { return } - - // Verify the RHS of the assignment is an if/switch expression - guard let startOfConditionalExpression = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: assignmentIndex), - ["if", "switch"].contains(formatter.tokens[startOfConditionalExpression].string), - let conditionalBranches = formatter.conditionalBranches(at: startOfConditionalExpression), - let lastBranch = conditionalBranches.last - else { return } - - // If the entire expression is on a single line, we leave the formatting as-is - guard !formatter.onSameLine(startOfConditionalExpression, lastBranch.endOfBranch) else { - return - } - - // The `=` should be on the same line as the rest of the property - if !formatter.onSameLine(endOfPropertyDefinition, assignmentIndex), - formatter.last(.nonSpaceOrComment, before: assignmentIndex)?.isLinebreak == true, - let previousToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: assignmentIndex), - formatter.onSameLine(endOfPropertyDefinition, previousToken) - { - // Move the assignment operator to follow the previous token. - // Also remove any trailing space after the previous position - // of the assignment operator. - if formatter.tokens[assignmentIndex + 1].isSpaceOrLinebreak { - formatter.removeToken(at: assignmentIndex + 1) - } - - formatter.removeToken(at: assignmentIndex) - formatter.insert([.space(" "), .operator("=", .infix)], at: previousToken + 1) - } - - // And there should be a line break between the `=` and the `if` / `switch` keyword - else if !formatter.tokens[(assignmentIndex + 1) ..< startOfConditionalExpression].contains(where: \.isLinebreak) { - formatter.insertLinebreak(at: startOfConditionalExpression - 1) - } - } - } - - public let blankLineAfterSwitchCase = FormatRule( - help: """ - Insert a blank line after multiline switch cases (excluding the last case, - which is followed by a closing brace). - """, - disabledByDefault: true, - orderAfter: ["redundantBreak"] - ) { formatter in - formatter.forEach(.keyword("switch")) { switchIndex, _ in - guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } - - for switchCase in switchCases.reversed() { - // Any switch statement that spans multiple lines should be followed by a blank line - // (excluding the last case, which is followed by a closing brace). - if switchCase.spansMultipleLines, - !switchCase.isLastCase, - !switchCase.isFollowedByBlankLine - { - switchCase.insertTrailingBlankLine(using: formatter) - } - - // The last case should never be followed by a blank line, since it's - // already followed by a closing brace. - if switchCase.isLastCase, - switchCase.isFollowedByBlankLine - { - switchCase.removeTrailingBlankLine(using: formatter) - } - } - } - } - - public let consistentSwitchCaseSpacing = FormatRule( - help: "Ensures consistent spacing among all of the cases in a switch statement.", - orderAfter: ["blankLineAfterSwitchCase"] - ) { formatter in - formatter.forEach(.keyword("switch")) { switchIndex, _ in - guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } - - // When counting the switch cases, exclude the last case (which should never have a trailing blank line). - let countWithTrailingBlankLine = switchCases.filter { $0.isFollowedByBlankLine && !$0.isLastCase }.count - let countWithoutTrailingBlankLine = switchCases.filter { !$0.isFollowedByBlankLine && !$0.isLastCase }.count - - // We want the spacing to be consistent for all switch cases, - // so use whichever formatting is used for the majority of cases. - var allCasesShouldHaveBlankLine = countWithTrailingBlankLine >= countWithoutTrailingBlankLine - - // When the `blankLinesBetweenChainedFunctions` rule is enabled, and there is a switch case - // that is required to span multiple lines, then all cases must span multiple lines. - // (Since if this rule removed the blank line from that case, it would contradict the other rule) - if formatter.options.enabledRules.contains(FormatRules.blankLineAfterSwitchCase.name), - switchCases.contains(where: { $0.spansMultipleLines && !$0.isLastCase }) - { - allCasesShouldHaveBlankLine = true - } - - for switchCase in switchCases.reversed() { - if !switchCase.isFollowedByBlankLine, allCasesShouldHaveBlankLine, !switchCase.isLastCase { - switchCase.insertTrailingBlankLine(using: formatter) - } - - if switchCase.isFollowedByBlankLine, !allCasesShouldHaveBlankLine || switchCase.isLastCase { - switchCase.removeTrailingBlankLine(using: formatter) - } - } - } - } - - public let redundantProperty = FormatRule( - help: "Simplifies redundant property definitions that are immediately returned.", - disabledByDefault: true, - orderAfter: ["propertyType"] - ) { formatter in - formatter.forEach(.keyword) { introducerIndex, introducerToken in - // Find properties like `let identifier = value` followed by `return identifier` - guard ["let", "var"].contains(introducerToken.string), - let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), - let (assignmentIndex, expressionRange) = property.value, - let returnIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: expressionRange.upperBound), - formatter.tokens[returnIndex] == .keyword("return"), - let returnedValueIndex = formatter.index(of: .nonSpaceOrComment, after: returnIndex), - let returnedExpression = formatter.parseExpressionRange(startingAt: returnedValueIndex, allowConditionalExpressions: true), - formatter.tokens[returnedExpression] == [.identifier(property.identifier)] - else { return } - - let returnRange = formatter.startOfLine(at: returnIndex) ... formatter.endOfLine(at: returnedExpression.upperBound) - let propertyRange = introducerIndex ... expressionRange.upperBound - - guard !propertyRange.overlaps(returnRange) else { return } - - // Remove the line with the `return identifier` statement. - formatter.removeTokens(in: returnRange) - - // If there's nothing but whitespace between the end of the expression - // and the return statement, we can remove all of it. But if there's a comment, - // we should preserve it. - let rangeBetweenExpressionAndReturn = (expressionRange.upperBound + 1) ..< (returnRange.lowerBound - 1) - if formatter.tokens[rangeBetweenExpressionAndReturn].allSatisfy(\.isSpaceOrLinebreak) { - formatter.removeTokens(in: rangeBetweenExpressionAndReturn) - } - - // Replace the `let identifier = value` with `return value` - formatter.replaceTokens( - in: introducerIndex ..< expressionRange.lowerBound, - with: [.keyword("return"), .space(" ")] - ) - } - } - - public let redundantTypedThrows = FormatRule(help: "Converts `throws(any Error)` to `throws`, and converts `throws(Never)` to non-throwing.") { formatter in - - formatter.forEach(.keyword("throws")) { throwsIndex, _ in - guard // Typed throws was added in Swift 6.0: https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md - formatter.options.swiftVersion >= "6.0", - let startOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: throwsIndex), - formatter.tokens[startOfScope] == .startOfScope("("), - let endOfScope = formatter.endOfScope(at: startOfScope) - else { return } - - let throwsTypeRange = (startOfScope + 1) ..< endOfScope - let throwsType: String = formatter.tokens[throwsTypeRange].map { $0.string }.joined() - - if throwsType == "Never" { - if formatter.tokens[endOfScope + 1].isSpace { - formatter.removeTokens(in: throwsIndex ... endOfScope + 1) - } else { - formatter.removeTokens(in: throwsIndex ... endOfScope) - } - } - - // We don't remove `(Error)` because we can't guarantee it will reference the `Swift.Error` protocol - // (it's relatively common to define a custom error like `enum Error: Swift.Error { ... }`). - if throwsType == "any Error" || throwsType == "any Swift.Error" || throwsType == "Swift.Error" { - formatter.removeTokens(in: startOfScope ... endOfScope) - } - } - } - - public let propertyType = FormatRule( - help: "Convert property declarations to use inferred types (`let foo = Foo()`) or explicit types (`let foo: Foo = .init()`).", - disabledByDefault: true, - orderAfter: ["redundantType"], - options: ["inferredtypes", "preservesymbols"], - sharedOptions: ["redundanttype"] - ) { formatter in - formatter.forEach(.operator("=", .infix)) { equalsIndex, _ in - // Preserve all properties in conditional statements like `if let foo = Bar() { ... }` - guard !formatter.isConditionalStatement(at: equalsIndex) else { return } - - // Determine whether the type should use the inferred syntax (`let foo = Foo()`) - // of the explicit syntax (`let foo: Foo = .init()`). - let useInferredType: Bool - switch formatter.options.redundantType { - case .inferred: - useInferredType = true - - case .explicit: - useInferredType = false - - case .inferLocalsOnly: - switch formatter.declarationScope(at: equalsIndex) { - case .global, .type: - useInferredType = false - case .local: - useInferredType = true - } - } - - guard let introducerIndex = formatter.indexOfLastSignificantKeyword(at: equalsIndex), - ["var", "let"].contains(formatter.tokens[introducerIndex].string), - let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), - let rhsExpressionRange = property.value?.expressionRange - else { return } - - let rhsStartIndex = rhsExpressionRange.lowerBound - - if useInferredType { - guard let type = property.type else { return } - let typeTokens = formatter.tokens[type.range] - - // Preserve the existing formatting if the LHS type is optional. - // - `let foo: Foo? = .foo` is valid, but `let foo = Foo?.foo` - // is invalid if `.foo` is defined on `Foo` but not `Foo?`. - guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { return } - - // Preserve the existing formatting if the LHS type is an existential (indicated with `any`). - // - The `extension MyProtocol where Self == MyType { ... }` syntax - // creates static members where `let foo: any MyProtocol = .myType` - // is valid, but `let foo = (any MyProtocol).myType` isn't. - guard typeTokens.first?.string != "any" else { return } - - // Preserve the existing formatting if the RHS expression has a top-level infix operator. - // - `let value: ClosedRange = .zero ... 10` would not be valid to convert to - // `let value = ClosedRange.zero ... 10`. - if let nextInfixOperatorIndex = formatter.index(after: rhsStartIndex, where: { token in - token.isOperator(ofType: .infix) && token != .operator(".", .infix) - }), - rhsExpressionRange.contains(nextInfixOperatorIndex) - { - return - } - - // Preserve the formatting as-is if the type is manually excluded - if formatter.options.preserveSymbols.contains(type.name) { - return - } - - // If the RHS starts with a leading dot, then we know its accessing some static member on this type. - if formatter.tokens[rhsStartIndex].isOperator(".") { - // Preserve the formatting as-is if the identifier is manually excluded - if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), - formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) - { return } - - // Update the . token from a prefix operator to an infix operator. - formatter.replaceToken(at: rhsStartIndex, with: .operator(".", .infix)) - - // Insert a copy of the type on the RHS before the dot - formatter.insert(typeTokens, at: rhsStartIndex) - } - - // If the RHS is an if/switch expression, check that each branch starts with a leading dot - else if formatter.options.inferredTypesInConditionalExpressions, - ["if", "switch"].contains(formatter.tokens[rhsStartIndex].string), - let conditonalBranches = formatter.conditionalBranches(at: rhsStartIndex) - { - var hasInvalidConditionalBranch = false - formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in - guard let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { - hasInvalidConditionalBranch = true - return - } - - if !formatter.tokens[firstTokenInBranch].isOperator(".") { - hasInvalidConditionalBranch = true - } - - // Preserve the formatting as-is if the identifier is manually excluded - if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), - formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) - { - hasInvalidConditionalBranch = true - } - } - - guard !hasInvalidConditionalBranch else { return } - - // Insert a copy of the type on the RHS before the dot in each branch - formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in - guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return } - - // Update the . token from a prefix operator to an infix operator. - formatter.replaceToken(at: dotIndex, with: .operator(".", .infix)) - - // Insert a copy of the type on the RHS before the dot - formatter.insert(typeTokens, at: dotIndex) - } - } - - else { - return - } - - // Remove the colon and explicit type before the equals token - formatter.removeTokens(in: type.colonIndex ... type.range.upperBound) - } - - // If using explicit types, convert properties to the format `let foo: Foo = .init()`. - else { - guard // When parsing the type, exclude lowercase identifiers so `foo` isn't parsed as a type, - // and so `Foo.init` is parsed as `Foo` instead of `Foo.init`. - let rhsType = formatter.parseType(at: rhsStartIndex, excludeLowercaseIdentifiers: true), - property.type == nil, - let indexAfterIdentifier = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: property.identifierIndex), - formatter.tokens[indexAfterIdentifier] != .delimiter(":"), - let indexAfterType = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsType.range.upperBound), - [.operator(".", .infix), .startOfScope("(")].contains(formatter.tokens[indexAfterType]), - !rhsType.name.contains(".") - else { return } - - // Preserve the existing formatting if the RHS expression has a top-level operator. - // - `let foo = Foo.foo.bar` would not be valid to convert to `let foo: Foo = .foo.bar`. - let operatorSearchIndex = formatter.tokens[indexAfterType].isStartOfScope ? (indexAfterType - 1) : indexAfterType - if let nextInfixOperatorIndex = formatter.index(after: operatorSearchIndex, where: { token in - token.isOperator(ofType: .infix) - }), - rhsExpressionRange.contains(nextInfixOperatorIndex) - { - return - } - - // Preserve any types that have been manually excluded. - // Preserve any `Void` types and tuples, since they're special and don't support things like `.init` - guard !(formatter.options.preserveSymbols + ["Void"]).contains(rhsType.name), - !rhsType.name.hasPrefix("(") - else { return } - - // A type name followed by a `(` is an implicit `.init(`. Insert a `.init` - // so that the init call stays valid after we move the type to the LHS. - if formatter.tokens[indexAfterType] == .startOfScope("(") { - // Preserve the existing format if `init` is manually excluded - if formatter.options.preserveSymbols.contains("init") { - return - } - - formatter.insert([.operator(".", .prefix), .identifier("init")], at: indexAfterType) - } - - // If the type name is followed by an infix `.` operator, convert it to a prefix operator. - else if formatter.tokens[indexAfterType] == .operator(".", .infix) { - // Exclude types with dots followed by a member access. - // - For example with something like `Color.Theme.themeColor`, we don't know - // if the property is `static var themeColor: Color` or `static var themeColor: Color.Theme`. - // - This isn't a problem with something like `Color.Theme()`, which we can reasonably assume - // is an initializer - if rhsType.name.contains(".") { return } - - // Preserve the formatting as-is if the identifier is manually excluded. - // Don't convert `let foo = Foo.self` to `let foo: Foo = .self`, since `.self` returns the metatype - let symbolsToExclude = formatter.options.preserveSymbols + ["self"] - if let indexAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterType), - symbolsToExclude.contains(formatter.tokens[indexAfterDot].string) - { return } - - formatter.replaceToken(at: indexAfterType, with: .operator(".", .prefix)) - } - - // Move the type name to the LHS of the property, followed by a colon - let typeTokens = formatter.tokens[rhsType.range] - formatter.removeTokens(in: rhsType.range) - formatter.insert([.delimiter(":"), .space(" ")] + typeTokens, at: property.identifierIndex + 1) - } - } - } - - public let docCommentsBeforeAttributes = FormatRule( - help: "Place doc comments on declarations before any attributes.", - orderAfter: ["docComments"] - ) { formatter in - formatter.forEachToken(where: \.isDeclarationTypeKeyword) { keywordIndex, _ in - // Parse the attributes on this declaration if present - let startOfAttributes = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) - guard formatter.tokens[startOfAttributes].isAttribute else { return } - - let attributes = formatter.attributes(startingAt: startOfAttributes) - guard !attributes.isEmpty else { return } - - let attributesRange = attributes.first!.startIndex ... attributes.last!.endIndex - - // If there's a doc comment between the attributes and the rest of the declaration, - // move it above the attributes. - guard let linebreakAfterAttributes = formatter.index(of: .linebreak, after: attributesRange.upperBound), - let indexAfterAttributes = formatter.index(of: .nonSpaceOrLinebreak, after: linebreakAfterAttributes), - indexAfterAttributes < keywordIndex, - let restOfDeclaration = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attributesRange.upperBound), - formatter.isDocComment(startOfComment: indexAfterAttributes) - else { return } - - let commentRange = indexAfterAttributes ..< restOfDeclaration - let comment = formatter.tokens[commentRange] - - formatter.removeTokens(in: commentRange) - formatter.insert(comment, at: startOfAttributes) - } - } -} diff --git a/Sources/Rules/Acronyms.swift b/Sources/Rules/Acronyms.swift new file mode 100644 index 000000000..399b74ec0 --- /dev/null +++ b/Sources/Rules/Acronyms.swift @@ -0,0 +1,76 @@ +// +// Acronyms.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let acronyms = FormatRule( + help: "Capitalize acronyms when the first character is capitalized.", + disabledByDefault: true, + options: ["acronyms"] + ) { formatter in + formatter.forEachToken { index, token in + guard token.is(.identifier) || token.isComment else { return } + + var updatedText = token.string + + for acronym in formatter.options.acronyms { + let find = acronym.capitalized + let replace = acronym.uppercased() + + for replaceCandidateRange in token.string.ranges(of: find) { + let acronymShouldBeCapitalized: Bool + + if replaceCandidateRange.upperBound < token.string.indices.last! { + let indexAfterMatch = replaceCandidateRange.upperBound + let characterAfterMatch = token.string[indexAfterMatch] + + // Only treat this as an acronym if the next character is uppercased, + // to prevent "Id" from matching strings like "Identifier". + if characterAfterMatch.isUppercase || characterAfterMatch.isWhitespace { + acronymShouldBeCapitalized = true + } + + // But if the next character is 's', and then the character after the 's' is uppercase, + // allow the acronym to be capitalized (to handle the plural case, `Ids` to `IDs`) + else if characterAfterMatch == Character("s") { + if indexAfterMatch < token.string.indices.last! { + let characterAfterNext = token.string[token.string.index(after: indexAfterMatch)] + acronymShouldBeCapitalized = (characterAfterNext.isUppercase || characterAfterNext.isWhitespace) + } else { + acronymShouldBeCapitalized = true + } + } else { + acronymShouldBeCapitalized = false + } + } else { + acronymShouldBeCapitalized = true + } + + if acronymShouldBeCapitalized { + updatedText.replaceSubrange(replaceCandidateRange, with: replace) + } + } + } + + if token.string != updatedText { + let updatedToken: Token + switch token { + case .identifier: + updatedToken = .identifier(updatedText) + case .commentBody: + updatedToken = .commentBody(updatedText) + default: + return + } + + formatter.replaceToken(at: index, with: updatedToken) + } + } + } +} diff --git a/Sources/Rules/AndOperator.swift b/Sources/Rules/AndOperator.swift new file mode 100644 index 000000000..f46524f6d --- /dev/null +++ b/Sources/Rules/AndOperator.swift @@ -0,0 +1,85 @@ +// +// AndOperator.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace the `&&` operator with `,` where applicable + static let andOperator = FormatRule( + help: "Prefer comma over `&&` in `if`, `guard` or `while` conditions." + ) { formatter in + formatter.forEachToken { i, token in + switch token { + case .keyword("if"), .keyword("guard"), + .keyword("while") where formatter.last(.keyword, before: i) != .keyword("repeat"): + break + default: + return + } + guard var endIndex = formatter.index(of: .startOfScope("{"), after: i) else { + return + } + if formatter.options.swiftVersion < "5.3", formatter.isInResultBuilder(at: i) { + return + } + var index = i + 1 + var chevronIndex: Int? + outer: while index < endIndex { + switch formatter.tokens[index] { + case .operator("&&", .infix): + let endOfGroup = formatter.index(of: .delimiter(","), after: index) ?? endIndex + var nextOpIndex = index + while let next = formatter.index(of: .operator, after: nextOpIndex) { + if formatter.tokens[next] == .operator("||", .infix) { + index = endOfGroup + continue outer + } + nextOpIndex = next + } + if let chevronIndex = chevronIndex, + formatter.index(of: .operator(">", .infix), in: index ..< endIndex) != nil + { + // Check if this would cause ambiguity for chevrons + var tokens = Array(formatter.tokens[i ... endIndex]) + tokens[index - i] = .delimiter(",") + tokens.append(.endOfScope("}")) + let reparsed = tokenize(sourceCode(for: tokens)) + if reparsed[chevronIndex - i] == .startOfScope("<") { + return + } + } + formatter.replaceToken(at: index, with: .delimiter(",")) + if formatter.tokens[index - 1] == .space(" ") { + formatter.removeToken(at: index - 1) + endIndex -= 1 + index -= 1 + } else if let prevIndex = formatter.index(of: .nonSpace, before: index), + formatter.tokens[prevIndex].isLinebreak, let nonLinbreak = + formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex) + { + formatter.removeToken(at: index) + formatter.insert(.delimiter(","), at: nonLinbreak + 1) + if formatter.tokens[index + 1] == .space(" ") { + formatter.removeToken(at: index + 1) + endIndex -= 1 + } + } + case .operator("<", .infix): + chevronIndex = index + case .operator("||", .infix), .operator("=", .infix), .keyword("try"): + index = formatter.index(of: .delimiter(","), after: index) ?? endIndex + case .startOfScope: + index = formatter.endOfScope(at: index) ?? endIndex + default: + break + } + index += 1 + } + } + } +} diff --git a/Sources/Rules/AnyObjectProtocol.swift b/Sources/Rules/AnyObjectProtocol.swift new file mode 100644 index 000000000..dd1e3f20a --- /dev/null +++ b/Sources/Rules/AnyObjectProtocol.swift @@ -0,0 +1,31 @@ +// +// AnyObjectProtocol.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Prefer `AnyObject` over `class` for class-based protocols + static let anyObjectProtocol = FormatRule( + help: "Prefer `AnyObject` over `class` in protocol definitions." + ) { formatter in + formatter.forEach(.keyword("protocol")) { i, _ in + guard formatter.options.swiftVersion >= "4.1", + let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0.isIdentifier + }), let colonIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: { + $0 == .delimiter(":") + }), let classIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex, if: { + $0 == .keyword("class") + }) + else { + return + } + formatter.replaceToken(at: classIndex, with: .identifier("AnyObject")) + } + } +} diff --git a/Sources/Rules/ApplicationMain.swift b/Sources/Rules/ApplicationMain.swift new file mode 100644 index 000000000..96eeb1756 --- /dev/null +++ b/Sources/Rules/ApplicationMain.swift @@ -0,0 +1,32 @@ +// +// ApplicationMain.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace the obsolete `@UIApplicationMain` and `@NSApplicationMain` + /// attributes with `@main` in Swift 5.3 and above, per SE-0383 + static let applicationMain = FormatRule( + help: """ + Replace obsolete @UIApplicationMain and @NSApplicationMain attributes + with @main for Swift 5.3 and above. + """ + ) { formatter in + guard formatter.options.swiftVersion >= "5.3" else { + return + } + formatter.forEachToken(where: { + [ + .keyword("@UIApplicationMain"), + .keyword("@NSApplicationMain"), + ].contains($0) + }) { i, _ in + formatter.replaceToken(at: i, with: .keyword("@main")) + } + } +} diff --git a/Sources/Rules/AssertionFailures.swift b/Sources/Rules/AssertionFailures.swift new file mode 100644 index 000000000..96f1e2a05 --- /dev/null +++ b/Sources/Rules/AssertionFailures.swift @@ -0,0 +1,43 @@ +// +// AssertionFailures.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let assertionFailures = FormatRule( + help: """ + Changes all instances of assert(false, ...) to assertionFailure(...) + and precondition(false, ...) to preconditionFailure(...). + """ + ) { formatter in + formatter.forEachToken { i, token in + switch token { + case .identifier("assert"), .identifier("precondition"): + guard let scopeStart = formatter.index(of: .nonSpace, after: i, if: { + $0 == .startOfScope("(") + }), let identifierIndex = formatter.index(of: .nonSpaceOrLinebreak, after: scopeStart, if: { + $0 == .identifier("false") + }), var endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: identifierIndex) else { + return + } + + // if there are more arguments, replace the comma and space as well + if formatter.tokens[endIndex] == .delimiter(",") { + endIndex = formatter.index(of: .nonSpace, after: endIndex) ?? endIndex + } + + let replacements = ["assert": "assertionFailure", "precondition": "preconditionFailure"] + formatter.replaceTokens(in: i ..< endIndex, with: [ + .identifier(replacements[token.string]!), .startOfScope("("), + ]) + default: + break + } + } + } +} diff --git a/Sources/Rules/BlankLineAfterImports.swift b/Sources/Rules/BlankLineAfterImports.swift new file mode 100644 index 000000000..4aead8ef4 --- /dev/null +++ b/Sources/Rules/BlankLineAfterImports.swift @@ -0,0 +1,40 @@ +// +// BlankLineAfterImports.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Insert blank line after import statements + static let blankLineAfterImports = FormatRule( + help: """ + Insert blank line after import statements. + """, + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.keyword("import")) { currentImportIndex, _ in + guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex), + var nextIndex = formatter.index(of: .nonSpace, after: endOfLine) + else { + return + } + var keyword: Token = formatter.tokens[nextIndex] + while keyword == .startOfScope("#if") || keyword.isModifierKeyword || keyword.isAttribute, + let index = formatter.index(of: .keyword, after: nextIndex) + { + nextIndex = index + keyword = formatter.tokens[nextIndex] + } + switch formatter.tokens[nextIndex] { + case .linebreak, .keyword("import"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"): + break + default: + formatter.insertLinebreak(at: endOfLine) + } + } + } +} diff --git a/Sources/Rules/BlankLineAfterSwitchCase.swift b/Sources/Rules/BlankLineAfterSwitchCase.swift new file mode 100644 index 000000000..9543c8e46 --- /dev/null +++ b/Sources/Rules/BlankLineAfterSwitchCase.swift @@ -0,0 +1,43 @@ +// +// BlankLineAfterSwitchCase.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let blankLineAfterSwitchCase = FormatRule( + help: """ + Insert a blank line after multiline switch cases (excluding the last case, + which is followed by a closing brace). + """, + disabledByDefault: true, + orderAfter: [.redundantBreak] + ) { formatter in + formatter.forEach(.keyword("switch")) { switchIndex, _ in + guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } + + for switchCase in switchCases.reversed() { + // Any switch statement that spans multiple lines should be followed by a blank line + // (excluding the last case, which is followed by a closing brace). + if switchCase.spansMultipleLines, + !switchCase.isLastCase, + !switchCase.isFollowedByBlankLine + { + switchCase.insertTrailingBlankLine(using: formatter) + } + + // The last case should never be followed by a blank line, since it's + // already followed by a closing brace. + if switchCase.isLastCase, + switchCase.isFollowedByBlankLine + { + switchCase.removeTrailingBlankLine(using: formatter) + } + } + } + } +} diff --git a/Sources/Rules/BlankLinesAroundMark.swift b/Sources/Rules/BlankLinesAroundMark.swift new file mode 100644 index 000000000..fffa239fd --- /dev/null +++ b/Sources/Rules/BlankLinesAroundMark.swift @@ -0,0 +1,38 @@ +// +// BlankLinesAroundMark.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Adds a blank line around MARK: comments + static let blankLinesAroundMark = FormatRule( + help: "Insert blank line before and after `MARK:` comments.", + options: ["lineaftermarks"], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEachToken { i, token in + guard case let .commentBody(comment) = token, comment.hasPrefix("MARK:"), + let startIndex = formatter.index(of: .nonSpace, before: i), + formatter.tokens[startIndex] == .startOfScope("//") else { return } + if let nextIndex = formatter.index(of: .linebreak, after: i), + let nextToken = formatter.next(.nonSpace, after: nextIndex), + !nextToken.isLinebreak, nextToken != .endOfScope("}"), + formatter.options.lineAfterMarks + { + formatter.insertLinebreak(at: nextIndex) + } + if formatter.options.insertBlankLines, + let lastIndex = formatter.index(of: .linebreak, before: startIndex), + let lastToken = formatter.last(.nonSpace, before: lastIndex), + !lastToken.isLinebreak, lastToken != .startOfScope("{") + { + formatter.insertLinebreak(at: lastIndex) + } + } + } +} diff --git a/Sources/Rules/BlankLinesAtEndOfScope.swift b/Sources/Rules/BlankLinesAtEndOfScope.swift new file mode 100644 index 000000000..982dec99e --- /dev/null +++ b/Sources/Rules/BlankLinesAtEndOfScope.swift @@ -0,0 +1,63 @@ +// +// BlankLinesAtEndOfScope.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove blank lines immediately before a closing brace, bracket, paren or chevron + /// unless it's followed by more code on the same line (e.g. } else { ) + static let blankLinesAtEndOfScope = FormatRule( + help: "Remove trailing blank line at the end of a scope.", + orderAfter: [.organizeDeclarations], + sharedOptions: ["typeblanklines"] + ) { formatter in + formatter.forEach(.startOfScope) { startOfScopeIndex, _ in + guard let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex) else { return } + let endOfScope = formatter.tokens[endOfScopeIndex] + + guard ["}", ")", "]", ">"].contains(endOfScope.string), + // If there is extra code after the closing scope on the same line, ignore it + (formatter.next(.nonSpaceOrComment, after: endOfScopeIndex).map { $0.isLinebreak }) ?? true + else { return } + + // Consumers can choose whether or not this rule should apply to type bodies + if !formatter.options.removeStartOrEndBlankLinesFromTypes, + ["class", "actor", "struct", "enum", "protocol", "extension"].contains( + formatter.lastSignificantKeyword(at: startOfScopeIndex, excluding: ["where"])) + { + return + } + + // Find previous non-space token + var index = endOfScopeIndex - 1 + var indexOfFirstLineBreak: Int? + var indexOfLastLineBreak: Int? + loop: while let token = formatter.token(at: index) { + switch token { + case .linebreak: + indexOfFirstLineBreak = index + if indexOfLastLineBreak == nil { + indexOfLastLineBreak = index + } + case .space: + break + default: + break loop + } + index -= 1 + } + if formatter.options.removeBlankLines, + let indexOfFirstLineBreak = indexOfFirstLineBreak, + indexOfFirstLineBreak != indexOfLastLineBreak + { + formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak!) + return + } + } + } +} diff --git a/Sources/Rules/BlankLinesAtStartOfScope.swift b/Sources/Rules/BlankLinesAtStartOfScope.swift new file mode 100644 index 000000000..c787d7c39 --- /dev/null +++ b/Sources/Rules/BlankLinesAtStartOfScope.swift @@ -0,0 +1,53 @@ +// +// BlankLinesAtStartOfScope.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove blank lines immediately after an opening brace, bracket, paren or chevron + static let blankLinesAtStartOfScope = FormatRule( + help: "Remove leading blank line at the start of a scope.", + orderAfter: [.organizeDeclarations], + options: ["typeblanklines"] + ) { formatter in + formatter.forEach(.startOfScope) { i, token in + guard ["{", "(", "[", "<"].contains(token.string), + let indexOfFirstLineBreak = formatter.index(of: .nonSpaceOrComment, after: i), + // If there is extra code on the same line, ignore it + formatter.tokens[indexOfFirstLineBreak].isLinebreak + else { return } + + // Consumers can choose whether or not this rule should apply to type bodies + if !formatter.options.removeStartOrEndBlankLinesFromTypes, + ["class", "actor", "struct", "enum", "protocol", "extension"].contains( + formatter.lastSignificantKeyword(at: i, excluding: ["where"])) + { + return + } + + // Find next non-space token + var index = indexOfFirstLineBreak + 1 + var indexOfLastLineBreak = indexOfFirstLineBreak + loop: while let token = formatter.token(at: index) { + switch token { + case .linebreak: + indexOfLastLineBreak = index + case .space: + break + default: + break loop + } + index += 1 + } + if formatter.options.removeBlankLines, indexOfFirstLineBreak != indexOfLastLineBreak { + formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak) + return + } + } + } +} diff --git a/Sources/Rules/BlankLinesBetweenChainedFunctions.swift b/Sources/Rules/BlankLinesBetweenChainedFunctions.swift new file mode 100644 index 000000000..9f7cef0c2 --- /dev/null +++ b/Sources/Rules/BlankLinesBetweenChainedFunctions.swift @@ -0,0 +1,43 @@ +// +// BlankLinesBetweenChainedFunctions.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove blank lines between chained functions but keep the linebreaks + static let blankLinesBetweenChainedFunctions = FormatRule( + help: """ + Remove blank lines between chained functions but keep the linebreaks. + """ + ) { formatter in + formatter.forEach(.operator(".", .infix)) { i, _ in + let endOfLine = formatter.endOfLine(at: i) + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfLine), + formatter.tokens[nextIndex] == .operator(".", .infix), + // Make sure to preserve any code comment between the two lines + let nextTokenOrComment = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine) + { + if formatter.tokens[nextTokenOrComment].isComment { + if formatter.options.enabledRules.contains(FormatRule.blankLinesAroundMark.name), + case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenOrComment), + body.hasPrefix("MARK:") + { + return + } + if let endOfComment = formatter.index(of: .comment, before: nextIndex) { + let endOfLine = formatter.endOfLine(at: endOfComment) + let startOfLine = formatter.startOfLine(at: nextIndex) + formatter.removeTokens(in: endOfLine + 1 ..< startOfLine) + } + } + let startOfLine = formatter.startOfLine(at: nextTokenOrComment) + formatter.removeTokens(in: endOfLine + 1 ..< startOfLine) + } + } + } +} diff --git a/Sources/Rules/BlankLinesBetweenImports.swift b/Sources/Rules/BlankLinesBetweenImports.swift new file mode 100644 index 000000000..9b751c073 --- /dev/null +++ b/Sources/Rules/BlankLinesBetweenImports.swift @@ -0,0 +1,32 @@ +// +// BlankLinesBetweenImports.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove blank lines between import statements + static let blankLinesBetweenImports = FormatRule( + help: """ + Remove blank lines between import statements. + """, + disabledByDefault: true, + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.keyword("import")) { currentImportIndex, _ in + guard let endOfLine = formatter.index(of: .linebreak, after: currentImportIndex), + let nextImportIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfLine, if: { + $0 == .keyword("@testable") || $0 == .keyword("import") + }) + else { + return + } + + formatter.replaceTokens(in: endOfLine ..< nextImportIndex, with: formatter.linebreakToken(for: currentImportIndex + 1)) + } + } +} diff --git a/Sources/Rules/BlankLinesBetweenScopes.swift b/Sources/Rules/BlankLinesBetweenScopes.swift new file mode 100644 index 000000000..304edd4f3 --- /dev/null +++ b/Sources/Rules/BlankLinesBetweenScopes.swift @@ -0,0 +1,109 @@ +// +// BlankLinesBetweenScopes.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Adds a blank line immediately after a closing brace, unless followed by another closing brace + static let blankLinesBetweenScopes = FormatRule( + help: """ + Insert blank line before class, struct, enum, extension, protocol or function + declarations. + """, + sharedOptions: ["linebreaks"] + ) { formatter in + var spaceableScopeStack = [true] + var isSpaceableScopeType = false + formatter.forEachToken(onlyWhereEnabled: false) { i, token in + outer: switch token { + case .keyword("class"), + .keyword("actor"), + .keyword("struct"), + .keyword("extension"), + .keyword("enum"): + isSpaceableScopeType = + (formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import")) + case .keyword("func"), .keyword("var"): + isSpaceableScopeType = false + case .startOfScope("{"): + spaceableScopeStack.append(isSpaceableScopeType) + isSpaceableScopeType = false + case .endOfScope("}"): + spaceableScopeStack.removeLast() + guard spaceableScopeStack.last == true, + let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: i), + formatter.lastIndex(of: .linebreak, in: openingBraceIndex + 1 ..< i) != nil + else { + // Inline braces + break + } + var i = i + if let nextTokenIndex = formatter.index(of: .nonSpace, after: i, if: { + $0 == .startOfScope("(") + }), let closingParenIndex = formatter.index(of: + .endOfScope(")"), after: nextTokenIndex) + { + i = closingParenIndex + } + guard let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i), + formatter.isEnabled, formatter.options.insertBlankLines, + let firstLinebreakIndex = formatter.index(of: .linebreak, in: i + 1 ..< nextTokenIndex), + formatter.index(of: .linebreak, in: firstLinebreakIndex + 1 ..< nextTokenIndex) == nil + else { + break + } + if var nextNonCommentIndex = + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) + { + while formatter.tokens[nextNonCommentIndex] == .startOfScope("#if"), + let nextIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + after: formatter.endOfLine(at: nextNonCommentIndex) + ) + { + nextNonCommentIndex = nextIndex + } + switch formatter.tokens[nextNonCommentIndex] { + case .error, .endOfScope, + .operator(".", _), .delimiter(","), .delimiter(":"), + .keyword("else"), .keyword("catch"), .keyword("#else"): + break outer + case .keyword("while"): + if let previousBraceIndex = formatter.index(of: .startOfScope("{"), before: i), + formatter.last(.nonSpaceOrCommentOrLinebreak, before: previousBraceIndex) + == .keyword("repeat") + { + break outer + } + default: + if formatter.isLabel(at: nextNonCommentIndex), let colonIndex + = formatter.index(of: .delimiter(":"), after: nextNonCommentIndex), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex) + == .startOfScope("{") + { + break outer + } + } + } + switch formatter.tokens[nextTokenIndex] { + case .startOfScope("//"): + if case let .commentBody(body)? = formatter.next(.nonSpace, after: nextTokenIndex), + body.trimmingCharacters(in: .whitespaces).lowercased().hasPrefix("sourcery:") + { + break + } + formatter.insertLinebreak(at: firstLinebreakIndex) + default: + formatter.insertLinebreak(at: firstLinebreakIndex) + } + default: + break + } + } + } +} diff --git a/Sources/Rules/BlockComments.swift b/Sources/Rules/BlockComments.swift new file mode 100644 index 000000000..c35bb8799 --- /dev/null +++ b/Sources/Rules/BlockComments.swift @@ -0,0 +1,138 @@ +// +// BlockComments.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let blockComments = FormatRule( + help: "Convert block comments to consecutive single line comments.", + disabledByDefault: true + ) { formatter in + formatter.forEachToken { i, token in + switch token { + case .startOfScope("/*"): + guard var endIndex = formatter.endOfScope(at: i) else { + return formatter.fatalError("Expected */", at: i) + } + + // We can only convert block comments to single-line comments + // if there are no non-comment tokens on the same line. + // - For example, we can't convert `if foo { /* code */ }` + // to a line comment because it would comment out the closing brace. + // + // To guard against this, we verify that there is only + // comment or whitespace tokens on the remainder of this line + guard formatter.next(.nonSpace, after: endIndex)?.isLinebreak != false else { + return + } + + var isDocComment = false + var stripLeadingStars = true + func replaceCommentBody(at index: Int) -> Int { + var delta = 0 + var space = "" + if case let .space(s) = formatter.tokens[index] { + formatter.removeToken(at: index) + space = s + delta -= 1 + } + if case let .commentBody(body)? = formatter.token(at: index) { + var body = Substring(body) + if stripLeadingStars { + if body.hasPrefix("*") { + body = body.drop(while: { $0 == "*" }) + } else { + stripLeadingStars = false + } + } + let prefix = isDocComment ? "/" : "" + if !prefix.isEmpty || !body.isEmpty, !body.hasPrefix(" ") { + space += " " + } + formatter.replaceToken( + at: index, + with: .commentBody(prefix + space + body) + ) + } else if isDocComment { + formatter.insert(.commentBody("/"), at: index) + delta += 1 + } + return delta + } + + // Replace opening delimiter + var startIndex = i + let indent = formatter.currentIndentForLine(at: i) + if case let .commentBody(body) = formatter.tokens[i + 1] { + isDocComment = body.hasPrefix("*") + let commentBody = body.drop(while: { $0 == "*" }) + formatter.replaceToken(at: i + 1, with: .commentBody("/" + commentBody)) + } + formatter.replaceToken(at: i, with: .startOfScope("//")) + if let nextToken = formatter.token(at: i + 1), + nextToken.isSpaceOrLinebreak || nextToken.string == (isDocComment ? "/" : ""), + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i + 1), + nextIndex > i + 2 + { + let range = i + 1 ..< nextIndex + formatter.removeTokens(in: range) + endIndex -= range.count + startIndex = i + 1 + endIndex += replaceCommentBody(at: startIndex) + } + + // Replace ending delimiter + if let i = formatter.index(of: .nonSpace, before: endIndex, if: { + $0.isLinebreak + }) { + let range = i ... endIndex + formatter.removeTokens(in: range) + endIndex -= range.count + } + + // remove /* and */ + var index = i + while index <= endIndex { + switch formatter.tokens[index] { + case .startOfScope("/*"): + formatter.removeToken(at: index) + endIndex -= 1 + if formatter.tokens[index - 1].isSpace { + formatter.removeToken(at: index - 1) + index -= 1 + endIndex -= 1 + } + case .endOfScope("*/"): + formatter.removeToken(at: index) + endIndex -= 1 + if formatter.tokens[index - 1].isSpace { + formatter.removeToken(at: index - 1) + index -= 1 + endIndex -= 1 + } + case .linebreak: + endIndex += formatter.insertSpace(indent, at: index + 1) + guard let i = formatter.index(of: .nonSpace, after: index) else { + index += 1 + continue + } + index = i + formatter.insert(.startOfScope("//"), at: index) + var delta = 1 + replaceCommentBody(at: index + 1) + index += delta + endIndex += delta + default: + index += 1 + } + } + default: + break + } + } + } +} diff --git a/Sources/Rules/Braces.swift b/Sources/Rules/Braces.swift new file mode 100644 index 000000000..7020978a3 --- /dev/null +++ b/Sources/Rules/Braces.swift @@ -0,0 +1,89 @@ +// +// Braces.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Implement brace-wrapping rules + static let braces = FormatRule( + help: "Wrap braces in accordance with selected style (K&R or Allman).", + options: ["allman"], + sharedOptions: ["linebreaks", "maxwidth", "indent", "tabwidth", "assetliterals"] + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + guard let closingBraceIndex = formatter.endOfScope(at: i), + // Check this isn't an inline block + formatter.index(of: .linebreak, in: i + 1 ..< closingBraceIndex) != nil, + let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), + ![.delimiter(","), .keyword("in")].contains(prevToken), + !prevToken.is(.startOfScope) + else { + return + } + if let penultimateToken = formatter.last(.nonSpaceOrComment, before: closingBraceIndex), + !penultimateToken.isLinebreak + { + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: closingBraceIndex) + formatter.insertLinebreak(at: closingBraceIndex) + if formatter.token(at: closingBraceIndex - 1)?.isSpace == true { + formatter.removeToken(at: closingBraceIndex - 1) + } + } + if formatter.options.allmanBraces { + // Implement Allman-style braces, where opening brace appears on the next line + switch formatter.last(.nonSpace, before: i) ?? .space("") { + case .identifier, .keyword, .endOfScope, .number, + .operator("?", .postfix), .operator("!", .postfix): + formatter.insertLinebreak(at: i) + if let breakIndex = formatter.index(of: .linebreak, after: i + 1), + let nextIndex = formatter.index(of: .nonSpace, after: breakIndex, if: { $0.isLinebreak }) + { + formatter.removeTokens(in: breakIndex ..< nextIndex) + } + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: i + 1) + if formatter.tokens[i - 1].isSpace { + formatter.removeToken(at: i - 1) + } + default: + break + } + } else { + // Implement K&R-style braces, where opening brace appears on the same line + guard let prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), + formatter.tokens[prevIndex ..< i].contains(where: { $0.isLinebreak }), + !formatter.tokens[prevIndex].isComment + else { + return + } + + var maxWidth = formatter.options.maxWidth + if maxWidth == 0 { + maxWidth = .max + } + + // Check that unwrapping wouldn't exceed line length + let endOfLine = formatter.endOfLine(at: i) + let length = formatter.lineLength(from: i, upTo: endOfLine) + let prevLineLength = formatter.lineLength(at: prevIndex) + guard prevLineLength + length + 1 <= maxWidth else { + return + } + + // Avoid conflicts with wrapMultilineStatementBraces + // (Can't refer to `FormatRule.wrapMultilineStatementBraces` directly + // because it creates a cicrcular reference) + if formatter.options.enabledRules.contains("wrapMultilineStatementBraces"), + formatter.shouldWrapMultilineStatementBrace(at: i) + { + return + } + formatter.replaceTokens(in: prevIndex + 1 ..< i, with: .space(" ")) + } + } + } +} diff --git a/Sources/Rules/ConditionalAssignment.swift b/Sources/Rules/ConditionalAssignment.swift new file mode 100644 index 000000000..f1593dc6a --- /dev/null +++ b/Sources/Rules/ConditionalAssignment.swift @@ -0,0 +1,251 @@ +// +// ConditionalAssignment.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let conditionalAssignment = FormatRule( + help: "Assign properties using if / switch expressions.", + orderAfter: [.redundantReturn], + options: ["condassignment"] + ) { formatter in + // If / switch expressions were added in Swift 5.9 (SE-0380) + guard formatter.options.swiftVersion >= "5.9" else { + return + } + + formatter.forEach(.keyword) { startOfConditional, keywordToken in + // Look for an if/switch expression where the first branch starts with `identifier =` + guard ["if", "switch"].contains(keywordToken.string), + let conditionalBranches = formatter.conditionalBranches(at: startOfConditional), + var startOfFirstBranch = conditionalBranches.first?.startOfBranch + else { return } + + // Traverse any nested if/switch branches until we find the first code branch + while let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfFirstBranch), + ["if", "switch"].contains(formatter.tokens[firstTokenInBranch].string), + let nestedConditionalBranches = formatter.conditionalBranches(at: firstTokenInBranch), + let startOfNestedBranch = nestedConditionalBranches.first?.startOfBranch + { + startOfFirstBranch = startOfNestedBranch + } + + // Check if the first branch starts with the pattern `lvalue =`. + guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfFirstBranch), + let lvalueRange = formatter.parseExpressionRange(startingAt: firstTokenIndex), + let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: lvalueRange.upperBound), + formatter.tokens[equalsIndex] == .operator("=", .infix) + else { return } + + guard conditionalBranches.allSatisfy({ formatter.isExhaustiveSingleStatementAssignment($0, lvalueRange: lvalueRange) }), + formatter.conditionalBranchesAreExhaustive(conditionKeywordIndex: startOfConditional, branches: conditionalBranches) + else { + return + } + + // If this expression follows a property like `let identifier: Type`, we just + // have to insert an `=` between property and the conditional. + // - Find the introducer (let/var), parse the property, and verify that the identifier + // matches the identifier assigned on each conditional branch. + if let introducerIndex = formatter.indexOfLastSignificantKeyword(at: startOfConditional, excluding: ["if", "switch"]), + ["let", "var"].contains(formatter.tokens[introducerIndex].string), + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + formatter.tokens[lvalueRange.lowerBound].string == property.identifier, + property.value == nil, + let typeRange = property.type?.range, + let nextTokenAfterProperty = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: typeRange.upperBound), + nextTokenAfterProperty == startOfConditional + { + formatter.removeAssignmentFromAllBranches(of: conditionalBranches) + + let rangeBetweenTypeAndConditional = (typeRange.upperBound + 1) ..< startOfConditional + + // If there are no comments between the type and conditional, + // we reformat it from: + // + // let foo: Foo\n + // if condition { + // + // to: + // + // let foo: Foo = if condition { + // + if formatter.tokens[rangeBetweenTypeAndConditional].allSatisfy(\.isSpaceOrLinebreak) { + formatter.replaceTokens(in: rangeBetweenTypeAndConditional, with: [ + .space(" "), + .operator("=", .infix), + .space(" "), + ]) + } + + // But if there are comments, then we shouldn't just delete them. + // Instead we just insert `= ` after the type. + else { + formatter.insert([.operator("=", .infix), .space(" ")], at: startOfConditional) + } + } + + // Otherwise we insert an `identifier =` before the if/switch expression + else if !formatter.options.conditionalAssignmentOnlyAfterNewProperties { + // In this case we should only apply the conversion if this is a top-level condition, + // and not nested in some parent condition. In large complex if/switch conditions + // with multiple layers of nesting, for example, this prevents us from making any + // changes unless the entire set of nested conditions can be converted as a unit. + // - First attempt to find and parse a parent if / switch condition. + var startOfParentScope = formatter.startOfScope(at: startOfConditional) + + // If we're inside a switch case, expand to look at the whole switch statement + while let currentStartOfParentScope = startOfParentScope, + formatter.tokens[currentStartOfParentScope] == .startOfScope(":"), + let caseToken = formatter.index(of: .endOfScope("case"), before: currentStartOfParentScope) + { + startOfParentScope = formatter.startOfScope(at: caseToken) + } + + if let startOfParentScope = startOfParentScope, + let mostRecentIfOrSwitch = formatter.index(of: .keyword, before: startOfParentScope, if: { ["if", "switch"].contains($0.string) }), + let conditionalBranches = formatter.conditionalBranches(at: mostRecentIfOrSwitch), + let startOfFirstParentBranch = conditionalBranches.first?.startOfBranch, + let endOfLastParentBranch = conditionalBranches.last?.endOfBranch, + // If this condition is contained within a parent condition, do nothing. + // We should only convert the entire set of nested conditions together as a unit. + (startOfFirstParentBranch ... endOfLastParentBranch).contains(startOfConditional) + { return } + + let lvalueTokens = formatter.tokens[lvalueRange] + + // Now we can remove the `identifier =` from each branch, + // and instead add it before the if / switch expression. + formatter.removeAssignmentFromAllBranches(of: conditionalBranches) + + let identifierEqualsTokens = lvalueTokens + [ + .space(" "), + .operator("=", .infix), + .space(" "), + ] + + formatter.insert(identifierEqualsTokens, at: startOfConditional) + } + } + } +} + +private extension Formatter { + // Whether or not the conditional statement that starts at the given index + // has branches that are exhaustive + func conditionalBranchesAreExhaustive( + conditionKeywordIndex: Int, + branches: [Formatter.ConditionalBranch] + ) + -> Bool + { + // Switch statements are compiler-guaranteed to be exhaustive + if tokens[conditionKeywordIndex] == .keyword("switch") { + return true + } + + // If statements are only exhaustive if the last branch + // is `else` (not `else if`). + else if tokens[conditionKeywordIndex] == .keyword("if"), + let lastCondition = branches.last, + let tokenBeforeLastCondition = index(of: .nonSpaceOrCommentOrLinebreak, before: lastCondition.startOfBranch) + { + return tokens[tokenBeforeLastCondition] == .keyword("else") + } + + return false + } + + // Whether or not the given conditional branch body qualifies as a single statement + // that assigns a value to `identifier`. This is either: + // 1. a single assignment to `lvalue =` + // 2. a single `if` or `switch` statement where each of the branches also qualify, + // and the statement is exhaustive. + func isExhaustiveSingleStatementAssignment(_ branch: Formatter.ConditionalBranch, lvalueRange: ClosedRange) -> Bool { + guard let firstTokenIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return false } + + // If this is an if/switch statement, verify that all of the branches are also + // single-statement assignments and that the statement is exhaustive. + if let conditionalBranches = conditionalBranches(at: firstTokenIndex), + let lastConditionalStatement = conditionalBranches.last + { + let allBranchesAreExhaustiveSingleStatement = conditionalBranches.allSatisfy { branch in + isExhaustiveSingleStatementAssignment(branch, lvalueRange: lvalueRange) + } + + let isOnlyStatementInScope = next(.nonSpaceOrCommentOrLinebreak, after: lastConditionalStatement.endOfBranch)?.isEndOfScope == true + + let isExhaustive = conditionalBranchesAreExhaustive( + conditionKeywordIndex: firstTokenIndex, + branches: conditionalBranches + ) + + return allBranchesAreExhaustiveSingleStatement + && isOnlyStatementInScope + && isExhaustive + } + + // Otherwise we expect this to be of the pattern `lvalue = (statement)` + else if let firstExpressionRange = parseExpressionRange(startingAt: firstTokenIndex), + tokens[firstExpressionRange] == tokens[lvalueRange], + let equalsIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: firstExpressionRange.upperBound), + tokens[equalsIndex] == .operator("=", .infix), + let valueStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex) + { + // We know this branch starts with `identifier =`, but have to check that the + // remaining code in the branch is a single statement. To do that we can + // create a temporary formatter with the branch body _excluding_ `identifier =`. + let assignmentStatementRange = valueStartIndex ..< branch.endOfBranch + var tempScopeTokens = [Token]() + tempScopeTokens.append(.startOfScope("{")) + tempScopeTokens.append(contentsOf: tokens[assignmentStatementRange]) + tempScopeTokens.append(.endOfScope("}")) + + let tempFormatter = Formatter(tempScopeTokens, options: options) + guard tempFormatter.blockBodyHasSingleStatement( + atStartOfScope: 0, + includingConditionalStatements: true, + includingReturnStatements: false + ) else { + return false + } + + // In Swift 5.9, there's a bug that prevents you from writing an + // if or switch expression using an `as?` on one of the branches: + // https://github.com/apple/swift/issues/68764 + // + // let result = if condition { + // foo as? String + // } else { + // "bar" + // } + // + if tempFormatter.conditionalBranchHasUnsupportedCastOperator(startOfScopeIndex: 0) { + return false + } + + return true + } + + return false + } + + // Removes the `identifier =` from each conditional branch + func removeAssignmentFromAllBranches(of conditionalBranches: [ConditionalBranch]) { + forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in + guard let firstTokenIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch), + let firstExpressionRange = parseExpressionRange(startingAt: firstTokenIndex), + let equalsIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: firstExpressionRange.upperBound), + tokens[equalsIndex] == .operator("=", .infix), + let valueStartIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex) + else { return } + + removeTokens(in: firstTokenIndex ..< valueStartIndex) + } + } +} diff --git a/Sources/Rules/ConsecutiveBlankLines.swift b/Sources/Rules/ConsecutiveBlankLines.swift new file mode 100644 index 000000000..65650506f --- /dev/null +++ b/Sources/Rules/ConsecutiveBlankLines.swift @@ -0,0 +1,32 @@ +// +// ConsecutiveBlankLines.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Collapse all consecutive blank lines into a single blank line + static let consecutiveBlankLines = FormatRule( + help: "Replace consecutive blank lines with a single blank line." + ) { formatter in + formatter.forEach(.linebreak) { i, _ in + guard let prevIndex = formatter.index(of: .nonSpace, before: i, if: { $0.isLinebreak }) else { + return + } + if let scope = formatter.currentScope(at: i), scope.isMultilineStringDelimiter { + return + } + if let nextIndex = formatter.index(of: .nonSpace, after: i) { + if formatter.tokens[nextIndex].isLinebreak { + formatter.removeTokens(in: i + 1 ... nextIndex) + } + } else if !formatter.options.fragment { + formatter.removeTokens(in: i ..< formatter.tokens.count) + } + } + } +} diff --git a/Sources/Rules/ConsecutiveSpaces.swift b/Sources/Rules/ConsecutiveSpaces.swift new file mode 100644 index 000000000..396ee4a0e --- /dev/null +++ b/Sources/Rules/ConsecutiveSpaces.swift @@ -0,0 +1,48 @@ +// +// ConsecutiveSpaces.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Collapse all consecutive space characters to a single space, except at + /// the start of a line or inside a comment or string, as these have no semantic + /// meaning and lead to noise in commits. + static let consecutiveSpaces = FormatRule( + help: "Replace consecutive spaces with a single space." + ) { formatter in + formatter.forEach(.space) { i, token in + switch token { + case .space(""): + formatter.removeToken(at: i) + case .space(" "): + break + default: + guard let prevToken = formatter.token(at: i - 1), + let nextToken = formatter.token(at: i + 1) + else { + return + } + switch prevToken { + case .linebreak, .startOfScope("/*"), .startOfScope("//"), .commentBody: + return + case .endOfScope("*/") where nextToken == .startOfScope("/*") && + formatter.currentScope(at: i) == .startOfScope("/*"): + return + default: + break + } + switch nextToken { + case .linebreak, .endOfScope("*/"), .commentBody: + return + default: + formatter.replaceToken(at: i, with: .space(" ")) + } + } + } + } +} diff --git a/Sources/Rules/ConsistentSwitchCaseSpacing.swift b/Sources/Rules/ConsistentSwitchCaseSpacing.swift new file mode 100644 index 000000000..6ba72d51c --- /dev/null +++ b/Sources/Rules/ConsistentSwitchCaseSpacing.swift @@ -0,0 +1,47 @@ +// +// ConsistentSwitchCaseSpacing.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let consistentSwitchCaseSpacing = FormatRule( + help: "Ensures consistent spacing among all of the cases in a switch statement.", + orderAfter: [.blankLineAfterSwitchCase] + ) { formatter in + formatter.forEach(.keyword("switch")) { switchIndex, _ in + guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } + + // When counting the switch cases, exclude the last case (which should never have a trailing blank line). + let countWithTrailingBlankLine = switchCases.filter { $0.isFollowedByBlankLine && !$0.isLastCase }.count + let countWithoutTrailingBlankLine = switchCases.filter { !$0.isFollowedByBlankLine && !$0.isLastCase }.count + + // We want the spacing to be consistent for all switch cases, + // so use whichever formatting is used for the majority of cases. + var allCasesShouldHaveBlankLine = countWithTrailingBlankLine >= countWithoutTrailingBlankLine + + // When the `blankLinesBetweenChainedFunctions` rule is enabled, and there is a switch case + // that is required to span multiple lines, then all cases must span multiple lines. + // (Since if this rule removed the blank line from that case, it would contradict the other rule) + if formatter.options.enabledRules.contains(FormatRule.blankLineAfterSwitchCase.name), + switchCases.contains(where: { $0.spansMultipleLines && !$0.isLastCase }) + { + allCasesShouldHaveBlankLine = true + } + + for switchCase in switchCases.reversed() { + if !switchCase.isFollowedByBlankLine, allCasesShouldHaveBlankLine, !switchCase.isLastCase { + switchCase.insertTrailingBlankLine(using: formatter) + } + + if switchCase.isFollowedByBlankLine, !allCasesShouldHaveBlankLine || switchCase.isLastCase { + switchCase.removeTrailingBlankLine(using: formatter) + } + } + } + } +} diff --git a/Sources/Rules/DocComments.swift b/Sources/Rules/DocComments.swift new file mode 100644 index 000000000..408d51809 --- /dev/null +++ b/Sources/Rules/DocComments.swift @@ -0,0 +1,177 @@ +// +// DocComments.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let docComments = FormatRule( + help: "Use doc comments for API declarations, otherwise use regular comments.", + disabledByDefault: true, + orderAfter: [.fileHeader], + options: ["doccomments"] + ) { formatter in + formatter.forEach(.startOfScope) { index, token in + guard [.startOfScope("//"), .startOfScope("/*")].contains(token), + let endOfComment = formatter.endOfScope(at: index), + let nextDeclarationIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfComment) + else { + return + } + + func shouldBeDocComment(at index: Int, endOfComment: Int) -> Bool { + // Check if this is a special type of comment that isn't documentation + if case let .commentBody(body)? = formatter.next(.nonSpace, after: index), body.isCommentDirective { + return false + } + + // Check if this token defines a declaration that supports doc comments + var declarationToken = formatter.tokens[nextDeclarationIndex] + if declarationToken.isAttribute || declarationToken.isModifierKeyword, + let index = formatter.index(after: nextDeclarationIndex, where: { $0.isDeclarationTypeKeyword }) + { + declarationToken = formatter.tokens[index] + } + guard declarationToken.isDeclarationTypeKeyword(excluding: ["import"]) else { + return false + } + + // Only use doc comments on declarations in type bodies, or top-level declarations + if let startOfEnclosingScope = formatter.index(of: .startOfScope, before: index) { + switch formatter.tokens[startOfEnclosingScope] { + case .startOfScope("#if"): + break + case .startOfScope("{"): + guard let scope = formatter.lastSignificantKeyword(at: startOfEnclosingScope, excluding: ["where"]), + ["class", "actor", "struct", "enum", "protocol", "extension"].contains(scope) + else { + return false + } + default: + return false + } + } + + // If there are blank lines between comment and declaration, comment is not treated as doc comment + let trailingTokens = formatter.tokens[(endOfComment - 1) ... nextDeclarationIndex] + let lines = trailingTokens.split(omittingEmptySubsequences: false, whereSeparator: \.isLinebreak) + if lines.contains(where: { $0.allSatisfy(\.isSpace) }) { + return false + } + + // Only comments at the start of a line can be doc comments + if let previousToken = formatter.index(of: .nonSpaceOrLinebreak, before: index) { + let commentLine = formatter.startOfLine(at: index) + let previousTokenLine = formatter.startOfLine(at: previousToken) + + if commentLine == previousTokenLine { + return false + } + } + + // Comments inside conditional statements are not doc comments + return !formatter.isConditionalStatement(at: index) + } + + var commentIndices = [index] + if token == .startOfScope("//") { + var i = index + while let prevLineIndex = formatter.index(of: .linebreak, before: i), + case let lineStartIndex = formatter.startOfLine(at: prevLineIndex, excludingIndent: true), + formatter.token(at: lineStartIndex) == .startOfScope("//") + { + commentIndices.append(lineStartIndex) + i = lineStartIndex + } + i = index + while let nextLineIndex = formatter.index(of: .linebreak, after: i), + let lineStartIndex = formatter.index(of: .nonSpace, after: nextLineIndex), + formatter.token(at: lineStartIndex) == .startOfScope("//") + { + commentIndices.append(lineStartIndex) + i = lineStartIndex + } + } + + let useDocComment = shouldBeDocComment(at: index, endOfComment: endOfComment) + guard commentIndices.allSatisfy({ + shouldBeDocComment(at: $0, endOfComment: endOfComment) == useDocComment + }) else { + return + } + + // Determine whether or not this is the start of a list of sequential declarations, like: + // + // // The placeholder names we use in test cases + // case foo + // case bar + // case baaz + // + // In these cases it's not obvious whether or not the comment refers to the property or + // the entire group, so we preserve the existing formatting. + var preserveRegularComments = false + if useDocComment, + let declarationKeyword = formatter.index(after: endOfComment, where: \.isDeclarationTypeKeyword), + let endOfDeclaration = formatter.endOfDeclaration(atDeclarationKeyword: declarationKeyword, fallBackToEndOfScope: false), + let nextDeclarationKeyword = formatter.index(after: endOfDeclaration, where: \.isDeclarationTypeKeyword) + { + let linebreaksBetweenDeclarations = formatter.tokens[declarationKeyword ... nextDeclarationKeyword] + .filter { $0.isLinebreak }.count + + // If there is only a single line break between the start of this declaration and the subsequent declaration, + // then they are written sequentially in a block. In this case, don't convert regular comments to doc comments. + if linebreaksBetweenDeclarations == 1 { + preserveRegularComments = true + } + } + + // Doc comment tokens like `///` and `/**` aren't parsed as a + // single `.startOfScope` token -- they're parsed as: + // `.startOfScope("//"), .commentBody("/ ...")` or + // `.startOfScope("/*"), .commentBody("* ...")` + let startOfDocCommentBody: String + switch token.string { + case "//": + startOfDocCommentBody = "/" + case "/*": + startOfDocCommentBody = "*" + default: + return + } + + let isDocComment = formatter.isDocComment(startOfComment: index) + + if isDocComment, + let commentBody = formatter.token(at: index + 1), + commentBody.isCommentBody + { + if useDocComment, !isDocComment, !preserveRegularComments { + let updatedCommentBody = "\(startOfDocCommentBody)\(commentBody.string)" + formatter.replaceToken(at: index + 1, with: .commentBody(updatedCommentBody)) + } else if !useDocComment, isDocComment, !formatter.options.preserveDocComments { + let prefix = commentBody.string.prefix(while: { String($0) == startOfDocCommentBody }) + + // Do nothing if this is a unusual comment like `//////////////////` + // or `/****************`. We can't just remove one of the tokens, because + // that would make this rule have a different output each time, but we + // shouldn't remove all of them since that would be unexpected. + if prefix.count > 1 { + return + } + + formatter.replaceToken( + at: index + 1, + with: .commentBody(String(commentBody.string.dropFirst())) + ) + } + + } else if useDocComment, !preserveRegularComments { + formatter.insert(.commentBody(startOfDocCommentBody), at: index + 1) + } + } + } +} diff --git a/Sources/Rules/DocCommentsBeforeAttributes.swift b/Sources/Rules/DocCommentsBeforeAttributes.swift new file mode 100644 index 000000000..1c52ac2ed --- /dev/null +++ b/Sources/Rules/DocCommentsBeforeAttributes.swift @@ -0,0 +1,42 @@ +// +// DocCommentsBeforeAttributes.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let docCommentsBeforeAttributes = FormatRule( + help: "Place doc comments on declarations before any attributes.", + orderAfter: [.docComments] + ) { formatter in + formatter.forEachToken(where: \.isDeclarationTypeKeyword) { keywordIndex, _ in + // Parse the attributes on this declaration if present + let startOfAttributes = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) + guard formatter.tokens[startOfAttributes].isAttribute else { return } + + let attributes = formatter.attributes(startingAt: startOfAttributes) + guard !attributes.isEmpty else { return } + + let attributesRange = attributes.first!.startIndex ... attributes.last!.endIndex + + // If there's a doc comment between the attributes and the rest of the declaration, + // move it above the attributes. + guard let linebreakAfterAttributes = formatter.index(of: .linebreak, after: attributesRange.upperBound), + let indexAfterAttributes = formatter.index(of: .nonSpaceOrLinebreak, after: linebreakAfterAttributes), + indexAfterAttributes < keywordIndex, + let restOfDeclaration = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: attributesRange.upperBound), + formatter.isDocComment(startOfComment: indexAfterAttributes) + else { return } + + let commentRange = indexAfterAttributes ..< restOfDeclaration + let comment = formatter.tokens[commentRange] + + formatter.removeTokens(in: commentRange) + formatter.insert(comment, at: startOfAttributes) + } + } +} diff --git a/Sources/Rules/DuplicateImports.swift b/Sources/Rules/DuplicateImports.swift new file mode 100644 index 000000000..3e8c2c1ad --- /dev/null +++ b/Sources/Rules/DuplicateImports.swift @@ -0,0 +1,35 @@ +// +// DuplicateImports.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove duplicate import statements + static let duplicateImports = FormatRule( + help: "Remove duplicate import statements." + ) { formatter in + for var importRanges in formatter.parseImports().reversed() { + for i in importRanges.indices.reversed() { + let range = importRanges.remove(at: i) + guard let j = importRanges.firstIndex(where: { $0.module == range.module }) else { + continue + } + let range2 = importRanges[j] + if Set(range.attributes).isSubset(of: range2.attributes) { + formatter.removeTokens(in: range.range) + continue + } + if j >= i { + formatter.removeTokens(in: range2.range) + importRanges.remove(at: j) + } + importRanges.append(range) + } + } + } +} diff --git a/Sources/Rules/ElseOnSameLine.swift b/Sources/Rules/ElseOnSameLine.swift new file mode 100644 index 000000000..ad3c22b5b --- /dev/null +++ b/Sources/Rules/ElseOnSameLine.swift @@ -0,0 +1,118 @@ +// +// ElseOnSameLine.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that an `else` statement following `if { ... }` appears on the same line + /// as the closing brace. This has no effect on the `else` part of a `guard` statement. + /// Also applies to `catch` after `try` and `while` after `repeat`. + static let elseOnSameLine = FormatRule( + help: """ + Place `else`, `catch` or `while` keyword in accordance with current style (same or + next line). + """, + orderAfter: [.wrapMultilineStatementBraces], + options: ["elseposition", "guardelse"], + sharedOptions: ["allman", "linebreaks"] + ) { formatter in + func bracesContainLinebreak(_ endIndex: Int) -> Bool { + guard let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { + return false + } + return (startIndex ..< endIndex).contains(where: { formatter.tokens[$0].isLinebreak }) + } + formatter.forEachToken { i, token in + switch token { + case .keyword("while"): + if let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + $0 == .endOfScope("}") + }), let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex), + formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex) == .keyword("repeat") { + fallthrough + } + case .keyword("else"): + guard var prevIndex = formatter.index(of: .nonSpace, before: i), + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + !$0.isComment + }) + else { + return + } + let isOnNewLine = formatter.tokens[prevIndex].isLinebreak + if isOnNewLine { + prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) ?? prevIndex + } + if formatter.tokens[prevIndex] == .endOfScope("}") { + fallthrough + } + guard let guardIndex = formatter.indexOfLastSignificantKeyword(at: prevIndex + 1, excluding: [ + "var", "let", "case", + ]), formatter.tokens[guardIndex] == .keyword("guard") else { + return + } + let shouldWrap: Bool + switch formatter.options.guardElsePosition { + case .auto: + // Only wrap if else or following brace is on next line + shouldWrap = isOnNewLine || + formatter.tokens[i + 1 ..< nextIndex].contains { $0.isLinebreak } + case .nextLine: + // Only wrap if guard statement spans multiple lines + shouldWrap = isOnNewLine || + formatter.tokens[guardIndex + 1 ..< nextIndex].contains { $0.isLinebreak } + case .sameLine: + shouldWrap = false + } + if shouldWrap { + if !formatter.options.allmanBraces { + formatter.replaceTokens(in: i + 1 ..< nextIndex, with: .space(" ")) + } + if !isOnNewLine { + formatter.replaceTokens(in: prevIndex + 1 ..< i, with: + formatter.linebreakToken(for: prevIndex + 1)) + formatter.insertSpace(formatter.currentIndentForLine(at: guardIndex), at: prevIndex + 2) + } + } else if isOnNewLine { + formatter.replaceTokens(in: prevIndex + 1 ..< i, with: .space(" ")) + } + case .keyword("catch"): + guard let prevIndex = formatter.index(of: .nonSpace, before: i) else { + return + } + + let precededByBlankLine = formatter.tokens[prevIndex].isLinebreak + && formatter.lastToken(before: prevIndex, where: { !$0.isSpaceOrComment })?.isLinebreak == true + + if precededByBlankLine { + return + } + + let shouldWrap = formatter.options.allmanBraces || formatter.options.elseOnNextLine + if !shouldWrap, formatter.tokens[prevIndex].isLinebreak { + if let prevBraceIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex, if: { + $0 == .endOfScope("}") + }), bracesContainLinebreak(prevBraceIndex) { + formatter.replaceTokens(in: prevBraceIndex + 1 ..< i, with: .space(" ")) + } + } else if shouldWrap, let token = formatter.token(at: prevIndex), !token.isLinebreak, + let prevBraceIndex = (token == .endOfScope("}")) ? prevIndex : + formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex, if: { + $0 == .endOfScope("}") + }), bracesContainLinebreak(prevBraceIndex) + { + formatter.replaceTokens(in: prevIndex + 1 ..< i, with: + formatter.linebreakToken(for: prevIndex + 1)) + formatter.insertSpace(formatter.currentIndentForLine(at: prevIndex + 1), at: prevIndex + 2) + } + default: + break + } + } + } +} diff --git a/Sources/Rules/EmptyBraces.swift b/Sources/Rules/EmptyBraces.swift new file mode 100644 index 000000000..f02a4932b --- /dev/null +++ b/Sources/Rules/EmptyBraces.swift @@ -0,0 +1,41 @@ +// +// EmptyBraces.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove white-space between empty braces + static let emptyBraces = FormatRule( + help: "Remove whitespace inside empty braces.", + options: ["emptybraces"], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + guard let closingIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0 == .endOfScope("}") + }) else { + return + } + if let token = formatter.next(.nonSpaceOrComment, after: closingIndex), + [.keyword("else"), .keyword("catch")].contains(token) + { + return + } + let range = i + 1 ..< closingIndex + switch formatter.options.emptyBracesSpacing { + case .noSpace: + formatter.removeTokens(in: range) + case .spaced: + formatter.replaceTokens(in: range, with: .space(" ")) + case .linebreak: + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: range.endIndex) + formatter.replaceTokens(in: range, with: formatter.linebreakToken(for: i + 1)) + } + } + } +} diff --git a/Sources/Rules/EnumNamespaces.swift b/Sources/Rules/EnumNamespaces.swift new file mode 100644 index 000000000..8fb6af65b --- /dev/null +++ b/Sources/Rules/EnumNamespaces.swift @@ -0,0 +1,124 @@ +// +// EnumNamespaces.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Converts types used for hosting only static members into enums to avoid instantiation. + static let enumNamespaces = FormatRule( + help: """ + Convert types used for hosting only static members into enums (an empty enum is + the canonical way to create a namespace in Swift as it can't be instantiated). + """, + options: ["enumnamespaces"] + ) { formatter in + formatter.forEachToken(where: { [.keyword("class"), .keyword("struct")].contains($0) }) { i, token in + if token == .keyword("class") { + guard let next = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i), + // exit if structs only + formatter.options.enumNamespaces != .structsOnly, + // exit if class is a type modifier + !(next.isKeywordOrAttribute || next.isModifierKeyword), + // exit for class as protocol conformance + formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .delimiter(":"), + // exit if not closed for extension + formatter.modifiersForDeclaration(at: i, contains: "final") + else { + return + } + } + guard let braceIndex = formatter.index(of: .startOfScope("{"), after: i), + // exit if import statement + formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .keyword("import"), + // exit if has attribute(s) + !formatter.modifiersForDeclaration(at: i, contains: { $1.hasPrefix("@") }), + // exit if type is conforming any other types + !formatter.tokens[i ... braceIndex].contains(.delimiter(":")), + let endIndex = formatter.index(of: .endOfScope("}"), after: braceIndex), + case let .identifier(name)? = formatter.next(.identifier, after: i + 1) + else { + return + } + let range = braceIndex + 1 ..< endIndex + if formatter.rangeHostsOnlyStaticMembersAtTopLevel(range), + !formatter.rangeContainsTypeInit(name, in: range), !formatter.rangeContainsSelfAssignment(range) + { + formatter.replaceToken(at: i, with: .keyword("enum")) + + if let finalIndex = formatter.indexOfModifier("final", forDeclarationAt: i), + let nextIndex = formatter.index(of: .nonSpace, after: finalIndex) + { + formatter.removeTokens(in: finalIndex ..< nextIndex) + } + } + } + } +} + +private extension Formatter { + func rangeHostsOnlyStaticMembersAtTopLevel(_ range: Range) -> Bool { + // exit for empty declarations + guard next(.nonSpaceOrCommentOrLinebreak, in: range) != nil else { + return false + } + + var j = range.startIndex + while j < range.endIndex, let token = token(at: j) { + if token == .startOfScope("{"), + let skip = index(of: .endOfScope("}"), after: j) + { + j = skip + continue + } + // exit if there's a explicit init + if token == .keyword("init") { + return false + } else if [.keyword("let"), + .keyword("var"), + .keyword("func")].contains(token), + !modifiersForDeclaration(at: j, contains: "static") + { + return false + } + j += 1 + } + return true + } + + func rangeContainsTypeInit(_ type: String, in range: Range) -> Bool { + for i in range { + guard case let .identifier(name) = tokens[i], + [type, "Self", "self"].contains(name) + else { + continue + } + if let nextIndex = index(of: .nonSpaceOrComment, after: i), + let nextToken = token(at: nextIndex), nextToken == .startOfScope("(") || + (nextToken == .operator(".", .infix) && [.identifier("init"), .identifier("self")] + .contains(next(.nonSpaceOrComment, after: nextIndex) ?? .space(""))) + { + return true + } + } + return false + } + + func rangeContainsSelfAssignment(_ range: Range) -> Bool { + for i in range { + guard case .identifier("self") = tokens[i] else { + continue + } + if let token = last(.nonSpaceOrCommentOrLinebreak, before: i), + [.operator("=", .infix), .delimiter(":"), .startOfScope("(")].contains(token) + { + return true + } + } + return false + } +} diff --git a/Sources/Rules/ExtensionAccessControl.swift b/Sources/Rules/ExtensionAccessControl.swift new file mode 100644 index 000000000..1d3bb20ab --- /dev/null +++ b/Sources/Rules/ExtensionAccessControl.swift @@ -0,0 +1,121 @@ +// +// ExtensionAccessControl.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let extensionAccessControl = FormatRule( + help: "Configure the placement of an extension's access control keyword.", + options: ["extensionacl"] + ) { formatter in + guard !formatter.options.fragment else { return } + + let declarations = formatter.parseDeclarations() + let updatedDeclarations = formatter.mapRecursiveDeclarations(declarations) { declaration, _ in + guard case let .type("extension", open, body, close, _) = declaration else { + return declaration + } + + let visibilityKeyword = formatter.visibility(of: declaration) + // `private` visibility at top level of file is equivalent to `fileprivate` + let extensionVisibility = (visibilityKeyword == .private) ? .fileprivate : visibilityKeyword + + switch formatter.options.extensionACLPlacement { + // If all declarations in the extension have the same visibility, + // remove the keyword from the individual declarations and + // place it on the extension itself. + case .onExtension: + if extensionVisibility == nil, + let delimiterIndex = declaration.openTokens.firstIndex(of: .delimiter(":")), + declaration.openTokens.firstIndex(of: .keyword("where")).map({ $0 > delimiterIndex }) ?? true + { + // Extension adds protocol conformance so can't have visibility modifier + return declaration + } + + let visibilityOfBodyDeclarations = formatter + .mapDeclarations(body) { + formatter.visibility(of: $0) ?? extensionVisibility ?? .internal + } + .compactMap { $0 } + + let counts = Set(visibilityOfBodyDeclarations).sorted().map { visibility in + (visibility, count: visibilityOfBodyDeclarations.filter { $0 == visibility }.count) + } + + guard let memberVisibility = counts.max(by: { $0.count < $1.count })?.0, + memberVisibility <= extensionVisibility ?? .public, + // Check that most common level is also most visible + memberVisibility == visibilityOfBodyDeclarations.max(), + // `private` can't be hoisted without changing code behavior + // (private applied at extension level is equivalent to `fileprivate`) + memberVisibility > .private + else { return declaration } + + if memberVisibility > extensionVisibility ?? .internal { + // Check type being extended does not have lower visibility + for d in declarations where d.name == declaration.name { + if case let .type(kind, _, _, _, _) = d { + if kind != "extension", formatter.visibility(of: d) ?? .internal < memberVisibility { + // Cannot make extension with greater visibility than type being extended + return declaration + } + break + } + } + } + + let extensionWithUpdatedVisibility: Declaration + if memberVisibility == extensionVisibility || + (memberVisibility == .internal && visibilityKeyword == nil) + { + extensionWithUpdatedVisibility = declaration + } else { + extensionWithUpdatedVisibility = formatter.add(memberVisibility, to: declaration) + } + + return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in + let visibility = formatter.visibility(of: bodyDeclaration) + if memberVisibility > visibility ?? extensionVisibility ?? .internal { + if visibility == nil { + return formatter.add(.internal, to: bodyDeclaration) + } + return bodyDeclaration + } + return formatter.remove(memberVisibility, from: bodyDeclaration) + } + + // Move the extension's visibility keyword to each individual declaration + case .onDeclarations: + // If the extension visibility is unspecified then there isn't any work to do + guard let extensionVisibility = extensionVisibility else { + return declaration + } + + // Remove the visibility keyword from the extension declaration itself + let extensionWithUpdatedVisibility = formatter.remove(visibilityKeyword!, from: declaration) + + // And apply the extension's visibility to each of its child declarations + // that don't have an explicit visibility keyword + return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in + if formatter.visibility(of: bodyDeclaration) == nil { + // If there was no explicit visibility keyword, then this declaration + // was using the visibility of the extension itself. + return formatter.add(extensionVisibility, to: bodyDeclaration) + } else { + // Keep the existing visibility + return bodyDeclaration + } + } + } + } + + let updatedTokens = updatedDeclarations.flatMap { $0.tokens } + formatter.replaceTokens(in: formatter.tokens.indices, with: updatedTokens) + } +} diff --git a/Sources/Rules/FileHeader.swift b/Sources/Rules/FileHeader.swift new file mode 100644 index 000000000..970477918 --- /dev/null +++ b/Sources/Rules/FileHeader.swift @@ -0,0 +1,83 @@ +// +// FileHeader.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Strip header comments from the file + static let fileHeader = FormatRule( + help: "Use specified source file header template for all files.", + runOnceOnly: true, + options: ["header", "dateformat", "timezone"], + sharedOptions: ["linebreaks"] + ) { formatter in + var headerTokens = [Token]() + var directives = [String]() + switch formatter.options.fileHeader { + case .ignore: + return + case var .replace(string): + let file = formatter.options.fileInfo + let options = ReplacementOptions( + dateFormat: formatter.options.dateFormat, + timeZone: formatter.options.timeZone + ) + + for (key, replacement) in formatter.options.fileInfo.replacements { + if let replacementStr = replacement.resolve(file, options) { + while let range = string.range(of: "{\(key.rawValue)}") { + string.replaceSubrange(range, with: replacementStr) + } + } + } + headerTokens = tokenize(string) + directives = headerTokens.compactMap { + guard case let .commentBody(body) = $0 else { + return nil + } + return body.commentDirective + } + } + + guard let headerRange = formatter.headerCommentTokenRange(includingDirectives: directives) else { + return + } + + if headerTokens.isEmpty { + formatter.removeTokens(in: headerRange) + return + } + + var lastHeaderTokenIndex = headerRange.endIndex - 1 + let endIndex = lastHeaderTokenIndex + headerTokens.count + if formatter.tokens.endIndex > endIndex, headerTokens == Array(formatter.tokens[ + lastHeaderTokenIndex + 1 ... endIndex + ]) { + lastHeaderTokenIndex += headerTokens.count + } + let headerLinebreaks = headerTokens.reduce(0) { result, token -> Int in + result + (token.isLinebreak ? 1 : 0) + } + if lastHeaderTokenIndex < formatter.tokens.count - 1 { + headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 1)) + if lastHeaderTokenIndex < formatter.tokens.count - 2, + !formatter.tokens[lastHeaderTokenIndex + 1 ... lastHeaderTokenIndex + 2].allSatisfy({ + $0.isLinebreak + }) + { + headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 2)) + } + } + if let index = formatter.index(of: .nonSpace, after: lastHeaderTokenIndex, if: { + $0.isLinebreak + }) { + lastHeaderTokenIndex = index + } + formatter.replaceTokens(in: headerRange.startIndex ..< lastHeaderTokenIndex + 1, with: headerTokens) + } +} diff --git a/Sources/Rules/GenericExtensions.swift b/Sources/Rules/GenericExtensions.swift new file mode 100644 index 000000000..e81f60f18 --- /dev/null +++ b/Sources/Rules/GenericExtensions.swift @@ -0,0 +1,127 @@ +// +// GenericExtensions.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let genericExtensions = FormatRule( + help: """ + Use angle brackets (`extension Array`) for generic type extensions + instead of type constraints (`extension Array where Element == Foo`). + """, + options: ["generictypes"] + ) { formatter in + formatter.forEach(.keyword("extension")) { extensionIndex, _ in + guard // Angle brackets syntax in extensions is only supported in Swift 5.7+ + formatter.options.swiftVersion >= "5.7", + let typeNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: extensionIndex), + let extendedType = formatter.token(at: typeNameIndex)?.string, + // If there's already an open angle bracket after the generic type name + // then the extension is already using the target syntax, so there's + // no work to do + formatter.next(.nonSpaceOrCommentOrLinebreak, after: typeNameIndex) != .startOfScope("<"), + let openBraceIndex = formatter.index(of: .startOfScope("{"), after: typeNameIndex), + let whereIndex = formatter.index(of: .keyword("where"), after: typeNameIndex), + whereIndex < openBraceIndex + else { return } + + // Prepopulate a `Self` generic type, which is implicitly present in extension definitions + let selfType = Formatter.GenericType( + name: "Self", + definitionSourceRange: typeNameIndex ... typeNameIndex, + conformances: [ + Formatter.GenericType.GenericConformance( + name: extendedType, + typeName: "Self", + type: .concreteType, + sourceRange: typeNameIndex ... typeNameIndex + ), + ] + ) + + var genericTypes = [selfType] + + // Parse the generic constraints in the where clause + formatter.parseGenericTypes( + from: whereIndex, + to: openBraceIndex, + into: &genericTypes, + qualifyGenericTypeName: { genericTypeName in + // In an extension all types implicitly refer to `Self`. + // For example, `Element == Foo` is actually fully-qualified as + // `Self.Element == Foo`. Using the fully-qualified `Self.Element` name + // here makes it so the generic constraint is populated as a child + // of `selfType`. + if !genericTypeName.hasPrefix("Self.") { + return "Self." + genericTypeName + } else { + return genericTypeName + } + } + ) + + var knownGenericTypes: [(name: String, genericTypes: [String])] = [ + (name: "Collection", genericTypes: ["Element"]), + (name: "Sequence", genericTypes: ["Element"]), + (name: "Array", genericTypes: ["Element"]), + (name: "Set", genericTypes: ["Element"]), + (name: "Dictionary", genericTypes: ["Key", "Value"]), + (name: "Optional", genericTypes: ["Wrapped"]), + ] + + // Users can provide additional generic types via the `generictypes` option + for userProvidedType in formatter.options.genericTypes.components(separatedBy: ";") { + guard let openAngleBracket = userProvidedType.firstIndex(of: "<"), + let closeAngleBracket = userProvidedType.firstIndex(of: ">") + else { continue } + + let typeName = String(userProvidedType[..= "5.5" else { return } + + formatter.forEachToken(where: { + $0 == .startOfScope("(") || $0 == .startOfScope("[") + }) { i, _ in + formatter.hoistEffectKeyword("await", inScopeAt: i) { prevIndex in + formatter.isSymbol(at: prevIndex, in: formatter.options.asyncCapturing) + } + } + } +} diff --git a/Sources/Rules/HoistPatternLet.swift b/Sources/Rules/HoistPatternLet.swift new file mode 100644 index 000000000..96a3982a0 --- /dev/null +++ b/Sources/Rules/HoistPatternLet.swift @@ -0,0 +1,167 @@ +// +// HoistPatternLet.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Move `let` and `var` inside patterns to the beginning + static let hoistPatternLet = FormatRule( + help: "Reposition `let` or `var` bindings within pattern.", + options: ["patternlet"] + ) { formatter in + func indicesOf(_ keyword: String, in range: CountableRange) -> [Int]? { + var indices = [Int]() + var keywordFound = false, identifierFound = false + var count = 0 + for index in range { + switch formatter.tokens[index] { + case .keyword(keyword): + indices.append(index) + keywordFound = true + case .identifier("_"): + break + case .identifier where formatter.last(.nonSpaceOrComment, before: index) != .operator(".", .prefix): + identifierFound = true + if keywordFound { + count += 1 + } + case .delimiter(","): + guard keywordFound || !identifierFound else { + return nil + } + keywordFound = false + identifierFound = false + case .startOfScope("{"): + return nil + case .startOfScope("<"): + // See: https://github.com/nicklockwood/SwiftFormat/issues/768 + return nil + default: + break + } + } + return (keywordFound || !identifierFound) && count > 0 ? indices : nil + } + + formatter.forEach(.startOfScope("(")) { i, _ in + let hoist = formatter.options.hoistPatternLet + // Check if pattern already starts with let/var + guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), + let prevIndex = formatter.index(before: i, where: { + switch $0 { + case .operator(".", _), .keyword("let"), .keyword("var"), + .endOfScope("*/"): + return false + case .endOfScope, .delimiter, .operator, .keyword: + return true + default: + return false + } + }) + else { + return + } + switch formatter.tokens[prevIndex] { + case .endOfScope("case"), .keyword("case"), .keyword("catch"): + break + case .delimiter(","): + loop: for token in formatter.tokens[0 ..< prevIndex].reversed() { + switch token { + case .endOfScope("case"), .keyword("catch"): + break loop + case .keyword("var"), .keyword("let"): + break + case .keyword: + // Tuple assignment + return + default: + break + } + } + default: + return + } + let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: prevIndex) + ?? (prevIndex + 1) + if case let .keyword(keyword) = formatter.tokens[startIndex], + ["let", "var"].contains(keyword) + { + if hoist { + // No changes needed + return + } + // Find variable indices + var indices = [Int]() + var index = i + 1 + var wasParenOrCommaOrLabel = true + while index < endIndex { + let token = formatter.tokens[index] + switch token { + case .delimiter(","), .startOfScope("("), .delimiter(":"): + wasParenOrCommaOrLabel = true + case .identifier("_"), .identifier("true"), .identifier("false"), .identifier("nil"): + wasParenOrCommaOrLabel = false + case let .identifier(name) where wasParenOrCommaOrLabel: + wasParenOrCommaOrLabel = false + let next = formatter.next(.nonSpaceOrComment, after: index) + if next != .operator(".", .infix), next != .delimiter(":") { + indices.append(index) + } + case _ where token.isSpaceOrCommentOrLinebreak: + break + case .startOfScope("["): + guard let next = formatter.endOfScope(at: index) else { + return formatter.fatalError("Expected ]", at: index) + } + index = next + default: + wasParenOrCommaOrLabel = false + } + index += 1 + } + // Insert keyword at indices + for index in indices.reversed() { + formatter.insert([.keyword(keyword), .space(" ")], at: index) + } + // Remove keyword + let range = ((formatter.index(of: .nonSpace, before: startIndex) ?? + (prevIndex - 1)) + 1) ... startIndex + formatter.removeTokens(in: range) + } else if hoist { + // Find let/var keyword indices + var keyword = "let" + guard let indices: [Int] = { + guard let indices = indicesOf(keyword, in: i + 1 ..< endIndex) else { + keyword = "var" + return indicesOf(keyword, in: i + 1 ..< endIndex) + } + return indices + }() else { + return + } + // Remove keywords inside parens + for index in indices.reversed() { + if formatter.tokens[index + 1].isSpace { + formatter.removeToken(at: index + 1) + } + formatter.removeToken(at: index) + } + // Insert keyword before parens + formatter.insert(.keyword(keyword), at: startIndex) + if let nextToken = formatter.token(at: startIndex + 1), !nextToken.isSpaceOrLinebreak { + formatter.insert(.space(" "), at: startIndex + 1) + } + if let prevToken = formatter.token(at: startIndex - 1), + !prevToken.isSpaceOrCommentOrLinebreak, !prevToken.isStartOfScope + { + formatter.insert(.space(" "), at: startIndex) + } + } + } + } +} diff --git a/Sources/Rules/HoistTry.swift b/Sources/Rules/HoistTry.swift new file mode 100644 index 000000000..52a327e95 --- /dev/null +++ b/Sources/Rules/HoistTry.swift @@ -0,0 +1,28 @@ +// +// HoistTry.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let hoistTry = FormatRule( + help: "Move inline `try` keyword(s) to start of expression.", + options: ["throwcapturing"] + ) { formatter in + let names = formatter.options.throwCapturing.union(["expect"]) + formatter.forEachToken(where: { + $0 == .startOfScope("(") || $0 == .startOfScope("[") + }) { i, _ in + formatter.hoistEffectKeyword("try", inScopeAt: i) { prevIndex in + guard case let .identifier(name) = formatter.tokens[prevIndex] else { + return false + } + return name.hasPrefix("XCTAssert") || formatter.isSymbol(at: prevIndex, in: names) + } + } + } +} diff --git a/Sources/Rules/Indent.swift b/Sources/Rules/Indent.swift new file mode 100644 index 000000000..9a54d12ca --- /dev/null +++ b/Sources/Rules/Indent.swift @@ -0,0 +1,797 @@ +// +// Indent.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/27/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Indent code according to standard scope indenting rules. + /// The type (tab or space) and level (2 spaces, 4 spaces, etc.) of the + /// indenting can be configured with the `options` parameter of the formatter. + static let indent = FormatRule( + help: "Indent code in accordance with the scope level.", + orderAfter: [.trailingSpace, .wrap, .wrapArguments], + options: ["indent", "tabwidth", "smarttabs", "indentcase", "ifdef", "xcodeindentation", "indentstrings"], + sharedOptions: ["trimwhitespace", "allman", "wrapconditions", "wrapternary"] + ) { formatter in + var scopeStack: [Token] = [] + var scopeStartLineIndexes: [Int] = [] + var lastNonSpaceOrLinebreakIndex = -1 + var lastNonSpaceIndex = -1 + var indentStack = [""] + var stringBodyIndentStack = [""] + var indentCounts = [1] + var linewrapStack = [false] + var lineIndex = 0 + + func inFunctionDeclarationWhereReturnTypeIsWrappedToStartOfLine(at i: Int) -> Bool { + guard let returnOperatorIndex = formatter.startOfReturnType(at: i) else { + return false + } + return formatter.last(.nonSpaceOrComment, before: returnOperatorIndex)?.isLinebreak == true + } + + func isFirstStackedClosureArgument(at i: Int) -> Bool { + assert(formatter.tokens[i] == .startOfScope("{")) + if let prevIndex = formatter.index(of: .nonSpace, before: i), + let prevToken = formatter.token(at: prevIndex), prevToken == .startOfScope("(") || + (prevToken == .delimiter(":") && formatter.token(at: prevIndex - 1)?.isIdentifier == true + && formatter.last(.nonSpace, before: prevIndex - 1) == .startOfScope("(")), + let endIndex = formatter.endOfScope(at: i), + let commaIndex = formatter.index(of: .nonSpace, after: endIndex, if: { + $0 == .delimiter(",") + }), + formatter.next(.nonSpaceOrComment, after: commaIndex)?.isLinebreak == true + { + return true + } + return false + } + + if formatter.options.fragment, + let firstIndex = formatter.index(of: .nonSpaceOrLinebreak, after: -1), + let indentToken = formatter.token(at: firstIndex - 1), case let .space(string) = indentToken + { + indentStack[0] = string + } + formatter.forEachToken(onlyWhereEnabled: false) { i, token in + func popScope() { + if linewrapStack.last == true { + indentStack.removeLast() + stringBodyIndentStack.removeLast() + } + indentStack.removeLast() + stringBodyIndentStack.removeLast() + indentCounts.removeLast() + linewrapStack.removeLast() + scopeStartLineIndexes.removeLast() + scopeStack.removeLast() + } + + func stringBodyIndent(at i: Int) -> String { + var space = "" + let start = formatter.startOfLine(at: i) + if let index = formatter.index(of: .nonSpace, in: start ..< i), + case let .stringBody(string) = formatter.tokens[index], + string.unicodeScalars.first?.isSpace == true + { + var index = string.startIndex + while index < string.endIndex, string[index].unicodeScalars.first!.isSpace { + space.append(string[index]) + index = string.index(after: index) + } + } + return space + } + + var i = i + switch token { + case let .startOfScope(string): + switch string { + case ":" where scopeStack.last == .endOfScope("case"): + popScope() + case "{" where !formatter.isStartOfClosure(at: i, in: scopeStack.last) && + linewrapStack.last == true: + indentStack.removeLast() + linewrapStack[linewrapStack.count - 1] = false + default: + break + } + // Handle start of scope + scopeStack.append(token) + var indentCount: Int + if lineIndex > scopeStartLineIndexes.last ?? -1 { + indentCount = 1 + } else if token.isMultilineStringDelimiter, let endIndex = formatter.endOfScope(at: i), + let closingIndex = formatter.index(of: .endOfScope(")"), after: endIndex), + formatter.next(.linebreak, in: endIndex + 1 ..< closingIndex) != nil + { + indentCount = 1 + } else if scopeStack.count > 1, scopeStack[scopeStack.count - 2] == .startOfScope(":") { + indentCount = 1 + } else { + indentCount = indentCounts.last! + 1 + } + var indent = indentStack[indentStack.count - indentCount] + + switch string { + case "/*": + if scopeStack.count < 2 || scopeStack[scopeStack.count - 2] != .startOfScope("/*") { + // Comments only indent one space + indent += " " + } + case ":": + indent += formatter.options.indent + if formatter.options.indentCase, + scopeStack.count < 2 || scopeStack[scopeStack.count - 2] != .startOfScope("#if") + { + indent += formatter.options.indent + } + case "#if": + if let lineIndex = formatter.index(of: .linebreak, after: i), + let nextKeyword = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lineIndex), [ + .endOfScope("case"), .endOfScope("default"), .keyword("@unknown"), + ].contains(nextKeyword) + { + indent = indentStack[indentStack.count - indentCount - 1] + if formatter.options.indentCase { + indent += formatter.options.indent + } + } + switch formatter.options.ifdefIndent { + case .indent: + i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) + indent += formatter.options.indent + case .noIndent: + i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) + case .outdent: + i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) + } + case "{" where isFirstStackedClosureArgument(at: i): + guard var prevIndex = formatter.index(of: .nonSpace, before: i) else { + assertionFailure() + break + } + if formatter.tokens[prevIndex] == .delimiter(":") { + guard formatter.token(at: prevIndex - 1)?.isIdentifier == true, + let parenIndex = formatter.index(of: .nonSpace, before: prevIndex - 1, if: { + $0 == .startOfScope("(") + }) + else { + let stringIndent = stringBodyIndent(at: i) + stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent + indent += stringIndent + formatter.options.indent + break + } + prevIndex = parenIndex + } + let startIndex = formatter.startOfLine(at: i) + indent = formatter.spaceEquivalentToTokens(from: startIndex, upTo: prevIndex + 1) + indentStack[indentStack.count - 1] = indent + indent += formatter.options.indent + indentCount -= 1 + case "{" where formatter.isStartOfClosure(at: i): + // When a trailing closure starts on the same line as the end of a multi-line + // method call the trailing closure body should be double-indented + if let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i), + formatter.tokens[prevIndex] == .endOfScope(")"), + case let prevIndent = formatter.currentIndentForLine(at: prevIndex), + prevIndent == indent + formatter.options.indent + { + if linewrapStack.last == false { + linewrapStack[linewrapStack.count - 1] = true + indentStack.append(prevIndent) + stringBodyIndentStack.append("") + } + indent = prevIndent + } + let stringIndent = stringBodyIndent(at: i) + stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent + indent += stringIndent + formatter.options.indent + case _ where token.isStringDelimiter, "//": + break + case "[", "(": + guard let linebreakIndex = formatter.index(of: .linebreak, after: i), + let nextIndex = formatter.index(of: .nonSpace, after: i), + nextIndex != linebreakIndex + else { + fallthrough + } + if formatter.last(.nonSpaceOrComment, before: linebreakIndex) != .delimiter(","), + formatter.next(.nonSpaceOrComment, after: linebreakIndex) != .delimiter(",") + { + fallthrough + } + let start = formatter.startOfLine(at: i) + // Align indent with previous value + let lastIndentCount = indentCounts.last ?? 0 + if indentCount > lastIndentCount { + indentCount = lastIndentCount + indentCounts[indentCounts.count - 1] = 1 + } + indent = formatter.spaceEquivalentToTokens(from: start, upTo: nextIndex) + default: + let stringIndent = stringBodyIndent(at: i) + stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent + indent += stringIndent + formatter.options.indent + } + indentStack.append(indent) + stringBodyIndentStack.append("") + indentCounts.append(indentCount) + scopeStartLineIndexes.append(lineIndex) + linewrapStack.append(false) + case .space: + if i == 0, !formatter.options.fragment, + formatter.token(at: i + 1)?.isLinebreak != true + { + formatter.removeToken(at: i) + } + case .error("}"), .error("]"), .error(")"), .error(">"): + // Handled over-terminated fragment + if let prevToken = formatter.token(at: i - 1) { + if case let .space(string) = prevToken { + let prevButOneToken = formatter.token(at: i - 2) + if prevButOneToken == nil || prevButOneToken!.isLinebreak { + indentStack[0] = string + } + } else if prevToken.isLinebreak { + indentStack[0] = "" + } + } + return + case .keyword("#else"), .keyword("#elseif"): + var indent = indentStack[indentStack.count - 2] + if scopeStack.last == .startOfScope(":") { + indent = indentStack[indentStack.count - 4] + if formatter.options.indentCase { + indent += formatter.options.indent + } + } + let start = formatter.startOfLine(at: i) + switch formatter.options.ifdefIndent { + case .indent, .noIndent: + i += formatter.insertSpaceIfEnabled(indent, at: start) + case .outdent: + i += formatter.insertSpaceIfEnabled("", at: start) + } + case .keyword("@unknown") where scopeStack.last != .startOfScope("#if"): + var indent = indentStack[indentStack.count - 2] + if formatter.options.indentCase { + indent += formatter.options.indent + } + let start = formatter.startOfLine(at: i) + let stringIndent = stringBodyIndentStack.last! + i += formatter.insertSpaceIfEnabled(stringIndent + indent, at: start) + case .keyword("in") where scopeStack.last == .startOfScope("{"): + if let startIndex = formatter.index(of: .startOfScope("{"), before: i), + formatter.index(of: .keyword("for"), in: startIndex + 1 ..< i) == nil, + let paramsIndex = formatter.index(of: .startOfScope, in: startIndex + 1 ..< i), + !formatter.tokens[startIndex + 1 ..< paramsIndex].contains(where: { + $0.isLinebreak + }), formatter.tokens[paramsIndex + 1 ..< i].contains(where: { + $0.isLinebreak + }) + { + indentStack[indentStack.count - 1] += formatter.options.indent + } + case .operator("=", .infix): + // If/switch expressions on their own line following an `=` assignment should always be indented + guard let nextKeyword = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + ["if", "switch"].contains(formatter.tokens[nextKeyword].string), + !formatter.onSameLine(i, nextKeyword) + else { fallthrough } + + let indent = (indentStack.last ?? "") + formatter.options.indent + indentStack.append(indent) + stringBodyIndentStack.append("") + indentCounts.append(1) + scopeStartLineIndexes.append(lineIndex) + linewrapStack.append(false) + scopeStack.append(.operator("=", .infix)) + scopeStartLineIndexes.append(lineIndex) + default: + // If this is the final `endOfScope` in a conditional assignment, + // we have to end the scope introduced by that assignment operator. + defer { + if token == .endOfScope("}"), let startOfScope = formatter.startOfScope(at: i) { + // Find the `=` before this start of scope, which isn't itself part of the conditional statement + var previousAssignmentIndex = formatter.index(of: .operator("=", .infix), before: startOfScope) + while let currentPreviousAssignmentIndex = previousAssignmentIndex, + formatter.isConditionalStatement(at: currentPreviousAssignmentIndex) + { + previousAssignmentIndex = formatter.index(of: .operator("=", .infix), before: currentPreviousAssignmentIndex) + } + + // Make sure the `=` actually created a new scope + if scopeStack.last == .operator("=", .infix), + // Parse the conditional branches following the `=` assignment operator + let previousAssignmentIndex = previousAssignmentIndex, + let nextTokenAfterAssignment = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: previousAssignmentIndex), + let conditionalBranches = formatter.conditionalBranches(at: nextTokenAfterAssignment), + // If this is the very end of the conditional assignment following the `=`, + // then we can end the scope. + conditionalBranches.last?.endOfBranch == i + { + popScope() + } + } + } + + // Handle end of scope + if let scope = scopeStack.last, token.isEndOfScope(scope) { + let indentCount = indentCounts.last! - 1 + popScope() + guard !token.isLinebreak, lineIndex > scopeStartLineIndexes.last ?? -1 else { + break + } + // If indentCount > 0, drop back to previous indent level + if indentCount > 0 { + indentStack.removeLast(indentCount) + stringBodyIndentStack.removeLast(indentCount) + for _ in 0 ..< indentCount { + indentStack.append(indentStack.last ?? "") + stringBodyIndentStack.append(stringBodyIndentStack.last ?? "") + } + } + + // Don't reduce indent if line doesn't start with end of scope + let start = formatter.startOfLine(at: i) + guard let firstIndex = formatter.index(of: .nonSpaceOrComment, after: start - 1) else { + break + } + if firstIndex != i { + break + } + func isInIfdef() -> Bool { + guard scopeStack.last == .startOfScope("#if") else { + return false + } + var index = i - 1 + while index > 0 { + switch formatter.tokens[index] { + case .keyword("switch"): + return false + case .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): + return true + default: + index -= 1 + } + } + return false + } + if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .outdent { + i += formatter.insertSpaceIfEnabled("", at: start) + } else { + var indent = indentStack.last ?? "" + if token.isSwitchCaseOrDefault, + formatter.options.indentCase, !isInIfdef() + { + indent += formatter.options.indent + } + let stringIndent = stringBodyIndentStack.last! + i += formatter.insertSpaceIfEnabled(stringIndent + indent, at: start) + } + } else if token == .endOfScope("#endif"), indentStack.count > 1 { + var indent = indentStack[indentStack.count - 2] + if scopeStack.last == .startOfScope(":"), indentStack.count > 1 { + indent = indentStack[indentStack.count - 4] + if formatter.options.indentCase { + indent += formatter.options.indent + } + popScope() + } + switch formatter.options.ifdefIndent { + case .indent, .noIndent: + i += formatter.insertSpaceIfEnabled(indent, at: formatter.startOfLine(at: i)) + case .outdent: + i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) + } + if scopeStack.last == .startOfScope("#if") { + popScope() + } + } + } + switch token { + case .endOfScope("case"): + scopeStack.append(token) + var indent = (indentStack.last ?? "") + if formatter.next(.nonSpaceOrComment, after: i)?.isLinebreak == true { + indent += formatter.options.indent + } else { + if formatter.options.indentCase { + indent += formatter.options.indent + } + // Align indent with previous case value + indent += formatter.spaceEquivalentToWidth(5) + } + indentStack.append(indent) + stringBodyIndentStack.append("") + indentCounts.append(1) + scopeStartLineIndexes.append(lineIndex) + linewrapStack.append(false) + fallthrough + case .endOfScope("default"), .keyword("@unknown"), + .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): + var index = formatter.startOfLine(at: i) + if index == i || index == i - 1 { + let indent: String + if case let .space(space) = formatter.tokens[index] { + indent = space + } else { + indent = "" + } + index -= 1 + while let prevToken = formatter.token(at: index - 1), prevToken.isComment, + let startIndex = formatter.index(of: .nonSpaceOrComment, before: index), + formatter.tokens[startIndex].isLinebreak + { + // Set indent for comment immediately before this line to match this line + if !formatter.isCommentedCode(at: startIndex + 1) { + formatter.insertSpaceIfEnabled(indent, at: startIndex + 1) + } + if case .endOfScope("*/") = prevToken, + var index = formatter.index(of: .startOfScope("/*"), after: startIndex) + { + while let linebreakIndex = formatter.index(of: .linebreak, after: index) { + formatter.insertSpaceIfEnabled(indent + " ", at: linebreakIndex + 1) + index = linebreakIndex + } + } + index = startIndex + } + } + case .linebreak: + // Detect linewrap + let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) + let linewrapped = lastNonSpaceOrLinebreakIndex > -1 && ( + !formatter.isEndOfStatement(at: lastNonSpaceOrLinebreakIndex, in: scopeStack.last) || + (nextTokenIndex.map { formatter.isTrailingClosureLabel(at: $0) } == true) || + !(nextTokenIndex == nil || [ + .endOfScope("}"), .endOfScope("]"), .endOfScope(")"), + ].contains(formatter.tokens[nextTokenIndex!]) || + formatter.isStartOfStatement(at: nextTokenIndex!, in: scopeStack.last) || ( + ((formatter.tokens[nextTokenIndex!].isIdentifier && !(formatter.tokens[nextTokenIndex!] == .identifier("async") && formatter.currentScope(at: nextTokenIndex!) != .startOfScope("("))) || [ + .keyword("try"), .keyword("await"), + ].contains(formatter.tokens[nextTokenIndex!])) && + formatter.last(.nonSpaceOrCommentOrLinebreak, before: nextTokenIndex!).map { + $0 != .keyword("return") && !$0.isOperator(ofType: .infix) + } ?? false) || ( + formatter.tokens[nextTokenIndex!] == .delimiter(",") && [ + "<", "[", "(", "case", + ].contains(formatter.currentScope(at: nextTokenIndex!)?.string ?? "") + ) + ) + ) + + // Determine current indent + var indent = indentStack.last ?? "" + if linewrapped, lineIndex == scopeStartLineIndexes.last { + indent = indentStack.count > 1 ? indentStack[indentStack.count - 2] : "" + } + lineIndex += 1 + + func shouldIndentNextLine(at i: Int) -> Bool { + // If there is a linebreak after certain symbols, we should add + // an additional indentation to the lines at the same indention scope + // after this line. + let endOfLine = formatter.endOfLine(at: i) + switch formatter.token(at: endOfLine - 1) { + case .keyword("return")?, .operator("=", .infix)?: + let endOfNextLine = formatter.endOfLine(at: endOfLine + 1) + switch formatter.last(.nonSpaceOrCommentOrLinebreak, before: endOfNextLine) { + case .operator(_, .infix)?, .delimiter(",")?: + return false + case .endOfScope(")")?: + return !formatter.options.xcodeIndentation + default: + return formatter.lastIndex(of: .startOfScope, + in: i ..< endOfNextLine) == nil + } + default: + return false + } + } + + guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: i), + let nextToken = formatter.token(at: nextNonSpaceIndex) + else { + break + } + + // Begin wrap scope + if linewrapStack.last == true { + if !linewrapped { + indentStack.removeLast() + linewrapStack[linewrapStack.count - 1] = false + indent = indentStack.last! + } else { + let shouldIndentLeadingDotStatement: Bool + if formatter.options.xcodeIndentation { + if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i), + formatter.token(at: formatter.startOfLine( + at: prevIndex, excludingIndent: true + )) == .endOfScope("}"), + formatter.index(of: .linebreak, in: prevIndex + 1 ..< i) != nil + { + shouldIndentLeadingDotStatement = false + } else { + shouldIndentLeadingDotStatement = true + } + } else { + shouldIndentLeadingDotStatement = ( + formatter.startOfConditionalStatement(at: i) != nil + && formatter.options.wrapConditions == .beforeFirst + ) + } + if shouldIndentLeadingDotStatement, + formatter.next(.nonSpace, after: i) == .operator(".", .infix), + let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i), + case let lineStart = formatter.index(of: .linebreak, before: prevIndex + 1) ?? + formatter.startOfLine(at: prevIndex), + let startIndex = formatter.index(of: .nonSpace, after: lineStart), + formatter.isStartOfStatement(at: startIndex) || ( + (formatter.tokens[startIndex].isIdentifier || [ + .keyword("try"), .keyword("await"), + ].contains(formatter.tokens[startIndex]) || + formatter.isTrailingClosureLabel(at: startIndex)) && + formatter.last(.nonSpaceOrCommentOrLinebreak, before: startIndex).map { + $0 != .keyword("return") && !$0.isOperator(ofType: .infix) + } ?? false) + { + indent += formatter.options.indent + indentStack[indentStack.count - 1] = indent + } + + // When inside conditionals, unindent after any commas (which separate conditions) + // that were indented by the block above + if !formatter.options.xcodeIndentation, + formatter.options.wrapConditions == .beforeFirst, + formatter.isConditionalStatement(at: i), + formatter.lastToken(before: i, where: { + $0.is(.nonSpaceOrCommentOrLinebreak) + }) == .delimiter(","), + let conditionBeginIndex = formatter.index(before: i, where: { + ["if", "guard", "while", "for"].contains($0.string) + }), + formatter.currentIndentForLine(at: conditionBeginIndex) + .count < indent.count + formatter.options.indent.count + { + indent = formatter.currentIndentForLine(at: conditionBeginIndex) + formatter.options.indent + indentStack[indentStack.count - 1] = indent + } + + let startOfLineIndex = formatter.startOfLine(at: i, excludingIndent: true) + let startOfLine = formatter.tokens[startOfLineIndex] + + if formatter.options.wrapTernaryOperators == .beforeOperators, + startOfLine == .operator(":", .infix) || startOfLine == .operator("?", .infix) + { + // Push a ? scope onto the stack so we can easily know + // that the next : is the closing operator of this ternary + if startOfLine.string == "?" { + // We smuggle the index of this operator in the scope stack + // so we can recover it trivially when handling the + // corresponding : operator. + scopeStack.append(.operator("?-\(startOfLineIndex)", .infix)) + } + + // Indent any operator-leading lines following a compomnent operator + // of a wrapped ternary operator expression, except for the : + // following a ? + if let nextToken = formatter.next(.nonSpace, after: i), + nextToken.isOperator(ofType: .infix), + nextToken != .operator(":", .infix) + { + indent += formatter.options.indent + indentStack[indentStack.count - 1] = indent + } + } + + // Make sure the indentation for this : operator matches + // the indentation of the previous ? operator + if formatter.options.wrapTernaryOperators == .beforeOperators, + formatter.next(.nonSpace, after: i) == .operator(":", .infix), + let scope = scopeStack.last, + scope.string.hasPrefix("?"), + scope.isOperator(ofType: .infix), + let previousOperatorIndex = scope.string.components(separatedBy: "-").last.flatMap({ Int($0) }) + { + scopeStack.removeLast() + indent = formatter.currentIndentForLine(at: previousOperatorIndex) + indentStack[indentStack.count - 1] = indent + } + } + } else if linewrapped { + func isWrappedDeclaration() -> Bool { + guard let keywordIndex = formatter + .indexOfLastSignificantKeyword(at: i, excluding: [ + "where", "throws", "rethrows", + ]), !formatter.tokens[keywordIndex ..< i].contains(.endOfScope("}")), + case let .keyword(keyword) = formatter.tokens[keywordIndex], + ["class", "actor", "struct", "enum", "protocol", "extension", + "func"].contains(keyword) + else { + return false + } + + let end = formatter.endOfLine(at: i + 1) + guard let lastToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: end + 1), + [.startOfScope("{"), .endOfScope("}")].contains(lastToken) else { return false } + + return true + } + + // Don't indent line starting with dot if previous line was just a closing brace + var lastToken = formatter.tokens[lastNonSpaceOrLinebreakIndex] + if formatter.options.allmanBraces, nextToken == .startOfScope("{"), + formatter.isStartOfClosure(at: nextNonSpaceIndex) + { + // Don't indent further + } else if formatter.token(at: nextTokenIndex ?? -1) == .operator(".", .infix) || + formatter.isLabel(at: nextTokenIndex ?? -1) + { + var lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) + let startToken = formatter.token(at: lineStart) + if let startToken = startToken, [ + .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif") + ].contains(startToken) { + if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lineStart) { + lastNonSpaceOrLinebreakIndex = index + lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) + } + } + if formatter.token(at: lineStart) == .operator(".", .infix), + [.keyword("#else"), .keyword("#elseif"), .endOfScope("#endif")].contains(startToken) + { + indent = formatter.currentIndentForLine(at: lineStart) + } else if formatter.tokens[lineStart ..< lastNonSpaceOrLinebreakIndex].allSatisfy({ + $0.isEndOfScope || $0.isSpaceOrComment + }) { + if lastToken.isEndOfScope { + indent = formatter.currentIndentForLine(at: lastNonSpaceOrLinebreakIndex) + } + if !lastToken.isEndOfScope || lastToken == .endOfScope("case") || + formatter.options.xcodeIndentation, ![ + .endOfScope("}"), .endOfScope(")") + ].contains(lastToken) + { + indent += formatter.options.indent + } + } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { + indent += formatter.linewrapIndent(at: i) + } + } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { + indent += formatter.linewrapIndent(at: i) + } + + linewrapStack[linewrapStack.count - 1] = true + indentStack.append(indent) + stringBodyIndentStack.append("") + } + // Avoid indenting commented code + guard !formatter.isCommentedCode(at: nextNonSpaceIndex) else { + break + } + // Apply indent + switch nextToken { + case .linebreak: + if formatter.options.truncateBlankLines { + formatter.insertSpaceIfEnabled("", at: i + 1) + } else if scopeStack.last?.isStringDelimiter == true, + formatter.token(at: i + 1)?.isSpace == true + { + formatter.insertSpaceIfEnabled(indent, at: i + 1) + } + case .error, .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif"), + .startOfScope("#if") where formatter.options.ifdefIndent != .indent: + break + case .startOfScope("/*"), .commentBody, .endOfScope("*/"): + nextNonSpaceIndex = formatter.endOfScope(at: nextNonSpaceIndex) ?? nextNonSpaceIndex + fallthrough + case .startOfScope("//"): + nextNonSpaceIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, + after: nextNonSpaceIndex) ?? nextNonSpaceIndex + nextNonSpaceIndex = formatter.index(of: .nonSpaceOrLinebreak, + before: nextNonSpaceIndex) ?? nextNonSpaceIndex + if let lineIndex = formatter.index(of: .linebreak, after: nextNonSpaceIndex), + let nextToken = formatter.next(.nonSpace, after: lineIndex), + [.startOfScope("#if"), .keyword("#else"), .keyword("#elseif")].contains(nextToken) + { + break + } + fallthrough + case .startOfScope("#if"): + if let lineIndex = formatter.index(of: .linebreak, after: nextNonSpaceIndex), + let nextKeyword = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lineIndex), [ + .endOfScope("case"), .endOfScope("default"), .keyword("@unknown"), + ].contains(nextKeyword) + { + break + } + formatter.insertSpaceIfEnabled(indent, at: i + 1) + case .endOfScope, .keyword("@unknown"): + if let scope = scopeStack.last { + switch scope { + case .startOfScope("/*"), .startOfScope("#if"), + .keyword("#else"), .keyword("#elseif"), + .startOfScope where scope.isStringDelimiter: + formatter.insertSpaceIfEnabled(indent, at: i + 1) + default: + break + } + } + default: + var lastIndex = lastNonSpaceOrLinebreakIndex > -1 ? lastNonSpaceOrLinebreakIndex : i + while formatter.token(at: lastIndex) == .endOfScope("#endif"), + let index = formatter.index(of: .startOfScope, before: lastIndex, if: { + $0 == .startOfScope("#if") + }) + { + lastIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + before: index + ) ?? index + } + let lastToken = formatter.tokens[lastIndex] + if [.endOfScope("}"), .endOfScope(")")].contains(lastToken), + lastIndex == formatter.startOfLine(at: lastIndex, excludingIndent: true), + formatter.token(at: nextNonSpaceIndex) == .operator(".", .infix) || + (lastToken == .endOfScope("}") && formatter.isLabel(at: nextNonSpaceIndex)) + { + indent = formatter.currentIndentForLine(at: lastIndex) + } + if formatter.options.fragment, lastToken == .delimiter(",") { + break // Can't reliably indent + } + formatter.insertSpaceIfEnabled(indent, at: i + 1) + } + + if linewrapped, shouldIndentNextLine(at: i) { + indentStack[indentStack.count - 1] += formatter.options.indent + } + default: + break + } + // Track token for line wraps + if !token.isSpaceOrComment { + lastNonSpaceIndex = i + if !token.isLinebreak { + lastNonSpaceOrLinebreakIndex = i + } + } + } + + if formatter.options.indentStrings { + formatter.forEach(.startOfScope("\"\"\"")) { stringStartIndex, _ in + let baseIndent = formatter.currentIndentForLine(at: stringStartIndex) + let expectedIndent = baseIndent + formatter.options.indent + + guard let stringEndIndex = formatter.endOfScope(at: stringStartIndex), + // Preserve the default indentation if the opening """ is on a line by itself + formatter.startOfLine(at: stringStartIndex, excludingIndent: true) != stringStartIndex + else { return } + + for linebreakIndex in (stringStartIndex ..< stringEndIndex).reversed() + where formatter.tokens[linebreakIndex].isLinebreak + { + // If this line is completely blank, do nothing + // - This prevents conflicts with the trailingSpace rule + if formatter.nextToken(after: linebreakIndex)?.isLinebreak == true { + continue + } + + let indentIndex = linebreakIndex + 1 + if formatter.tokens[indentIndex].is(.space) { + formatter.replaceToken(at: indentIndex, with: .space(expectedIndent)) + } else { + formatter.insert(.space(expectedIndent), at: indentIndex) + } + } + } + } + } +} diff --git a/Sources/Rules/InitCoderUnavailable.swift b/Sources/Rules/InitCoderUnavailable.swift new file mode 100644 index 000000000..6df612e8c --- /dev/null +++ b/Sources/Rules/InitCoderUnavailable.swift @@ -0,0 +1,60 @@ +// +// InitCoderUnavailable.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Add @available(*, unavailable) to init?(coder aDecoder: NSCoder) + static let initCoderUnavailable = FormatRule( + help: """ + Add `@available(*, unavailable)` attribute to required `init(coder:)` when + it hasn't been implemented. + """, + options: ["initcodernil"], + sharedOptions: ["linebreaks"] + ) { formatter in + let unavailableTokens = tokenize("@available(*, unavailable)") + formatter.forEach(.identifier("required")) { i, _ in + // look for required init?(coder + guard var initIndex = formatter.index(of: .keyword("init"), after: i) else { return } + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: initIndex, if: { + $0 == .operator("?", .postfix) + }) { + initIndex = nextIndex + } + + guard let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: initIndex, if: { + $0 == .startOfScope("(") + }), let coderIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: parenIndex, if: { + $0 == .identifier("coder") + }), let endParenIndex = formatter.index(of: .endOfScope(")"), after: coderIndex), + let braceIndex = formatter.index(of: .startOfScope("{"), after: endParenIndex) + else { return } + + // make sure the implementation is empty or fatalError + guard let firstTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: braceIndex, if: { + [.endOfScope("}"), .identifier("fatalError")].contains($0) + }) else { return } + + if formatter.options.initCoderNil, + formatter.token(at: firstTokenIndex) == .identifier("fatalError"), + let fatalParenEndOfScope = formatter.index(of: .endOfScope, after: firstTokenIndex + 1) + { + formatter.replaceTokens(in: firstTokenIndex ... fatalParenEndOfScope, with: [.identifier("nil")]) + } + + // avoid adding attribute if it's already there + if formatter.modifiersForDeclaration(at: i, contains: "@available") { return } + + let startIndex = formatter.startOfModifiers(at: i, includingAttributes: true) + formatter.insert(.space(formatter.currentIndentForLine(at: startIndex)), at: startIndex) + formatter.insertLinebreak(at: startIndex) + formatter.insert(unavailableTokens, at: startIndex) + } + } +} diff --git a/Sources/Rules/IsEmpty.swift b/Sources/Rules/IsEmpty.swift new file mode 100644 index 000000000..36b72f628 --- /dev/null +++ b/Sources/Rules/IsEmpty.swift @@ -0,0 +1,95 @@ +// +// IsEmpty.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace count == 0 with isEmpty + static let isEmpty = FormatRule( + help: "Prefer `isEmpty` over comparing `count` against zero.", + disabledByDefault: true + ) { formatter in + formatter.forEach(.identifier("count")) { i, _ in + guard let dotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i, if: { + $0.isOperator(".") + }), let opIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0.isOperator + }), let endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: opIndex, if: { + $0 == .number("0", .integer) + }) else { + return + } + var isOptional = false + var index = dotIndex + var wasIdentifier = false + loop: while true { + guard let prev = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index) else { + break + } + switch formatter.tokens[prev] { + case .operator("!", _), .operator(".", _): + break // Ignored + case .operator("?", _): + if formatter.tokens[prev - 1].isSpace { + break loop + } + isOptional = true + case let .operator(op, .infix): + guard ["||", "&&", ":"].contains(op) else { + return + } + break loop + case .keyword, .delimiter, .startOfScope: + break loop + case .identifier: + if wasIdentifier { + break loop + } + wasIdentifier = true + index = prev + continue + case .endOfScope: + guard !wasIdentifier, let start = formatter.index(of: .startOfScope, before: prev) else { + break loop + } + wasIdentifier = false + index = start + continue + default: + break + } + wasIdentifier = false + index = prev + } + let isEmpty: Bool + switch formatter.tokens[opIndex] { + case .operator("==", .infix): isEmpty = true + case .operator("!=", .infix), .operator(">", .infix): isEmpty = false + default: return + } + if isEmpty { + if isOptional { + formatter.replaceTokens(in: i ... endIndex, with: [ + .identifier("isEmpty"), .space(" "), .operator("==", .infix), .space(" "), .identifier("true"), + ]) + } else { + formatter.replaceTokens(in: i ... endIndex, with: .identifier("isEmpty")) + } + } else { + if isOptional { + formatter.replaceTokens(in: i ... endIndex, with: [ + .identifier("isEmpty"), .space(" "), .operator("!=", .infix), .space(" "), .identifier("true"), + ]) + } else { + formatter.replaceTokens(in: i ... endIndex, with: .identifier("isEmpty")) + formatter.insert(.operator("!", .prefix), at: index) + } + } + } + } +} diff --git a/Sources/Rules/LeadingDelimiters.swift b/Sources/Rules/LeadingDelimiters.swift new file mode 100644 index 000000000..76912cf83 --- /dev/null +++ b/Sources/Rules/LeadingDelimiters.swift @@ -0,0 +1,37 @@ +// +// LeadingDelimiters.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let leadingDelimiters = FormatRule( + help: "Move leading delimiters to the end of the previous line.", + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.delimiter) { i, _ in + guard let endOfLine = formatter.index(of: .nonSpace, before: i, if: { + $0.isLinebreak + }) else { + return + } + let nextIndex = formatter.index(of: .nonSpace, after: i) ?? (i + 1) + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex) + formatter.insertLinebreak(at: nextIndex) + formatter.removeTokens(in: i + 1 ..< nextIndex) + guard case .commentBody? = formatter.last(.nonSpace, before: endOfLine) else { + formatter.removeTokens(in: endOfLine ..< i) + return + } + let startIndex = formatter.index(of: .nonSpaceOrComment, before: endOfLine) ?? -1 + formatter.removeTokens(in: endOfLine ..< i) + let comment = Array(formatter.tokens[startIndex + 1 ..< endOfLine]) + formatter.insert(comment, at: endOfLine + 1) + formatter.removeTokens(in: startIndex + 1 ..< endOfLine) + } + } +} diff --git a/Sources/Rules/LinebreakAtEndOfFile.swift b/Sources/Rules/LinebreakAtEndOfFile.swift new file mode 100644 index 000000000..ce5fa0ce1 --- /dev/null +++ b/Sources/Rules/LinebreakAtEndOfFile.swift @@ -0,0 +1,34 @@ +// +// LinebreakAtEndOfFile.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Always end file with a linebreak, to avoid incompatibility with certain unix tools: + /// http://stackoverflow.com/questions/2287967/why-is-it-recommended-to-have-empty-line-in-the-end-of-file + static let linebreakAtEndOfFile = FormatRule( + help: "Add empty blank line at end of file.", + sharedOptions: ["linebreaks"] + ) { formatter in + guard !formatter.options.fragment else { return } + var wasLinebreak = true + formatter.forEachToken(onlyWhereEnabled: false) { _, token in + switch token { + case .linebreak: + wasLinebreak = true + case .space: + break + default: + wasLinebreak = false + } + } + if formatter.isEnabled, !wasLinebreak { + formatter.insertLinebreak(at: formatter.tokens.count) + } + } +} diff --git a/Sources/Rules/Linebreaks.swift b/Sources/Rules/Linebreaks.swift new file mode 100644 index 000000000..c30b27dfb --- /dev/null +++ b/Sources/Rules/Linebreaks.swift @@ -0,0 +1,21 @@ +// +// Linebreaks.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Standardise linebreak characters as whatever is specified in the options (\n by default) + static let linebreaks = FormatRule( + help: "Use specified linebreak character for all linebreaks (CR, LF or CRLF).", + options: ["linebreaks"] + ) { formatter in + formatter.forEach(.linebreak) { i, _ in + formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i)) + } + } +} diff --git a/Sources/Rules/MarkTypes.swift b/Sources/Rules/MarkTypes.swift new file mode 100644 index 000000000..b8895e962 --- /dev/null +++ b/Sources/Rules/MarkTypes.swift @@ -0,0 +1,254 @@ +// +// MarkTypes.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let markTypes = FormatRule( + help: "Add a MARK comment before top-level types and extensions.", + runOnceOnly: true, + disabledByDefault: true, + options: ["marktypes", "typemark", "markextensions", "extensionmark", "groupedextension"], + sharedOptions: ["lineaftermarks"] + ) { formatter in + var declarations = formatter.parseDeclarations() + + // Do nothing if there is only one top-level declaration in the file (excluding imports) + let declarationsWithoutImports = declarations.filter { $0.keyword != "import" } + guard declarationsWithoutImports.count > 1 else { + return + } + + for (index, declaration) in declarations.enumerated() { + guard case let .type(kind, open, body, close, _) = declaration else { continue } + + guard var typeName = declaration.name else { + continue + } + + let markMode: MarkMode + let commentTemplate: String + let isGroupedExtension: Bool + switch declaration.keyword { + case "extension": + // TODO: this should be stored in declaration at parse time + markMode = formatter.options.markExtensions + + // We provide separate mark comment customization points for + // extensions that are "grouped" with (e.g. following) their extending type, + // vs extensions that are completely separate. + // + // struct Foo { } + // extension Foo { } // This extension is "grouped" with its extending type + // extension String { } // This extension is standalone (not grouped with any type) + // + let isGroupedWithExtendingType: Bool + if let indexOfExtendingType = declarations[.. [Token] in + var openingFormatter = Formatter(openingTokens) + + guard let keywordIndex = openingFormatter.index(after: -1, where: { + $0.string == declaration.keyword + }) else { return openingTokens } + + // If this declaration is extension, check if it has any conformances + var conformanceNames: String? + if declaration.keyword == "extension", + var conformanceSearchIndex = openingFormatter.index(of: .delimiter(":"), after: keywordIndex) + { + var conformances = [String]() + + let endOfConformances = openingFormatter.index(of: .keyword("where"), after: keywordIndex) + ?? openingFormatter.index(of: .startOfScope("{"), after: keywordIndex) + ?? openingFormatter.tokens.count + + while let token = openingFormatter.token(at: conformanceSearchIndex), + conformanceSearchIndex < endOfConformances + { + if token.isIdentifier { + let (fullyQualifiedName, next) = openingFormatter.fullyQualifiedName(startingAt: conformanceSearchIndex) + conformances.append(fullyQualifiedName) + conformanceSearchIndex = next + } + + conformanceSearchIndex += 1 + } + + if !conformances.isEmpty { + conformanceNames = conformances.joined(separator: ", ") + } + } + + // Build the types expected mark comment by replacing `%t`s with the type name + // and `%c`s with the list of conformances added in the extension (if applicable) + var markForType: String? + + if !commentTemplate.contains("%c") { + markForType = commentTemplate.replacingOccurrences(of: "%t", with: typeName) + } else if commentTemplate.contains("%c"), let conformanceNames = conformanceNames { + markForType = commentTemplate + .replacingOccurrences(of: "%t", with: typeName) + .replacingOccurrences(of: "%c", with: conformanceNames) + } + + // If this is an extension without any conformances, but contains exactly + // one body declaration (a type), we can mark the extension with the nested type's name + // (e.g. `// MARK: Foo.Bar`). + if declaration.keyword == "extension", + conformanceNames == nil + { + // Find all of the nested extensions, so we can form the fully qualified + // name of the inner-most type (e.g. `Foo.Bar.Baaz.Quux`). + var extensions = [declaration] + + while let innerExtension = extensions.last, + let extensionBody = innerExtension.body, + extensionBody.count == 1, + extensionBody[0].keyword == "extension" + { + extensions.append(extensionBody[0]) + } + + let innermostExtension = extensions.last! + let extensionNames = extensions.compactMap { $0.name }.joined(separator: ".") + + if let extensionBody = innermostExtension.body, + extensionBody.count == 1, + let nestedType = extensionBody.first, + nestedType.definesType, + let nestedTypeName = nestedType.name + { + let fullyQualifiedName = "\(extensionNames).\(nestedTypeName)" + + if isGroupedExtension { + markForType = "// \(formatter.options.groupedExtensionMarkComment)" + .replacingOccurrences(of: "%c", with: fullyQualifiedName) + } else { + markForType = "// \(formatter.options.typeMarkComment)" + .replacingOccurrences(of: "%t", with: fullyQualifiedName) + } + } + } + + guard let expectedComment = markForType else { + return openingFormatter.tokens + } + + // Remove any lines that have the same prefix as the comment template + // - We can't really do exact matches here like we do for `organizeDeclaration` + // category separators, because there's a much wider variety of options + // that a user could use for the type name (orphaned renames, etc.) + var commentPrefixes = Set(["// MARK: ", "// MARK: - "]) + + if let typeNameSymbolIndex = commentTemplate.firstIndex(of: "%") { + commentPrefixes.insert(String(commentTemplate.prefix(upTo: typeNameSymbolIndex))) + } + + openingFormatter.forEach(.startOfScope("//")) { index, _ in + let startOfLine = openingFormatter.startOfLine(at: index) + let endOfLine = openingFormatter.endOfLine(at: index) + + let commentLine = sourceCode(for: Array(openingFormatter.tokens[index ... endOfLine])) + + for commentPrefix in commentPrefixes { + if commentLine.lowercased().hasPrefix(commentPrefix.lowercased()) { + // If we found a line that matched the comment prefix, + // remove it and any linebreak immediately after it. + if openingFormatter.token(at: endOfLine + 1)?.isLinebreak == true { + openingFormatter.removeToken(at: endOfLine + 1) + } + + openingFormatter.removeTokens(in: startOfLine ... endOfLine) + break + } + } + } + + // When inserting a mark before the first declaration, + // we should make sure we place it _after_ the file header. + var markInsertIndex = 0 + if index == 0 { + // Search for the end of the file header, which ends when we hit a + // blank line or any non-space/comment/lintbreak + var endOfFileHeader = 0 + + while openingFormatter.token(at: endOfFileHeader)?.isSpaceOrCommentOrLinebreak == true { + endOfFileHeader += 1 + + if openingFormatter.token(at: endOfFileHeader)?.isLinebreak == true, + openingFormatter.next(.nonSpace, after: endOfFileHeader)?.isLinebreak == true + { + markInsertIndex = endOfFileHeader + 2 + break + } + } + } + + // Insert the expected comment at the start of the declaration + let endMarkDeclaration = formatter.options.lineAfterMarks ? "\n\n" : "\n" + openingFormatter.insert(tokenize("\(expectedComment)\(endMarkDeclaration)"), at: markInsertIndex) + + // If the previous declaration doesn't end in a blank line, + // add an additional linebreak to balance the mark. + if index != 0 { + declarations[index - 1] = formatter.mapClosingTokens(in: declarations[index - 1]) { + formatter.endingWithBlankLine($0) + } + } + + return openingFormatter.tokens + } + } + + let updatedTokens = declarations.flatMap { $0.tokens } + formatter.replaceTokens(in: 0 ..< formatter.tokens.count, with: updatedTokens) + } +} diff --git a/Sources/Rules/ModifierOrder.swift b/Sources/Rules/ModifierOrder.swift new file mode 100644 index 000000000..b4de405ca --- /dev/null +++ b/Sources/Rules/ModifierOrder.swift @@ -0,0 +1,74 @@ +// +// ModifierOrder.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Standardise the order of property modifiers + static let modifierOrder = FormatRule( + help: "Use consistent ordering for member modifiers.", + options: ["modifierorder"] + ) { formatter in + formatter.forEach(.keyword) { i, token in + switch token.string { + case "let", "func", "var", "class", "actor", "extension", "init", "enum", + "struct", "typealias", "subscript", "associatedtype", "protocol": + break + default: + return + } + var modifiers = [String: [Token]]() + var lastModifier: (name: String, tokens: [Token])? + func pushModifier() { + lastModifier.map { modifiers[$0.name] = $0.tokens } + } + var lastIndex = i + var previousIndex = lastIndex + loop: while let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lastIndex) { + switch formatter.tokens[index] { + case .operator(_, .prefix), .operator(_, .infix), .keyword("case"): + // Last modifier was invalid + lastModifier = nil + lastIndex = previousIndex + break loop + case let token where token.isModifierKeyword: + pushModifier() + lastModifier = (token.string, [Token](formatter.tokens[index ..< lastIndex])) + previousIndex = lastIndex + lastIndex = index + case .endOfScope(")"): + if case let .identifier(param)? = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index), + let openParenIndex = formatter.index(of: .startOfScope("("), before: index), + let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: openParenIndex), + let token = formatter.token(at: index), token.isModifierKeyword + { + pushModifier() + let modifier = token.string + (param == "set" ? "(set)" : "") + lastModifier = (modifier, [Token](formatter.tokens[index ..< lastIndex])) + previousIndex = lastIndex + lastIndex = index + } else { + break loop + } + default: + // Not a modifier + break loop + } + } + pushModifier() + guard !modifiers.isEmpty else { return } + var sortedModifiers = [Token]() + for modifier in formatter.modifierOrder { + if let tokens = modifiers[modifier] { + sortedModifiers += tokens + } + } + formatter.replaceTokens(in: lastIndex ..< i, with: sortedModifiers) + } + } +} diff --git a/Sources/Rules/NoExplicitOwnership.swift b/Sources/Rules/NoExplicitOwnership.swift new file mode 100644 index 000000000..e443875bb --- /dev/null +++ b/Sources/Rules/NoExplicitOwnership.swift @@ -0,0 +1,47 @@ +// +// NoExplicitOwnership.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let noExplicitOwnership = FormatRule( + help: "Don't use explicit ownership modifiers (borrowing / consuming).", + disabledByDefault: true + ) { formatter in + formatter.forEachToken { keywordIndex, token in + guard [.identifier("borrowing"), .identifier("consuming")].contains(token), + let nextTokenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: keywordIndex) + else { return } + + // Use of `borrowing` and `consuming` as ownership modifiers + // immediately precede a valid type, or the `func` keyword. + // You could also simply use these names as a property, + // like `let borrowing = foo` or `func myFunc(borrowing foo: Foo)`. + // As a simple heuristic to detect the difference, attempt to parse the + // following tokens as a type, and require that it doesn't start with lower-case letter. + let isValidOwnershipModifier: Bool + if formatter.tokens[nextTokenIndex] == .keyword("func") { + isValidOwnershipModifier = true + } + + else if let type = formatter.parseType(at: nextTokenIndex), + type.name.first?.isLowercase == false + { + isValidOwnershipModifier = true + } + + else { + isValidOwnershipModifier = false + } + + if isValidOwnershipModifier { + formatter.removeTokens(in: keywordIndex ..< nextTokenIndex) + } + } + } +} diff --git a/Sources/Rules/NumberFormatting.swift b/Sources/Rules/NumberFormatting.swift new file mode 100644 index 000000000..5504c1372 --- /dev/null +++ b/Sources/Rules/NumberFormatting.swift @@ -0,0 +1,105 @@ +// +// NumberFormatting.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Standardize formatting of numeric literals + static let numberFormatting = FormatRule( + help: """ + Use consistent grouping for numeric literals. Groups will be separated by `_` + delimiters to improve readability. For each numeric type you can specify a group + size (the number of digits in each group) and a threshold (the minimum number of + digits in a number before grouping is applied). + """, + options: ["decimalgrouping", "binarygrouping", "octalgrouping", "hexgrouping", + "fractiongrouping", "exponentgrouping", "hexliteralcase", "exponentcase"] + ) { formatter in + func applyGrouping(_ grouping: Grouping, to number: inout String) { + switch grouping { + case .none, .group: + number = number.replacingOccurrences(of: "_", with: "") + case .ignore: + return + } + guard case let .group(group, threshold) = grouping, group > 0, number.count >= threshold else { + return + } + var output = Substring() + var index = number.endIndex + var count = 0 + repeat { + index = number.index(before: index) + if count > 0, count % group == 0 { + output.insert("_", at: output.startIndex) + } + count += 1 + output.insert(number[index], at: output.startIndex) + } while index != number.startIndex + number = String(output) + } + formatter.forEachToken { i, token in + guard case let .number(number, type) = token else { + return + } + let grouping: Grouping + let prefix: String, exponentSeparator: String, parts: [String] + switch type { + case .integer, .decimal: + grouping = formatter.options.decimalGrouping + prefix = "" + exponentSeparator = formatter.options.uppercaseExponent ? "E" : "e" + parts = number.components(separatedBy: CharacterSet(charactersIn: ".eE")) + case .binary: + grouping = formatter.options.binaryGrouping + prefix = "0b" + exponentSeparator = "" + parts = [String(number[prefix.endIndex...])] + case .octal: + grouping = formatter.options.octalGrouping + prefix = "0o" + exponentSeparator = "" + parts = [String(number[prefix.endIndex...])] + case .hex: + grouping = formatter.options.hexGrouping + prefix = "0x" + exponentSeparator = formatter.options.uppercaseExponent ? "P" : "p" + parts = number[prefix.endIndex...].components(separatedBy: CharacterSet(charactersIn: ".pP")).map { + formatter.options.uppercaseHex ? $0.uppercased() : $0.lowercased() + } + } + var main = parts[0], fraction = "", exponent = "" + switch parts.count { + case 2 where number.contains("."): + fraction = parts[1] + case 2: + exponent = parts[1] + case 3: + fraction = parts[1] + exponent = parts[2] + default: + break + } + applyGrouping(grouping, to: &main) + if formatter.options.fractionGrouping { + applyGrouping(grouping, to: &fraction) + } + if formatter.options.exponentGrouping { + applyGrouping(grouping, to: &exponent) + } + var result = prefix + main + if !fraction.isEmpty { + result += "." + fraction + } + if !exponent.isEmpty { + result += exponentSeparator + exponent + } + formatter.replaceToken(at: i, with: .number(result, type)) + } + } +} diff --git a/Sources/Rules/OpaqueGenericParameters.swift b/Sources/Rules/OpaqueGenericParameters.swift new file mode 100644 index 000000000..d12f024d6 --- /dev/null +++ b/Sources/Rules/OpaqueGenericParameters.swift @@ -0,0 +1,273 @@ +// +// OpaqueGenericParameters.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let opaqueGenericParameters = FormatRule( + help: """ + Use opaque generic parameters (`some Protocol`) instead of generic parameters + with constraints (`T where T: Protocol`, etc) where equivalent. Also supports + primary associated types for common standard library types, so definitions like + `T where T: Collection, T.Element == Foo` are updated to `some Collection`. + """, + options: ["someany"] + ) { formatter in + formatter.forEach(.keyword) { keywordIndex, keyword in + guard // Opaque generic parameter syntax is only supported in Swift 5.7+ + formatter.options.swiftVersion >= "5.7", + // Apply this rule to any function-like declaration + [.keyword("func"), .keyword("init"), .keyword("subscript")].contains(keyword), + // Validate that this is a generic method using angle bracket syntax, + // and find the indices for all of the key tokens + let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), + let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), + let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), + let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), + genericSignatureStartIndex < paramListStartIndex, + genericSignatureEndIndex < paramListStartIndex, + let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), + let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) + else { return } + + var genericTypes = [Formatter.GenericType]() + + // Parse the generics in the angle brackets (e.g. ``) + formatter.parseGenericTypes( + from: genericSignatureStartIndex, + to: genericSignatureEndIndex, + into: &genericTypes + ) + + // Parse additional conformances and constraints after the `where` keyword if present + // (e.g. `where Foo: Fooable, Foo.Bar: Barable, Foo.Baaz == Baazable`) + var whereTokenIndex: Int? + if let whereIndex = formatter.index(of: .keyword("where"), after: paramListEndIndex), + whereIndex < openBraceIndex + { + whereTokenIndex = whereIndex + formatter.parseGenericTypes(from: whereIndex, to: openBraceIndex, into: &genericTypes) + } + + // Parse the return type if present + var returnTypeTokens: [Token]? + if let returnIndex = formatter.index(of: .operator("->", .infix), after: paramListEndIndex), + returnIndex < openBraceIndex, returnIndex < whereTokenIndex ?? openBraceIndex + { + let returnTypeRange = (returnIndex + 1) ..< (whereTokenIndex ?? openBraceIndex) + returnTypeTokens = Array(formatter.tokens[returnTypeRange]) + } + + let genericParameterListRange = (genericSignatureStartIndex + 1) ..< genericSignatureEndIndex + let genericParameterListTokens = formatter.tokens[genericParameterListRange] + + let parameterListRange = (paramListStartIndex + 1) ..< paramListEndIndex + let parameterListTokens = formatter.tokens[parameterListRange] + + let bodyRange = (openBraceIndex + 1) ..< closeBraceIndex + let bodyTokens = formatter.tokens[bodyRange] + + for genericType in genericTypes { + // If the generic type doesn't occur in the generic parameter list (<...>), + // then we inherited it from the generic context and can't replace the type + // with an opaque parameter. + if !genericParameterListTokens.contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + continue + } + + // We can only remove the generic type if it appears exactly once in the parameter list. + // - If the generic type occurs _multiple_ times in the parameter list, + // it isn't eligible to be removed. For example `(T, T) where T: Foo` + // requires the two params to be the same underlying type, but + // `(some Foo, some Foo)` does not. + // - If the generic type occurs _zero_ times in the parameter list + // then removing the generic parameter would also remove any + // potentially-important constraints (for example, if the type isn't + // used in the function parameters / body and is only constrained relative + // to generic types in the parent type scope). If this generic parameter + // is truly unused and redundant then the compiler would emit an error. + let countInParameterList = parameterListTokens.filter { $0.string == genericType.name }.count + if countInParameterList != 1 { + genericType.eligibleToRemove = false + continue + } + + // If the generic type occurs in the body of the function, then it can't be removed + if bodyTokens.contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + continue + } + + // If the generic type is referenced in any attributes, then it can't be removed + let startOfModifiers = formatter.startOfModifiers(at: keywordIndex, includingAttributes: true) + let modifierTokens = formatter.tokens[startOfModifiers ..< keywordIndex] + if modifierTokens.contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + continue + } + + // If the generic type is used in a constraint of any other generic type, then the type + // can't be removed without breaking that other type + let otherGenericTypes = genericTypes.filter { $0.name != genericType.name } + let otherTypeConformances = otherGenericTypes.flatMap { $0.conformances } + for otherTypeConformance in otherTypeConformances { + let conformanceTokens = formatter.tokens[otherTypeConformance.sourceRange] + if conformanceTokens.contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + } + } + + // In some weird cases you can also have a generic constraint that references a generic + // type from the parent context with the same name. We can't change these, since it + // can cause the build to break + for conformance in genericType.conformances { + if tokenize(conformance.name).contains(where: { $0.string == genericType.name }) { + genericType.eligibleToRemove = false + } + } + + // A generic used as a return type is different from an opaque result type (SE-244). + // For example in `-> T where T: Fooable`, the generic type is caller-specified, + // but with `-> some Fooable` the generic type is specified by the function implementation. + // Because those represent different concepts, we can't convert between them, + // so have to mark the generic type as ineligible if it appears in the return type. + if let returnTypeTokens = returnTypeTokens, + returnTypeTokens.contains(where: { $0.string == genericType.name }) + { + genericType.eligibleToRemove = false + continue + } + + // If the method that generates the opaque parameter syntax doesn't succeed, + // then this type is ineligible (because it used a generic constraint that + // can't be represented using this syntax). + // TODO: this option probably needs to be captured earlier to support comment directives + if genericType.asOpaqueParameter(useSomeAny: formatter.options.useSomeAny) == nil { + genericType.eligibleToRemove = false + continue + } + + // If the generic type is used as a closure type parameter, it can't be removed or the compiler + // will emit a "'some' cannot appear in parameter position in parameter type " error + for tokenIndex in keywordIndex ... closeBraceIndex { + // Check if this is the start of a closure + if formatter.tokens[tokenIndex] == .startOfScope("("), + tokenIndex != paramListStartIndex, + let endOfScope = formatter.endOfScope(at: tokenIndex), + let tokenAfterParen = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfScope), + [.operator("->", .infix), .keyword("throws"), .identifier("async")].contains(tokenAfterParen), + // Check if the closure type parameters contains this generic type + formatter.tokens[tokenIndex ... endOfScope].contains(where: { $0.string == genericType.name }) + { + genericType.eligibleToRemove = false + } + } + + // Extract the comma-separated list of function parameters, + // so we can check conditions on the individual parameters + let parameterListTokenIndices = (paramListStartIndex + 1) ..< paramListEndIndex + + // Split the parameter list at each comma that's directly within the paren list scope + let parameters = parameterListTokenIndices + .split(whereSeparator: { index in + let token = formatter.tokens[index] + return token == .delimiter(",") + && formatter.endOfScope(at: index) == paramListEndIndex + }) + .map { parameterIndices in + parameterIndices.map { index in + formatter.tokens[index] + } + } + + for parameterTokens in parameters { + // Variadic parameters don't support opaque generic syntax, so we have to check + // if any use cases of this type in the parameter list are variadic + if parameterTokens.contains(.operator("...", .postfix)), + parameterTokens.contains(.identifier(genericType.name)) + { + genericType.eligibleToRemove = false + } + } + } + + let genericsEligibleToRemove = genericTypes.filter { $0.eligibleToRemove } + let sourceRangesToRemove = Set(genericsEligibleToRemove.flatMap { type in + [type.definitionSourceRange] + type.conformances.map { $0.sourceRange } + }) + + // We perform modifications to the function signature in reverse order + // so we don't invalidate any of the indices we've recorded. So first + // we remove components of the where clause. + if let whereIndex = formatter.index(of: .keyword("where"), after: paramListEndIndex), + whereIndex < openBraceIndex + { + let whereClauseSourceRanges = sourceRangesToRemove.filter { $0.lowerBound > whereIndex } + formatter.removeTokens(in: Array(whereClauseSourceRanges)) + + if let newOpenBraceIndex = formatter.index(of: .startOfScope("{"), after: whereIndex) { + // if where clause is completely empty, we need to remove the where token as well + if formatter.index(of: .nonSpaceOrLinebreak, after: whereIndex) == newOpenBraceIndex { + formatter.removeTokens(in: whereIndex ..< newOpenBraceIndex) + } + // remove trailing comma + else if let commaIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + before: newOpenBraceIndex, if: { $0 == .delimiter(",") } + ) { + formatter.removeToken(at: commaIndex) + if formatter.tokens[commaIndex - 1].isSpace, + formatter.tokens[commaIndex].isSpaceOrLinebreak + { + formatter.removeToken(at: commaIndex - 1) + } + } + } + } + + // Replace all of the uses of generic types that are eligible to remove + // with the corresponding opaque parameter declaration + for index in parameterListRange.reversed() { + if let matchingGenericType = genericsEligibleToRemove.first(where: { $0.name == formatter.tokens[index].string }), + var opaqueParameter = matchingGenericType.asOpaqueParameter(useSomeAny: formatter.options.useSomeAny) + { + // If this instance of the type is followed by a `.` or `?` then we have to wrap the new type in parens + // (e.g. changing `Foo.Type` to `some Any.Type` breaks the build, it needs to be `(some Any).Type`) + if let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: index), + [.operator(".", .infix), .operator("?", .postfix)].contains(nextToken) + { + opaqueParameter.insert(.startOfScope("("), at: 0) + opaqueParameter.append(.endOfScope(")")) + } + + formatter.replaceToken(at: index, with: opaqueParameter) + } + } + + // Remove types from the generic parameter list + let genericParameterListSourceRanges = sourceRangesToRemove.filter { $0.lowerBound < genericSignatureEndIndex } + formatter.removeTokens(in: Array(genericParameterListSourceRanges)) + + // If we left a dangling comma at the end of the generic parameter list, we need to clean it up + if let newGenericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), + let trailingCommaIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: newGenericSignatureEndIndex), + formatter.tokens[trailingCommaIndex] == .delimiter(",") + { + formatter.removeTokens(in: trailingCommaIndex ..< newGenericSignatureEndIndex) + } + + // If we removed all of the generic types, we also have to remove the angle brackets + if let newGenericSignatureEndIndex = formatter.index(of: .nonSpaceOrLinebreak, after: genericSignatureStartIndex), + formatter.token(at: newGenericSignatureEndIndex) == .endOfScope(">") + { + formatter.removeTokens(in: genericSignatureStartIndex ... newGenericSignatureEndIndex) + } + } + } +} diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift new file mode 100644 index 000000000..850166917 --- /dev/null +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -0,0 +1,45 @@ +// +// OrganizeDeclarations.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let organizeDeclarations = FormatRule( + help: "Organize declarations within class, struct, enum, actor, and extension bodies.", + runOnceOnly: true, + disabledByDefault: true, + orderAfter: [.extensionAccessControl, .redundantFileprivate], + options: [ + "categorymark", "markcategories", "beforemarks", + "lifecycle", "organizetypes", "structthreshold", "classthreshold", + "enumthreshold", "extensionlength", "organizationmode", + "visibilityorder", "typeorder", "visibilitymarks", "typemarks", + ], + sharedOptions: ["sortedpatterns", "lineaftermarks"] + ) { formatter in + guard !formatter.options.fragment else { return } + + formatter.mapRecursiveDeclarations { declaration in + switch declaration { + // Organize the body of type declarations + case let .type(kind, open, body, close, originalRange): + let organizedType = formatter.organizeDeclaration((kind, open, body, close)) + return .type( + kind: organizedType.kind, + open: organizedType.open, + body: organizedType.body, + close: organizedType.close, + originalRange: originalRange + ) + + case .conditionalCompilation, .declaration: + return declaration + } + } + } +} diff --git a/Sources/Rules/PreferForLoop.swift b/Sources/Rules/PreferForLoop.swift new file mode 100644 index 000000000..abfd109a9 --- /dev/null +++ b/Sources/Rules/PreferForLoop.swift @@ -0,0 +1,291 @@ +// +// PreferForLoop.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/27/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let preferForLoop = FormatRule( + help: "Convert functional `forEach` calls to for loops.", + options: ["anonymousforeach", "onelineforeach"] + ) { formatter in + formatter.forEach(.identifier("forEach")) { forEachIndex, _ in + // Make sure this is a function call preceded by a `.` + guard let functionCallDotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: forEachIndex), + formatter.tokens[functionCallDotIndex] == .operator(".", .infix), + let indexAfterForEach = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: forEachIndex), + let indexBeforeFunctionCallDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: functionCallDotIndex) + else { return } + + // Parse either `{ ... }` or `({ ... })` + let forEachCallOpenParenIndex: Int? + let closureOpenBraceIndex: Int + let closureCloseBraceIndex: Int + let forEachCallCloseParenIndex: Int? + + switch formatter.tokens[indexAfterForEach] { + case .startOfScope("{"): + guard let endOfClosureScope = formatter.endOfScope(at: indexAfterForEach) else { return } + + forEachCallOpenParenIndex = nil + closureOpenBraceIndex = indexAfterForEach + closureCloseBraceIndex = endOfClosureScope + forEachCallCloseParenIndex = nil + + case .startOfScope("("): + guard let endOfFunctionCall = formatter.endOfScope(at: indexAfterForEach), + let indexAfterOpenParen = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterForEach), + formatter.tokens[indexAfterOpenParen] == .startOfScope("{"), + let endOfClosureScope = formatter.endOfScope(at: indexAfterOpenParen) + else { return } + + forEachCallOpenParenIndex = indexAfterForEach + closureOpenBraceIndex = indexAfterOpenParen + closureCloseBraceIndex = endOfClosureScope + forEachCallCloseParenIndex = endOfFunctionCall + + default: + return + } + + // Abort early for single-line loops + guard !formatter.options.preserveSingleLineForEach || formatter + .tokens[closureOpenBraceIndex ..< closureCloseBraceIndex].contains(where: { $0.isLinebreak }) + else { return } + + // Ignore closures with capture lists for now since they're rare + // in this context and add complexity + guard let firstIndexInClosureBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureOpenBraceIndex), + formatter.tokens[firstIndexInClosureBody] != .startOfScope("[") + else { return } + + // Parse the value that `forEach` is being called on + let forLoopSubjectRange: ClosedRange + var forLoopSubjectIdentifier: String? + + // Parse a functional chain backwards from the `forEach` token + var currentIndex = forEachIndex + + while let previousDotIndex = formatter.index(of: .nonSpaceOrLinebreak, before: currentIndex), + formatter.tokens[previousDotIndex] == .operator(".", .infix), + let tokenBeforeDotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: previousDotIndex) + { + guard let startOfChainComponent = formatter.startOfChainComponent(at: tokenBeforeDotIndex, forLoopSubjectIdentifier: &forLoopSubjectIdentifier) else { + // If we parse a dot we expect to parse at least one additional component in the chain. + // Otherwise we'd have a malformed chain that starts with a dot, so abort. + return + } + + currentIndex = startOfChainComponent + } + + guard currentIndex != forEachIndex else { return } + forLoopSubjectRange = currentIndex ... indexBeforeFunctionCallDot + + // If there is a `try` before the `forEach` we cannot know if the subject is async/throwing or the body, + // which makes it impossible to know if we should move it or *remove* it, so we must abort (same for await). + if let tokenIndexBeforeForLoop = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: currentIndex), + var prevToken = formatter.token(at: tokenIndexBeforeForLoop) + { + if prevToken.isUnwrapOperator { + prevToken = formatter.last(.nonSpaceOrComment, before: tokenIndexBeforeForLoop) ?? .space("") + } + if [.keyword("try"), .keyword("await")].contains(prevToken) { + return + } + } + + // If the chain includes linebreaks, don't convert it to a for loop. + // + // In this case converting something like: + // + // placeholderStrings + // .filter { $0.style == .fooBar } + // .map { $0.uppercased() } + // .forEach { print($0) } + // + // to: + // + // for placeholderString in placeholderStrings + // .filter { $0.style == .fooBar } + // .map { $0.uppercased() } { print($0) } + // + // would be a pretty obvious downgrade. + if formatter.tokens[forLoopSubjectRange].contains(where: \.isLinebreak) { + return + } + + /// The names of the argument to the `forEach` closure. + /// e.g. `["foo"]` in `forEach { foo in ... }` + /// or `["foo, bar"]` in `forEach { (foo: Foo, bar: Bar) in ... }` + let forEachValueNames: [String] + let inKeywordIndex: Int? + let isAnonymousClosure: Bool + + if let argumentList = formatter.parseClosureArgumentList(at: closureOpenBraceIndex) { + isAnonymousClosure = false + forEachValueNames = argumentList.argumentNames + inKeywordIndex = argumentList.inKeywordIndex + } else { + isAnonymousClosure = true + inKeywordIndex = nil + + if formatter.options.preserveAnonymousForEach { + return + } + + // We can't introduce an identifier that matches a keyword or already exists in + // the loop body so choose the first eligible option from a set of potential names + var eligibleValueNames = ["item", "element", "value"] + if var identifier = forLoopSubjectIdentifier?.singularized(), !identifier.isSwiftKeyword { + eligibleValueNames = [identifier] + eligibleValueNames + } + + // The chosen name shouldn't already exist in the closure body + guard let chosenValueName = eligibleValueNames.first(where: { name in + !formatter.tokens[closureOpenBraceIndex ... closureCloseBraceIndex].contains(where: { $0.string == name }) + }) else { return } + + forEachValueNames = [chosenValueName] + } + + // Validate that the closure body is eligible to be converted to a for loop + for closureBodyIndex in closureOpenBraceIndex ... closureCloseBraceIndex { + guard !formatter.indexIsWithinNestedClosure(closureBodyIndex, startOfScopeIndex: closureOpenBraceIndex) else { continue } + + // We can only handle anonymous closures that just use $0, since we don't have good names to + // use for other arguments like $1, $2, etc. If the closure has an anonymous argument + // other than just $0 then we have to ignore it. + if formatter.tokens[closureBodyIndex].string.hasPrefix("$"), + let intValue = Int(formatter.tokens[closureBodyIndex].string.dropFirst()), + intValue != 0 + { + return + } + + // We can convert `return`s to `continue`, but only when `return` is on its own line. + // It's legal to write something like `return print("foo")` in a `forEach` as long as + // you're still returning a `Void` value. Since `continue print("foo")` isn't legal, + // we should just ignore this closure. + if formatter.tokens[closureBodyIndex] == .keyword("return"), + let tokenAfterReturnKeyword = formatter.next(.nonSpaceOrComment, after: closureBodyIndex), + !tokenAfterReturnKeyword.isLinebreak + { + return + } + } + + // Start updating the `forEach` call to a `for .. in .. {` loop + for closureBodyIndex in closureOpenBraceIndex ... closureCloseBraceIndex { + guard !formatter.indexIsWithinNestedClosure(closureBodyIndex, startOfScopeIndex: closureOpenBraceIndex) else { continue } + + // The for loop won't have any `$0` identifiers anymore, so we have to + // update those to the value at the current loop index + if isAnonymousClosure, formatter.tokens[closureBodyIndex].string == "$0" { + formatter.replaceToken(at: closureBodyIndex, with: .identifier(forEachValueNames[0])) + } + + // In a `forEach` closure, `return` continues to the next loop iteration. + // To get the same behavior in a for loop we convert `return`s to `continue`s. + if formatter.tokens[closureBodyIndex] == .keyword("return") { + formatter.replaceToken(at: closureBodyIndex, with: .keyword("continue")) + } + } + + if let forEachCallCloseParenIndex = forEachCallCloseParenIndex { + formatter.removeToken(at: forEachCallCloseParenIndex) + } + + // Construct the new for loop + var newTokens: [Token] = [ + .keyword("for"), + .space(" "), + ] + + let forEachValueNameTokens: [Token] + if forEachValueNames.count == 1 { + newTokens.append(.identifier(forEachValueNames[0])) + } else { + newTokens.append(contentsOf: tokenize("(\(forEachValueNames.joined(separator: ", ")))")) + } + + newTokens.append(contentsOf: [ + .space(" "), + .keyword("in"), + .space(" "), + ]) + + newTokens.append(contentsOf: formatter.tokens[forLoopSubjectRange]) + + newTokens.append(contentsOf: [ + .space(" "), + .startOfScope("{"), + ]) + + formatter.replaceTokens( + in: (forLoopSubjectRange.lowerBound) ... (inKeywordIndex ?? closureOpenBraceIndex), + with: newTokens + ) + } + } +} + +private extension Formatter { + // Returns the start index of the chain component ending at the given index + func startOfChainComponent(at index: Int, forLoopSubjectIdentifier: inout String?) -> Int? { + // The previous item in a dot chain can either be: + // 1. an identifier like `foo.` + // 2. a function call like `foo(...).` + // 3. a subscript like `foo[...]. + // 4. a trailing closure like `map { ... }` + // 5. Some other combination of parens / subscript like `(foo).` + // or even `foo["bar"]()()`. + // And any of these can be preceeded by one of the others + switch tokens[index] { + case let .identifier(identifierName): + // Allowlist certain dot chain elements that should be ignored. + // For example, in `foos.reversed().forEach { ... }` we want + // `forLoopSubjectIdentifier` to be `foos` rather than `reversed`. + let chainElementsToIgnore = Set([ + "reversed", "sorted", "shuffled", "enumerated", "dropFirst", "dropLast", + "map", "flatMap", "compactMap", "filter", "reduce", "lazy", + ]) + + if forLoopSubjectIdentifier == nil || chainElementsToIgnore.contains(forLoopSubjectIdentifier ?? "") { + // Since we have to pick a single identifier to represent the subject of the for loop, + // just use the last identifier in the chain + forLoopSubjectIdentifier = identifierName + } + + return index + + case .endOfScope(")"), .endOfScope("]"): + let closingParenIndex = index + guard let startOfScopeIndex = startOfScope(at: closingParenIndex), + let previousNonSpaceNonCommentIndex = self.index(of: .nonSpaceOrComment, before: startOfScopeIndex) + else { return nil } + + // When we find parens for a function call or braces for a subscript, + // continue parsing at the previous non-space non-comment token. + // - If the previous token is a newline then this isn't a function call + // and we'd stop parsing. `foo ()` is a function call but `foo\n()` isn't. + return startOfChainComponent(at: previousNonSpaceNonCommentIndex, forLoopSubjectIdentifier: &forLoopSubjectIdentifier) ?? startOfScopeIndex + + case .endOfScope("}"): + // Stop parsing if we reach a trailing closure. + // Converting this to a for loop would result in unusual looking syntax like + // `for string in strings.map { $0.uppercased() } { print(string) }` + // which causes a warning to be emitted: "trailing closure in this context is + // confusable with the body of the statement; pass as a parenthesized argument + // to silence this warning". + return nil + + default: + return nil + } + } +} diff --git a/Sources/Rules/PreferKeyPath.swift b/Sources/Rules/PreferKeyPath.swift new file mode 100644 index 000000000..81b4a179b --- /dev/null +++ b/Sources/Rules/PreferKeyPath.swift @@ -0,0 +1,79 @@ +// +// PreferKeyPath.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let preferKeyPath = FormatRule( + help: "Convert trivial `map { $0.foo }` closures to keyPath-based syntax." + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + guard formatter.options.swiftVersion >= "5.2", + var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) + else { + return + } + var prevToken = formatter.tokens[prevIndex] + var label: String? + if prevToken == .delimiter(":"), + let labelIndex = formatter.index(of: .nonSpace, before: prevIndex), + case let .identifier(name) = formatter.tokens[labelIndex], + let prevIndex2 = formatter.index(of: .nonSpaceOrLinebreak, before: labelIndex) + { + label = name + prevToken = formatter.tokens[prevIndex2] + prevIndex = prevIndex2 + } + let parenthesized = prevToken == .startOfScope("(") + if parenthesized { + prevToken = formatter.last(.nonSpaceOrLinebreak, before: prevIndex) ?? prevToken + } + guard case let .identifier(name) = prevToken, + ["map", "flatMap", "compactMap", "allSatisfy", "filter", "contains"].contains(name), + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0 == .identifier("$0") + }), + let endIndex = formatter.endOfScope(at: i), + let lastIndex = formatter.index(of: .nonSpaceOrLinebreak, before: endIndex) + else { + return + } + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), + formatter.isLabel(at: nextIndex) + { + return + } + if name == "contains" { + if label != "where" { + return + } + } else if label != nil { + return + } + var replacementTokens: [Token] + if nextIndex == lastIndex { + // TODO: add this when https://bugs.swift.org/browse/SR-12897 is fixed + // replacementTokens = tokenize("\\.self") + return + } else { + let tokens = formatter.tokens[nextIndex + 1 ... lastIndex] + guard tokens.allSatisfy({ $0.isSpace || $0.isIdentifier || $0.isOperator(".") }) else { + return + } + replacementTokens = [.operator("\\", .prefix)] + tokens + } + if let label = label { + replacementTokens = [.identifier(label), .delimiter(":"), .space(" ")] + replacementTokens + } + if !parenthesized { + replacementTokens = [.startOfScope("(")] + replacementTokens + [.endOfScope(")")] + } + formatter.replaceTokens(in: prevIndex + 1 ... endIndex, with: replacementTokens) + } + } +} diff --git a/Sources/Rules/PropertyType.swift b/Sources/Rules/PropertyType.swift new file mode 100644 index 000000000..79631a3aa --- /dev/null +++ b/Sources/Rules/PropertyType.swift @@ -0,0 +1,208 @@ +// +// PropertyType.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let propertyType = FormatRule( + help: "Convert property declarations to use inferred types (`let foo = Foo()`) or explicit types (`let foo: Foo = .init()`).", + disabledByDefault: true, + orderAfter: [.redundantType], + options: ["inferredtypes", "preservesymbols"], + sharedOptions: ["redundanttype"] + ) { formatter in + formatter.forEach(.operator("=", .infix)) { equalsIndex, _ in + // Preserve all properties in conditional statements like `if let foo = Bar() { ... }` + guard !formatter.isConditionalStatement(at: equalsIndex) else { return } + + // Determine whether the type should use the inferred syntax (`let foo = Foo()`) + // of the explicit syntax (`let foo: Foo = .init()`). + let useInferredType: Bool + switch formatter.options.redundantType { + case .inferred: + useInferredType = true + + case .explicit: + useInferredType = false + + case .inferLocalsOnly: + switch formatter.declarationScope(at: equalsIndex) { + case .global, .type: + useInferredType = false + case .local: + useInferredType = true + } + } + + guard let introducerIndex = formatter.indexOfLastSignificantKeyword(at: equalsIndex), + ["var", "let"].contains(formatter.tokens[introducerIndex].string), + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + let rhsExpressionRange = property.value?.expressionRange + else { return } + + let rhsStartIndex = rhsExpressionRange.lowerBound + + if useInferredType { + guard let type = property.type else { return } + let typeTokens = formatter.tokens[type.range] + + // Preserve the existing formatting if the LHS type is optional. + // - `let foo: Foo? = .foo` is valid, but `let foo = Foo?.foo` + // is invalid if `.foo` is defined on `Foo` but not `Foo?`. + guard !["?", "!"].contains(typeTokens.last?.string ?? "") else { return } + + // Preserve the existing formatting if the LHS type is an existential (indicated with `any`). + // - The `extension MyProtocol where Self == MyType { ... }` syntax + // creates static members where `let foo: any MyProtocol = .myType` + // is valid, but `let foo = (any MyProtocol).myType` isn't. + guard typeTokens.first?.string != "any" else { return } + + // Preserve the existing formatting if the RHS expression has a top-level infix operator. + // - `let value: ClosedRange = .zero ... 10` would not be valid to convert to + // `let value = ClosedRange.zero ... 10`. + if let nextInfixOperatorIndex = formatter.index(after: rhsStartIndex, where: { token in + token.isOperator(ofType: .infix) && token != .operator(".", .infix) + }), + rhsExpressionRange.contains(nextInfixOperatorIndex) + { + return + } + + // Preserve the formatting as-is if the type is manually excluded + if formatter.options.preserveSymbols.contains(type.name) { + return + } + + // If the RHS starts with a leading dot, then we know its accessing some static member on this type. + if formatter.tokens[rhsStartIndex].isOperator(".") { + // Preserve the formatting as-is if the identifier is manually excluded + if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), + formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) + { return } + + // Update the . token from a prefix operator to an infix operator. + formatter.replaceToken(at: rhsStartIndex, with: .operator(".", .infix)) + + // Insert a copy of the type on the RHS before the dot + formatter.insert(typeTokens, at: rhsStartIndex) + } + + // If the RHS is an if/switch expression, check that each branch starts with a leading dot + else if formatter.options.inferredTypesInConditionalExpressions, + ["if", "switch"].contains(formatter.tokens[rhsStartIndex].string), + let conditonalBranches = formatter.conditionalBranches(at: rhsStartIndex) + { + var hasInvalidConditionalBranch = false + formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in + guard let firstTokenInBranch = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { + hasInvalidConditionalBranch = true + return + } + + if !formatter.tokens[firstTokenInBranch].isOperator(".") { + hasInvalidConditionalBranch = true + } + + // Preserve the formatting as-is if the identifier is manually excluded + if let identifierAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsStartIndex), + formatter.options.preserveSymbols.contains(formatter.tokens[identifierAfterDot].string) + { + hasInvalidConditionalBranch = true + } + } + + guard !hasInvalidConditionalBranch else { return } + + // Insert a copy of the type on the RHS before the dot in each branch + formatter.forEachRecursiveConditionalBranch(in: conditonalBranches) { branch in + guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: branch.startOfBranch) else { return } + + // Update the . token from a prefix operator to an infix operator. + formatter.replaceToken(at: dotIndex, with: .operator(".", .infix)) + + // Insert a copy of the type on the RHS before the dot + formatter.insert(typeTokens, at: dotIndex) + } + } + + else { + return + } + + // Remove the colon and explicit type before the equals token + formatter.removeTokens(in: type.colonIndex ... type.range.upperBound) + } + + // If using explicit types, convert properties to the format `let foo: Foo = .init()`. + else { + guard // When parsing the type, exclude lowercase identifiers so `foo` isn't parsed as a type, + // and so `Foo.init` is parsed as `Foo` instead of `Foo.init`. + let rhsType = formatter.parseType(at: rhsStartIndex, excludeLowercaseIdentifiers: true), + property.type == nil, + let indexAfterIdentifier = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: property.identifierIndex), + formatter.tokens[indexAfterIdentifier] != .delimiter(":"), + let indexAfterType = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: rhsType.range.upperBound), + [.operator(".", .infix), .startOfScope("(")].contains(formatter.tokens[indexAfterType]), + !rhsType.name.contains(".") + else { return } + + // Preserve the existing formatting if the RHS expression has a top-level operator. + // - `let foo = Foo.foo.bar` would not be valid to convert to `let foo: Foo = .foo.bar`. + let operatorSearchIndex = formatter.tokens[indexAfterType].isStartOfScope ? (indexAfterType - 1) : indexAfterType + if let nextInfixOperatorIndex = formatter.index(after: operatorSearchIndex, where: { token in + token.isOperator(ofType: .infix) + }), + rhsExpressionRange.contains(nextInfixOperatorIndex) + { + return + } + + // Preserve any types that have been manually excluded. + // Preserve any `Void` types and tuples, since they're special and don't support things like `.init` + guard !(formatter.options.preserveSymbols + ["Void"]).contains(rhsType.name), + !rhsType.name.hasPrefix("(") + else { return } + + // A type name followed by a `(` is an implicit `.init(`. Insert a `.init` + // so that the init call stays valid after we move the type to the LHS. + if formatter.tokens[indexAfterType] == .startOfScope("(") { + // Preserve the existing format if `init` is manually excluded + if formatter.options.preserveSymbols.contains("init") { + return + } + + formatter.insert([.operator(".", .prefix), .identifier("init")], at: indexAfterType) + } + + // If the type name is followed by an infix `.` operator, convert it to a prefix operator. + else if formatter.tokens[indexAfterType] == .operator(".", .infix) { + // Exclude types with dots followed by a member access. + // - For example with something like `Color.Theme.themeColor`, we don't know + // if the property is `static var themeColor: Color` or `static var themeColor: Color.Theme`. + // - This isn't a problem with something like `Color.Theme()`, which we can reasonably assume + // is an initializer + if rhsType.name.contains(".") { return } + + // Preserve the formatting as-is if the identifier is manually excluded. + // Don't convert `let foo = Foo.self` to `let foo: Foo = .self`, since `.self` returns the metatype + let symbolsToExclude = formatter.options.preserveSymbols + ["self"] + if let indexAfterDot = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: indexAfterType), + symbolsToExclude.contains(formatter.tokens[indexAfterDot].string) + { return } + + formatter.replaceToken(at: indexAfterType, with: .operator(".", .prefix)) + } + + // Move the type name to the LHS of the property, followed by a colon + let typeTokens = formatter.tokens[rhsType.range] + formatter.removeTokens(in: rhsType.range) + formatter.insert([.delimiter(":"), .space(" ")] + typeTokens, at: property.identifierIndex + 1) + } + } + } +} diff --git a/Sources/Rules/RedundantBackticks.swift b/Sources/Rules/RedundantBackticks.swift new file mode 100644 index 000000000..869afe2bc --- /dev/null +++ b/Sources/Rules/RedundantBackticks.swift @@ -0,0 +1,23 @@ +// +// RedundantBackticks.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant backticks around non-keywords, or in places where keywords don't need escaping + static let redundantBackticks = FormatRule( + help: "Remove redundant backticks around identifiers." + ) { formatter in + formatter.forEach(.identifier) { i, token in + guard token.string.first == "`", !formatter.backticksRequired(at: i) else { + return + } + formatter.replaceToken(at: i, with: .identifier(token.unescaped())) + } + } +} diff --git a/Sources/Rules/RedundantBreak.swift b/Sources/Rules/RedundantBreak.swift new file mode 100644 index 000000000..bb4c5fc79 --- /dev/null +++ b/Sources/Rules/RedundantBreak.swift @@ -0,0 +1,31 @@ +// +// RedundantBreak.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant `break` keyword from switch cases + static let redundantBreak = FormatRule( + help: "Remove redundant `break` in switch case." + ) { formatter in + formatter.forEach(.keyword("break")) { i, _ in + guard formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .startOfScope(":"), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isEndOfScope == true, + var startIndex = formatter.index(of: .nonSpace, before: i), + let endIndex = formatter.index(of: .nonSpace, after: i), + formatter.currentScope(at: i) == .startOfScope(":") + else { + return + } + if !formatter.tokens[startIndex].isLinebreak || !formatter.tokens[endIndex].isLinebreak { + startIndex += 1 + } + formatter.removeTokens(in: startIndex ..< endIndex) + } + } +} diff --git a/Sources/Rules/RedundantClosure.swift b/Sources/Rules/RedundantClosure.swift new file mode 100644 index 000000000..bd5e3bb7c --- /dev/null +++ b/Sources/Rules/RedundantClosure.swift @@ -0,0 +1,193 @@ +// +// RedundantClosure.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantClosure = FormatRule( + help: """ + Removes redundant closures bodies, containing a single statement, + which are called immediately. + """, + disabledByDefault: false, + orderAfter: [.redundantReturn] + ) { formatter in + formatter.forEach(.startOfScope("{")) { closureStartIndex, _ in + var startIndex = closureStartIndex + if formatter.isStartOfClosure(at: closureStartIndex), + var closureEndIndex = formatter.endOfScope(at: closureStartIndex), + // Closures that are called immediately are redundant + // (as long as there's exactly one statement inside them) + var closureCallOpenParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureEndIndex), + var closureCallCloseParenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureCallOpenParenIndex), + formatter.token(at: closureCallOpenParenIndex) == .startOfScope("("), + formatter.token(at: closureCallCloseParenIndex) == .endOfScope(")"), + // Make sure to exclude closures that are completely empty, + // because removing them could break the build. + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureStartIndex) != closureEndIndex + { + /// Whether or not this closure has a single, simple expression in its body. + /// These closures can always be simplified / removed regardless of the context. + let hasSingleSimpleExpression = formatter.blockBodyHasSingleStatement( + atStartOfScope: closureStartIndex, + includingConditionalStatements: false, + includingReturnStatements: true + ) + + /// Whether or not this closure has a single if/switch expression in its body. + /// Since if/switch expressions are only valid in the `return` position or as an `=` assignment, + /// these closures can only sometimes be simplified / removed. + let hasSingleConditionalExpression = !hasSingleSimpleExpression && + formatter.blockBodyHasSingleStatement( + atStartOfScope: closureStartIndex, + includingConditionalStatements: true, + includingReturnStatements: true, + includingReturnInConditionalStatements: false + ) + + guard hasSingleSimpleExpression || hasSingleConditionalExpression else { + return + } + + // This rule also doesn't support closures with an `in` token. + // - We can't just remove this, because it could have important type information. + // For example, `let double = { () -> Double in 100 }()` and `let double = 100` have different types. + // - We could theoretically support more sophisticated checks / transforms here, + // but this seems like an edge case so we choose not to handle it. + for inIndex in closureStartIndex ... closureEndIndex + where formatter.token(at: inIndex) == .keyword("in") + { + if !formatter.indexIsWithinNestedClosure(inIndex, startOfScopeIndex: closureStartIndex) { + return + } + } + + // If the closure calls a single function, which throws or returns `Never`, + // then removing the closure will cause a compilation failure. + // - We maintain a list of known functions that return `Never`. + // We could expand this to be user-provided if necessary. + for i in closureStartIndex ... closureEndIndex { + switch formatter.tokens[i] { + case .identifier("fatalError"), .identifier("preconditionFailure"), .keyword("throw"): + if !formatter.indexIsWithinNestedClosure(i, startOfScopeIndex: closureStartIndex) { + return + } + default: + break + } + } + + // If closure is preceded by try and/or await then remove those too + if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex, if: { + $0 == .keyword("await") + }) { + startIndex = prevIndex + } + if let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex, if: { + $0 == .keyword("try") + }) { + startIndex = prevIndex + } + + // Since if/switch expressions are only valid in the `return` position or as an `=` assignment, + // these closures can only sometimes be simplified / removed. + if hasSingleConditionalExpression { + // Find the `{` start of scope or `=` and verify that the entire following expression consists of just this closure. + var startOfScopeContainingClosure = formatter.startOfScope(at: startIndex) + var assignmentBeforeClosure = formatter.index(of: .operator("=", .infix), before: startIndex) + + if let assignmentBeforeClosure = assignmentBeforeClosure, formatter.isConditionalStatement(at: assignmentBeforeClosure) { + // Not valid to use conditional expression directly in condition body + return + } + + let potentialStartOfExpressionContainingClosure: Int? + switch (startOfScopeContainingClosure, assignmentBeforeClosure) { + case (nil, nil): + potentialStartOfExpressionContainingClosure = nil + case (.some(let startOfScope), nil): + guard formatter.tokens[startOfScope] == .startOfScope("{") else { return } + potentialStartOfExpressionContainingClosure = startOfScope + case (nil, let .some(assignmentBeforeClosure)): + potentialStartOfExpressionContainingClosure = assignmentBeforeClosure + case let (.some(startOfScope), .some(assignmentBeforeClosure)): + potentialStartOfExpressionContainingClosure = max(startOfScope, assignmentBeforeClosure) + } + + if let potentialStartOfExpressionContainingClosure = potentialStartOfExpressionContainingClosure { + guard var startOfExpressionIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: potentialStartOfExpressionContainingClosure) + else { return } + + // Skip over any return token that may be present + if formatter.tokens[startOfExpressionIndex] == .keyword("return"), + let nextTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfExpressionIndex) + { + startOfExpressionIndex = nextTokenIndex + } + + // Parse the expression and require that entire expression is simply just this closure. + guard let expressionRange = formatter.parseExpressionRange(startingAt: startOfExpressionIndex), + expressionRange == startIndex ... closureCallCloseParenIndex + else { return } + } + } + + // If the closure is a property with an explicit `Void` type, + // we can't remove the closure since the build would break + // if the method is `@discardableResult` + // https://github.com/nicklockwood/SwiftFormat/issues/1236 + if let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex), + formatter.token(at: equalsIndex) == .operator("=", .infix), + let colonIndex = formatter.index(of: .delimiter(":"), before: equalsIndex), + let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: colonIndex), + formatter.endOfVoidType(at: nextIndex) != nil + { + return + } + + // First we remove the spaces and linebreaks between the { } and the remainder of the closure body + // - This requires a bit of bookkeeping, but makes sure we don't remove any + // whitespace characters outside of the closure itself + while formatter.token(at: closureStartIndex + 1)?.isSpaceOrLinebreak == true { + formatter.removeToken(at: closureStartIndex + 1) + + closureCallOpenParenIndex -= 1 + closureCallCloseParenIndex -= 1 + closureEndIndex -= 1 + } + + while formatter.token(at: closureEndIndex - 1)?.isSpaceOrLinebreak == true { + formatter.removeToken(at: closureEndIndex - 1) + + closureCallOpenParenIndex -= 1 + closureCallCloseParenIndex -= 1 + closureEndIndex -= 1 + } + + // remove the trailing }() tokens, working backwards to not invalidate any indices + formatter.removeToken(at: closureCallCloseParenIndex) + formatter.removeToken(at: closureCallOpenParenIndex) + formatter.removeToken(at: closureEndIndex) + + // Remove the initial return token, and any trailing space, if present. + if let returnIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closureStartIndex), + formatter.token(at: returnIndex)?.string == "return" + { + while formatter.token(at: returnIndex + 1)?.isSpaceOrLinebreak == true { + formatter.removeToken(at: returnIndex + 1) + } + + formatter.removeToken(at: returnIndex) + } + + // Finally, remove then open `{` token + formatter.removeTokens(in: startIndex ... closureStartIndex) + } + } + } +} diff --git a/Sources/Rules/RedundantExtensionACL.swift b/Sources/Rules/RedundantExtensionACL.swift new file mode 100644 index 000000000..d26f8b3be --- /dev/null +++ b/Sources/Rules/RedundantExtensionACL.swift @@ -0,0 +1,35 @@ +// +// RedundantExtensionACL.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant access control level modifiers in extensions + static let redundantExtensionACL = FormatRule( + help: "Remove redundant access control modifiers." + ) { formatter in + formatter.forEach(.keyword("extension")) { i, _ in + var acl = "" + guard formatter.modifiersForDeclaration(at: i, contains: { + acl = $1 + return _FormatRules.aclModifiers.contains(acl) + }), let startIndex = formatter.index(of: .startOfScope("{"), after: i), + var endIndex = formatter.index(of: .endOfScope("}"), after: startIndex) else { + return + } + if acl == "private" { acl = "fileprivate" } + while let aclIndex = formatter.lastIndex(of: .keyword(acl), in: startIndex + 1 ..< endIndex) { + formatter.removeToken(at: aclIndex) + if formatter.token(at: aclIndex)?.isSpace == true { + formatter.removeToken(at: aclIndex) + } + endIndex = aclIndex + } + } + } +} diff --git a/Sources/Rules/RedundantFileprivate.swift b/Sources/Rules/RedundantFileprivate.swift new file mode 100644 index 000000000..4b3c1a759 --- /dev/null +++ b/Sources/Rules/RedundantFileprivate.swift @@ -0,0 +1,203 @@ +// +// RedundantFileprivate.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace `fileprivate` with `private` where possible + static let redundantFileprivate = FormatRule( + help: "Prefer `private` over `fileprivate` where equivalent." + ) { formatter in + guard !formatter.options.fragment else { return } + + var hasUnreplacedFileprivates = false + formatter.forEach(.keyword("fileprivate")) { i, _ in + // check if definition is at file-scope + if formatter.index(of: .startOfScope, before: i) == nil { + formatter.replaceToken(at: i, with: .keyword("private")) + } else { + hasUnreplacedFileprivates = true + } + } + guard hasUnreplacedFileprivates else { + return + } + let importRanges = formatter.parseImports() + var fileJustContainsOneType: Bool? + func ifCodeInRange(_ range: CountableRange) -> Bool { + var index = range.lowerBound + while index < range.upperBound, let nextIndex = + formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: index ..< range.upperBound) + { + guard let importRange = importRanges.first(where: { + $0.contains(where: { $0.range.contains(nextIndex) }) + }) else { + return true + } + index = importRange.last!.range.upperBound + 1 + } + return false + } + func isTypeInitialized(_ name: String, in range: CountableRange) -> Bool { + for i in range { + switch formatter.tokens[i] { + case .identifier(name): + guard let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) else { + break + } + switch formatter.tokens[nextIndex] { + case .operator(".", .infix): + if formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("init") { + return true + } + case .startOfScope("("): + return true + case .startOfScope("{"): + if formatter.isStartOfClosure(at: nextIndex) { + return true + } + default: + break + } + case .identifier("init"): + // TODO: this will return true if *any* type is initialized using type inference. + // Is there a way to narrow this down a bit? + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator(".", .prefix) { + return true + } + default: + break + } + } + return false + } + // TODO: improve this logic to handle shadowing + func areMembers(_ names: [String], of type: String, + referencedIn range: CountableRange) -> Bool + { + var i = range.lowerBound + while i < range.upperBound { + switch formatter.tokens[i] { + case .keyword("struct"), .keyword("extension"), .keyword("enum"), .keyword("actor"), + .keyword("class") where formatter.declarationType(at: i) == "class": + guard let startIndex = formatter.index(of: .startOfScope("{"), after: i), + let endIndex = formatter.endOfScope(at: startIndex) + else { + break + } + guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + formatter.tokens[nameIndex] != .identifier(type) + else { + i = endIndex + break + } + for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] + where names.contains(name) + { + return true + } + i = endIndex + case let .identifier(name) where names.contains(name): + if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + $0 == .operator(".", .infix) + }), formatter.last(.nonSpaceOrCommentOrLinebreak, before: dotIndex) + != .identifier("self") + { + return true + } + default: + break + } + i += 1 + } + return false + } + func isInitOverridden(for type: String, in range: CountableRange) -> Bool { + for i in range { + if case .keyword("init") = formatter.tokens[i], + let scopeStart = formatter.index(of: .startOfScope("{"), after: i), + formatter.index(of: .identifier("super"), after: scopeStart) != nil, + let scopeIndex = formatter.index(of: .startOfScope("{"), before: i), + let colonIndex = formatter.index(of: .delimiter(":"), before: scopeIndex), + formatter.next( + .nonSpaceOrCommentOrLinebreak, + in: colonIndex + 1 ..< scopeIndex + ) == .identifier(type) + { + return true + } + } + return false + } + formatter.forEach(.keyword("fileprivate")) { i, _ in + // Check if definition is a member of a file-scope type + guard formatter.options.swiftVersion >= "4", + let scopeIndex = formatter.index(of: .startOfScope, before: i, if: { + $0 == .startOfScope("{") + }), let typeIndex = formatter.index(of: .keyword, before: scopeIndex, if: { + ["class", "actor", "struct", "enum", "extension"].contains($0.string) + }), let nameIndex = formatter.index(of: .identifier, in: typeIndex ..< scopeIndex), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: nameIndex)?.isOperator(".") == false, + case let .identifier(typeName) = formatter.tokens[nameIndex], + let endIndex = formatter.index(of: .endOfScope, after: scopeIndex), + formatter.currentScope(at: typeIndex) == nil + else { + return + } + // Get member type + guard let keywordIndex = formatter.index(of: .keyword, in: i + 1 ..< endIndex), + let memberType = formatter.declarationType(at: keywordIndex), + // TODO: check if member types are exposed in the interface, otherwise convert them too + ["let", "var", "func", "init"].contains(memberType) + else { + return + } + // Check that type doesn't (potentially) conform to a protocol + // TODO: use a whitelist of known protocols to make this check less blunt + guard !formatter.tokens[typeIndex ..< scopeIndex].contains(.delimiter(":")) else { + return + } + // Check for code outside of main type definition + let startIndex = formatter.startOfModifiers(at: typeIndex, includingAttributes: true) + if fileJustContainsOneType == nil { + fileJustContainsOneType = !ifCodeInRange(0 ..< startIndex) && + !ifCodeInRange(endIndex + 1 ..< formatter.tokens.count) + } + if fileJustContainsOneType == true { + formatter.replaceToken(at: i, with: .keyword("private")) + return + } + // Check if type name is initialized outside type, and if so don't + // change any fileprivate members in case we break memberwise initializer + // TODO: check if struct contains an overridden init; if so we can skip this check + if formatter.tokens[typeIndex] == .keyword("struct"), + isTypeInitialized(typeName, in: 0 ..< startIndex) || + isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count) + { + return + } + // Check if member is referenced outside type + if memberType == "init" { + // Make initializer private if it's not called anywhere + if !isTypeInitialized(typeName, in: 0 ..< startIndex), + !isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count), + !isInitOverridden(for: typeName, in: 0 ..< startIndex), + !isInitOverridden(for: typeName, in: endIndex + 1 ..< formatter.tokens.count) + { + formatter.replaceToken(at: i, with: .keyword("private")) + } + } else if let _names = formatter.namesInDeclaration(at: keywordIndex), + case let names = _names + _names.map({ "$\($0)" }), + !areMembers(names, of: typeName, referencedIn: 0 ..< startIndex), + !areMembers(names, of: typeName, referencedIn: endIndex + 1 ..< formatter.tokens.count) + { + formatter.replaceToken(at: i, with: .keyword("private")) + } + } + } +} diff --git a/Sources/Rules/RedundantGet.swift b/Sources/Rules/RedundantGet.swift new file mode 100644 index 000000000..333228fec --- /dev/null +++ b/Sources/Rules/RedundantGet.swift @@ -0,0 +1,34 @@ +// +// RedundantGet.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant `get {}` clause inside read-only computed property + static let redundantGet = FormatRule( + help: "Remove unneeded `get` clause inside computed properties." + ) { formatter in + formatter.forEach(.identifier("get")) { i, _ in + if formatter.isAccessorKeyword(at: i, checkKeyword: false), + let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + $0 == .startOfScope("{") + }), let openIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0 == .startOfScope("{") + }), + let closeIndex = formatter.index(of: .endOfScope("}"), after: openIndex), + let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closeIndex, if: { + $0 == .endOfScope("}") + }) + { + formatter.removeTokens(in: closeIndex ..< nextIndex) + formatter.removeTokens(in: prevIndex + 1 ... openIndex) + // TODO: fix-up indenting of lines in between removed braces + } + } + } +} diff --git a/Sources/Rules/RedundantInit.swift b/Sources/Rules/RedundantInit.swift new file mode 100644 index 000000000..21423bc12 --- /dev/null +++ b/Sources/Rules/RedundantInit.swift @@ -0,0 +1,68 @@ +// +// RedundantInit.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Strip redundant `.init` from type instantiations + static let redundantInit = FormatRule( + help: "Remove explicit `init` if not required.", + orderAfter: [.propertyType] + ) { formatter in + formatter.forEach(.identifier("init")) { initIndex, _ in + guard let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: initIndex, if: { + $0.isOperator(".") + }), let openParenIndex = formatter.index(of: .nonSpaceOrLinebreak, after: initIndex, if: { + $0 == .startOfScope("(") + }), let closeParenIndex = formatter.index(of: .endOfScope(")"), after: openParenIndex), + formatter.last(.nonSpaceOrCommentOrLinebreak, before: closeParenIndex) != .delimiter(":"), + let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: dotIndex), + let prevToken = formatter.token(at: prevIndex), + formatter.isValidEndOfType(at: prevIndex), + // Find and parse the type that comes before the .init call + let startOfTypeIndex = Array(0 ..< dotIndex).reversed().last(where: { typeIndex in + guard let type = formatter.parseType(at: typeIndex) else { return false } + return (formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: type.range.upperBound) == dotIndex + // Since `Foo.init` is potentially a valid type, the `.init` may be parsed as part of the type name + || type.range.upperBound == initIndex) + // If this is actually a method call like `type(of: foo).init()`, the token before the "type" + // (which in this case looks like a tuple) will be an identifier. + && !(formatter.last(.nonSpaceOrComment, before: typeIndex)?.isIdentifier ?? false) + }), + let type = formatter.parseType(at: startOfTypeIndex), + // Filter out values that start with a lowercase letter. + // This covers edge cases like `super.init()`, where the `init` is not redundant. + let firstChar = type.name.components(separatedBy: ".").last?.first, + firstChar != "$", + String(firstChar).uppercased() == String(firstChar) + else { return } + + let lineStart = formatter.startOfLine(at: prevIndex, excludingIndent: true) + if [.startOfScope("#if"), .keyword("#elseif")].contains(formatter.tokens[lineStart]) { + return + } + var j = dotIndex + while let prevIndex = formatter.index( + of: prevToken, before: j + ) ?? formatter.index( + of: .startOfScope, before: j + ) { + j = prevIndex + if prevToken == formatter.tokens[prevIndex], + let prevPrevToken = formatter.last( + .nonSpaceOrCommentOrLinebreak, before: prevIndex + ), [.keyword("let"), .keyword("var")].contains(prevPrevToken) + { + return + } + } + formatter.removeTokens(in: initIndex + 1 ..< openParenIndex) + formatter.removeTokens(in: dotIndex ... initIndex) + } + } +} diff --git a/Sources/Rules/RedundantInternal.swift b/Sources/Rules/RedundantInternal.swift new file mode 100644 index 000000000..570e3c7f8 --- /dev/null +++ b/Sources/Rules/RedundantInternal.swift @@ -0,0 +1,42 @@ +// +// RedundantInternal.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantInternal = FormatRule( + help: "Remove redundant internal access control." + ) { formatter in + formatter.forEach(.keyword("internal")) { internalKeywordIndex, _ in + // Don't remove import acl + if formatter.next(.nonSpaceOrComment, after: internalKeywordIndex) == .keyword("import") { + return + } + + // If we're inside an extension, then `internal` is only redundant if the extension itself is `internal`. + if let startOfScope = formatter.startOfScope(at: internalKeywordIndex), + let typeKeywordIndex = formatter.indexOfLastSignificantKeyword(at: startOfScope, excluding: ["where"]), + formatter.tokens[typeKeywordIndex] == .keyword("extension"), + // In the language grammar, the ACL level always directly precedes the + // `extension` keyword if present. + let previousToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeKeywordIndex), + ["public", "package", "internal", "private", "fileprivate"].contains(previousToken.string), + previousToken.string != "internal" + { + // The extension has an explicit ACL other than `internal`, so is not internal. + // We can't remove the `internal` keyword since the declaration would change + // to the ACL of the extension. + return + } + + guard formatter.token(at: internalKeywordIndex + 1)?.isSpace == true else { return } + + formatter.removeTokens(in: internalKeywordIndex ... (internalKeywordIndex + 1)) + } + } +} diff --git a/Sources/Rules/RedundantLet.swift b/Sources/Rules/RedundantLet.swift new file mode 100644 index 000000000..54c45f79d --- /dev/null +++ b/Sources/Rules/RedundantLet.swift @@ -0,0 +1,42 @@ +// +// RedundantLet.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant let/var for unnamed variables + static let redundantLet = FormatRule( + help: "Remove redundant `let`/`var` from ignored variables." + ) { formatter in + formatter.forEach(.identifier("_")) { i, _ in + guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":"), + let prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + [.keyword("let"), .keyword("var")].contains($0) + }), + let nextNonSpaceIndex = formatter.index(of: .nonSpaceOrLinebreak, after: prevIndex) + else { + return + } + if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: prevIndex) { + switch prevToken { + case .keyword("if"), .keyword("guard"), .keyword("while"), .identifier("async"), + .keyword where prevToken.isAttribute, + .delimiter(",") where formatter.currentScope(at: i) != .startOfScope("("): + return + default: + break + } + } + // Crude check for Result Builder + if formatter.isInResultBuilder(at: i) { + return + } + formatter.removeTokens(in: prevIndex ..< nextNonSpaceIndex) + } + } +} diff --git a/Sources/Rules/RedundantLetError.swift b/Sources/Rules/RedundantLetError.swift new file mode 100644 index 000000000..cc6cd358d --- /dev/null +++ b/Sources/Rules/RedundantLetError.swift @@ -0,0 +1,28 @@ +// +// RedundantLetError.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant `let error` from `catch` statements + static let redundantLetError = FormatRule( + help: "Remove redundant `let error` from `catch` clause." + ) { formatter in + formatter.forEach(.keyword("catch")) { i, _ in + if let letIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0 == .keyword("let") + }), let errorIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: letIndex, if: { + $0 == .identifier("error") + }), let scopeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: errorIndex, if: { + $0 == .startOfScope("{") + }) { + formatter.removeTokens(in: letIndex ..< scopeIndex) + } + } + } +} diff --git a/Sources/Rules/RedundantNilInit.swift b/Sources/Rules/RedundantNilInit.swift new file mode 100644 index 000000000..36f202c0b --- /dev/null +++ b/Sources/Rules/RedundantNilInit.swift @@ -0,0 +1,76 @@ +// +// RedundantNilInit.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove or insert redundant `= nil` initialization for Optional properties + static let redundantNilInit = FormatRule( + help: "Remove/insert redundant `nil` default value (Optional vars are nil by default).", + options: ["nilinit"] + ) { formatter in + func search(from index: Int, isStoredProperty: Bool) { + if let optionalIndex = formatter.index(of: .unwrapOperator, after: index) { + if formatter.index(of: .endOfStatement, in: index + 1 ..< optionalIndex) != nil { + return + } + let previousToken = formatter.tokens[optionalIndex - 1] + if !previousToken.isSpaceOrCommentOrLinebreak && previousToken != .keyword("as") { + let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: optionalIndex, if: { + $0 == .operator("=", .infix) + }) + switch formatter.options.nilInit { + case .remove: + if let equalsIndex = equalsIndex, let nilIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { + $0 == .identifier("nil") + }) { + formatter.removeTokens(in: optionalIndex + 1 ... nilIndex) + } + case .insert: + if isStoredProperty && equalsIndex == nil { + let tokens: [Token] = [.space(" "), .operator("=", .infix), .space(" "), .identifier("nil")] + formatter.insert(tokens, at: optionalIndex + 1) + } + } + } + search(from: optionalIndex, isStoredProperty: isStoredProperty) + } + } + + // Check modifiers don't include `lazy` + formatter.forEach(.keyword("var")) { i, _ in + if formatter.modifiersForDeclaration(at: i, contains: { + $1 == "lazy" || ($1 != "@objc" && $1.hasPrefix("@")) + }) || formatter.isInResultBuilder(at: i) { + return // Can't remove the init + } + // Check this isn't a Codable + if let scopeIndex = formatter.index(of: .startOfScope("{"), before: i) { + var prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: scopeIndex) + loop: while let index = prevIndex { + switch formatter.tokens[index] { + case .identifier("Codable"), .identifier("Decodable"): + return // Can't safely remove the default value + case .keyword("struct") where formatter.options.swiftVersion < "5.2": + if formatter.index(of: .keyword("init"), after: scopeIndex) == nil { + return // Can't safely remove the default value + } + break loop + case .keyword: + break loop + default: + break + } + prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: index) + } + } + // Find the nil + search(from: i, isStoredProperty: formatter.isStoredProperty(atIntroducerIndex: i)) + } + } +} diff --git a/Sources/Rules/RedundantObjc.swift b/Sources/Rules/RedundantObjc.swift new file mode 100644 index 000000000..7b56d7429 --- /dev/null +++ b/Sources/Rules/RedundantObjc.swift @@ -0,0 +1,87 @@ +// +// RedundantObjc.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant @objc annotation + static let redundantObjc = FormatRule( + help: "Remove redundant `@objc` annotations." + ) { formatter in + let objcAttributes = [ + "@IBOutlet", "@IBAction", "@IBSegueAction", + "@IBDesignable", "@IBInspectable", "@GKInspectable", + "@NSManaged", + ] + + formatter.forEach(.keyword("@objc")) { i, _ in + guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .startOfScope("(") else { + return + } + var index = i + loop: while var nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index) { + switch formatter.tokens[nextIndex] { + case .keyword("class"), .keyword("actor"), .keyword("enum"), + // Not actually allowed currently, but: future-proofing! + .keyword("protocol"), .keyword("struct"): + return + case .keyword("private"), .keyword("fileprivate"): + if formatter.next(.nonSpaceOrComment, after: nextIndex) == .startOfScope("(") { + break + } + // Can't safely remove objc from private members + return + case let token where token.isAttribute: + if let startIndex = formatter.index(of: .startOfScope("("), after: nextIndex), + let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) + { + nextIndex = endIndex + } + case let token: + guard token.isModifierKeyword else { + break loop + } + } + index = nextIndex + } + func removeAttribute() { + formatter.removeToken(at: i) + if formatter.token(at: i)?.isSpace == true { + formatter.removeToken(at: i) + } else if formatter.token(at: i - 1)?.isSpace == true { + formatter.removeToken(at: i - 1) + } + } + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i, if: { + $0.isAttribute && objcAttributes.contains($0.string) + }) != nil || formatter.next(.nonSpaceOrCommentOrLinebreak, after: i, if: { + $0.isAttribute && objcAttributes.contains($0.string) + }) != nil { + removeAttribute() + return + } + guard let scopeStart = formatter.index(of: .startOfScope("{"), before: i), + let keywordIndex = formatter.index(of: .keyword, before: scopeStart) + else { + return + } + switch formatter.tokens[keywordIndex] { + case .keyword("class"), .keyword("actor"): + if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objcMembers") { + removeAttribute() + } + case .keyword("extension"): + if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objc") { + removeAttribute() + } + default: + break + } + } + } +} diff --git a/Sources/Rules/RedundantOptionalBinding.swift b/Sources/Rules/RedundantOptionalBinding.swift new file mode 100644 index 000000000..6c36fb8e2 --- /dev/null +++ b/Sources/Rules/RedundantOptionalBinding.swift @@ -0,0 +1,43 @@ +// +// RedundantOptionalBinding.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantOptionalBinding = FormatRule( + help: "Remove redundant identifiers in optional binding conditions.", + // We can convert `if let foo = self.foo` to just `if let foo`, + // but only if `redundantSelf` can first remove the `self.`. + orderAfter: [.redundantSelf] + ) { formatter in + formatter.forEachToken { i, token in + // `if let foo` conditions were added in Swift 5.7 (SE-0345) + if formatter.options.swiftVersion >= "5.7", + + [.keyword("let"), .keyword("var")].contains(token), + formatter.isConditionalStatement(at: i), + + let identiferIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + let identifier = formatter.token(at: identiferIndex), + + let equalsIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: identiferIndex, if: { + $0 == .operator("=", .infix) + }), + + let nextIdentifierIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex, if: { + $0 == identifier + }), + + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIdentifierIndex), + [.startOfScope("{"), .delimiter(","), .keyword("else")].contains(nextToken) + { + formatter.removeTokens(in: identiferIndex + 1 ... nextIdentifierIndex) + } + } + } +} diff --git a/Sources/Rules/RedundantParens.swift b/Sources/Rules/RedundantParens.swift new file mode 100644 index 000000000..adf298a54 --- /dev/null +++ b/Sources/Rules/RedundantParens.swift @@ -0,0 +1,228 @@ +// +// RedundantParens.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant parens around the arguments for loops, if statements, closures, etc. + static let redundantParens = FormatRule( + help: "Remove redundant parentheses." + ) { formatter in + func nestedParens(in range: ClosedRange) -> ClosedRange? { + guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: range.lowerBound, if: { + $0 == .startOfScope("(") + }), let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: range.upperBound, if: { + $0 == .endOfScope(")") + }), formatter.index(of: .endOfScope(")"), after: startIndex) == endIndex else { + return nil + } + return startIndex ... endIndex + } + + // TODO: unify with conditionals logic in trailingClosures + let conditionals = Set(["in", "while", "if", "case", "switch", "where", "for", "guard"]) + + formatter.forEach(.startOfScope("(")) { i, _ in + guard var closingIndex = formatter.index(of: .endOfScope(")"), after: i), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .keyword("repeat") + else { + return + } + var innerParens = nestedParens(in: i ... closingIndex) + while let range = innerParens, nestedParens(in: range) != nil { + // TODO: this could be a lot more efficient if we kept track of the + // removed token indices instead of recalculating paren positions every time + formatter.removeParen(at: range.upperBound) + formatter.removeParen(at: range.lowerBound) + closingIndex = formatter.index(of: .endOfScope(")"), after: i)! + innerParens = nestedParens(in: i ... closingIndex) + } + var isClosure = false + let previousIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) ?? -1 + let prevToken = formatter.token(at: previousIndex) ?? .space("") + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) ?? .space("") + switch nextToken { + case .operator("->", .infix), .keyword("throws"), .keyword("rethrows"), + .identifier("async"), .keyword("in"): + if prevToken != .keyword("throws"), + formatter.index(before: i, where: { + [.endOfScope(")"), .operator("->", .infix), .keyword("for")].contains($0) + }) == nil, + let scopeIndex = formatter.startOfScope(at: i) + { + isClosure = formatter.isStartOfClosure(at: scopeIndex) && formatter.isInClosureArguments(at: i) + } + if !isClosure, nextToken != .keyword("in") { + return // It's a closure type, function declaration or for loop + } + case .operator: + if case let .operator(inner, _)? = formatter.last(.nonSpace, before: closingIndex), + !["?", "!"].contains(inner) + { + return + } + default: + break + } + switch prevToken { + case .stringBody, .operator("?", .postfix), .operator("!", .postfix), .operator("->", .infix): + return + case .identifier: // TODO: are trailing closures allowed in other cases? + // Parens before closure + guard closingIndex == formatter.index(of: .nonSpace, after: i), + let openingIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: closingIndex, if: { + $0 == .startOfScope("{") + }), + formatter.isStartOfClosure(at: openingIndex) + else { + return + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + case _ where isClosure: + if formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) == closingIndex || + formatter.index(of: .delimiter(":"), in: i + 1 ..< closingIndex) != nil || + formatter.tokens[i + 1 ..< closingIndex].contains(.identifier("self")) + { + return + } + if let index = formatter.tokens[i + 1 ..< closingIndex].firstIndex(of: .identifier("_")), + formatter.next(.nonSpaceOrComment, after: index)?.isIdentifier == true + { + return + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + case let .keyword(name) where !conditionals.contains(name) && !["let", "var", "return"].contains(name): + return + case .endOfScope("}"), .endOfScope(")"), .endOfScope("]"), .endOfScope(">"): + if formatter.tokens[previousIndex + 1 ..< i].contains(where: { $0.isLinebreak }) { + fallthrough + } + return // Probably a method invocation + case .delimiter(","), .endOfScope, .keyword: + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) ?? .space("") + guard formatter.index(of: .endOfScope("}"), before: closingIndex) == nil, + ![.endOfScope("}"), .endOfScope(">")].contains(prevToken) || + ![.startOfScope("{"), .delimiter(",")].contains(nextToken) + else { + return + } + let string = prevToken.string + if ![.startOfScope("{"), .delimiter(","), .startOfScope(":")].contains(nextToken), + !(string == "for" && nextToken == .keyword("in")), + !(string == "guard" && nextToken == .keyword("else")) + { + // TODO: this is confusing - refactor to move fallthrough to end of case + fallthrough + } + if formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< closingIndex) == nil || + formatter.index(of: .delimiter(","), in: i + 1 ..< closingIndex) != nil + { + // Might be a tuple, so we won't remove the parens + // TODO: improve the logic here so we don't misidentify function calls as tuples + return + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + case .operator(_, .infix): + guard let nextIndex = formatter.index(of: .nonSpaceOrComment, after: i, if: { + $0 == .startOfScope("{") + }), let lastIndex = formatter.index(of: .endOfScope("}"), after: nextIndex), + formatter.index(of: .nonSpaceOrComment, before: closingIndex) == lastIndex else { + fallthrough + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + default: + if let range = innerParens { + formatter.removeParen(at: range.upperBound) + formatter.removeParen(at: range.lowerBound) + closingIndex = formatter.index(of: .endOfScope(")"), after: i)! + innerParens = nil + } + if prevToken == .startOfScope("("), + formatter.last(.nonSpaceOrComment, before: previousIndex) == .identifier("Selector") + { + return + } + if case .operator = formatter.tokens[closingIndex - 1], + case .operator(_, .infix)? = formatter.token(at: closingIndex + 1) + { + return + } + let nextNonLinebreak = formatter.next(.nonSpaceOrComment, after: closingIndex) + if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + case .operator = formatter.tokens[index] + { + if nextToken.isOperator(".") || (index == i + 1 && + formatter.token(at: i - 1)?.isSpaceOrCommentOrLinebreak == false) + { + return + } + switch nextNonLinebreak { + case .startOfScope("[")?, .startOfScope("(")?, .operator(_, .postfix)?: + return + default: + break + } + } + guard formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) != closingIndex, + formatter.index(in: i + 1 ..< closingIndex, where: { + switch $0 { + case .operator(_, .infix), .identifier("any"), .identifier("some"), .identifier("each"), + .keyword("as"), .keyword("is"), .keyword("try"), .keyword("await"): + switch prevToken { + // TODO: add option to always strip parens in this case (or only for boolean operators?) + case .operator("=", .infix) where $0 == .operator("->", .infix): + break + case .operator(_, .prefix), .operator(_, .infix), .keyword("as"), .keyword("is"): + return true + default: + break + } + switch nextToken { + case .operator(_, .postfix), .operator(_, .infix), .keyword("as"), .keyword("is"): + return true + default: + break + } + switch nextNonLinebreak { + case .startOfScope("[")?, .startOfScope("(")?, .operator(_, .postfix)?: + return true + default: + return false + } + case .operator(_, .postfix): + switch prevToken { + case .operator(_, .prefix), .keyword("as"), .keyword("is"): + return true + default: + return false + } + case .delimiter(","), .delimiter(":"), .delimiter(";"), + .operator(_, .none), .startOfScope("{"): + return true + default: + return false + } + }) == nil, + formatter.index(in: i + 1 ..< closingIndex, where: { $0.isUnwrapOperator }) ?? closingIndex >= + formatter.index(of: .nonSpace, before: closingIndex) ?? closingIndex - 1 + else { + return + } + if formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) == .keyword("#file") { + return + } + formatter.removeParen(at: closingIndex) + formatter.removeParen(at: i) + } + } + } +} diff --git a/Sources/Rules/RedundantPattern.swift b/Sources/Rules/RedundantPattern.swift new file mode 100644 index 000000000..411770fa3 --- /dev/null +++ b/Sources/Rules/RedundantPattern.swift @@ -0,0 +1,72 @@ +// +// RedundantPattern.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant pattern in case statements + static let redundantPattern = FormatRule( + help: "Remove redundant pattern matching parameter syntax." + ) { formatter in + func redundantBindings(in range: Range) -> Bool { + var isEmpty = true + for token in formatter.tokens[range.lowerBound ..< range.upperBound] { + switch token { + case .identifier("_"): + isEmpty = false + case .space, .linebreak, .delimiter(","), .keyword("let"), .keyword("var"): + break + default: + return false + } + } + return !isEmpty + } + + formatter.forEach(.startOfScope("(")) { i, _ in + let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i) + if let prevIndex = prevIndex, let prevToken = formatter.token(at: prevIndex), + [.keyword("case"), .endOfScope("case")].contains(prevToken) + { + // Not safe to remove + return + } + guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex), + [.startOfScope(":"), .operator("=", .infix)].contains(nextToken), + redundantBindings(in: i + 1 ..< endIndex) + else { + return + } + formatter.removeTokens(in: i ... endIndex) + if let prevIndex = prevIndex, formatter.tokens[prevIndex].isIdentifier, + formatter.last(.nonSpaceOrComment, before: prevIndex)?.string == "." + { + if let endOfScopeIndex = formatter.index( + before: prevIndex, + where: { tkn in tkn == .endOfScope("case") || tkn == .keyword("case") } + ), + let varOrLetIndex = formatter.index(after: endOfScopeIndex, where: { tkn in + tkn == .keyword("let") || tkn == .keyword("var") + }), + let operatorIndex = formatter.index(of: .operator, before: prevIndex), + varOrLetIndex < operatorIndex + { + formatter.removeTokens(in: varOrLetIndex ..< operatorIndex) + } + return + } + + // Was an assignment + formatter.insert(.identifier("_"), at: i) + if formatter.token(at: i - 1).map({ $0.isSpaceOrLinebreak }) != true { + formatter.insert(.space(" "), at: i) + } + } + } +} diff --git a/Sources/Rules/RedundantProperty.swift b/Sources/Rules/RedundantProperty.swift new file mode 100644 index 000000000..dfeee6bac --- /dev/null +++ b/Sources/Rules/RedundantProperty.swift @@ -0,0 +1,52 @@ +// +// RedundantProperty.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantProperty = FormatRule( + help: "Simplifies redundant property definitions that are immediately returned.", + disabledByDefault: true, + orderAfter: [.propertyType] + ) { formatter in + formatter.forEach(.keyword) { introducerIndex, introducerToken in + // Find properties like `let identifier = value` followed by `return identifier` + guard ["let", "var"].contains(introducerToken.string), + let property = formatter.parsePropertyDeclaration(atIntroducerIndex: introducerIndex), + let (assignmentIndex, expressionRange) = property.value, + let returnIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: expressionRange.upperBound), + formatter.tokens[returnIndex] == .keyword("return"), + let returnedValueIndex = formatter.index(of: .nonSpaceOrComment, after: returnIndex), + let returnedExpression = formatter.parseExpressionRange(startingAt: returnedValueIndex, allowConditionalExpressions: true), + formatter.tokens[returnedExpression] == [.identifier(property.identifier)] + else { return } + + let returnRange = formatter.startOfLine(at: returnIndex) ... formatter.endOfLine(at: returnedExpression.upperBound) + let propertyRange = introducerIndex ... expressionRange.upperBound + + guard !propertyRange.overlaps(returnRange) else { return } + + // Remove the line with the `return identifier` statement. + formatter.removeTokens(in: returnRange) + + // If there's nothing but whitespace between the end of the expression + // and the return statement, we can remove all of it. But if there's a comment, + // we should preserve it. + let rangeBetweenExpressionAndReturn = (expressionRange.upperBound + 1) ..< (returnRange.lowerBound - 1) + if formatter.tokens[rangeBetweenExpressionAndReturn].allSatisfy(\.isSpaceOrLinebreak) { + formatter.removeTokens(in: rangeBetweenExpressionAndReturn) + } + + // Replace the `let identifier = value` with `return value` + formatter.replaceTokens( + in: introducerIndex ..< expressionRange.lowerBound, + with: [.keyword("return"), .space(" ")] + ) + } + } +} diff --git a/Sources/Rules/RedundantRawValues.swift b/Sources/Rules/RedundantRawValues.swift new file mode 100644 index 000000000..1cf3a3a2e --- /dev/null +++ b/Sources/Rules/RedundantRawValues.swift @@ -0,0 +1,50 @@ +// +// RedundantRawValues.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant raw string values for case statements + static let redundantRawValues = FormatRule( + help: "Remove redundant raw string values for enum cases." + ) { formatter in + formatter.forEach(.keyword("enum")) { i, _ in + guard let nameIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, after: i, if: { $0.isIdentifier } + ), let colonIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, after: nameIndex, if: { $0 == .delimiter(":") } + ), formatter.next(.nonSpaceOrCommentOrLinebreak, after: colonIndex) == .identifier("String"), + let braceIndex = formatter.index(of: .startOfScope("{"), after: colonIndex) else { + return + } + var lastIndex = formatter.index(of: .keyword("case"), after: braceIndex) + while var index = lastIndex { + guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { + $0.isIdentifier + }) else { break } + if let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: nameIndex, if: { + $0 == .operator("=", .infix) + }), let quoteIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { + $0 == .startOfScope("\"") + }), formatter.token(at: quoteIndex + 2) == .endOfScope("\"") { + if formatter.tokens[nameIndex].unescaped() == formatter.token(at: quoteIndex + 1)?.string { + formatter.removeTokens(in: nameIndex + 1 ... quoteIndex + 2) + index = nameIndex + } else { + index = quoteIndex + 2 + } + } else { + index = nameIndex + } + lastIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { + $0 == .delimiter(",") + }) ?? formatter.index(of: .keyword("case"), after: index) + } + } + } +} diff --git a/Sources/Rules/RedundantReturn.swift b/Sources/Rules/RedundantReturn.swift new file mode 100644 index 000000000..9d176344c --- /dev/null +++ b/Sources/Rules/RedundantReturn.swift @@ -0,0 +1,218 @@ +// +// RedundantReturn.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant return keyword + static let redundantReturn = FormatRule( + help: "Remove unneeded `return` keyword." + ) { formatter in + // indices of returns that are safe to remove + var returnIndices = [Int]() + + // Also handle redundant void returns in void functions, which can always be removed. + // - The following code is the original implementation of the `redundantReturn` rule + // and is partially redundant with the below code so could be simplified in the future. + formatter.forEach(.keyword("return")) { i, _ in + guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) else { + return + } + defer { + // Check return wasn't removed already + if formatter.token(at: i) == .keyword("return") { + returnIndices.append(i) + } + } + switch formatter.tokens[startIndex] { + case .keyword("in"): + break + case .startOfScope("{"): + guard var prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex) else { + break + } + if formatter.options.swiftVersion < "5.1", formatter.isAccessorKeyword(at: prevIndex) { + return + } + if formatter.tokens[prevIndex] == .endOfScope(")"), + let j = formatter.index(of: .startOfScope("("), before: prevIndex) + { + prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: j) ?? j + if formatter.tokens[prevIndex] == .operator("?", .postfix) { + prevIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex) ?? prevIndex + } + let prevToken = formatter.tokens[prevIndex] + guard prevToken.isIdentifier || prevToken == .keyword("init") else { + return + } + } + let prevToken = formatter.tokens[prevIndex] + guard ![.delimiter(":"), .startOfScope("(")].contains(prevToken), + var prevKeywordIndex = formatter.indexOfLastSignificantKeyword( + at: startIndex, excluding: ["where"] + ) + else { + break + } + switch formatter.tokens[prevKeywordIndex].string { + case "let", "var": + guard formatter.options.swiftVersion >= "5.1" || prevToken == .operator("=", .infix) || + formatter.lastIndex(of: .operator("=", .infix), in: prevKeywordIndex + 1 ..< prevIndex) != nil, + !formatter.isConditionalStatement(at: prevKeywordIndex) + else { + return + } + case "func", "throws", "rethrows", "init", "subscript": + if formatter.options.swiftVersion < "5.1", + formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .endOfScope("}") + { + return + } + default: + return + } + default: + guard let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0 == .endOfScope("}") + }), let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { + return + } + if !formatter.isStartOfClosure(at: startIndex), !["func", "throws", "rethrows"] + .contains(formatter.lastSignificantKeyword(at: startIndex, excluding: ["where"]) ?? "") + { + return + } + } + // Don't remove return if it's followed by more code + guard let endIndex = formatter.endOfScope(at: i), + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) == endIndex + else { + return + } + if formatter.index(of: .nonSpaceOrLinebreak, after: i) == endIndex, + let startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i) + { + formatter.removeTokens(in: startIndex + 1 ... i) + return + } + formatter.removeToken(at: i) + if var nextIndex = formatter.index(of: .nonSpace, after: i - 1, if: { $0.isLinebreak }) { + if let i = formatter.index(of: .nonSpaceOrLinebreak, after: nextIndex) { + nextIndex = i - 1 + } + formatter.removeTokens(in: i ... nextIndex) + } else if formatter.token(at: i)?.isSpace == true { + formatter.removeToken(at: i) + } + } + + // Explicit returns are redundant in closures, functions, etc with a single statement body + formatter.forEach(.startOfScope("{")) { startOfScopeIndex, _ in + // Closures always supported implicit returns, but other types of scopes + // only support implicit return in Swift 5.1+ (SE-0255) + let isClosure = formatter.isStartOfClosure(at: startOfScopeIndex) + if formatter.options.swiftVersion < "5.1", !isClosure { + return + } + + // Make sure this is a type of scope that supports implicit returns + if !isClosure, formatter.isConditionalStatement(at: startOfScopeIndex, excluding: ["where"]) || + ["do", "else", "catch"].contains(formatter.lastSignificantKeyword(at: startOfScopeIndex, excluding: ["throws"])) + { + return + } + + // Only strip return from conditional block if conditionalAssignment rule is enabled + let stripConditionalReturn = formatter.options.enabledRules.contains("conditionalAssignment") + + // Make sure the body only has a single statement + guard formatter.blockBodyHasSingleStatement( + atStartOfScope: startOfScopeIndex, + includingConditionalStatements: true, + includingReturnStatements: true, + includingReturnInConditionalStatements: stripConditionalReturn + ) else { + return + } + + // Make sure we aren't in a failable `init?`, where explicit return is required unless it's the only statement + if !isClosure, let lastSignificantKeywordIndex = formatter.indexOfLastSignificantKeyword(at: startOfScopeIndex), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: startOfScopeIndex) != .keyword("return"), + formatter.tokens[lastSignificantKeywordIndex] == .keyword("init"), + let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: lastSignificantKeywordIndex), + nextToken == .operator("?", .postfix) + { + return + } + + // Find all of the return keywords to remove before we remove any of them, + // so we can apply additional validation first. + var returnKeywordRangesToRemove = [Range]() + var hasReturnThatCantBeRemoved = false + + /// Finds the return keywords to remove and stores them in `returnKeywordRangesToRemove` + func removeReturn(atStartOfScope startOfScopeIndex: Int) { + // If this scope is a single-statement if or switch statement then we have to recursively + // remove the return from each branch of the if statement + let startOfBody = formatter.startOfBody(atStartOfScope: startOfScopeIndex) + + if let firstTokenInBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfBody), + let conditionalBranches = formatter.conditionalBranches(at: firstTokenInBody) + { + for branch in conditionalBranches.reversed() { + // In Swift 5.9, there's a bug that prevents you from writing an + // if or switch expression using an `as?` on one of the branches: + // https://github.com/apple/swift/issues/68764 + // + // if condition { + // foo as? String + // } else { + // "bar" + // } + // + if formatter.conditionalBranchHasUnsupportedCastOperator( + startOfScopeIndex: branch.startOfBranch) + { + hasReturnThatCantBeRemoved = true + return + } + + removeReturn(atStartOfScope: branch.startOfBranch) + } + } + + // Otherwise this is a simple case with a single return at the start of the scope + else if let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex), + let returnIndex = formatter.index(of: .keyword("return"), after: startOfScopeIndex), + returnIndices.contains(returnIndex), + returnIndex < endOfScopeIndex, + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: returnIndex), + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: returnIndex)! < endOfScopeIndex + { + let range = returnIndex ..< nextIndex + for (i, index) in returnIndices.enumerated().reversed() { + if range.contains(index) { + returnIndices.remove(at: i) + } else if index > returnIndex { + returnIndices[i] -= range.count + } + } + returnKeywordRangesToRemove.append(range) + } + } + + removeReturn(atStartOfScope: startOfScopeIndex) + + guard !hasReturnThatCantBeRemoved else { return } + + for returnKeywordRangeToRemove in returnKeywordRangesToRemove.sorted(by: { $0.startIndex > $1.startIndex }) { + formatter.removeTokens(in: returnKeywordRangeToRemove) + } + } + } +} diff --git a/Sources/Rules/RedundantSelf.swift b/Sources/Rules/RedundantSelf.swift new file mode 100644 index 000000000..0ffeba016 --- /dev/null +++ b/Sources/Rules/RedundantSelf.swift @@ -0,0 +1,21 @@ +// +// RedundantSelf.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Insert or remove redundant self keyword + static let redundantSelf = FormatRule( + help: "Insert/remove explicit `self` where applicable.", + options: ["self", "selfrequired"] + ) { formatter in + _ = formatter.options.selfRequired + _ = formatter.options.explicitSelf + formatter.addOrRemoveSelf(static: false) + } +} diff --git a/Sources/Rules/RedundantStaticSelf.swift b/Sources/Rules/RedundantStaticSelf.swift new file mode 100644 index 000000000..a4f28cb3d --- /dev/null +++ b/Sources/Rules/RedundantStaticSelf.swift @@ -0,0 +1,18 @@ +// +// RedundantStaticSelf.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant Self keyword + static let redundantStaticSelf = FormatRule( + help: "Remove explicit `Self` where applicable." + ) { formatter in + formatter.addOrRemoveSelf(static: true) + } +} diff --git a/Sources/Rules/RedundantType.swift b/Sources/Rules/RedundantType.swift new file mode 100644 index 000000000..1dabb689a --- /dev/null +++ b/Sources/Rules/RedundantType.swift @@ -0,0 +1,190 @@ +// +// RedundantType.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Removes explicit type declarations from initialization declarations + static let redundantType = FormatRule( + help: "Remove redundant type from variable declarations.", + options: ["redundanttype"] + ) { formatter in + formatter.forEach(.operator("=", .infix)) { i, _ in + guard let keyword = formatter.lastSignificantKeyword(at: i), + ["var", "let"].contains(keyword) + else { + return + } + + let equalsIndex = i + guard let colonIndex = formatter.index(before: i, where: { + [.delimiter(":"), .operator("=", .infix)].contains($0) + }), formatter.tokens[colonIndex] == .delimiter(":"), + let typeEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) + else { return } + + // Compares whether or not two types are equivalent + func compare(typeStartingAfter j: Int, withTypeStartingAfter i: Int) + -> (matches: Bool, i: Int, j: Int, wasValue: Bool) + { + var i = i, j = j, wasValue = false + + while let typeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + typeIndex <= typeEndIndex, + let valueIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: j) + { + let typeToken = formatter.tokens[typeIndex] + let valueToken = formatter.tokens[valueIndex] + if !wasValue { + switch valueToken { + case _ where valueToken.isStringDelimiter, .number, + .identifier("true"), .identifier("false"): + if formatter.options.redundantType == .explicit { + // We never remove the value in this case, so exit early + return (false, i, j, wasValue) + } + wasValue = true + default: + break + } + } + guard typeToken == formatter.typeToken(forValueToken: valueToken) else { + return (false, i, j, wasValue) + } + // Avoid introducing "inferred to have type 'Void'" warning + if formatter.options.redundantType == .inferred, typeToken == .identifier("Void") || + typeToken == .endOfScope(")") && formatter.tokens[i] == .startOfScope("(") + { + return (false, i, j, wasValue) + } + i = typeIndex + j = valueIndex + if formatter.tokens[j].isStringDelimiter, let next = formatter.endOfScope(at: j) { + j = next + } + } + guard i == typeEndIndex else { + return (false, i, j, wasValue) + } + + // Check for ternary + if let endOfExpression = formatter.endOfExpression(at: j, upTo: [.operator("?", .infix)]), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfExpression) == .operator("?", .infix) + { + return (false, i, j, wasValue) + } + + return (true, i, j, wasValue) + } + + // The implementation of RedundantType uses inferred or explicit, + // potentially depending on the context. + let isInferred: Bool + let declarationKeywordIndex: Int? + switch formatter.options.redundantType { + case .inferred: + isInferred = true + declarationKeywordIndex = nil + case .explicit: + isInferred = false + declarationKeywordIndex = formatter.declarationIndexAndScope(at: equalsIndex).index + case .inferLocalsOnly: + let (index, scope) = formatter.declarationIndexAndScope(at: equalsIndex) + switch scope { + case .global, .type: + isInferred = false + declarationKeywordIndex = index + case .local: + isInferred = true + declarationKeywordIndex = nil + } + } + + // Explicit type can't be safely removed from @Model classes + // https://github.com/nicklockwood/SwiftFormat/issues/1649 + if !isInferred, + let declarationKeywordIndex = declarationKeywordIndex, + formatter.modifiersForDeclaration(at: declarationKeywordIndex, contains: "@Model") + { + return + } + + // Removes a type already processed by `compare(typeStartingAfter:withTypeStartingAfter:)` + func removeType(after indexBeforeStartOfType: Int, i: Int, j: Int, wasValue: Bool) { + if isInferred { + formatter.removeTokens(in: colonIndex ... typeEndIndex) + if formatter.tokens[colonIndex - 1].isSpace { + formatter.removeToken(at: colonIndex - 1) + } + } else if !wasValue, let valueStartIndex = formatter + .index(of: .nonSpaceOrCommentOrLinebreak, after: indexBeforeStartOfType), + !formatter.isConditionalStatement(at: i), + let endIndex = formatter.endOfExpression(at: j, upTo: []), + endIndex > j + { + let allowChains = formatter.options.swiftVersion >= "5.4" + if formatter.next(.nonSpaceOrComment, after: j) == .startOfScope("(") { + if allowChains || formatter.index( + of: .operator(".", .infix), + in: j ..< endIndex + ) == nil { + formatter.replaceTokens(in: valueStartIndex ... j, with: [ + .operator(".", .infix), .identifier("init"), + ]) + } + } else if let nextIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + after: j, + if: { $0 == .operator(".", .infix) } + ), allowChains || formatter.index( + of: .operator(".", .infix), + in: (nextIndex + 1) ..< endIndex + ) == nil { + formatter.removeTokens(in: valueStartIndex ... j) + } + } + } + + // In Swift 5.9+ (SE-0380) we need to handle if / switch expressions by checking each branch + if formatter.options.swiftVersion >= "5.9", + let tokenAfterEquals = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: equalsIndex), + let conditionalBranches = formatter.conditionalBranches(at: tokenAfterEquals), + formatter.allRecursiveConditionalBranches( + in: conditionalBranches, + satisfy: { branch in + compare(typeStartingAfter: branch.startOfBranch, withTypeStartingAfter: colonIndex).matches + } + ) + { + if isInferred { + formatter.removeTokens(in: colonIndex ... typeEndIndex) + if formatter.tokens[colonIndex - 1].isSpace { + formatter.removeToken(at: colonIndex - 1) + } + } else { + formatter.forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in + let (_, i, j, wasValue) = compare( + typeStartingAfter: branch.startOfBranch, + withTypeStartingAfter: colonIndex + ) + + removeType(after: branch.startOfBranch, i: i, j: j, wasValue: wasValue) + } + } + } + + // Otherwise this is just a simple assignment expression where the RHS is a single value + else { + let (matches, i, j, wasValue) = compare(typeStartingAfter: equalsIndex, withTypeStartingAfter: colonIndex) + if matches { + removeType(after: equalsIndex, i: i, j: j, wasValue: wasValue) + } + } + } + } +} diff --git a/Sources/Rules/RedundantTypedThrows.swift b/Sources/Rules/RedundantTypedThrows.swift new file mode 100644 index 000000000..f40a0d7eb --- /dev/null +++ b/Sources/Rules/RedundantTypedThrows.swift @@ -0,0 +1,41 @@ +// +// RedundantTypedThrows.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let redundantTypedThrows = FormatRule( + help: "Converts `throws(any Error)` to `throws`, and converts `throws(Never)` to non-throwing.") + { formatter in + formatter.forEach(.keyword("throws")) { throwsIndex, _ in + guard // Typed throws was added in Swift 6.0: https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md + formatter.options.swiftVersion >= "6.0", + let startOfScope = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: throwsIndex), + formatter.tokens[startOfScope] == .startOfScope("("), + let endOfScope = formatter.endOfScope(at: startOfScope) + else { return } + + let throwsTypeRange = (startOfScope + 1) ..< endOfScope + let throwsType: String = formatter.tokens[throwsTypeRange].map { $0.string }.joined() + + if throwsType == "Never" { + if formatter.tokens[endOfScope + 1].isSpace { + formatter.removeTokens(in: throwsIndex ... endOfScope + 1) + } else { + formatter.removeTokens(in: throwsIndex ... endOfScope) + } + } + + // We don't remove `(Error)` because we can't guarantee it will reference the `Swift.Error` protocol + // (it's relatively common to define a custom error like `enum Error: Swift.Error { ... }`). + if throwsType == "any Error" || throwsType == "any Swift.Error" || throwsType == "Swift.Error" { + formatter.removeTokens(in: startOfScope ... endOfScope) + } + } + } +} diff --git a/Sources/Rules/RedundantVoidReturnType.swift b/Sources/Rules/RedundantVoidReturnType.swift new file mode 100644 index 000000000..c8c4013a2 --- /dev/null +++ b/Sources/Rules/RedundantVoidReturnType.swift @@ -0,0 +1,46 @@ +// +// RedundantVoidReturnType.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove redundant void return values for function and closure declarations + static let redundantVoidReturnType = FormatRule( + help: "Remove explicit `Void` return type.", + options: ["closurevoid"] + ) { formatter in + formatter.forEach(.operator("->", .infix)) { i, _ in + guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), + let endIndex = formatter.endOfVoidType(at: startIndex) + else { + return + } + + // If this is the explicit return type of a closure, it should + // always be safe to remove + if formatter.options.closureVoidReturn == .remove, + formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) == .keyword("in") + { + formatter.removeTokens(in: i ..< formatter.index(of: .nonSpace, after: endIndex)!) + return + } + + guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) == .startOfScope("{") + else { return } + + guard let prevIndex = formatter.index(of: .endOfScope(")"), before: i), + let parenIndex = formatter.index(of: .startOfScope("("), before: prevIndex), + let startToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: parenIndex), + startToken.isIdentifier || [.startOfScope("{"), .endOfScope("]")].contains(startToken) + else { + return + } + formatter.removeTokens(in: i ..< formatter.index(of: .nonSpace, after: endIndex)!) + } + } +} diff --git a/Sources/Rules/Semicolons.swift b/Sources/Rules/Semicolons.swift new file mode 100644 index 000000000..215317019 --- /dev/null +++ b/Sources/Rules/Semicolons.swift @@ -0,0 +1,52 @@ +// +// Semicolons.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove semicolons, except where doing so would change the meaning of the code + static let semicolons = FormatRule( + help: "Remove semicolons.", + options: ["semicolons"], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.delimiter(";")) { i, _ in + if let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { + let prevTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) + let prevToken = prevTokenIndex.map { formatter.tokens[$0] } + if prevToken == nil || nextToken == .endOfScope("}") { + // Safe to remove + formatter.removeToken(at: i) + } else if prevToken == .keyword("return") || ( + formatter.options.swiftVersion < "3" && + // Might be a traditional for loop (not supported in Swift 3 and above) + formatter.currentScope(at: i) == .startOfScope("(") + ) { + // Not safe to remove or replace + } else if case .identifier? = prevToken, formatter.last( + .nonSpaceOrCommentOrLinebreak, before: prevTokenIndex! + ) == .keyword("var") { + // Not safe to remove or replace + } else if formatter.next(.nonSpaceOrComment, after: i)?.isLinebreak == true { + // Safe to remove + formatter.removeToken(at: i) + } else if !formatter.options.allowInlineSemicolons { + // Replace with a linebreak + if formatter.token(at: i + 1)?.isSpace == true { + formatter.removeToken(at: i + 1) + } + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: i + 1) + formatter.replaceToken(at: i, with: formatter.linebreakToken(for: i)) + } + } else { + // Safe to remove + formatter.removeToken(at: i) + } + } + } +} diff --git a/Sources/Rules/SortDeclarations.swift b/Sources/Rules/SortDeclarations.swift new file mode 100644 index 000000000..7af49d110 --- /dev/null +++ b/Sources/Rules/SortDeclarations.swift @@ -0,0 +1,155 @@ +// +// SortDeclarations.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let sortDeclarations = FormatRule( + help: """ + Sorts the body of declarations with // swiftformat:sort + and declarations between // swiftformat:sort:begin and + // swiftformat:sort:end comments. + """, + options: ["sortedpatterns"], + sharedOptions: ["organizetypes"] + ) { formatter in + formatter.forEachToken( + where: { + $0.isCommentBody && $0.string.contains("swiftformat:sort") + || $0.isDeclarationTypeKeyword(including: Array(Token.swiftTypeKeywords)) + } + ) { index, token in + + let rangeToSort: ClosedRange + let numberOfLeadingLinebreaks: Int + + // For `:sort:begin`, directives, we sort the declarations + // between the `:begin` and and `:end` comments + let shouldBePartiallySorted = token.string.contains("swiftformat:sort:begin") + + let identifier = formatter.next(.identifier, after: index) + let shouldBeSortedByNamePattern = formatter.options.alphabeticallySortedDeclarationPatterns.contains { + identifier?.string.contains($0) ?? false + } + let shouldBeSortedByMarkComment = token.isCommentBody && !token.string.contains(":sort:") + // For `:sort` directives and types with matching name pattern, we sort the declarations + // between the open and close brace of the following type + let shouldBeFullySorted = shouldBeSortedByNamePattern || shouldBeSortedByMarkComment + + if shouldBePartiallySorted { + guard let endCommentIndex = formatter.tokens[index...].firstIndex(where: { + $0.isComment && $0.string.contains("swiftformat:sort:end") + }), + let sortRangeStart = formatter.index(of: .nonSpaceOrComment, after: index), + let firstRangeToken = formatter.index(of: .nonLinebreak, after: sortRangeStart), + let lastRangeToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: endCommentIndex - 2) + else { return } + + rangeToSort = sortRangeStart ... lastRangeToken + numberOfLeadingLinebreaks = firstRangeToken - sortRangeStart + } else if shouldBeFullySorted { + guard let typeOpenBrace = formatter.index(of: .startOfScope("{"), after: index), + let typeCloseBrace = formatter.endOfScope(at: typeOpenBrace), + let firstTypeBodyToken = formatter.index(of: .nonLinebreak, after: typeOpenBrace), + let lastTypeBodyToken = formatter.index(of: .nonLinebreak, before: typeCloseBrace), + let declarationKeyword = formatter.lastSignificantKeyword(at: typeOpenBrace), + lastTypeBodyToken > typeOpenBrace + else { return } + + // Sorting the body of a type conflicts with the `organizeDeclarations` + // keyword if enabled for this type of declaration. In that case, + // defer to the sorting implementation in `organizeDeclarations`. + if formatter.options.enabledRules.contains(FormatRule.organizeDeclarations.name), + formatter.options.organizeTypes.contains(declarationKeyword) + { + return + } + + rangeToSort = firstTypeBodyToken ... lastTypeBodyToken + // We don't include any leading linebreaks in the range to sort, + // since `firstTypeBodyToken` is the first `nonLinebreak` in the body + numberOfLeadingLinebreaks = 0 + } else { + return + } + + var declarations = Formatter(Array(formatter.tokens[rangeToSort])) + .parseDeclarations() + .enumerated() + .sorted(by: { lhs, rhs -> Bool in + let (lhsIndex, lhsDeclaration) = lhs + let (rhsIndex, rhsDeclaration) = rhs + + // Primarily sort by name, to alphabetize + if let lhsName = lhsDeclaration.name, + let rhsName = rhsDeclaration.name, + lhsName != rhsName + { + return lhsName.localizedCompare(rhsName) == .orderedAscending + } + + // Otherwise preserve the existing order + else { + return lhsIndex < rhsIndex + } + + }) + .map { $0.element } + + // Make sure there's at least one newline between each declaration + for i in 0 ..< max(0, declarations.count - 1) { + let declaration = declarations[i] + let nextDeclaration = declarations[i + 1] + + if declaration.tokens.last?.isLinebreak == false, + nextDeclaration.tokens.first?.isLinebreak == false + { + declarations[i + 1] = formatter.mapOpeningTokens(in: nextDeclaration) { openTokens in + let openFormatter = Formatter(openTokens) + openFormatter.insertLinebreak(at: 0) + return openFormatter.tokens + } + } + } + + var sortedFormatter = Formatter(declarations.flatMap { $0.tokens }) + + // Make sure the type has the same number of leading line breaks + // as it did before sorting + if let currentLeadingLinebreakCount = sortedFormatter.tokens.firstIndex(where: { !$0.isLinebreak }) { + if numberOfLeadingLinebreaks != currentLeadingLinebreakCount { + sortedFormatter.removeTokens(in: 0 ..< currentLeadingLinebreakCount) + + for _ in 0 ..< numberOfLeadingLinebreaks { + sortedFormatter.insertLinebreak(at: 0) + } + } + + } else { + for _ in 0 ..< numberOfLeadingLinebreaks { + sortedFormatter.insertLinebreak(at: 0) + } + } + + // There are always expected to be zero trailing line breaks, + // so we remove any trailing line breaks + // (this is because `typeBodyRange` specifically ends before the first + // trailing linebreak) + while sortedFormatter.tokens.last?.isLinebreak == true { + sortedFormatter.removeLastToken() + } + + if Array(formatter.tokens[rangeToSort]) != sortedFormatter.tokens { + formatter.replaceTokens( + in: rangeToSort, + with: sortedFormatter.tokens + ) + } + } + } +} diff --git a/Sources/Rules/SortImports.swift b/Sources/Rules/SortImports.swift new file mode 100644 index 000000000..615068dea --- /dev/null +++ b/Sources/Rules/SortImports.swift @@ -0,0 +1,54 @@ +// +// SortImports.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Sort import statements + static let sortImports = FormatRule( + help: "Sort import statements alphabetically.", + options: ["importgrouping"], + sharedOptions: ["linebreaks"] + ) { formatter in + func sortRanges(_ ranges: [Formatter.ImportRange]) -> [Formatter.ImportRange] { + if case .alpha = formatter.options.importGrouping { + return ranges.sorted(by: <) + } else if case .length = formatter.options.importGrouping { + return ranges.sorted { $0.module.count < $1.module.count } + } + // Group @testable imports at the top or bottom + // TODO: need more general solution for handling other import attributes + return ranges.sorted { + // If both have a @testable keyword, or neither has one, just sort alphabetically + guard $0.isTestable != $1.isTestable else { + return $0 < $1 + } + return formatter.options.importGrouping == .testableFirst ? $0.isTestable : $1.isTestable + } + } + + for var importRanges in formatter.parseImports().reversed() { + guard importRanges.count > 1 else { continue } + let range: Range = importRanges.first!.range.lowerBound ..< importRanges.last!.range.upperBound + let sortedRanges = sortRanges(importRanges) + var insertedLinebreak = false + var sortedTokens = sortedRanges.flatMap { inputRange -> [Token] in + var tokens = Array(formatter.tokens[inputRange.range]) + if tokens.first?.isLinebreak == false { + insertedLinebreak = true + tokens.insert(formatter.linebreakToken(for: tokens.startIndex), at: tokens.startIndex) + } + return tokens + } + if insertedLinebreak { + sortedTokens.removeFirst() + } + formatter.replaceTokens(in: range, with: sortedTokens) + } + } +} diff --git a/Sources/Rules/SortSwitchCases.swift b/Sources/Rules/SortSwitchCases.swift new file mode 100644 index 000000000..ef183eae8 --- /dev/null +++ b/Sources/Rules/SortSwitchCases.swift @@ -0,0 +1,92 @@ +// +// SortSwitchCases.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Sorts switch cases alphabetically + static let sortSwitchCases = FormatRule( + help: "Sort switch cases alphabetically.", + disabledByDefault: true + ) { formatter in + formatter.parseSwitchCaseRanges() + .reversed() // don't mess with indexes + .forEach { switchCaseRanges in + guard switchCaseRanges.count > 1, // nothing to sort + let firstCaseIndex = switchCaseRanges.first?.beforeDelimiterRange.lowerBound else { return } + + let indentCounts = switchCaseRanges.map { formatter.currentIndentForLine(at: $0.beforeDelimiterRange.lowerBound).count } + let maxIndentCount = indentCounts.max() ?? 0 + + func sortableValue(for token: Token) -> String? { + switch token { + case let .identifier(name): + return name + case let .stringBody(body): + return body + case let .number(value, .hex): + return Int(value.dropFirst(2), radix: 16) + .map(String.init) ?? value + case let .number(value, .octal): + return Int(value.dropFirst(2), radix: 8) + .map(String.init) ?? value + case let .number(value, .binary): + return Int(value.dropFirst(2), radix: 2) + .map(String.init) ?? value + case let .number(value, _): + return value + default: + return nil + } + } + + let sorted = switchCaseRanges.sorted { case1, case2 -> Bool in + let lhs = formatter.tokens[case1.beforeDelimiterRange] + .compactMap(sortableValue) + let rhs = formatter.tokens[case2.beforeDelimiterRange] + .compactMap(sortableValue) + for (lhs, rhs) in zip(lhs, rhs) { + switch lhs.localizedStandardCompare(rhs) { + case .orderedAscending: + return true + case .orderedDescending: + return false + case .orderedSame: + continue + } + } + return lhs.count < rhs.count + } + + let sortedTokens = sorted.map { formatter.tokens[$0.beforeDelimiterRange] } + let sortedComments = sorted.map { formatter.tokens[$0.afterDelimiterRange] } + + // ignore if there's a where keyword and it is not in the last place. + let firstWhereIndex = sortedTokens.firstIndex(where: { slice in slice.contains(.keyword("where")) }) + guard firstWhereIndex == nil || firstWhereIndex == sortedTokens.count - 1 else { return } + + for switchCase in switchCaseRanges.enumerated().reversed() { + let newTokens = Array(sortedTokens[switchCase.offset]) + var newComments = Array(sortedComments[switchCase.offset]) + let oldComments = formatter.tokens[switchCaseRanges[switchCase.offset].afterDelimiterRange] + + if newComments.last?.isLinebreak == oldComments.last?.isLinebreak { + formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].afterDelimiterRange, with: newComments) + } else if newComments.count > 1, + newComments.last?.isLinebreak == true, oldComments.last?.isLinebreak == false + { + // indent the new content + newComments.append(.space(String(repeating: " ", count: maxIndentCount))) + formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].afterDelimiterRange, with: newComments) + } + + formatter.replaceTokens(in: switchCaseRanges[switchCase.offset].beforeDelimiterRange, with: newTokens) + } + } + } +} diff --git a/Sources/Rules/SortTypealiases.swift b/Sources/Rules/SortTypealiases.swift new file mode 100644 index 000000000..85630ee7b --- /dev/null +++ b/Sources/Rules/SortTypealiases.swift @@ -0,0 +1,135 @@ +// +// SortTypealiases.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let sortTypealiases = FormatRule( + help: "Sort protocol composition typealiases alphabetically." + ) { formatter in + formatter.forEach(.keyword("typealias")) { typealiasIndex, _ in + guard let (equalsIndex, andTokenIndices, endIndex) = formatter.parseProtocolCompositionTypealias(at: typealiasIndex), + let typealiasNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) + else { + return + } + + var seenTypes = Set() + + // Split the typealias into individual elements. + // Any comments on their own line are grouped with the following element. + let delimiters = [equalsIndex] + andTokenIndices + var parsedElements: [(startIndex: Int, delimiterIndex: Int, endIndex: Int, type: String, allTokens: [Token], isDuplicate: Bool)] = [] + + for delimiter in delimiters.indices { + let endOfPreviousElement = parsedElements.last?.endIndex ?? typealiasNameIndex + let elementStartIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfPreviousElement) ?? delimiters[delimiter] + + // Start with the end index just being the end of the type name + var elementEndIndex: Int + let nextElementIsOnSameLine: Bool + if delimiter == delimiters.indices.last { + elementEndIndex = endIndex + nextElementIsOnSameLine = false + } else { + let nextDelimiterIndex = delimiters[delimiter + 1] + elementEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: nextDelimiterIndex) ?? (nextDelimiterIndex - 1) + + let endOfLine = formatter.endOfLine(at: elementEndIndex) + nextElementIsOnSameLine = formatter.endOfLine(at: nextDelimiterIndex) == endOfLine + } + + // Handle comments in multiline typealiases + if !nextElementIsOnSameLine { + // Any comments on the same line as the type name should be considered part of this element. + // Any comments after the linebreak are consisidered part of the next element. + // To do that we just extend this element to the end of the current line. + elementEndIndex = formatter.endOfLine(at: elementEndIndex) - 1 + } + + let tokens = Array(formatter.tokens[elementStartIndex ... elementEndIndex]) + let typeName = tokens + .filter { !$0.isSpaceOrCommentOrLinebreak && !$0.isOperator } + .map { $0.string }.joined() + + // While we're here, also filter out any duplicates. + // Since we're sorting, duplicates would sit right next to each other + // which makes them especially obvious. + let isDuplicate = seenTypes.contains(typeName) + seenTypes.insert(typeName) + + parsedElements.append(( + startIndex: elementStartIndex, + delimiterIndex: delimiters[delimiter], + endIndex: elementEndIndex, + type: typeName, + allTokens: tokens, + isDuplicate: isDuplicate + )) + } + + // Sort each element by type name + var sortedElements = parsedElements.sorted(by: { lhsElement, rhsElement in + lhsElement.type.lexicographicallyPrecedes(rhsElement.type) + }) + + // Don't modify the file if the typealias is already sorted + if parsedElements.map(\.startIndex) == sortedElements.map(\.startIndex) { + return + } + + let firstNonDuplicateIndex = sortedElements.firstIndex(where: { !$0.isDuplicate }) + + for elementIndex in sortedElements.indices { + // Revalidate all of the delimiters after sorting + // (the first delimiter should be `=` and all others should be `&` + let delimiterIndexInTokens = sortedElements[elementIndex].delimiterIndex - sortedElements[elementIndex].startIndex + + if elementIndex == firstNonDuplicateIndex { + sortedElements[elementIndex].allTokens[delimiterIndexInTokens] = .operator("=", .infix) + } else { + sortedElements[elementIndex].allTokens[delimiterIndexInTokens] = .operator("&", .infix) + } + + // Make sure there's always a linebreak after any comments, to prevent + // them from accidentially commenting out following elements of the typealias + if elementIndex != sortedElements.indices.last, + sortedElements[elementIndex].allTokens.last?.isComment == true, + let nextToken = formatter.nextToken(after: parsedElements[elementIndex].endIndex), + !nextToken.isLinebreak + { + sortedElements[elementIndex].allTokens.append(.linebreak("\n", 0)) + } + + // If this element starts with a comment, that's because the comment + // was originally on a line all by itself. To preserve this, make sure + // there's a linebreak before the comment. + if elementIndex != sortedElements.indices.first, + sortedElements[elementIndex].allTokens.first?.isComment == true, + let previousToken = formatter.lastToken(before: parsedElements[elementIndex].startIndex, where: { !$0.isSpace }), + !previousToken.isLinebreak + { + sortedElements[elementIndex].allTokens.insert(.linebreak("\n", 0), at: 0) + } + } + + // Replace each index in the parsed list with the corresponding index in the sorted list, + // working backwards to not invalidate any existing indices + for (originalElement, newElement) in zip(parsedElements, sortedElements).reversed() { + if newElement.isDuplicate, let tokenBeforeElement = formatter.index(of: .nonSpaceOrLinebreak, before: originalElement.startIndex) { + formatter.removeTokens(in: (tokenBeforeElement + 1) ... originalElement.endIndex) + } else { + formatter.replaceTokens( + in: originalElement.startIndex ... originalElement.endIndex, + with: newElement.allTokens + ) + } + } + } + } +} diff --git a/Sources/Rules/SortedImports.swift b/Sources/Rules/SortedImports.swift new file mode 100644 index 000000000..173d1f36d --- /dev/null +++ b/Sources/Rules/SortedImports.swift @@ -0,0 +1,23 @@ +// +// SortedImports.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Deprecated + static let sortedImports = FormatRule( + help: "Sort import statements alphabetically.", + deprecationMessage: "Use sortImports instead.", + options: ["importgrouping"], + sharedOptions: ["linebreaks"] + ) { formatter in + _ = formatter.options.importGrouping + _ = formatter.options.linebreak + FormatRule.sortImports.apply(with: formatter) + } +} diff --git a/Sources/Rules/SortedSwitchCases.swift b/Sources/Rules/SortedSwitchCases.swift new file mode 100644 index 000000000..4593b3490 --- /dev/null +++ b/Sources/Rules/SortedSwitchCases.swift @@ -0,0 +1,19 @@ +// +// SortedSwitchCases.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Deprecated + static let sortedSwitchCases = FormatRule( + help: "Sort switch cases alphabetically.", + deprecationMessage: "Use sortSwitchCases instead." + ) { formatter in + FormatRule.sortSwitchCases.apply(with: formatter) + } +} diff --git a/Sources/Rules/SpaceAroundBraces.swift b/Sources/Rules/SpaceAroundBraces.swift new file mode 100644 index 000000000..405c76346 --- /dev/null +++ b/Sources/Rules/SpaceAroundBraces.swift @@ -0,0 +1,39 @@ +// +// SpaceAroundBraces.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that there is space between an opening brace and the preceding + /// identifier, and between a closing brace and the following identifier. + static let spaceAroundBraces = FormatRule( + help: "Add or remove space around curly braces." + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + if let prevToken = formatter.token(at: i - 1) { + switch prevToken { + case .space, .linebreak, .operator(_, .prefix), .operator(_, .infix), + .startOfScope where !prevToken.isStringDelimiter: + break + default: + formatter.insert(.space(" "), at: i) + } + } + } + formatter.forEach(.endOfScope("}")) { i, _ in + if let nextToken = formatter.token(at: i + 1) { + switch nextToken { + case .identifier, .keyword: + formatter.insert(.space(" "), at: i + 1) + default: + break + } + } + } + } +} diff --git a/Sources/Rules/SpaceAroundBrackets.swift b/Sources/Rules/SpaceAroundBrackets.swift new file mode 100644 index 000000000..ed00b288c --- /dev/null +++ b/Sources/Rules/SpaceAroundBrackets.swift @@ -0,0 +1,71 @@ +// +// SpaceAroundBrackets.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Implement the following rules with respect to the spacing around square brackets: + /// * There is no space between an opening bracket and the preceding identifier, + /// unless the identifier is one of the specified keywords + /// * There is no space between an opening bracket and the preceding closing brace + /// * There is no space between an opening bracket and the preceding closing square bracket + /// * There is space between a closing bracket and following identifier + /// * There is space between a closing bracket and following opening brace + static let spaceAroundBrackets = FormatRule( + help: "Add or remove space around square brackets." + ) { formatter in + formatter.forEach(.startOfScope("[")) { i, _ in + let index = i - 1 + guard let prevToken = formatter.token(at: index) else { + return + } + switch prevToken { + case .keyword, + .identifier("borrowing") where formatter.isTypePosition(at: index), + .identifier("consuming") where formatter.isTypePosition(at: index), + .identifier("sending") where formatter.isTypePosition(at: index): + formatter.insert(.space(" "), at: i) + case .space: + let index = i - 2 + if let token = formatter.token(at: index) { + switch token { + case .identifier("borrowing") where formatter.isTypePosition(at: index), + .identifier("consuming") where formatter.isTypePosition(at: index), + .identifier("sending") where formatter.isTypePosition(at: index): + break + case .identifier, .number, .endOfScope("]"), .endOfScope("}"), .endOfScope(")"): + formatter.removeToken(at: i - 1) + default: + break + } + } + default: + break + } + } + formatter.forEach(.endOfScope("]")) { i, _ in + guard let nextToken = formatter.token(at: i + 1) else { + return + } + switch nextToken { + case .identifier, .keyword, .startOfScope("{"), + .startOfScope("(") where formatter.isInClosureArguments(at: i): + formatter.insert(.space(" "), at: i + 1) + case .space: + switch formatter.token(at: i + 2) { + case .startOfScope("(")? where !formatter.isInClosureArguments(at: i + 2), .startOfScope("[")?: + formatter.removeToken(at: i + 1) + default: + break + } + default: + break + } + } + } +} diff --git a/Sources/Rules/SpaceAroundComments.swift b/Sources/Rules/SpaceAroundComments.swift new file mode 100644 index 000000000..1fb3a6ad0 --- /dev/null +++ b/Sources/Rules/SpaceAroundComments.swift @@ -0,0 +1,46 @@ +// +// SpaceAroundComments.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/27/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Add space around comments, except at the start or end of a line + static let spaceAroundComments = FormatRule( + help: "Add space before and/or after comments." + ) { formatter in + formatter.forEach(.startOfScope("//")) { i, _ in + if let prevToken = formatter.token(at: i - 1), !prevToken.isSpaceOrLinebreak { + formatter.insert(.space(" "), at: i) + } + } + formatter.forEach(.endOfScope("*/")) { i, _ in + guard let startIndex = formatter.index(of: .startOfScope("/*"), before: i), + case let .commentBody(commentStart)? = formatter.next(.nonSpaceOrLinebreak, after: startIndex), + case let .commentBody(commentEnd)? = formatter.last(.nonSpaceOrLinebreak, before: i), + !commentStart.hasPrefix("@"), !commentEnd.hasSuffix("@") + else { + return + } + if let nextToken = formatter.token(at: i + 1) { + if !nextToken.isSpaceOrLinebreak { + if nextToken != .delimiter(",") { + formatter.insert(.space(" "), at: i + 1) + } + } else if formatter.next(.nonSpace, after: i + 1) == .delimiter(",") { + formatter.removeToken(at: i + 1) + } + } + if let prevToken = formatter.token(at: startIndex - 1), !prevToken.isSpaceOrLinebreak { + if case let .commentBody(text) = prevToken, text.last?.unicodeScalars.last?.isSpace == true { + return + } + formatter.insert(.space(" "), at: startIndex) + } + } + } +} diff --git a/Sources/Rules/SpaceAroundGenerics.swift b/Sources/Rules/SpaceAroundGenerics.swift new file mode 100644 index 000000000..1c029fe62 --- /dev/null +++ b/Sources/Rules/SpaceAroundGenerics.swift @@ -0,0 +1,24 @@ +// +// SpaceAroundGenerics.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure there is no space between an opening chevron and the preceding identifier + static let spaceAroundGenerics = FormatRule( + help: "Remove space around angle brackets." + ) { formatter in + formatter.forEach(.startOfScope("<")) { i, _ in + if formatter.token(at: i - 1)?.isSpace == true, + formatter.token(at: i - 2)?.isIdentifierOrKeyword == true + { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/SpaceAroundOperators.swift b/Sources/Rules/SpaceAroundOperators.swift new file mode 100644 index 000000000..4e3f8956a --- /dev/null +++ b/Sources/Rules/SpaceAroundOperators.swift @@ -0,0 +1,142 @@ +// +// SpaceAroundOperators.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Implement the following rules with respect to the spacing around operators: + /// * Infix operators are separated from their operands by a space on either + /// side. Does not affect prefix/postfix operators, as required by syntax. + /// * Delimiters, such as commas and colons, are consistently followed by a + /// single space, unless it appears at the end of a line, and is not + /// preceded by a space, unless it appears at the beginning of a line. + static let spaceAroundOperators = FormatRule( + help: "Add or remove space around operators or delimiters.", + options: ["operatorfunc", "nospaceoperators", "ranges", "typedelimiter"] + ) { formatter in + formatter.forEachToken { i, token in + switch token { + case .operator(_, .none): + switch formatter.token(at: i + 1) { + case nil, .linebreak?, .endOfScope?, .operator?, .delimiter?, + .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: + break + case .space?: + switch formatter.next(.nonSpaceOrLinebreak, after: i) { + case nil, .linebreak?, .endOfScope?, .delimiter?, + .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: + formatter.removeToken(at: i + 1) + default: + break + } + default: + formatter.insert(.space(" "), at: i + 1) + } + case .operator("?", .postfix), .operator("!", .postfix): + if let prevToken = formatter.token(at: i - 1), + formatter.token(at: i + 1)?.isSpaceOrLinebreak == false, + [.keyword("as"), .keyword("try")].contains(prevToken) + { + formatter.insert(.space(" "), at: i + 1) + } + case .operator(".", _): + if formatter.token(at: i + 1)?.isSpace == true { + formatter.removeToken(at: i + 1) + } + guard let prevIndex = formatter.index(of: .nonSpace, before: i) else { + formatter.removeTokens(in: 0 ..< i) + break + } + let spaceRequired: Bool + switch formatter.tokens[prevIndex] { + case .operator(_, .infix), .startOfScope: + return + case let token where token.isUnwrapOperator: + if let prevToken = formatter.last(.nonSpace, before: prevIndex), + [.keyword("as"), .keyword("try")].contains(prevToken) + { + spaceRequired = true + } else { + spaceRequired = false + } + case .operator(_, .prefix): + spaceRequired = false + case let token: + spaceRequired = !token.isAttribute && !token.isLvalue + } + if formatter.token(at: i - 1)?.isSpaceOrLinebreak == true { + if !spaceRequired { + formatter.removeToken(at: i - 1) + } + } else if spaceRequired { + formatter.insertSpace(" ", at: i) + } + case .operator("?", .infix): + break // Spacing around ternary ? is not optional + case let .operator(name, .infix) where formatter.options.noSpaceOperators.contains(name) || + (!formatter.options.spaceAroundRangeOperators && token.isRangeOperator): + if formatter.token(at: i + 1)?.isSpace == true, + formatter.token(at: i - 1)?.isSpace == true, + let nextToken = formatter.next(.nonSpace, after: i), + !nextToken.isCommentOrLinebreak, !nextToken.isOperator, + let prevToken = formatter.last(.nonSpace, before: i), + !prevToken.isCommentOrLinebreak, !prevToken.isOperator || prevToken.isUnwrapOperator + { + formatter.removeToken(at: i + 1) + formatter.removeToken(at: i - 1) + } + case .operator(_, .infix): + if formatter.token(at: i + 1)?.isSpaceOrLinebreak == false { + formatter.insert(.space(" "), at: i + 1) + } + if formatter.token(at: i - 1)?.isSpaceOrLinebreak == false { + formatter.insert(.space(" "), at: i) + } + case .operator(_, .prefix): + if let prevIndex = formatter.index(of: .nonSpace, before: i, if: { + [.startOfScope("["), .startOfScope("("), .startOfScope("<")].contains($0) + }) { + formatter.removeTokens(in: prevIndex + 1 ..< i) + } else if let prevToken = formatter.token(at: i - 1), + !prevToken.isSpaceOrLinebreak, !prevToken.isOperator + { + formatter.insert(.space(" "), at: i) + } + case .delimiter(":"): + // TODO: make this check more robust, and remove redundant space + if formatter.token(at: i + 1)?.isIdentifier == true, + formatter.token(at: i + 2) == .delimiter(":") + { + // It's a selector + break + } + fallthrough + case .operator(_, .postfix), .delimiter(","), .delimiter(";"), .startOfScope(":"): + switch formatter.token(at: i + 1) { + case nil, .space?, .linebreak?, .endOfScope?, .operator?, .delimiter?: + break + default: + // Ensure there is a space after the token + formatter.insert(.space(" "), at: i + 1) + } + + let spaceBeforeToken = formatter.token(at: i - 1)?.isSpace == true + && formatter.token(at: i - 2)?.isLinebreak == false + + if spaceBeforeToken, formatter.options.typeDelimiterSpacing == .spaceAfter { + // Remove space before the token + formatter.removeToken(at: i - 1) + } else if !spaceBeforeToken, formatter.options.typeDelimiterSpacing == .spaced { + formatter.insertSpace(" ", at: i) + } + default: + break + } + } + } +} diff --git a/Sources/Rules/SpaceAroundParens.swift b/Sources/Rules/SpaceAroundParens.swift new file mode 100644 index 000000000..17ca42623 --- /dev/null +++ b/Sources/Rules/SpaceAroundParens.swift @@ -0,0 +1,111 @@ +// +// SpaceAroundParens.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Implement the following rules with respect to the spacing around parens: + /// * There is no space between an opening paren and the preceding identifier, + /// unless the identifier is one of the specified keywords + /// * There is no space between an opening paren and the preceding closing brace + /// * There is no space between an opening paren and the preceding closing square bracket + /// * There is space between a closing paren and following identifier + /// * There is space between a closing paren and following opening brace + /// * There is no space between a closing paren and following opening square bracket + static let spaceAroundParens = FormatRule( + help: "Add or remove space around parentheses." + ) { formatter in + func spaceAfter(_ keywordOrAttribute: String, index: Int) -> Bool { + switch keywordOrAttribute { + case "@autoclosure": + if formatter.options.swiftVersion < "3", + let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: index), + formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("escaping") + { + assert(formatter.tokens[nextIndex] == .startOfScope("(")) + return false + } + return true + case "@escaping", "@noescape", "@Sendable": + return true + case _ where keywordOrAttribute.hasPrefix("@"): + if let i = formatter.index(of: .startOfScope("("), after: index) { + return formatter.isParameterList(at: i) + } + return false + case "private", "fileprivate", "internal", + "init", "subscript", "throws": + return false + case "await": + return formatter.options.swiftVersion >= "5.5" || + formatter.options.swiftVersion == .undefined + default: + return keywordOrAttribute.first.map { !"@#".contains($0) } ?? true + } + } + + formatter.forEach(.startOfScope("(")) { i, _ in + let index = i - 1 + guard let prevToken = formatter.token(at: index) else { + return + } + switch prevToken { + case let .keyword(string) where spaceAfter(string, index: index): + fallthrough + case .endOfScope("]") where formatter.isInClosureArguments(at: index), + .endOfScope(")") where formatter.isAttribute(at: index), + .identifier("some") where formatter.isTypePosition(at: index), + .identifier("any") where formatter.isTypePosition(at: index), + .identifier("borrowing") where formatter.isTypePosition(at: index), + .identifier("consuming") where formatter.isTypePosition(at: index), + .identifier("isolated") where formatter.isTypePosition(at: index), + .identifier("sending") where formatter.isTypePosition(at: index): + formatter.insert(.space(" "), at: i) + case .space: + let index = i - 2 + guard let token = formatter.token(at: index) else { + return + } + switch token { + case .identifier("some") where formatter.isTypePosition(at: index), + .identifier("any") where formatter.isTypePosition(at: index), + .identifier("borrowing") where formatter.isTypePosition(at: index), + .identifier("consuming") where formatter.isTypePosition(at: index), + .identifier("isolated") where formatter.isTypePosition(at: index), + .identifier("sending") where formatter.isTypePosition(at: index): + break + case let .keyword(string) where !spaceAfter(string, index: index): + fallthrough + case .number, .identifier: + fallthrough + case .endOfScope("}"), .endOfScope(">"), + .endOfScope("]") where !formatter.isInClosureArguments(at: index), + .endOfScope(")") where !formatter.isAttribute(at: index): + formatter.removeToken(at: i - 1) + default: + break + } + default: + break + } + } + formatter.forEach(.endOfScope(")")) { i, _ in + guard let nextToken = formatter.token(at: i + 1) else { + return + } + switch nextToken { + case .identifier, .keyword, .startOfScope("{"): + formatter.insert(.space(" "), at: i + 1) + case .space where formatter.token(at: i + 2) == .startOfScope("["): + formatter.removeToken(at: i + 1) + default: + break + } + } + } +} diff --git a/Sources/Rules/SpaceInsideBraces.swift b/Sources/Rules/SpaceInsideBraces.swift new file mode 100644 index 000000000..1b60e87ab --- /dev/null +++ b/Sources/Rules/SpaceInsideBraces.swift @@ -0,0 +1,35 @@ +// +// SpaceInsideBraces.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that there is space immediately inside braces + static let spaceInsideBraces = FormatRule( + help: "Add space inside curly braces." + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + if let nextToken = formatter.token(at: i + 1) { + if !nextToken.isSpaceOrLinebreak, + ![.endOfScope("}"), .startOfScope("{")].contains(nextToken) + { + formatter.insert(.space(" "), at: i + 1) + } + } + } + formatter.forEach(.endOfScope("}")) { i, _ in + if let prevToken = formatter.token(at: i - 1) { + if !prevToken.isSpaceOrLinebreak, + ![.endOfScope("}"), .startOfScope("{")].contains(prevToken) + { + formatter.insert(.space(" "), at: i) + } + } + } + } +} diff --git a/Sources/Rules/SpaceInsideBrackets.swift b/Sources/Rules/SpaceInsideBrackets.swift new file mode 100644 index 000000000..f374842b8 --- /dev/null +++ b/Sources/Rules/SpaceInsideBrackets.swift @@ -0,0 +1,31 @@ +// +// SpaceInsideBrackets.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove space immediately inside square brackets + static let spaceInsideBrackets = FormatRule( + help: "Remove space inside square brackets." + ) { formatter in + formatter.forEach(.startOfScope("[")) { i, _ in + if formatter.token(at: i + 1)?.isSpace == true, + formatter.token(at: i + 2)?.isComment == false + { + formatter.removeToken(at: i + 1) + } + } + formatter.forEach(.endOfScope("]")) { i, _ in + if formatter.token(at: i - 1)?.isSpace == true, + formatter.token(at: i - 2)?.isCommentOrLinebreak == false + { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/SpaceInsideComments.swift b/Sources/Rules/SpaceInsideComments.swift new file mode 100644 index 000000000..e1dfa10d7 --- /dev/null +++ b/Sources/Rules/SpaceInsideComments.swift @@ -0,0 +1,56 @@ +// +// SpaceInsideComments.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Add space inside comments, taking care not to mangle headerdoc or + /// carefully preformatted comments, such as star boxes, etc. + static let spaceInsideComments = FormatRule( + help: "Add leading and/or trailing space inside comments." + ) { formatter in + formatter.forEach(.startOfScope("//")) { i, _ in + guard case let .commentBody(string)? = formatter.token(at: i + 1), + let first = string.first else { return } + if "/!:".contains(first) { + let nextIndex = string.index(after: string.startIndex) + if nextIndex < string.endIndex, case let next = string[nextIndex], !" \t/".contains(next) { + let string = String(string.first!) + " " + String(string.dropFirst()) + formatter.replaceToken(at: i + 1, with: .commentBody(string)) + } + } else if !" \t".contains(first), !string.hasPrefix("===") { // Special-case check for swift stdlib codebase + formatter.insert(.space(" "), at: i + 1) + } + } + formatter.forEach(.startOfScope("/*")) { i, _ in + guard case let .commentBody(string)? = formatter.token(at: i + 1), + !string.hasPrefix("---"), !string.hasPrefix("@"), !string.hasSuffix("---"), !string.hasSuffix("@") + else { + return + } + if let first = string.first, "*!:".contains(first) { + let nextIndex = string.index(after: string.startIndex) + if nextIndex < string.endIndex, case let next = string[nextIndex], + !" /t".contains(next), !string.hasPrefix("**"), !string.hasPrefix("*/") + { + let string = String(string.first!) + " " + String(string.dropFirst()) + formatter.replaceToken(at: i + 1, with: .commentBody(string)) + } + } else { + formatter.insert(.space(" "), at: i + 1) + } + if let i = formatter.index(of: .endOfScope("*/"), after: i), let prevToken = formatter.token(at: i - 1) { + if !prevToken.isSpaceOrLinebreak, !prevToken.string.hasSuffix("*"), + !prevToken.string.trimmingCharacters(in: .whitespaces).isEmpty + { + formatter.insert(.space(" "), at: i) + } + } + } + } +} diff --git a/Sources/Rules/SpaceInsideGenerics.swift b/Sources/Rules/SpaceInsideGenerics.swift new file mode 100644 index 000000000..8a496e562 --- /dev/null +++ b/Sources/Rules/SpaceInsideGenerics.swift @@ -0,0 +1,29 @@ +// +// SpaceInsideGenerics.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove space immediately inside chevrons + static let spaceInsideGenerics = FormatRule( + help: "Remove space inside angle brackets." + ) { formatter in + formatter.forEach(.startOfScope("<")) { i, _ in + if formatter.token(at: i + 1)?.isSpace == true { + formatter.removeToken(at: i + 1) + } + } + formatter.forEach(.endOfScope(">")) { i, _ in + if formatter.token(at: i - 1)?.isSpace == true, + formatter.token(at: i - 2)?.isLinebreak == false + { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/SpaceInsideParens.swift b/Sources/Rules/SpaceInsideParens.swift new file mode 100644 index 000000000..7a3ebc452 --- /dev/null +++ b/Sources/Rules/SpaceInsideParens.swift @@ -0,0 +1,31 @@ +// +// SpaceInsideParens.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove space immediately inside parens + static let spaceInsideParens = FormatRule( + help: "Remove space inside parentheses." + ) { formatter in + formatter.forEach(.startOfScope("(")) { i, _ in + if formatter.token(at: i + 1)?.isSpace == true, + formatter.token(at: i + 2)?.isComment == false + { + formatter.removeToken(at: i + 1) + } + } + formatter.forEach(.endOfScope(")")) { i, _ in + if formatter.token(at: i - 1)?.isSpace == true, + formatter.token(at: i - 2)?.isCommentOrLinebreak == false + { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/Specifiers.swift b/Sources/Rules/Specifiers.swift new file mode 100644 index 000000000..7b9e637b7 --- /dev/null +++ b/Sources/Rules/Specifiers.swift @@ -0,0 +1,21 @@ +// +// Specifiers.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Deprecated + static let specifiers = FormatRule( + help: "Use consistent ordering for member modifiers.", + deprecationMessage: "Use modifierOrder instead.", + options: ["modifierorder"] + ) { formatter in + _ = formatter.options.modifierOrder + FormatRule.modifierOrder.apply(with: formatter) + } +} diff --git a/Sources/Rules/StrongOutlets.swift b/Sources/Rules/StrongOutlets.swift new file mode 100644 index 000000000..7f7210d5c --- /dev/null +++ b/Sources/Rules/StrongOutlets.swift @@ -0,0 +1,35 @@ +// +// StrongOutlets.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Strip unnecessary `weak` from @IBOutlet properties (except delegates and datasources) + static let strongOutlets = FormatRule( + help: "Remove `weak` modifier from `@IBOutlet` properties." + ) { formatter in + formatter.forEach(.keyword("@IBOutlet")) { i, _ in + guard let varIndex = formatter.index(of: .keyword("var"), after: i), + let weakIndex = (i ..< varIndex).first(where: { formatter.tokens[$0] == .identifier("weak") }), + case let .identifier(name)? = formatter.next(.identifier, after: varIndex) + else { + return + } + let lowercased = name.lowercased() + if lowercased.hasSuffix("delegate") || lowercased.hasSuffix("datasource") { + return + } + if formatter.tokens[weakIndex + 1].isSpace { + formatter.removeToken(at: weakIndex + 1) + } else if formatter.tokens[weakIndex - 1].isSpace { + formatter.removeToken(at: weakIndex - 1) + } + formatter.removeToken(at: weakIndex) + } + } +} diff --git a/Sources/Rules/StrongifiedSelf.swift b/Sources/Rules/StrongifiedSelf.swift new file mode 100644 index 000000000..99296e656 --- /dev/null +++ b/Sources/Rules/StrongifiedSelf.swift @@ -0,0 +1,28 @@ +// +// StrongifiedSelf.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Removed backticks from `self` when strongifying + static let strongifiedSelf = FormatRule( + help: "Remove backticks around `self` in Optional unwrap expressions." + ) { formatter in + formatter.forEach(.identifier("`self`")) { i, _ in + guard formatter.options.swiftVersion >= "4.2", + let equalIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { + $0 == .operator("=", .infix) + }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: equalIndex) == .identifier("self"), + formatter.isConditionalStatement(at: i) + else { + return + } + formatter.replaceToken(at: i, with: .identifier("self")) + } + } +} diff --git a/Sources/Rules/Todos.swift b/Sources/Rules/Todos.swift new file mode 100644 index 000000000..45a74a20c --- /dev/null +++ b/Sources/Rules/Todos.swift @@ -0,0 +1,69 @@ +// +// Todos.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that TODO, MARK and FIXME comments are followed by a : as required + static let todos = FormatRule( + help: "Use correct formatting for `TODO:`, `MARK:` or `FIXME:` comments." + ) { formatter in + formatter.forEachToken { i, token in + guard case var .commentBody(string) = token else { + return + } + var removedSpace = false + if string.hasPrefix("/"), let scopeStart = formatter.index(of: .startOfScope, before: i, if: { + $0 == .startOfScope("//") + }) { + if let prevLinebreak = formatter.index(of: .linebreak, before: scopeStart), + case .commentBody? = formatter.last(.nonSpace, before: prevLinebreak) + { + return + } + if let nextLinebreak = formatter.index(of: .linebreak, after: i), + case .startOfScope("//")? = formatter.next(.nonSpace, after: nextLinebreak) + { + return + } + removedSpace = true + string = string.replacingOccurrences(of: "^/(\\s+)", with: "", options: .regularExpression) + } + for pair in [ + "todo:": "TODO:", + "todo :": "TODO:", + "fixme:": "FIXME:", + "fixme :": "FIXME:", + "mark:": "MARK:", + "mark :": "MARK:", + "mark-": "MARK: -", + "mark -": "MARK: -", + ] where string.lowercased().hasPrefix(pair.0) { + string = pair.1 + string.dropFirst(pair.0.count) + } + guard let tag = ["TODO", "MARK", "FIXME"].first(where: { string.hasPrefix($0) }) else { + return + } + var suffix = String(string[tag.endIndex ..< string.endIndex]) + if let first = suffix.unicodeScalars.first, !" :".unicodeScalars.contains(first) { + // If not followed by a space or :, don't mess with it as it may be a custom format + return + } + while let first = suffix.unicodeScalars.first, " :".unicodeScalars.contains(first) { + suffix = String(suffix.dropFirst()) + } + if tag == "MARK", suffix.hasPrefix("-"), suffix != "-", !suffix.hasPrefix("- ") { + suffix = "- " + suffix.dropFirst() + } + formatter.replaceToken(at: i, with: .commentBody(tag + ":" + (suffix.isEmpty ? "" : " \(suffix)"))) + if removedSpace { + formatter.insertSpace(" ", at: i) + } + } + } +} diff --git a/Sources/Rules/TrailingClosures.swift b/Sources/Rules/TrailingClosures.swift new file mode 100644 index 000000000..d94a753b8 --- /dev/null +++ b/Sources/Rules/TrailingClosures.swift @@ -0,0 +1,68 @@ +// +// TrailingClosures.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Convert closure arguments to trailing closure syntax where possible + static let trailingClosures = FormatRule( + help: "Use trailing closure syntax where applicable.", + options: ["trailingclosures", "nevertrailing"] + ) { formatter in + let useTrailing = Set([ + "async", "asyncAfter", "sync", "autoreleasepool", + ] + formatter.options.trailingClosures) + + let nonTrailing = Set([ + "performBatchUpdates", + "expect", // Special case to support autoclosure arguments in the Nimble framework + ] + formatter.options.neverTrailing) + + formatter.forEach(.startOfScope("(")) { i, _ in + guard let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), + case let .identifier(name) = prevToken, // TODO: are trailing closures allowed in other cases? + !nonTrailing.contains(name), !formatter.isConditionalStatement(at: i) + else { + return + } + guard let closingIndex = formatter.index(of: .endOfScope(")"), after: i), let closingBraceIndex = + formatter.index(of: .nonSpaceOrComment, before: closingIndex, if: { $0 == .endOfScope("}") }), + let openingBraceIndex = formatter.index(of: .startOfScope("{"), before: closingBraceIndex), + formatter.index(of: .endOfScope("}"), before: openingBraceIndex) == nil + else { + return + } + guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: closingIndex) != .startOfScope("{"), + var startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: openingBraceIndex) + else { + return + } + switch formatter.tokens[startIndex] { + case .delimiter(","), .startOfScope("("): + break + case .delimiter(":"): + guard useTrailing.contains(name) else { + return + } + if let commaIndex = formatter.index(of: .delimiter(","), before: openingBraceIndex) { + startIndex = commaIndex + } else if formatter.index(of: .startOfScope("("), before: openingBraceIndex) == i { + startIndex = i + } else { + return + } + default: + return + } + let wasParen = (startIndex == i) + formatter.removeParen(at: closingIndex) + formatter.replaceTokens(in: startIndex ..< openingBraceIndex, with: + wasParen ? [.space(" ")] : [.endOfScope(")"), .space(" ")]) + } + } +} diff --git a/Sources/Rules/TrailingCommas.swift b/Sources/Rules/TrailingCommas.swift new file mode 100644 index 000000000..5197c2259 --- /dev/null +++ b/Sources/Rules/TrailingCommas.swift @@ -0,0 +1,55 @@ +// +// TrailingCommas.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Ensure that the last item in a multi-line array literal is followed by a comma. + /// This is useful for preventing noise in commits when items are added to end of array. + static let trailingCommas = FormatRule( + help: "Add or remove trailing comma from the last item in a collection literal.", + options: ["commas"] + ) { formatter in + formatter.forEach(.endOfScope("]")) { i, _ in + guard let prevTokenIndex = formatter.index(of: .nonSpaceOrComment, before: i), + let scopeType = formatter.scopeType(at: i) + else { + return + } + switch scopeType { + case .array, .dictionary: + switch formatter.tokens[prevTokenIndex] { + case .linebreak: + guard let prevTokenIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, before: prevTokenIndex + 1 + ) else { + break + } + switch formatter.tokens[prevTokenIndex] { + case .startOfScope("["), .delimiter(":"): + break // do nothing + case .delimiter(","): + if !formatter.options.trailingCommas { + formatter.removeToken(at: prevTokenIndex) + } + default: + if formatter.options.trailingCommas { + formatter.insert(.delimiter(","), at: prevTokenIndex + 1) + } + } + case .delimiter(","): + formatter.removeToken(at: prevTokenIndex) + default: + break + } + default: + return + } + } + } +} diff --git a/Sources/Rules/TrailingSpace.swift b/Sources/Rules/TrailingSpace.swift new file mode 100644 index 000000000..8dbc34a46 --- /dev/null +++ b/Sources/Rules/TrailingSpace.swift @@ -0,0 +1,27 @@ +// +// TrailingSpace.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove trailing space from the end of lines, as it has no semantic + /// meaning and leads to noise in commits. + static let trailingSpace = FormatRule( + help: "Remove trailing space at end of a line.", + orderAfter: [.wrap, .wrapArguments], + options: ["trimwhitespace"] + ) { formatter in + formatter.forEach(.space) { i, _ in + if formatter.token(at: i + 1)?.isLinebreak ?? true, + formatter.options.truncateBlankLines || formatter.token(at: i - 1)?.isLinebreak == false + { + formatter.removeToken(at: i) + } + } + } +} diff --git a/Sources/Rules/TypeSugar.swift b/Sources/Rules/TypeSugar.swift new file mode 100644 index 000000000..aa7d6687d --- /dev/null +++ b/Sources/Rules/TypeSugar.swift @@ -0,0 +1,105 @@ +// +// TypeSugar.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace Array, Dictionary and Optional with [T], [T: U] and T? + static let typeSugar = FormatRule( + help: "Prefer shorthand syntax for Arrays, Dictionaries and Optionals.", + options: ["shortoptionals"] + ) { formatter in + formatter.forEach(.startOfScope("<")) { i, _ in + guard let typeIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), + case let .identifier(identifier) = formatter.tokens[typeIndex], + let endIndex = formatter.index(of: .endOfScope(">"), after: i), + let typeStart = formatter.index(of: .nonSpaceOrLinebreak, in: i + 1 ..< endIndex), + let typeEnd = formatter.lastIndex(of: .nonSpaceOrLinebreak, in: i + 1 ..< endIndex) + else { + return + } + let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex, if: { + $0.isOperator(".") + }) + if let dotIndex = dotIndex, formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: dotIndex, if: { + ![.identifier("self"), .identifier("Type")].contains($0) + }) != nil, identifier != "Optional" { + return + } + // Workaround for https://bugs.swift.org/browse/SR-12856 + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeIndex) != .delimiter(":") || + formatter.currentScope(at: i) == .startOfScope("[") + { + var startIndex = i + if formatter.tokens[typeIndex] == .identifier("Dictionary") { + startIndex = formatter.index(of: .delimiter(","), in: i + 1 ..< endIndex) ?? startIndex + } + if let parenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startIndex, if: { + $0 == .startOfScope("(") + }), let underscoreIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: parenIndex, if: { + $0 == .identifier("_") + }), formatter.next(.nonSpaceOrCommentOrLinebreak, after: underscoreIndex)?.isIdentifier == true { + return + } + } + if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: typeIndex) { + switch prevToken { + case .keyword("struct"), .keyword("class"), .keyword("actor"), + .keyword("enum"), .keyword("protocol"), .keyword("typealias"): + return + default: + break + } + } + switch formatter.tokens[typeIndex] { + case .identifier("Array"): + formatter.replaceTokens(in: typeIndex ... endIndex, with: + [.startOfScope("[")] + formatter.tokens[typeStart ... typeEnd] + [.endOfScope("]")]) + case .identifier("Dictionary"): + guard let commaIndex = formatter.index(of: .delimiter(","), in: typeStart ..< typeEnd) else { + return + } + formatter.replaceToken(at: commaIndex, with: .delimiter(":")) + formatter.replaceTokens(in: typeIndex ... endIndex, with: + [.startOfScope("[")] + formatter.tokens[typeStart ... typeEnd] + [.endOfScope("]")]) + case .identifier("Optional"): + if formatter.options.shortOptionals == .exceptProperties, + let lastKeyword = formatter.lastSignificantKeyword(at: i), + ["var", "let"].contains(lastKeyword) + { + return + } + if formatter.lastSignificantKeyword(at: i) == "case" || + formatter.last(.endOfScope, before: i) == .endOfScope("case") + { + // https://bugs.swift.org/browse/SR-13838 + return + } + var typeTokens = formatter.tokens[typeStart ... typeEnd] + if [.operator("&", .infix), .operator("->", .infix), + .identifier("some"), .identifier("any")].contains(where: typeTokens.contains) + { + typeTokens.insert(.startOfScope("("), at: typeTokens.startIndex) + typeTokens.append(.endOfScope(")")) + } + typeTokens.append(.operator("?", .postfix)) + formatter.replaceTokens(in: typeIndex ... endIndex, with: typeTokens) + default: + return + } + // Drop leading Swift. namespace + if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: typeIndex, if: { + $0.isOperator(".") + }), let swiftTokenIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: dotIndex, if: { + $0 == .identifier("Swift") + }) { + formatter.removeTokens(in: swiftTokenIndex ..< typeIndex) + } + } + } +} diff --git a/Sources/Rules/UnusedArguments.swift b/Sources/Rules/UnusedArguments.swift new file mode 100644 index 000000000..4db129b8e --- /dev/null +++ b/Sources/Rules/UnusedArguments.swift @@ -0,0 +1,307 @@ +// +// UnusedArguments.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Replace unused arguments with an underscore + static let unusedArguments = FormatRule( + help: "Mark unused function arguments with `_`.", + options: ["stripunusedargs"] + ) { formatter in + guard !formatter.options.fragment else { return } + + func removeUsed(from argNames: inout [String], with associatedData: inout [T], + locals: Set = [], in range: CountableRange) + { + var isDeclaration = false + var wasDeclaration = false + var isConditional = false + var isGuard = false + var locals = locals + var tempLocals = Set() + func pushLocals() { + if isDeclaration, isConditional { + for name in tempLocals { + if let index = argNames.firstIndex(of: name), + !locals.contains(name) + { + argNames.remove(at: index) + associatedData.remove(at: index) + } + } + } + wasDeclaration = isDeclaration + isDeclaration = false + locals.formUnion(tempLocals) + tempLocals.removeAll() + } + var i = range.lowerBound + while i < range.upperBound { + if formatter.isStartOfStatement(at: i, treatingCollectionKeysAsStart: false), + // Immediately following an `=` operator, if or switch keywords + // are expressions rather than statements. + formatter.lastToken(before: i, where: { !$0.isSpaceOrCommentOrLinebreak })?.isOperator("=") != true + { + pushLocals() + wasDeclaration = false + } + let token = formatter.tokens[i] + outer: switch token { + case .keyword("guard"): + isGuard = true + case .keyword("let"), .keyword("var"), .keyword("func"), .keyword("for"): + isDeclaration = true + var i = i + while let scopeStart = formatter.index(of: .startOfScope("("), before: i) { + i = scopeStart + } + isConditional = formatter.isConditionalStatement(at: i) + case .identifier: + let name = token.unescaped() + guard let index = argNames.firstIndex(of: name), !locals.contains(name) else { + break + } + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i)?.isOperator(".") == false, + formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":") || + [.startOfScope("("), .startOfScope("[")].contains(formatter.currentScope(at: i) ?? .space("")) + { + if isDeclaration { + switch formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { + case .endOfScope(")")?, .operator("=", .infix)?, + .delimiter(",")? where !isConditional: + tempLocals.insert(name) + break outer + default: + break + } + } + argNames.remove(at: index) + associatedData.remove(at: index) + if argNames.isEmpty { + return + } + } + case .startOfScope("{"): + guard let endIndex = formatter.endOfScope(at: i) else { + return formatter.fatalError("Expected }", at: i) + } + if formatter.isStartOfClosure(at: i) { + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + } else if isGuard { + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + pushLocals() + } else { + let prevLocals = locals + pushLocals() + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + locals = prevLocals + } + + isGuard = false + i = endIndex + case .endOfScope("case"), .endOfScope("default"): + pushLocals() + guard let colonIndex = formatter.index(of: .startOfScope(":"), after: i) else { + return formatter.fatalError("Expected :", at: i) + } + guard let endIndex = formatter.endOfScope(at: colonIndex) else { + return formatter.fatalError("Expected end of case statement", + at: colonIndex) + } + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + i = endIndex - 1 + case .operator("=", .infix), .delimiter(":"), .startOfScope(":"), + .keyword("in"), .keyword("where"): + wasDeclaration = isDeclaration + isDeclaration = false + case .delimiter(","): + if let scope = formatter.currentScope(at: i), [ + .startOfScope("("), .startOfScope("["), .startOfScope("<"), + ].contains(scope) { + break + } + if isConditional { + if isGuard, wasDeclaration { + pushLocals() + } + wasDeclaration = false + } else { + let _wasDeclaration = wasDeclaration + pushLocals() + isDeclaration = _wasDeclaration + } + case .delimiter(";"): + pushLocals() + wasDeclaration = false + default: + break + } + i += 1 + } + } + // Closure arguments + formatter.forEach(.keyword("in")) { i, _ in + var argNames = [String]() + var nameIndexPairs = [(Int, Int)]() + guard let start = formatter.index(of: .startOfScope("{"), before: i) else { + return + } + var index = i - 1 + var argCountStack = [0] + while index > start { + let token = formatter.tokens[index] + switch token { + case .endOfScope("}"): + return + case .endOfScope("]"): + // TODO: handle unused capture list arguments + index = formatter.index(of: .startOfScope("["), before: index) ?? index + case .endOfScope(")"): + argCountStack.append(argNames.count) + case .startOfScope("("): + argCountStack.removeLast() + case .delimiter(","): + argCountStack[argCountStack.count - 1] = argNames.count + case .identifier("async") where + formatter.last(.nonSpaceOrLinebreak, before: index)?.isIdentifier == true: + fallthrough + case .operator("->", .infix), .keyword("throws"): + // Everything after this was part of return value + let count = argCountStack.last ?? 0 + argNames.removeSubrange(count ..< argNames.count) + nameIndexPairs.removeSubrange(count ..< nameIndexPairs.count) + case let .keyword(name) where + !token.isAttribute && !name.hasPrefix("#") && name != "inout": + return + case .identifier: + guard argCountStack.count < 3, + let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: index), [ + .delimiter(","), .startOfScope("("), .startOfScope("{"), .endOfScope("]"), + ].contains(prevToken), let scopeStart = formatter.index(of: .startOfScope, before: index), + ![.startOfScope("["), .startOfScope("<")].contains(formatter.tokens[scopeStart]) + else { + break + } + let name = token.unescaped() + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index), + let nextToken = formatter.token(at: nextIndex), case .identifier = nextToken, + formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .delimiter(":") + { + let internalName = nextToken.unescaped() + if internalName != "_" { + argNames.append(internalName) + nameIndexPairs.append((index, nextIndex)) + } + } else if name != "_" { + argNames.append(name) + nameIndexPairs.append((index, index)) + } + default: + break + } + index -= 1 + } + guard !argNames.isEmpty, let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: i) else { + return + } + removeUsed(from: &argNames, with: &nameIndexPairs, in: i + 1 ..< bodyEndIndex) + for pair in nameIndexPairs { + if case .identifier("_") = formatter.tokens[pair.0], pair.0 != pair.1 { + formatter.removeToken(at: pair.1) + if formatter.tokens[pair.1 - 1] == .space(" ") { + formatter.removeToken(at: pair.1 - 1) + } + } else { + formatter.replaceToken(at: pair.1, with: .identifier("_")) + } + } + } + // Function arguments + formatter.forEachToken { i, token in + guard formatter.options.stripUnusedArguments != .closureOnly, + case let .keyword(keyword) = token, ["func", "init", "subscript"].contains(keyword), + let startIndex = formatter.index(of: .startOfScope("("), after: i), + let endIndex = formatter.index(of: .endOfScope(")"), after: startIndex) else { return } + let isOperator = (keyword == "subscript") || + (keyword == "func" && formatter.next(.nonSpaceOrCommentOrLinebreak, after: i)?.isOperator == true) + var index = startIndex + var argNames = [String]() + var nameIndexPairs = [(Int, Int)]() + while index < endIndex { + guard let externalNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: index, if: { + if case let .identifier(name) = $0 { + return formatter.options.stripUnusedArguments != .unnamedOnly || name == "_" + } + // Probably an empty argument list + return false + }) else { return } + guard let nextIndex = + formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: externalNameIndex) else { return } + let nextToken = formatter.tokens[nextIndex] + switch nextToken { + case let .identifier(name): + if name != "_" { + argNames.append(nextToken.unescaped()) + nameIndexPairs.append((externalNameIndex, nextIndex)) + } + case .delimiter(":"): + let externalNameToken = formatter.tokens[externalNameIndex] + if case let .identifier(name) = externalNameToken, name != "_" { + argNames.append(externalNameToken.unescaped()) + nameIndexPairs.append((externalNameIndex, externalNameIndex)) + } + default: + return + } + index = formatter.index(of: .delimiter(","), after: index) ?? endIndex + } + guard !argNames.isEmpty, let bodyStartIndex = formatter.index(after: endIndex, where: { + switch $0 { + case .startOfScope("{"): // What we're looking for + return true + case .keyword("throws"), + .keyword("rethrows"), + .identifier("async"), + .keyword("where"), + .keyword("is"): + return false // Keep looking + case .keyword: + return true // Not valid between end of arguments and start of body + default: + return false // Keep looking + } + }), formatter.tokens[bodyStartIndex] == .startOfScope("{"), + let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: bodyStartIndex) else { + return + } + removeUsed(from: &argNames, with: &nameIndexPairs, in: bodyStartIndex + 1 ..< bodyEndIndex) + for pair in nameIndexPairs.reversed() { + if pair.0 == pair.1 { + if isOperator { + formatter.replaceToken(at: pair.0, with: .identifier("_")) + } else { + formatter.insert(.identifier("_"), at: pair.0 + 1) + formatter.insert(.space(" "), at: pair.0 + 1) + } + } else if case .identifier("_") = formatter.tokens[pair.0] { + formatter.removeToken(at: pair.1) + if formatter.tokens[pair.1 - 1] == .space(" ") { + formatter.removeToken(at: pair.1 - 1) + } + } else { + formatter.replaceToken(at: pair.1, with: .identifier("_")) + } + } + } + } +} diff --git a/Sources/Rules/UnusedPrivateDeclaration.swift b/Sources/Rules/UnusedPrivateDeclaration.swift new file mode 100644 index 000000000..3482c8656 --- /dev/null +++ b/Sources/Rules/UnusedPrivateDeclaration.swift @@ -0,0 +1,66 @@ +// +// UnusedPrivateDeclaration.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove unused private and fileprivate declarations + static let unusedPrivateDeclaration = FormatRule( + help: "Remove unused private and fileprivate declarations.", + disabledByDefault: true, + options: ["preservedecls"] + ) { formatter in + guard !formatter.options.fragment else { return } + + // Only remove unused properties, functions, or typealiases. + // - This rule doesn't currently support removing unused types, + // and it's more difficult to track the usage of other declaration + // types like `init`, `subscript`, `operator`, etc. + let allowlist = ["let", "var", "func", "typealias"] + let disallowedModifiers = ["override", "@objc", "@IBAction", "@IBSegueAction", "@IBOutlet", "@IBDesignable", "@IBInspectable", "@NSManaged", "@GKInspectable"] + + // Collect all of the `private` or `fileprivate` declarations in the file + var privateDeclarations: [Declaration] = [] + formatter.forEachRecursiveDeclaration { declaration in + let declarationModifiers = Set(declaration.modifiers) + let hasDisallowedModifiers = disallowedModifiers.contains(where: { declarationModifiers.contains($0) }) + + guard allowlist.contains(declaration.keyword), + let name = declaration.name, + !formatter.options.preservedPrivateDeclarations.contains(name), + !hasDisallowedModifiers + else { return } + + switch formatter.visibility(of: declaration) { + case .fileprivate, .private: + privateDeclarations.append(declaration) + case .none, .open, .public, .package, .internal: + break + } + } + + // Count the usage of each identifier in the file + var usage: [String: Int] = [:] + formatter.forEach(.identifier) { _, token in + usage[token.string, default: 0] += 1 + } + + // Remove any private or fileprivate declaration whose name only + // appears a single time in the source file + for declaration in privateDeclarations.reversed() { + // Strip backticks from name for a normalized base name for cases like `default` + guard let name = declaration.name?.trimmingCharacters(in: CharacterSet(charactersIn: "`")) else { continue } + // Check for regular usage, common property wrapper prefixes, and protected names + let variants = [name, "_\(name)", "$\(name)", "`\(name)`"] + let count = variants.compactMap { usage[$0] }.reduce(0, +) + if count <= 1 { + formatter.removeTokens(in: declaration.originalRange) + } + } + } +} diff --git a/Sources/Rules/Void.swift b/Sources/Rules/Void.swift new file mode 100644 index 000000000..7290b8783 --- /dev/null +++ b/Sources/Rules/Void.swift @@ -0,0 +1,138 @@ +// +// Void.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Normalize the use of void in closure arguments and return values + static let void = FormatRule( + help: "Use `Void` for type declarations and `()` for values.", + options: ["voidtype"] + ) { formatter in + let hasLocalVoid = formatter.hasLocalVoid() + + formatter.forEach(.identifier("Void")) { i, _ in + if let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0 == .endOfScope(")") + }), var prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), { + let token = formatter.tokens[prevIndex] + if token == .delimiter(":"), + let prevPrevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex), + formatter.tokens[prevPrevIndex] == .identifier("_"), + let startIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevPrevIndex), + formatter.tokens[startIndex] == .startOfScope("(") + { + prevIndex = startIndex + return true + } + return token == .startOfScope("(") + }() { + if formatter.isArgumentToken(at: nextIndex) || formatter.last( + .nonSpaceOrLinebreak, + before: prevIndex + )?.isIdentifier == true { + if !formatter.options.useVoid, !hasLocalVoid { + // Convert to parens + formatter.replaceToken(at: i, with: .endOfScope(")")) + formatter.insert(.startOfScope("("), at: i) + } + } else if formatter.options.useVoid { + // Strip parens + formatter.removeTokens(in: i + 1 ... nextIndex) + formatter.removeTokens(in: prevIndex ..< i) + } else { + // Remove Void + formatter.removeTokens(in: prevIndex + 1 ..< nextIndex) + } + } else if let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), + [.operator(".", .prefix), .operator(".", .infix), + .keyword("typealias")].contains(prevToken) + { + return + } else if formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) == + .operator(".", .infix) + { + return + } else if formatter.next(.nonSpace, after: i) == .startOfScope("(") { + if !hasLocalVoid { + formatter.removeToken(at: i) + } + } else if !formatter.options.useVoid || formatter.isArgumentToken(at: i), !hasLocalVoid { + // Convert to parens + formatter.replaceToken(at: i, with: [.startOfScope("("), .endOfScope(")")]) + } + } + formatter.forEach(.startOfScope("(")) { i, _ in + guard formatter.options.useVoid else { + return + } + guard let endIndex = formatter.index(of: .nonSpaceOrLinebreak, after: i, if: { + $0 == .endOfScope(")") + }), let prevToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: i), + !formatter.isArgumentToken(at: endIndex) else { + return + } + if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator("->", .infix) { + if !hasLocalVoid { + formatter.replaceTokens(in: i ... endIndex, with: .identifier("Void")) + } + } else if prevToken == .startOfScope("<") || + (prevToken == .delimiter(",") && formatter.currentScope(at: i) == .startOfScope("<")), + !hasLocalVoid + { + formatter.replaceTokens(in: i ... endIndex, with: .identifier("Void")) + } + // TODO: other cases + } + } +} + +private extension Formatter { + func isArgumentToken(at index: Int) -> Bool { + guard let nextToken = next(.nonSpaceOrCommentOrLinebreak, after: index) else { + return false + } + switch nextToken { + case .operator("->", .infix), .keyword("throws"), .keyword("rethrows"), .identifier("async"): + return true + case .startOfScope("{"): + if tokens[index] == .endOfScope(")"), + let index = self.index(of: .startOfScope("("), before: index), + let nameIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, before: index, if: { + $0.isIdentifier + }), last(.nonSpaceOrCommentOrLinebreak, before: nameIndex) == .keyword("func") + { + return true + } + return false + case .keyword("in"): + if tokens[index] == .endOfScope(")"), + let index = self.index(of: .startOfScope("("), before: index) + { + return last(.nonSpaceOrCommentOrLinebreak, before: index) == .startOfScope("{") + } + return false + default: + return false + } + } + + func hasLocalVoid() -> Bool { + for (i, token) in tokens.enumerated() where token == .identifier("Void") { + if let prevToken = last(.nonSpaceOrCommentOrLinebreak, before: i) { + switch prevToken { + case .keyword("typealias"), .keyword("struct"), .keyword("class"), .keyword("enum"): + return true + default: + break + } + } + } + return false + } +} diff --git a/Sources/Rules/Wrap.swift b/Sources/Rules/Wrap.swift new file mode 100644 index 000000000..4f431b8d9 --- /dev/null +++ b/Sources/Rules/Wrap.swift @@ -0,0 +1,68 @@ +// +// Wrap.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrap = FormatRule( + help: "Wrap lines that exceed the specified maximum width.", + options: ["maxwidth", "nowrapoperators", "assetliterals", "wrapternary"], + sharedOptions: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", "indent", + "trimwhitespace", "linebreaks", "tabwidth", "maxwidth", "smarttabs", "wrapreturntype", + "wrapconditions", "wraptypealiases", "wrapternary", "wrapeffects", "conditionswrap"] + ) { formatter in + let maxWidth = formatter.options.maxWidth + guard maxWidth > 0 else { return } + + // Wrap collections first to avoid conflict + formatter.wrapCollectionsAndArguments(completePartialWrapping: false, + wrapSingleArguments: false) + + // Wrap other line types + var currentIndex = 0 + var indent = "" + var alreadyLinewrapped = false + + func isLinewrapToken(_ token: Token?) -> Bool { + switch token { + case .delimiter?, .operator(_, .infix)?: + return true + default: + return false + } + } + + formatter.forEachToken(onlyWhereEnabled: false) { i, token in + if i < currentIndex { + return + } + if token.isLinebreak { + indent = formatter.currentIndentForLine(at: i + 1) + alreadyLinewrapped = isLinewrapToken(formatter.last(.nonSpaceOrComment, before: i)) + currentIndex = i + 1 + } else if let breakPoint = formatter.indexWhereLineShouldWrapInLine(at: i) { + if !alreadyLinewrapped { + indent += formatter.linewrapIndent(at: breakPoint) + } + alreadyLinewrapped = true + if formatter.isEnabled { + let spaceAdded = formatter.insertSpace(indent, at: breakPoint + 1) + formatter.insertLinebreak(at: breakPoint + 1) + currentIndex = breakPoint + spaceAdded + 2 + } else { + currentIndex = breakPoint + 1 + } + } else { + currentIndex = formatter.endOfLine(at: i) + } + } + + formatter.wrapCollectionsAndArguments(completePartialWrapping: true, + wrapSingleArguments: true) + } +} diff --git a/Sources/Rules/WrapArguments.swift b/Sources/Rules/WrapArguments.swift new file mode 100644 index 000000000..05bfafb37 --- /dev/null +++ b/Sources/Rules/WrapArguments.swift @@ -0,0 +1,24 @@ +// +// WrapArguments.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Normalize argument wrapping style + static let wrapArguments = FormatRule( + help: "Align wrapped function arguments or collection elements.", + orderAfter: [.wrap], + options: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", + "wrapreturntype", "wrapconditions", "wraptypealiases", "wrapeffects", "conditionswrap"], + sharedOptions: ["indent", "trimwhitespace", "linebreaks", + "tabwidth", "maxwidth", "smarttabs", "assetliterals", "wrapternary"] + ) { formatter in + formatter.wrapCollectionsAndArguments(completePartialWrapping: true, + wrapSingleArguments: false) + } +} diff --git a/Sources/Rules/WrapAttributes.swift b/Sources/Rules/WrapAttributes.swift new file mode 100644 index 000000000..3a0c8451f --- /dev/null +++ b/Sources/Rules/WrapAttributes.swift @@ -0,0 +1,113 @@ +// +// WrapAttributes.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapAttributes = FormatRule( + help: "Wrap @attributes onto a separate line, or keep them on the same line.", + options: ["funcattributes", "typeattributes", "varattributes", "storedvarattrs", "computedvarattrs", "complexattrs", "noncomplexattrs"], + sharedOptions: ["linebreaks", "maxwidth"] + ) { formatter in + formatter.forEach(.attribute) { i, _ in + // Ignore sequential attributes + guard let endIndex = formatter.endOfAttribute(at: i), + var keywordIndex = formatter.index( + of: .nonSpaceOrCommentOrLinebreak, + after: endIndex, if: { $0.isKeyword || $0.isModifierKeyword } + ) + else { + return + } + + // Skip modifiers + while formatter.isModifier(at: keywordIndex), + let nextIndex = formatter.index(of: .keyword, after: keywordIndex) + { + keywordIndex = nextIndex + } + + // Check which `AttributeMode` option to use + var attributeMode: AttributeMode + switch formatter.tokens[keywordIndex].string { + case "func", "init", "subscript": + attributeMode = formatter.options.funcAttributes + case "class", "actor", "struct", "enum", "protocol", "extension": + attributeMode = formatter.options.typeAttributes + case "var", "let": + let storedOrComputedAttributeMode: AttributeMode + if formatter.isStoredProperty(atIntroducerIndex: keywordIndex) { + storedOrComputedAttributeMode = formatter.options.storedVarAttributes + } else { + storedOrComputedAttributeMode = formatter.options.computedVarAttributes + } + + // If the relevant `storedvarattrs` or `computedvarattrs` option hasn't been configured, + // fall back to the previous (now deprecated) `varattributes` option. + if storedOrComputedAttributeMode == .preserve { + attributeMode = formatter.options.varAttributes + } else { + attributeMode = storedOrComputedAttributeMode + } + default: + return + } + + // If the complexAttributes option is configured, it takes precedence over other options + // if this is a complex attributes with arguments. + let attributeName = formatter.tokens[i].string + let isComplexAttribute = formatter.isComplexAttribute(at: i) + && !formatter.options.complexAttributesExceptions.contains(attributeName) + + if isComplexAttribute, formatter.options.complexAttributes != .preserve { + attributeMode = formatter.options.complexAttributes + } + + // Apply the `AttributeMode` + switch attributeMode { + case .preserve: + return + case .prevLine: + // Make sure there's a newline immediately following the attribute + if let nextIndex = formatter.index(of: .nonSpaceOrComment, after: endIndex), + formatter.token(at: nextIndex)?.isLinebreak != true + { + formatter.insertSpace(formatter.currentIndentForLine(at: i), at: nextIndex) + formatter.insertLinebreak(at: nextIndex) + // Remove any trailing whitespace left on the line with the attributes + if let prevToken = formatter.token(at: nextIndex - 1), prevToken.isSpace { + formatter.removeToken(at: nextIndex - 1) + } + } + case .sameLine: + // Make sure there isn't a newline immediately following the attribute + if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), + formatter.tokens[(endIndex + 1) ..< nextIndex].contains(where: { $0.isLinebreak }) + { + // If unwrapping the attribute causes the line to exceed the max width, + // leave it as-is. The existing formatting is likely better than how + // this would be re-unwrapped by the wrap rule. + let startOfLine = formatter.startOfLine(at: i) + let endOfLine = formatter.endOfLine(at: i) + let startOfNextLine = formatter.startOfLine(at: nextIndex, excludingIndent: true) + let endOfNextLine = formatter.endOfLine(at: nextIndex) + let combinedLine = formatter.tokens[startOfLine ... endOfLine].map { $0.string }.joined() + + formatter.tokens[startOfNextLine ..< endOfNextLine].map { $0.string }.joined() + + if formatter.options.maxWidth > 0, combinedLine.count > formatter.options.maxWidth { + return + } + + // Replace the newline with a space so the attribute doesn't + // merge with the next token. + formatter.replaceTokens(in: (endIndex + 1) ..< nextIndex, with: .space(" ")) + } + } + } + } +} diff --git a/Sources/Rules/WrapConditionalBodies.swift b/Sources/Rules/WrapConditionalBodies.swift new file mode 100644 index 000000000..d69fcb6b1 --- /dev/null +++ b/Sources/Rules/WrapConditionalBodies.swift @@ -0,0 +1,24 @@ +// +// WrapConditionalBodies.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapConditionalBodies = FormatRule( + help: "Wrap the bodies of inline conditional statements onto a new line.", + disabledByDefault: true, + sharedOptions: ["linebreaks", "indent"] + ) { formatter in + formatter.forEachToken(where: { [.keyword("if"), .keyword("else")].contains($0) }) { i, _ in + guard let startIndex = formatter.index(of: .startOfScope("{"), after: i) else { + return formatter.fatalError("Expected {", at: i) + } + formatter.wrapStatementBody(at: startIndex) + } + } +} diff --git a/Sources/Rules/WrapEnumCases.swift b/Sources/Rules/WrapEnumCases.swift new file mode 100644 index 000000000..ef330f5ff --- /dev/null +++ b/Sources/Rules/WrapEnumCases.swift @@ -0,0 +1,70 @@ +// +// WrapEnumCases.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Formats enum cases declaration into one case per line + static let wrapEnumCases = FormatRule( + help: "Rewrite comma-delimited enum cases to one case per line.", + disabledByDefault: true, + options: ["wrapenumcases"], + sharedOptions: ["linebreaks"] + ) { formatter in + + func shouldWrapCaseRangeGroup(_ caseRangeGroup: [Formatter.EnumCaseRange]) -> Bool { + guard let firstIndex = caseRangeGroup.first?.value.lowerBound, + let scopeStart = formatter.startOfScope(at: firstIndex), + formatter.tokens[scopeStart ..< firstIndex].contains(where: { $0.isLinebreak }) + else { + // Don't wrap if first case is on same line as opening `{` + return false + } + return formatter.options.wrapEnumCases == .always || caseRangeGroup.contains(where: { + formatter.tokens[$0.value].contains(where: { + [.startOfScope("("), .operator("=", .infix)].contains($0) + }) + }) + } + + formatter.parseEnumCaseRanges() + .filter(shouldWrapCaseRangeGroup) + .flatMap { $0 } + .filter { $0.endOfCaseRangeToken == .delimiter(",") } + .reversed() + .forEach { enumCase in + guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: enumCase.value.upperBound) else { + return + } + let caseIndex = formatter.lastIndex(of: .keyword("case"), in: 0 ..< enumCase.value.lowerBound) + let indent = formatter.currentIndentForLine(at: caseIndex ?? enumCase.value.lowerBound) + + if formatter.tokens[nextNonSpaceIndex] == .startOfScope("//") { + formatter.removeToken(at: enumCase.value.upperBound) + if formatter.token(at: enumCase.value.upperBound)?.isSpace == true, + formatter.token(at: enumCase.value.upperBound - 1)?.isSpace == true + { + formatter.removeToken(at: enumCase.value.upperBound - 1) + } + nextNonSpaceIndex = formatter.index(of: .linebreak, after: enumCase.value.upperBound) ?? nextNonSpaceIndex + } else { + formatter.removeTokens(in: enumCase.value.upperBound ..< nextNonSpaceIndex) + nextNonSpaceIndex = enumCase.value.upperBound + } + + if !formatter.tokens[nextNonSpaceIndex].isLinebreak { + formatter.insertLinebreak(at: nextNonSpaceIndex) + } + + let offset = indent.isEmpty ? 0 : 1 + formatter.insertSpace(indent, at: nextNonSpaceIndex + 1) + formatter.insert([.keyword("case")], at: nextNonSpaceIndex + 1 + offset) + formatter.insertSpace(" ", at: nextNonSpaceIndex + 2 + offset) + } + } +} diff --git a/Sources/Rules/WrapLoopBodies.swift b/Sources/Rules/WrapLoopBodies.swift new file mode 100644 index 000000000..42119dbc7 --- /dev/null +++ b/Sources/Rules/WrapLoopBodies.swift @@ -0,0 +1,29 @@ +// +// WrapLoopBodies.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapLoopBodies = FormatRule( + help: "Wrap the bodies of inline loop statements onto a new line.", + orderAfter: [.preferForLoop], + sharedOptions: ["linebreaks", "indent"] + ) { formatter in + formatter.forEachToken(where: { [ + .keyword("for"), + .keyword("while"), + .keyword("repeat"), + ].contains($0) }) { i, token in + if let startIndex = formatter.index(of: .startOfScope("{"), after: i) { + formatter.wrapStatementBody(at: startIndex) + } else if token == .keyword("for") { + return formatter.fatalError("Expected {", at: i) + } + } + } +} diff --git a/Sources/Rules/WrapMultilineConditionalAssignment.swift b/Sources/Rules/WrapMultilineConditionalAssignment.swift new file mode 100644 index 000000000..6459462c9 --- /dev/null +++ b/Sources/Rules/WrapMultilineConditionalAssignment.swift @@ -0,0 +1,60 @@ +// +// WrapMultilineConditionalAssignment.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapMultilineConditionalAssignment = FormatRule( + help: "Wrap multiline conditional assignment expressions after the assignment operator.", + disabledByDefault: true, + orderAfter: [.conditionalAssignment], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.keyword) { startOfCondition, keywordToken in + guard [.keyword("if"), .keyword("switch")].contains(keywordToken), + let assignmentIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startOfCondition), + formatter.tokens[assignmentIndex] == .operator("=", .infix), + let endOfPropertyDefinition = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: assignmentIndex) + else { return } + + // Verify the RHS of the assignment is an if/switch expression + guard let startOfConditionalExpression = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: assignmentIndex), + ["if", "switch"].contains(formatter.tokens[startOfConditionalExpression].string), + let conditionalBranches = formatter.conditionalBranches(at: startOfConditionalExpression), + let lastBranch = conditionalBranches.last + else { return } + + // If the entire expression is on a single line, we leave the formatting as-is + guard !formatter.onSameLine(startOfConditionalExpression, lastBranch.endOfBranch) else { + return + } + + // The `=` should be on the same line as the rest of the property + if !formatter.onSameLine(endOfPropertyDefinition, assignmentIndex), + formatter.last(.nonSpaceOrComment, before: assignmentIndex)?.isLinebreak == true, + let previousToken = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: assignmentIndex), + formatter.onSameLine(endOfPropertyDefinition, previousToken) + { + // Move the assignment operator to follow the previous token. + // Also remove any trailing space after the previous position + // of the assignment operator. + if formatter.tokens[assignmentIndex + 1].isSpaceOrLinebreak { + formatter.removeToken(at: assignmentIndex + 1) + } + + formatter.removeToken(at: assignmentIndex) + formatter.insert([.space(" "), .operator("=", .infix)], at: previousToken + 1) + } + + // And there should be a line break between the `=` and the `if` / `switch` keyword + else if !formatter.tokens[(assignmentIndex + 1) ..< startOfConditionalExpression].contains(where: \.isLinebreak) { + formatter.insertLinebreak(at: startOfConditionalExpression - 1) + } + } + } +} diff --git a/Sources/Rules/WrapMultilineStatementBraces.swift b/Sources/Rules/WrapMultilineStatementBraces.swift new file mode 100644 index 000000000..928ced2bd --- /dev/null +++ b/Sources/Rules/WrapMultilineStatementBraces.swift @@ -0,0 +1,35 @@ +// +// WrapMultilineStatementBraces.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + static let wrapMultilineStatementBraces = FormatRule( + help: "Wrap the opening brace of multiline statements.", + orderAfter: [.braces, .indent, .wrapArguments], + sharedOptions: ["linebreaks"] + ) { formatter in + formatter.forEach(.startOfScope("{")) { i, _ in + guard formatter.last(.nonSpaceOrComment, before: i)?.isLinebreak == false, + formatter.shouldWrapMultilineStatementBrace(at: i), + let endIndex = formatter.endOfScope(at: i) + else { + return + } + let indent = formatter.currentIndentForLine(at: endIndex) + // Insert linebreak + formatter.insertLinebreak(at: i) + // Align the opening brace with closing brace + formatter.insertSpace(indent, at: i + 1) + // Clean up trailing space on the previous line + if case .space? = formatter.token(at: i - 1) { + formatter.removeToken(at: i - 1) + } + } + } +} diff --git a/Sources/Rules/WrapSingleLineComments.swift b/Sources/Rules/WrapSingleLineComments.swift new file mode 100644 index 000000000..6e8314f2c --- /dev/null +++ b/Sources/Rules/WrapSingleLineComments.swift @@ -0,0 +1,65 @@ +// +// WrapSingleLineComments.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Wrap single-line comments that exceed given `FormatOptions.maxWidth` setting. + static let wrapSingleLineComments = FormatRule( + help: "Wrap single line `//` comments that exceed the specified `--maxwidth`.", + sharedOptions: ["maxwidth", "indent", "tabwidth", "assetliterals", "linebreaks"] + ) { formatter in + let delimiterLength = "//".count + var maxWidth = formatter.options.maxWidth + guard maxWidth > 3 else { + return + } + + formatter.forEach(.startOfScope("//")) { i, _ in + let startOfLine = formatter.startOfLine(at: i) + let endOfLine = formatter.endOfLine(at: i) + guard formatter.lineLength(from: startOfLine, upTo: endOfLine) > maxWidth else { + return + } + + guard let startIndex = formatter.index(of: .nonSpace, after: i), + case var .commentBody(comment) = formatter.tokens[startIndex], + !comment.isCommentDirective + else { + return + } + + var words = comment.components(separatedBy: " ") + comment = words.removeFirst() + let commentPrefix = comment == "/" ? "/ " : comment.hasPrefix("/") ? "/" : "" + let prefixLength = formatter.lineLength(upTo: startIndex) + var length = prefixLength + comment.count + while length <= maxWidth, let next = words.first, + length + next.count < maxWidth || + // Don't wrap if next word won't fit on a line by itself anyway + prefixLength + commentPrefix.count + next.count > maxWidth + { + comment += " \(next)" + length += next.count + 1 + words.removeFirst() + } + if words.isEmpty || comment == commentPrefix { + return + } + var prefix = formatter.tokens[i ..< startIndex] + if let token = formatter.token(at: startOfLine), case .space = token { + prefix.insert(token, at: prefix.startIndex) + } + formatter.replaceTokens(in: startIndex ..< endOfLine, with: [ + .commentBody(comment), formatter.linebreakToken(for: startIndex), + ] + prefix + [ + .commentBody(commentPrefix + words.joined(separator: " ")), + ]) + } + } +} diff --git a/Sources/Rules/WrapSwitchCases.swift b/Sources/Rules/WrapSwitchCases.swift new file mode 100644 index 000000000..f9703dfc9 --- /dev/null +++ b/Sources/Rules/WrapSwitchCases.swift @@ -0,0 +1,36 @@ +// +// WrapSwitchCases.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Writes one switch case per line + static let wrapSwitchCases = FormatRule( + help: "Wrap comma-delimited switch cases onto multiple lines.", + disabledByDefault: true, + sharedOptions: ["linebreaks", "tabwidth", "indent", "smarttabs"] + ) { formatter in + formatter.forEach(.endOfScope("case")) { i, _ in + guard var endIndex = formatter.index(of: .startOfScope(":"), after: i) else { return } + let lineStart = formatter.startOfLine(at: i) + let indent = formatter.spaceEquivalentToTokens(from: lineStart, upTo: i + 2) + + var startIndex = i + while let commaIndex = formatter.index(of: .delimiter(","), in: startIndex + 1 ..< endIndex), + let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: commaIndex) + { + if formatter.index(of: .linebreak, in: commaIndex ..< nextIndex) == nil { + formatter.insertLinebreak(at: commaIndex + 1) + let delta = formatter.insertSpace(indent, at: commaIndex + 2) + endIndex += 1 + delta + } + startIndex = commaIndex + } + } + } +} diff --git a/Sources/Rules/YodaConditions.swift b/Sources/Rules/YodaConditions.swift new file mode 100644 index 000000000..46b6c8b27 --- /dev/null +++ b/Sources/Rules/YodaConditions.swift @@ -0,0 +1,139 @@ +// +// YodaConditions.swift +// SwiftFormat +// +// Created by Cal Stephens on 7/28/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Reorders "yoda conditions" where constant is placed on lhs of a comparison + static let yodaConditions = FormatRule( + help: "Prefer constant values to be on the right-hand-side of expressions.", + options: ["yodaswap"] + ) { formatter in + func valuesInRangeAreConstant(_ range: CountableRange) -> Bool { + var index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: range) + while var i = index { + switch formatter.tokens[i] { + case .startOfScope where isConstant(at: i): + guard let endIndex = formatter.index(of: .endOfScope, after: i) else { + return false + } + i = endIndex + fallthrough + case _ where isConstant(at: i), .delimiter(","), .delimiter(":"): + index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< range.upperBound) + case .identifier: + guard let nextIndex = + formatter.index(of: .nonSpaceOrComment, in: i + 1 ..< range.upperBound), + formatter.tokens[nextIndex] == .delimiter(":") + else { + return false + } + // Identifier is a label + index = nextIndex + default: + return false + } + } + return true + } + func isConstant(at index: Int) -> Bool { + var index = index + while case .operator(_, .postfix) = formatter.tokens[index] { + index -= 1 + } + guard let token = formatter.token(at: index) else { + return false + } + switch token { + case .number, .identifier("true"), .identifier("false"), .identifier("nil"): + return true + case .endOfScope("]"), .endOfScope(")"): + guard let startIndex = formatter.index(of: .startOfScope, before: index), + !formatter.isSubscriptOrFunctionCall(at: startIndex) + else { + return false + } + return valuesInRangeAreConstant(startIndex + 1 ..< index) + case .startOfScope("["), .startOfScope("("): + guard !formatter.isSubscriptOrFunctionCall(at: index), + let endIndex = formatter.index(of: .endOfScope, after: index) + else { + return false + } + return valuesInRangeAreConstant(index + 1 ..< endIndex) + case .startOfScope, .endOfScope: + // TODO: what if string contains interpolation? + return token.isStringDelimiter + case _ where formatter.options.yodaSwap == .literalsOnly: + // Don't treat .members as constant + return false + case .operator(".", .prefix) where formatter.token(at: index + 1)?.isIdentifier == true, + .identifier where formatter.token(at: index - 1) == .operator(".", .prefix) && + formatter.token(at: index - 2) != .operator("\\", .prefix): + return true + default: + return false + } + } + func isOperator(at index: Int?) -> Bool { + guard let index = index else { + return false + } + switch formatter.tokens[index] { + // Discount operators with higher precedence than == + case .operator("=", .infix), + .operator("&&", .infix), .operator("||", .infix), + .operator("?", .infix), .operator(":", .infix): + return false + case .operator(_, .infix), .keyword("as"), .keyword("is"): + return true + default: + return false + } + } + func startOfValue(at index: Int) -> Int? { + var index = index + while case .operator(_, .postfix)? = formatter.token(at: index) { + index -= 1 + } + if case .endOfScope? = formatter.token(at: index) { + guard let i = formatter.index(of: .startOfScope, before: index) else { + return nil + } + index = i + } + while case .operator(_, .prefix)? = formatter.token(at: index - 1) { + index -= 1 + } + return index + } + + formatter.forEachToken { i, token in + guard case let .operator(op, .infix) = token, + let opIndex = ["==", "!=", "<", "<=", ">", ">="].firstIndex(of: op), + let prevIndex = formatter.index(of: .nonSpace, before: i), + isConstant(at: prevIndex), let startIndex = startOfValue(at: prevIndex), + !isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex)), + let nextIndex = formatter.index(of: .nonSpace, after: i), !isConstant(at: nextIndex) || + isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nextIndex)), + let endIndex = formatter.endOfExpression(at: nextIndex, upTo: [ + .operator("&&", .infix), .operator("||", .infix), + .operator("?", .infix), .operator(":", .infix), + ]) + else { + return + } + let inverseOp = ["==", "!=", ">", ">=", "<", "<="][opIndex] + let expression = Array(formatter.tokens[nextIndex ... endIndex]) + let constant = Array(formatter.tokens[startIndex ... prevIndex]) + formatter.replaceTokens(in: nextIndex ... endIndex, with: constant) + formatter.replaceToken(at: i, with: .operator(inverseOp, .infix)) + formatter.replaceTokens(in: startIndex ... prevIndex, with: expression) + } + } +} diff --git a/Sources/SwiftFormat.swift b/Sources/SwiftFormat.swift index 227083bf6..046dcc20a 100644 --- a/Sources/SwiftFormat.swift +++ b/Sources/SwiftFormat.swift @@ -180,7 +180,7 @@ public func enumerateFiles(withInputURL inputURL: URL, let fileOptions = options.fileOptions ?? .default if resourceValues.isRegularFile == true { if fileOptions.supportedFileExtensions.contains(inputURL.pathExtension) { - let fileHeaderRuleEnabled = options.rules?.contains(FormatRules.fileHeader.name) ?? false + let fileHeaderRuleEnabled = options.rules?.contains(FormatRule.fileHeader.name) ?? false let shouldGetGitInfo = fileHeaderRuleEnabled && options.formatOptions?.fileHeader.needsGitInfo == true @@ -499,7 +499,7 @@ private func applyRules( inferFormatOptions(sharedOptions, from: tokens, into: &options) // Check if required FileInfo is available - if rules.contains(FormatRules.fileHeader) { + if rules.contains(.fileHeader) { let header = options.fileHeader let fileInfo = options.fileInfo diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index 65bb1279c..3ab24f64d 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -36,12 +36,12 @@ 01A0EAA81D5DB4CF00A0A8E3 /* SwiftFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 01A0EAA71D5DB4CF00A0A8E3 /* SwiftFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; 01A0EAAF1D5DB4D000A0A8E3 /* SwiftFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01A0EAA41D5DB4CF00A0A8E3 /* SwiftFormat.framework */; }; 01A0EAB41D5DB4D000A0A8E3 /* RulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAB31D5DB4D000A0A8E3 /* RulesTests.swift */; }; - 01A0EAC11D5DB4F700A0A8E3 /* Rules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */; }; + 01A0EAC11D5DB4F700A0A8E3 /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; 01A0EAC21D5DB4F700A0A8E3 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; 01A0EAC51D5DB54A00A0A8E3 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; 01A0EACD1D5DB5F500A0A8E3 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EACC1D5DB5F500A0A8E3 /* main.swift */; }; 01A0EAD41D5DC08A00A0A8E3 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; - 01A0EAD51D5DC08A00A0A8E3 /* Rules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */; }; + 01A0EAD51D5DC08A00A0A8E3 /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; 01A0EAD61D5DC08A00A0A8E3 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; 01A8320724EC7F7600A9D0EB /* FormattingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */; }; 01A8320824EC7F7700A9D0EB /* FormattingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */; }; @@ -84,13 +84,457 @@ 08180DD02C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; 08180DD12C4EB68000FD60FF /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; 2E230CA22C4C1C0700A16E2E /* DeclarationHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */; }; + 2E2BAB8C2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */; }; + 2E2BAB8D2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */; }; + 2E2BAB8E2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */; }; + 2E2BAB8F2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */; }; + 2E2BABFF2C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */; }; + 2E2BAC002C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */; }; + 2E2BAC012C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */; }; + 2E2BAC022C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */; }; + 2E2BAC032C57F6DD00590239 /* RedundantBreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */; }; + 2E2BAC042C57F6DD00590239 /* RedundantBreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */; }; + 2E2BAC052C57F6DD00590239 /* RedundantBreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */; }; + 2E2BAC062C57F6DD00590239 /* RedundantBreak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */; }; + 2E2BAC072C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */; }; + 2E2BAC082C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */; }; + 2E2BAC092C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */; }; + 2E2BAC0A2C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */; }; + 2E2BAC0B2C57F6DD00590239 /* Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB942C57F6DD00590239 /* Indent.swift */; }; + 2E2BAC0C2C57F6DD00590239 /* Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB942C57F6DD00590239 /* Indent.swift */; }; + 2E2BAC0D2C57F6DD00590239 /* Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB942C57F6DD00590239 /* Indent.swift */; }; + 2E2BAC0E2C57F6DD00590239 /* Indent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB942C57F6DD00590239 /* Indent.swift */; }; + 2E2BAC0F2C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */; }; + 2E2BAC102C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */; }; + 2E2BAC112C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */; }; + 2E2BAC122C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */; }; + 2E2BAC132C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */; }; + 2E2BAC142C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */; }; + 2E2BAC152C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */; }; + 2E2BAC162C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */; }; + 2E2BAC172C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */; }; + 2E2BAC182C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */; }; + 2E2BAC192C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */; }; + 2E2BAC1A2C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */; }; + 2E2BAC1B2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */; }; + 2E2BAC1C2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */; }; + 2E2BAC1D2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */; }; + 2E2BAC1E2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */; }; + 2E2BAC1F2C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */; }; + 2E2BAC202C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */; }; + 2E2BAC212C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */; }; + 2E2BAC222C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */; }; + 2E2BAC232C57F6DD00590239 /* RedundantInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */; }; + 2E2BAC242C57F6DD00590239 /* RedundantInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */; }; + 2E2BAC252C57F6DD00590239 /* RedundantInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */; }; + 2E2BAC262C57F6DD00590239 /* RedundantInternal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */; }; + 2E2BAC272C57F6DD00590239 /* RedundantNilInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */; }; + 2E2BAC282C57F6DD00590239 /* RedundantNilInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */; }; + 2E2BAC292C57F6DD00590239 /* RedundantNilInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */; }; + 2E2BAC2A2C57F6DD00590239 /* RedundantNilInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */; }; + 2E2BAC2B2C57F6DD00590239 /* Todos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9C2C57F6DD00590239 /* Todos.swift */; }; + 2E2BAC2C2C57F6DD00590239 /* Todos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9C2C57F6DD00590239 /* Todos.swift */; }; + 2E2BAC2D2C57F6DD00590239 /* Todos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9C2C57F6DD00590239 /* Todos.swift */; }; + 2E2BAC2E2C57F6DD00590239 /* Todos.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9C2C57F6DD00590239 /* Todos.swift */; }; + 2E2BAC2F2C57F6DD00590239 /* SpaceInsideParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */; }; + 2E2BAC302C57F6DD00590239 /* SpaceInsideParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */; }; + 2E2BAC312C57F6DD00590239 /* SpaceInsideParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */; }; + 2E2BAC322C57F6DD00590239 /* SpaceInsideParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */; }; + 2E2BAC332C57F6DD00590239 /* Semicolons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */; }; + 2E2BAC342C57F6DD00590239 /* Semicolons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */; }; + 2E2BAC352C57F6DD00590239 /* Semicolons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */; }; + 2E2BAC362C57F6DD00590239 /* Semicolons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */; }; + 2E2BAC372C57F6DD00590239 /* HoistPatternLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */; }; + 2E2BAC382C57F6DD00590239 /* HoistPatternLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */; }; + 2E2BAC392C57F6DD00590239 /* HoistPatternLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */; }; + 2E2BAC3A2C57F6DD00590239 /* HoistPatternLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */; }; + 2E2BAC3B2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */; }; + 2E2BAC3C2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */; }; + 2E2BAC3D2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */; }; + 2E2BAC3E2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */; }; + 2E2BAC3F2C57F6DD00590239 /* DuplicateImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */; }; + 2E2BAC402C57F6DD00590239 /* DuplicateImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */; }; + 2E2BAC412C57F6DD00590239 /* DuplicateImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */; }; + 2E2BAC422C57F6DD00590239 /* DuplicateImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */; }; + 2E2BAC432C57F6DD00590239 /* RedundantGet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */; }; + 2E2BAC442C57F6DD00590239 /* RedundantGet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */; }; + 2E2BAC452C57F6DD00590239 /* RedundantGet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */; }; + 2E2BAC462C57F6DD00590239 /* RedundantGet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */; }; + 2E2BAC472C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */; }; + 2E2BAC482C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */; }; + 2E2BAC492C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */; }; + 2E2BAC4A2C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */; }; + 2E2BAC4B2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */; }; + 2E2BAC4C2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */; }; + 2E2BAC4D2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */; }; + 2E2BAC4E2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */; }; + 2E2BAC4F2C57F6DD00590239 /* SortImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA52C57F6DD00590239 /* SortImports.swift */; }; + 2E2BAC502C57F6DD00590239 /* SortImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA52C57F6DD00590239 /* SortImports.swift */; }; + 2E2BAC512C57F6DD00590239 /* SortImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA52C57F6DD00590239 /* SortImports.swift */; }; + 2E2BAC522C57F6DD00590239 /* SortImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA52C57F6DD00590239 /* SortImports.swift */; }; + 2E2BAC532C57F6DD00590239 /* SortedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA62C57F6DD00590239 /* SortedImports.swift */; }; + 2E2BAC542C57F6DD00590239 /* SortedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA62C57F6DD00590239 /* SortedImports.swift */; }; + 2E2BAC552C57F6DD00590239 /* SortedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA62C57F6DD00590239 /* SortedImports.swift */; }; + 2E2BAC562C57F6DD00590239 /* SortedImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA62C57F6DD00590239 /* SortedImports.swift */; }; + 2E2BAC572C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */; }; + 2E2BAC582C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */; }; + 2E2BAC592C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */; }; + 2E2BAC5A2C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */; }; + 2E2BAC5B2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */; }; + 2E2BAC5C2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */; }; + 2E2BAC5D2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */; }; + 2E2BAC5E2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */; }; + 2E2BAC5F2C57F6DD00590239 /* BlockComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA92C57F6DD00590239 /* BlockComments.swift */; }; + 2E2BAC602C57F6DD00590239 /* BlockComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA92C57F6DD00590239 /* BlockComments.swift */; }; + 2E2BAC612C57F6DD00590239 /* BlockComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA92C57F6DD00590239 /* BlockComments.swift */; }; + 2E2BAC622C57F6DD00590239 /* BlockComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABA92C57F6DD00590239 /* BlockComments.swift */; }; + 2E2BAC632C57F6DD00590239 /* StrongOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */; }; + 2E2BAC642C57F6DD00590239 /* StrongOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */; }; + 2E2BAC652C57F6DD00590239 /* StrongOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */; }; + 2E2BAC662C57F6DD00590239 /* StrongOutlets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */; }; + 2E2BAC672C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */; }; + 2E2BAC682C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */; }; + 2E2BAC692C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */; }; + 2E2BAC6A2C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */; }; + 2E2BAC6B2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */; }; + 2E2BAC6C2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */; }; + 2E2BAC6D2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */; }; + 2E2BAC6E2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */; }; + 2E2BAC6F2C57F6DD00590239 /* AssertionFailures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */; }; + 2E2BAC702C57F6DD00590239 /* AssertionFailures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */; }; + 2E2BAC712C57F6DD00590239 /* AssertionFailures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */; }; + 2E2BAC722C57F6DD00590239 /* AssertionFailures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */; }; + 2E2BAC732C57F6DD00590239 /* EmptyBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */; }; + 2E2BAC742C57F6DD00590239 /* EmptyBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */; }; + 2E2BAC752C57F6DD00590239 /* EmptyBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */; }; + 2E2BAC762C57F6DD00590239 /* EmptyBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */; }; + 2E2BAC772C57F6DD00590239 /* SpaceAroundComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */; }; + 2E2BAC782C57F6DD00590239 /* SpaceAroundComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */; }; + 2E2BAC792C57F6DD00590239 /* SpaceAroundComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */; }; + 2E2BAC7A2C57F6DD00590239 /* SpaceAroundComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */; }; + 2E2BAC7B2C57F6DD00590239 /* RedundantParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */; }; + 2E2BAC7C2C57F6DD00590239 /* RedundantParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */; }; + 2E2BAC7D2C57F6DD00590239 /* RedundantParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */; }; + 2E2BAC7E2C57F6DD00590239 /* RedundantParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */; }; + 2E2BAC7F2C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */; }; + 2E2BAC802C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */; }; + 2E2BAC812C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */; }; + 2E2BAC822C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */; }; + 2E2BAC832C57F6DD00590239 /* Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */; }; + 2E2BAC842C57F6DD00590239 /* Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */; }; + 2E2BAC852C57F6DD00590239 /* Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */; }; + 2E2BAC862C57F6DD00590239 /* Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */; }; + 2E2BAC872C57F6DD00590239 /* LeadingDelimiters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */; }; + 2E2BAC882C57F6DD00590239 /* LeadingDelimiters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */; }; + 2E2BAC892C57F6DD00590239 /* LeadingDelimiters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */; }; + 2E2BAC8A2C57F6DD00590239 /* LeadingDelimiters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */; }; + 2E2BAC8B2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */; }; + 2E2BAC8C2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */; }; + 2E2BAC8D2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */; }; + 2E2BAC8E2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */; }; + 2E2BAC8F2C57F6DD00590239 /* RedundantLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */; }; + 2E2BAC902C57F6DD00590239 /* RedundantLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */; }; + 2E2BAC912C57F6DD00590239 /* RedundantLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */; }; + 2E2BAC922C57F6DD00590239 /* RedundantLet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */; }; + 2E2BAC932C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */; }; + 2E2BAC942C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */; }; + 2E2BAC952C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */; }; + 2E2BAC962C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */; }; + 2E2BAC972C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */; }; + 2E2BAC982C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */; }; + 2E2BAC992C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */; }; + 2E2BAC9A2C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */; }; + 2E2BAC9B2C57F6DD00590239 /* RedundantInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */; }; + 2E2BAC9C2C57F6DD00590239 /* RedundantInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */; }; + 2E2BAC9D2C57F6DD00590239 /* RedundantInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */; }; + 2E2BAC9E2C57F6DD00590239 /* RedundantInit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */; }; + 2E2BAC9F2C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */; }; + 2E2BACA02C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */; }; + 2E2BACA12C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */; }; + 2E2BACA22C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */; }; + 2E2BACA32C57F6DD00590239 /* Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBA2C57F6DD00590239 /* Void.swift */; }; + 2E2BACA42C57F6DD00590239 /* Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBA2C57F6DD00590239 /* Void.swift */; }; + 2E2BACA52C57F6DD00590239 /* Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBA2C57F6DD00590239 /* Void.swift */; }; + 2E2BACA62C57F6DD00590239 /* Void.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBA2C57F6DD00590239 /* Void.swift */; }; + 2E2BACA72C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */; }; + 2E2BACA82C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */; }; + 2E2BACA92C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */; }; + 2E2BACAA2C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */; }; + 2E2BACAB2C57F6DD00590239 /* RedundantLetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */; }; + 2E2BACAC2C57F6DD00590239 /* RedundantLetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */; }; + 2E2BACAD2C57F6DD00590239 /* RedundantLetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */; }; + 2E2BACAE2C57F6DD00590239 /* RedundantLetError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */; }; + 2E2BACAF2C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */; }; + 2E2BACB02C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */; }; + 2E2BACB12C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */; }; + 2E2BACB22C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */; }; + 2E2BACB32C57F6DD00590239 /* RedundantClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */; }; + 2E2BACB42C57F6DD00590239 /* RedundantClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */; }; + 2E2BACB52C57F6DD00590239 /* RedundantClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */; }; + 2E2BACB62C57F6DD00590239 /* RedundantClosure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */; }; + 2E2BACB72C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */; }; + 2E2BACB82C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */; }; + 2E2BACB92C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */; }; + 2E2BACBA2C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */; }; + 2E2BACBB2C57F6DD00590239 /* FileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC02C57F6DD00590239 /* FileHeader.swift */; }; + 2E2BACBC2C57F6DD00590239 /* FileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC02C57F6DD00590239 /* FileHeader.swift */; }; + 2E2BACBD2C57F6DD00590239 /* FileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC02C57F6DD00590239 /* FileHeader.swift */; }; + 2E2BACBE2C57F6DD00590239 /* FileHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC02C57F6DD00590239 /* FileHeader.swift */; }; + 2E2BACBF2C57F6DD00590239 /* TypeSugar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */; }; + 2E2BACC02C57F6DD00590239 /* TypeSugar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */; }; + 2E2BACC12C57F6DD00590239 /* TypeSugar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */; }; + 2E2BACC22C57F6DD00590239 /* TypeSugar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */; }; + 2E2BACC32C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */; }; + 2E2BACC42C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */; }; + 2E2BACC52C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */; }; + 2E2BACC62C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */; }; + 2E2BACC72C57F6DD00590239 /* HeaderFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */; }; + 2E2BACC82C57F6DD00590239 /* HeaderFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */; }; + 2E2BACC92C57F6DD00590239 /* HeaderFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */; }; + 2E2BACCA2C57F6DD00590239 /* HeaderFileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */; }; + 2E2BACCB2C57F6DD00590239 /* IsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */; }; + 2E2BACCC2C57F6DD00590239 /* IsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */; }; + 2E2BACCD2C57F6DD00590239 /* IsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */; }; + 2E2BACCE2C57F6DD00590239 /* IsEmpty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */; }; + 2E2BACCF2C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */; }; + 2E2BACD02C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */; }; + 2E2BACD12C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */; }; + 2E2BACD22C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */; }; + 2E2BACD32C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */; }; + 2E2BACD42C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */; }; + 2E2BACD52C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */; }; + 2E2BACD62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */; }; + 2E2BACD72C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */; }; + 2E2BACD82C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */; }; + 2E2BACD92C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */; }; + 2E2BACDA2C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */; }; + 2E2BACDB2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */; }; + 2E2BACDC2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */; }; + 2E2BACDD2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */; }; + 2E2BACDE2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */; }; + 2E2BACDF2C57F6DD00590239 /* RedundantReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */; }; + 2E2BACE02C57F6DD00590239 /* RedundantReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */; }; + 2E2BACE12C57F6DD00590239 /* RedundantReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */; }; + 2E2BACE22C57F6DD00590239 /* RedundantReturn.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */; }; + 2E2BACE32C57F6DD00590239 /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */; }; + 2E2BACE42C57F6DD00590239 /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */; }; + 2E2BACE52C57F6DD00590239 /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */; }; + 2E2BACE62C57F6DD00590239 /* GenericExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */; }; + 2E2BACE72C57F6DD00590239 /* TrailingSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */; }; + 2E2BACE82C57F6DD00590239 /* TrailingSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */; }; + 2E2BACE92C57F6DD00590239 /* TrailingSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */; }; + 2E2BACEA2C57F6DD00590239 /* TrailingSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */; }; + 2E2BACEB2C57F6DD00590239 /* RedundantObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */; }; + 2E2BACEC2C57F6DD00590239 /* RedundantObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */; }; + 2E2BACED2C57F6DD00590239 /* RedundantObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */; }; + 2E2BACEE2C57F6DD00590239 /* RedundantObjc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */; }; + 2E2BACEF2C57F6DD00590239 /* ConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */; }; + 2E2BACF02C57F6DD00590239 /* ConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */; }; + 2E2BACF12C57F6DD00590239 /* ConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */; }; + 2E2BACF22C57F6DD00590239 /* ConditionalAssignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */; }; + 2E2BACF32C57F6DD00590239 /* PreferForLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */; }; + 2E2BACF42C57F6DD00590239 /* PreferForLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */; }; + 2E2BACF52C57F6DD00590239 /* PreferForLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */; }; + 2E2BACF62C57F6DD00590239 /* PreferForLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */; }; + 2E2BACF72C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */; }; + 2E2BACF82C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */; }; + 2E2BACF92C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */; }; + 2E2BACFA2C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */; }; + 2E2BACFB2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */; }; + 2E2BACFC2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */; }; + 2E2BACFD2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */; }; + 2E2BACFE2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */; }; + 2E2BACFF2C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */; }; + 2E2BAD002C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */; }; + 2E2BAD012C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */; }; + 2E2BAD022C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */; }; + 2E2BAD032C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */; }; + 2E2BAD042C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */; }; + 2E2BAD052C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */; }; + 2E2BAD062C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */; }; + 2E2BAD072C57F6DD00590239 /* RedundantPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */; }; + 2E2BAD082C57F6DD00590239 /* RedundantPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */; }; + 2E2BAD092C57F6DD00590239 /* RedundantPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */; }; + 2E2BAD0A2C57F6DD00590239 /* RedundantPattern.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */; }; + 2E2BAD0B2C57F6DD00590239 /* ApplicationMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */; }; + 2E2BAD0C2C57F6DD00590239 /* ApplicationMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */; }; + 2E2BAD0D2C57F6DD00590239 /* ApplicationMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */; }; + 2E2BAD0E2C57F6DD00590239 /* ApplicationMain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */; }; + 2E2BAD0F2C57F6DD00590239 /* RedundantProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */; }; + 2E2BAD102C57F6DD00590239 /* RedundantProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */; }; + 2E2BAD112C57F6DD00590239 /* RedundantProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */; }; + 2E2BAD122C57F6DD00590239 /* RedundantProperty.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */; }; + 2E2BAD132C57F6DD00590239 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD62C57F6DD00590239 /* Wrap.swift */; }; + 2E2BAD142C57F6DD00590239 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD62C57F6DD00590239 /* Wrap.swift */; }; + 2E2BAD152C57F6DD00590239 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD62C57F6DD00590239 /* Wrap.swift */; }; + 2E2BAD162C57F6DD00590239 /* Wrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD62C57F6DD00590239 /* Wrap.swift */; }; + 2E2BAD172C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */; }; + 2E2BAD182C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */; }; + 2E2BAD192C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */; }; + 2E2BAD1A2C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */; }; + 2E2BAD1B2C57F6DD00590239 /* ModifierOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */; }; + 2E2BAD1C2C57F6DD00590239 /* ModifierOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */; }; + 2E2BAD1D2C57F6DD00590239 /* ModifierOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */; }; + 2E2BAD1E2C57F6DD00590239 /* ModifierOrder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */; }; + 2E2BAD1F2C57F6DD00590239 /* EnumNamespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */; }; + 2E2BAD202C57F6DD00590239 /* EnumNamespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */; }; + 2E2BAD212C57F6DD00590239 /* EnumNamespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */; }; + 2E2BAD222C57F6DD00590239 /* EnumNamespaces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */; }; + 2E2BAD232C57F6DD00590239 /* RedundantSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */; }; + 2E2BAD242C57F6DD00590239 /* RedundantSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */; }; + 2E2BAD252C57F6DD00590239 /* RedundantSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */; }; + 2E2BAD262C57F6DD00590239 /* RedundantSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */; }; + 2E2BAD272C57F6DD00590239 /* PreferKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */; }; + 2E2BAD282C57F6DD00590239 /* PreferKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */; }; + 2E2BAD292C57F6DD00590239 /* PreferKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */; }; + 2E2BAD2A2C57F6DD00590239 /* PreferKeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */; }; + 2E2BAD2B2C57F6DD00590239 /* WrapEnumCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */; }; + 2E2BAD2C2C57F6DD00590239 /* WrapEnumCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */; }; + 2E2BAD2D2C57F6DD00590239 /* WrapEnumCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */; }; + 2E2BAD2E2C57F6DD00590239 /* WrapEnumCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */; }; + 2E2BAD2F2C57F6DD00590239 /* WrapAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */; }; + 2E2BAD302C57F6DD00590239 /* WrapAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */; }; + 2E2BAD312C57F6DD00590239 /* WrapAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */; }; + 2E2BAD322C57F6DD00590239 /* WrapAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */; }; + 2E2BAD332C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */; }; + 2E2BAD342C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */; }; + 2E2BAD352C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */; }; + 2E2BAD362C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */; }; + 2E2BAD372C57F6DD00590239 /* WrapSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */; }; + 2E2BAD382C57F6DD00590239 /* WrapSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */; }; + 2E2BAD392C57F6DD00590239 /* WrapSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */; }; + 2E2BAD3A2C57F6DD00590239 /* WrapSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */; }; + 2E2BAD3B2C57F6DD00590239 /* Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE02C57F6DD00590239 /* Braces.swift */; }; + 2E2BAD3C2C57F6DD00590239 /* Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE02C57F6DD00590239 /* Braces.swift */; }; + 2E2BAD3D2C57F6DD00590239 /* Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE02C57F6DD00590239 /* Braces.swift */; }; + 2E2BAD3E2C57F6DD00590239 /* Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE02C57F6DD00590239 /* Braces.swift */; }; + 2E2BAD3F2C57F6DD00590239 /* MarkTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */; }; + 2E2BAD402C57F6DD00590239 /* MarkTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */; }; + 2E2BAD412C57F6DD00590239 /* MarkTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */; }; + 2E2BAD422C57F6DD00590239 /* MarkTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */; }; + 2E2BAD432C57F6DD00590239 /* AndOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE22C57F6DD00590239 /* AndOperator.swift */; }; + 2E2BAD442C57F6DD00590239 /* AndOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE22C57F6DD00590239 /* AndOperator.swift */; }; + 2E2BAD452C57F6DD00590239 /* AndOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE22C57F6DD00590239 /* AndOperator.swift */; }; + 2E2BAD462C57F6DD00590239 /* AndOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE22C57F6DD00590239 /* AndOperator.swift */; }; + 2E2BAD472C57F6DD00590239 /* WrapLoopBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */; }; + 2E2BAD482C57F6DD00590239 /* WrapLoopBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */; }; + 2E2BAD492C57F6DD00590239 /* WrapLoopBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */; }; + 2E2BAD4A2C57F6DD00590239 /* WrapLoopBodies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */; }; + 2E2BAD4B2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */; }; + 2E2BAD4C2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */; }; + 2E2BAD4D2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */; }; + 2E2BAD4E2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */; }; + 2E2BAD4F2C57F6DD00590239 /* RedundantRawValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */; }; + 2E2BAD502C57F6DD00590239 /* RedundantRawValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */; }; + 2E2BAD512C57F6DD00590239 /* RedundantRawValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */; }; + 2E2BAD522C57F6DD00590239 /* RedundantRawValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */; }; + 2E2BAD532C57F6DD00590239 /* TrailingCommas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */; }; + 2E2BAD542C57F6DD00590239 /* TrailingCommas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */; }; + 2E2BAD552C57F6DD00590239 /* TrailingCommas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */; }; + 2E2BAD562C57F6DD00590239 /* TrailingCommas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */; }; + 2E2BAD572C57F6DD00590239 /* StrongifiedSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */; }; + 2E2BAD582C57F6DD00590239 /* StrongifiedSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */; }; + 2E2BAD592C57F6DD00590239 /* StrongifiedSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */; }; + 2E2BAD5A2C57F6DD00590239 /* StrongifiedSelf.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */; }; + 2E2BAD5B2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */; }; + 2E2BAD5C2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */; }; + 2E2BAD5D2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */; }; + 2E2BAD5E2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */; }; + 2E2BAD5F2C57F6DD00590239 /* RedundantBackticks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */; }; + 2E2BAD602C57F6DD00590239 /* RedundantBackticks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */; }; + 2E2BAD612C57F6DD00590239 /* RedundantBackticks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */; }; + 2E2BAD622C57F6DD00590239 /* RedundantBackticks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */; }; + 2E2BAD632C57F6DD00590239 /* SpaceAroundParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */; }; + 2E2BAD642C57F6DD00590239 /* SpaceAroundParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */; }; + 2E2BAD652C57F6DD00590239 /* SpaceAroundParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */; }; + 2E2BAD662C57F6DD00590239 /* SpaceAroundParens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */; }; + 2E2BAD672C57F6DD00590239 /* HoistAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */; }; + 2E2BAD682C57F6DD00590239 /* HoistAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */; }; + 2E2BAD692C57F6DD00590239 /* HoistAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */; }; + 2E2BAD6A2C57F6DD00590239 /* HoistAwait.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */; }; + 2E2BAD6B2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */; }; + 2E2BAD6C2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */; }; + 2E2BAD6D2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */; }; + 2E2BAD6E2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */; }; + 2E2BAD6F2C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */; }; + 2E2BAD702C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */; }; + 2E2BAD712C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */; }; + 2E2BAD722C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */; }; + 2E2BAD732C57F6DD00590239 /* TrailingClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */; }; + 2E2BAD742C57F6DD00590239 /* TrailingClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */; }; + 2E2BAD752C57F6DD00590239 /* TrailingClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */; }; + 2E2BAD762C57F6DD00590239 /* TrailingClosures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */; }; + 2E2BAD772C57F6DD00590239 /* SortedSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */; }; + 2E2BAD782C57F6DD00590239 /* SortedSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */; }; + 2E2BAD792C57F6DD00590239 /* SortedSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */; }; + 2E2BAD7A2C57F6DD00590239 /* SortedSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */; }; + 2E2BAD7B2C57F6DD00590239 /* Acronyms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF02C57F6DD00590239 /* Acronyms.swift */; }; + 2E2BAD7C2C57F6DD00590239 /* Acronyms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF02C57F6DD00590239 /* Acronyms.swift */; }; + 2E2BAD7D2C57F6DD00590239 /* Acronyms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF02C57F6DD00590239 /* Acronyms.swift */; }; + 2E2BAD7E2C57F6DD00590239 /* Acronyms.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF02C57F6DD00590239 /* Acronyms.swift */; }; + 2E2BAD7F2C57F6DD00590239 /* SortTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */; }; + 2E2BAD802C57F6DD00590239 /* SortTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */; }; + 2E2BAD812C57F6DD00590239 /* SortTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */; }; + 2E2BAD822C57F6DD00590239 /* SortTypealiases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */; }; + 2E2BAD832C57F6DD00590239 /* DocComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF22C57F6DD00590239 /* DocComments.swift */; }; + 2E2BAD842C57F6DD00590239 /* DocComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF22C57F6DD00590239 /* DocComments.swift */; }; + 2E2BAD852C57F6DD00590239 /* DocComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF22C57F6DD00590239 /* DocComments.swift */; }; + 2E2BAD862C57F6DD00590239 /* DocComments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF22C57F6DD00590239 /* DocComments.swift */; }; + 2E2BAD872C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */; }; + 2E2BAD882C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */; }; + 2E2BAD892C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */; }; + 2E2BAD8A2C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */; }; + 2E2BAD8B2C57F6DD00590239 /* PropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF42C57F6DD00590239 /* PropertyType.swift */; }; + 2E2BAD8C2C57F6DD00590239 /* PropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF42C57F6DD00590239 /* PropertyType.swift */; }; + 2E2BAD8D2C57F6DD00590239 /* PropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF42C57F6DD00590239 /* PropertyType.swift */; }; + 2E2BAD8E2C57F6DD00590239 /* PropertyType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF42C57F6DD00590239 /* PropertyType.swift */; }; + 2E2BAD8F2C57F6DD00590239 /* HoistTry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF52C57F6DD00590239 /* HoistTry.swift */; }; + 2E2BAD902C57F6DD00590239 /* HoistTry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF52C57F6DD00590239 /* HoistTry.swift */; }; + 2E2BAD912C57F6DD00590239 /* HoistTry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF52C57F6DD00590239 /* HoistTry.swift */; }; + 2E2BAD922C57F6DD00590239 /* HoistTry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF52C57F6DD00590239 /* HoistTry.swift */; }; + 2E2BAD932C57F6DD00590239 /* NumberFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */; }; + 2E2BAD942C57F6DD00590239 /* NumberFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */; }; + 2E2BAD952C57F6DD00590239 /* NumberFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */; }; + 2E2BAD962C57F6DD00590239 /* NumberFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */; }; + 2E2BAD972C57F6DD00590239 /* WrapArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */; }; + 2E2BAD982C57F6DD00590239 /* WrapArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */; }; + 2E2BAD992C57F6DD00590239 /* WrapArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */; }; + 2E2BAD9A2C57F6DD00590239 /* WrapArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */; }; + 2E2BAD9B2C57F6DD00590239 /* Specifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF82C57F6DD00590239 /* Specifiers.swift */; }; + 2E2BAD9C2C57F6DD00590239 /* Specifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF82C57F6DD00590239 /* Specifiers.swift */; }; + 2E2BAD9D2C57F6DD00590239 /* Specifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF82C57F6DD00590239 /* Specifiers.swift */; }; + 2E2BAD9E2C57F6DD00590239 /* Specifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF82C57F6DD00590239 /* Specifiers.swift */; }; + 2E2BAD9F2C57F6DD00590239 /* YodaConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */; }; + 2E2BADA02C57F6DD00590239 /* YodaConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */; }; + 2E2BADA12C57F6DD00590239 /* YodaConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */; }; + 2E2BADA22C57F6DD00590239 /* YodaConditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */; }; + 2E2BADA32C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */; }; + 2E2BADA42C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */; }; + 2E2BADA52C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */; }; + 2E2BADA62C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */; }; + 2E2BADA72C57F6DD00590239 /* UnusedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */; }; + 2E2BADA82C57F6DD00590239 /* UnusedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */; }; + 2E2BADA92C57F6DD00590239 /* UnusedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */; }; + 2E2BADAA2C57F6DD00590239 /* UnusedArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */; }; + 2E2BADAB2C57F6DD00590239 /* SortSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */; }; + 2E2BADAC2C57F6DD00590239 /* SortSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */; }; + 2E2BADAD2C57F6DD00590239 /* SortSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */; }; + 2E2BADAE2C57F6DD00590239 /* SortSwitchCases.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */; }; + 2E2BADAF2C57F6DD00590239 /* RedundantType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */; }; + 2E2BADB02C57F6DD00590239 /* RedundantType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */; }; + 2E2BADB12C57F6DD00590239 /* RedundantType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */; }; + 2E2BADB22C57F6DD00590239 /* RedundantType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */; }; + 2E2BADB32C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; + 2E2BADB42C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; + 2E2BADB52C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; + 2E2BADB62C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; 2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; }; 37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; }; 37D828AC24BF77DA0012FC0A /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9028F7831DA4B435009FE5B4 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; 9028F7841DA4B435009FE5B4 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; 9028F7851DA4B435009FE5B4 /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3987C1D763493009ADE61 /* Formatter.swift */; }; - 9028F7861DA4B435009FE5B4 /* Rules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */; }; + 9028F7861DA4B435009FE5B4 /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; 90C4B6CD1DA4B04A009EB000 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90C4B6CC1DA4B04A009EB000 /* AppDelegate.swift */; }; 90C4B6D11DA4B04A009EB000 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 90C4B6D01DA4B04A009EB000 /* Assets.xcassets */; }; 90C4B6D41DA4B04A009EB000 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 90C4B6D21DA4B04A009EB000 /* Main.storyboard */; }; @@ -119,7 +563,7 @@ E41CB5C52027700100C1BEDE /* FreeTextTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41CB5C42027700100C1BEDE /* FreeTextTableCellView.swift */; }; E43EF47C202FF47C00E523BD /* OptionDescriptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E43EF47B202FF47C00E523BD /* OptionDescriptorTests.swift */; }; E4872111201D3B830014845E /* Options.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F3DF8B1DB9FD3F00454944 /* Options.swift */; }; - E4872112201D3B860014845E /* Rules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */; }; + E4872112201D3B860014845E /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; E4872113201D3B890014845E /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3987C1D763493009ADE61 /* Formatter.swift */; }; E4872114201D3B8C0014845E /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; E487211D201D885A0014845E /* RulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E487211C201D885A0014845E /* RulesViewController.swift */; }; @@ -211,7 +655,7 @@ 01A0EAAE1D5DB4D000A0A8E3 /* SwiftFormatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftFormatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 01A0EAB31D5DB4D000A0A8E3 /* RulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulesTests.swift; sourceTree = ""; }; 01A0EAB51D5DB4D000A0A8E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Rules.swift; sourceTree = ""; }; + 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormatRule.swift; sourceTree = ""; }; 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tokenizer.swift; sourceTree = ""; }; 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftFormat.swift; sourceTree = ""; }; 01A0EACA1D5DB5F500A0A8E3 /* swiftformat */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = swiftformat; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -239,6 +683,117 @@ 01F3DF8B1DB9FD3F00454944 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = ""; }; 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InferenceTests.swift; sourceTree = ""; }; 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclarationHelpers.swift; sourceTree = ""; }; + 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleRegistry.generated.swift; sourceTree = ""; }; + 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitCoderUnavailable.swift; sourceTree = ""; }; + 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantBreak.swift; sourceTree = ""; }; + 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLineAfterSwitchCase.swift; sourceTree = ""; }; + 2E2BAB942C57F6DD00590239 /* Indent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Indent.swift; sourceTree = ""; }; + 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapMultilineConditionalAssignment.swift; sourceTree = ""; }; + 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsecutiveSpaces.swift; sourceTree = ""; }; + 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsistentSwitchCaseSpacing.swift; sourceTree = ""; }; + 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantExtensionACL.swift; sourceTree = ""; }; + 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantOptionalBinding.swift; sourceTree = ""; }; + 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantInternal.swift; sourceTree = ""; }; + 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantNilInit.swift; sourceTree = ""; }; + 2E2BAB9C2C57F6DD00590239 /* Todos.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Todos.swift; sourceTree = ""; }; + 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideParens.swift; sourceTree = ""; }; + 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Semicolons.swift; sourceTree = ""; }; + 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistPatternLet.swift; sourceTree = ""; }; + 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElseOnSameLine.swift; sourceTree = ""; }; + 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuplicateImports.swift; sourceTree = ""; }; + 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantGet.swift; sourceTree = ""; }; + 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundOperators.swift; sourceTree = ""; }; + 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAroundMark.swift; sourceTree = ""; }; + 2E2BABA52C57F6DD00590239 /* SortImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortImports.swift; sourceTree = ""; }; + 2E2BABA62C57F6DD00590239 /* SortedImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortedImports.swift; sourceTree = ""; }; + 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenChainedFunctions.swift; sourceTree = ""; }; + 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantFileprivate.swift; sourceTree = ""; }; + 2E2BABA92C57F6DD00590239 /* BlockComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockComments.swift; sourceTree = ""; }; + 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrongOutlets.swift; sourceTree = ""; }; + 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinebreakAtEndOfFile.swift; sourceTree = ""; }; + 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideGenerics.swift; sourceTree = ""; }; + 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssertionFailures.swift; sourceTree = ""; }; + 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyBraces.swift; sourceTree = ""; }; + 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundComments.swift; sourceTree = ""; }; + 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantParens.swift; sourceTree = ""; }; + 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundGenerics.swift; sourceTree = ""; }; + 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Linebreaks.swift; sourceTree = ""; }; + 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeadingDelimiters.swift; sourceTree = ""; }; + 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideComments.swift; sourceTree = ""; }; + 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantLet.swift; sourceTree = ""; }; + 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocCommentsBeforeAttributes.swift; sourceTree = ""; }; + 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsecutiveBlankLines.swift; sourceTree = ""; }; + 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantInit.swift; sourceTree = ""; }; + 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExplicitOwnership.swift; sourceTree = ""; }; + 2E2BABBA2C57F6DD00590239 /* Void.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Void.swift; sourceTree = ""; }; + 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSingleLineComments.swift; sourceTree = ""; }; + 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantLetError.swift; sourceTree = ""; }; + 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenScopes.swift; sourceTree = ""; }; + 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantClosure.swift; sourceTree = ""; }; + 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganizeDeclarations.swift; sourceTree = ""; }; + 2E2BABC02C57F6DD00590239 /* FileHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeader.swift; sourceTree = ""; }; + 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeSugar.swift; sourceTree = ""; }; + 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideBrackets.swift; sourceTree = ""; }; + 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFileName.swift; sourceTree = ""; }; + 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IsEmpty.swift; sourceTree = ""; }; + 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundBrackets.swift; sourceTree = ""; }; + 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAtEndOfScope.swift; sourceTree = ""; }; + 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAccessControl.swift; sourceTree = ""; }; + 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundBraces.swift; sourceTree = ""; }; + 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantReturn.swift; sourceTree = ""; }; + 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericExtensions.swift; sourceTree = ""; }; + 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingSpace.swift; sourceTree = ""; }; + 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantObjc.swift; sourceTree = ""; }; + 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalAssignment.swift; sourceTree = ""; }; + 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferForLoop.swift; sourceTree = ""; }; + 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStaticSelf.swift; sourceTree = ""; }; + 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenImports.swift; sourceTree = ""; }; + 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapMultilineStatementBraces.swift; sourceTree = ""; }; + 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideBraces.swift; sourceTree = ""; }; + 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantPattern.swift; sourceTree = ""; }; + 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationMain.swift; sourceTree = ""; }; + 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantProperty.swift; sourceTree = ""; }; + 2E2BABD62C57F6DD00590239 /* Wrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Wrap.swift; sourceTree = ""; }; + 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLineAfterImports.swift; sourceTree = ""; }; + 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModifierOrder.swift; sourceTree = ""; }; + 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumNamespaces.swift; sourceTree = ""; }; + 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantSelf.swift; sourceTree = ""; }; + 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferKeyPath.swift; sourceTree = ""; }; + 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapEnumCases.swift; sourceTree = ""; }; + 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapAttributes.swift; sourceTree = ""; }; + 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapConditionalBodies.swift; sourceTree = ""; }; + 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSwitchCases.swift; sourceTree = ""; }; + 2E2BABE02C57F6DD00590239 /* Braces.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Braces.swift; sourceTree = ""; }; + 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkTypes.swift; sourceTree = ""; }; + 2E2BABE22C57F6DD00590239 /* AndOperator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AndOperator.swift; sourceTree = ""; }; + 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapLoopBodies.swift; sourceTree = ""; }; + 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantVoidReturnType.swift; sourceTree = ""; }; + 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantRawValues.swift; sourceTree = ""; }; + 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingCommas.swift; sourceTree = ""; }; + 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrongifiedSelf.swift; sourceTree = ""; }; + 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyObjectProtocol.swift; sourceTree = ""; }; + 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantBackticks.swift; sourceTree = ""; }; + 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundParens.swift; sourceTree = ""; }; + 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistAwait.swift; sourceTree = ""; }; + 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAtStartOfScope.swift; sourceTree = ""; }; + 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpaqueGenericParameters.swift; sourceTree = ""; }; + 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingClosures.swift; sourceTree = ""; }; + 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortedSwitchCases.swift; sourceTree = ""; }; + 2E2BABF02C57F6DD00590239 /* Acronyms.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Acronyms.swift; sourceTree = ""; }; + 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortTypealiases.swift; sourceTree = ""; }; + 2E2BABF22C57F6DD00590239 /* DocComments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocComments.swift; sourceTree = ""; }; + 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedPrivateDeclaration.swift; sourceTree = ""; }; + 2E2BABF42C57F6DD00590239 /* PropertyType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyType.swift; sourceTree = ""; }; + 2E2BABF52C57F6DD00590239 /* HoistTry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistTry.swift; sourceTree = ""; }; + 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberFormatting.swift; sourceTree = ""; }; + 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapArguments.swift; sourceTree = ""; }; + 2E2BABF82C57F6DD00590239 /* Specifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Specifiers.swift; sourceTree = ""; }; + 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YodaConditions.swift; sourceTree = ""; }; + 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantTypedThrows.swift; sourceTree = ""; }; + 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedArguments.swift; sourceTree = ""; }; + 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortSwitchCases.swift; sourceTree = ""; }; + 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantType.swift; sourceTree = ""; }; + 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDeclarations.swift; sourceTree = ""; }; 2E7D30A32A7940C500C32174 /* Singularize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Singularize.swift; sourceTree = ""; }; 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; 90C4B6CA1DA4B04A009EB000 /* SwiftFormat for Xcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftFormat for Xcode.app"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -374,7 +929,8 @@ 01A0EAA61D5DB4CF00A0A8E3 /* Sources */ = { isa = PBXGroup; children = ( - 012939DB2C56A3AA00BD0A24 /* Reporters */, + 2E2BAB902C57F6DD00590239 /* Rules */, + 2E2BAB8B2C57F6B600590239 /* RuleRegistry.generated.swift */, 01F17E811E25870700DCD359 /* CommandLine.swift */, E4E4D3C82033F17C000D7CB1 /* EnumAssociable.swift */, 01B3987C1D763493009ADE61 /* Formatter.swift */, @@ -383,7 +939,7 @@ E4FABAD4202FEF060065716E /* OptionDescriptor.swift */, 01045A982119979400D2BE3D /* Arguments.swift */, 01045A90211988F100D2BE3D /* Inference.swift */, - 01A0EABE1D5DB4F700A0A8E3 /* Rules.swift */, + 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */, 01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */, 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */, 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */, @@ -394,6 +950,7 @@ 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */, 01BBD85821DAA2A000457380 /* Globs.swift */, D52F6A632A82E04600FE1448 /* GitFileInfo.swift */, + 012939DB2C56A3AA00BD0A24 /* Reporters */, ); path = Sources; sourceTree = ""; @@ -450,6 +1007,123 @@ path = Shared; sourceTree = ""; }; + 2E2BAB902C57F6DD00590239 /* Rules */ = { + isa = PBXGroup; + children = ( + 2E2BABF02C57F6DD00590239 /* Acronyms.swift */, + 2E2BABE22C57F6DD00590239 /* AndOperator.swift */, + 2E2BABE82C57F6DD00590239 /* AnyObjectProtocol.swift */, + 2E2BABD42C57F6DD00590239 /* ApplicationMain.swift */, + 2E2BABAD2C57F6DD00590239 /* AssertionFailures.swift */, + 2E2BABD72C57F6DD00590239 /* BlankLineAfterImports.swift */, + 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */, + 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */, + 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */, + 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */, + 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */, + 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */, + 2E2BABBD2C57F6DD00590239 /* BlankLinesBetweenScopes.swift */, + 2E2BABA92C57F6DD00590239 /* BlockComments.swift */, + 2E2BABE02C57F6DD00590239 /* Braces.swift */, + 2E2BABCD2C57F6DD00590239 /* ConditionalAssignment.swift */, + 2E2BABB72C57F6DD00590239 /* ConsecutiveBlankLines.swift */, + 2E2BAB962C57F6DD00590239 /* ConsecutiveSpaces.swift */, + 2E2BAB972C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift */, + 2E2BABF22C57F6DD00590239 /* DocComments.swift */, + 2E2BABB62C57F6DD00590239 /* DocCommentsBeforeAttributes.swift */, + 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */, + 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */, + 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */, + 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */, + 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */, + 2E2BABC02C57F6DD00590239 /* FileHeader.swift */, + 2E2BABCA2C57F6DD00590239 /* GenericExtensions.swift */, + 2E2BABC32C57F6DD00590239 /* HeaderFileName.swift */, + 2E2BABEB2C57F6DD00590239 /* HoistAwait.swift */, + 2E2BAB9F2C57F6DD00590239 /* HoistPatternLet.swift */, + 2E2BABF52C57F6DD00590239 /* HoistTry.swift */, + 2E2BAB942C57F6DD00590239 /* Indent.swift */, + 2E2BAB912C57F6DD00590239 /* InitCoderUnavailable.swift */, + 2E2BABC42C57F6DD00590239 /* IsEmpty.swift */, + 2E2BABB32C57F6DD00590239 /* LeadingDelimiters.swift */, + 2E2BABAB2C57F6DD00590239 /* LinebreakAtEndOfFile.swift */, + 2E2BABB22C57F6DD00590239 /* Linebreaks.swift */, + 2E2BABE12C57F6DD00590239 /* MarkTypes.swift */, + 2E2BABD82C57F6DD00590239 /* ModifierOrder.swift */, + 2E2BABB92C57F6DD00590239 /* NoExplicitOwnership.swift */, + 2E2BABF62C57F6DD00590239 /* NumberFormatting.swift */, + 2E2BABED2C57F6DD00590239 /* OpaqueGenericParameters.swift */, + 2E2BABBF2C57F6DD00590239 /* OrganizeDeclarations.swift */, + 2E2BABCE2C57F6DD00590239 /* PreferForLoop.swift */, + 2E2BABDB2C57F6DD00590239 /* PreferKeyPath.swift */, + 2E2BABF42C57F6DD00590239 /* PropertyType.swift */, + 2E2BABE92C57F6DD00590239 /* RedundantBackticks.swift */, + 2E2BAB922C57F6DD00590239 /* RedundantBreak.swift */, + 2E2BABBE2C57F6DD00590239 /* RedundantClosure.swift */, + 2E2BAB982C57F6DD00590239 /* RedundantExtensionACL.swift */, + 2E2BABA82C57F6DD00590239 /* RedundantFileprivate.swift */, + 2E2BABA22C57F6DD00590239 /* RedundantGet.swift */, + 2E2BABB82C57F6DD00590239 /* RedundantInit.swift */, + 2E2BAB9A2C57F6DD00590239 /* RedundantInternal.swift */, + 2E2BABB52C57F6DD00590239 /* RedundantLet.swift */, + 2E2BABBC2C57F6DD00590239 /* RedundantLetError.swift */, + 2E2BAB9B2C57F6DD00590239 /* RedundantNilInit.swift */, + 2E2BABCC2C57F6DD00590239 /* RedundantObjc.swift */, + 2E2BAB992C57F6DD00590239 /* RedundantOptionalBinding.swift */, + 2E2BABB02C57F6DD00590239 /* RedundantParens.swift */, + 2E2BABD32C57F6DD00590239 /* RedundantPattern.swift */, + 2E2BABD52C57F6DD00590239 /* RedundantProperty.swift */, + 2E2BABE52C57F6DD00590239 /* RedundantRawValues.swift */, + 2E2BABC92C57F6DD00590239 /* RedundantReturn.swift */, + 2E2BABDA2C57F6DD00590239 /* RedundantSelf.swift */, + 2E2BABCF2C57F6DD00590239 /* RedundantStaticSelf.swift */, + 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */, + 2E2BABFA2C57F6DD00590239 /* RedundantTypedThrows.swift */, + 2E2BABE42C57F6DD00590239 /* RedundantVoidReturnType.swift */, + 2E2BAB9E2C57F6DD00590239 /* Semicolons.swift */, + 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */, + 2E2BABA62C57F6DD00590239 /* SortedImports.swift */, + 2E2BABEF2C57F6DD00590239 /* SortedSwitchCases.swift */, + 2E2BABA52C57F6DD00590239 /* SortImports.swift */, + 2E2BABFC2C57F6DD00590239 /* SortSwitchCases.swift */, + 2E2BABF12C57F6DD00590239 /* SortTypealiases.swift */, + 2E2BABC82C57F6DD00590239 /* SpaceAroundBraces.swift */, + 2E2BABC52C57F6DD00590239 /* SpaceAroundBrackets.swift */, + 2E2BABAF2C57F6DD00590239 /* SpaceAroundComments.swift */, + 2E2BABB12C57F6DD00590239 /* SpaceAroundGenerics.swift */, + 2E2BABA32C57F6DD00590239 /* SpaceAroundOperators.swift */, + 2E2BABEA2C57F6DD00590239 /* SpaceAroundParens.swift */, + 2E2BABD22C57F6DD00590239 /* SpaceInsideBraces.swift */, + 2E2BABC22C57F6DD00590239 /* SpaceInsideBrackets.swift */, + 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */, + 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */, + 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */, + 2E2BABF82C57F6DD00590239 /* Specifiers.swift */, + 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */, + 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */, + 2E2BAB9C2C57F6DD00590239 /* Todos.swift */, + 2E2BABEE2C57F6DD00590239 /* TrailingClosures.swift */, + 2E2BABE62C57F6DD00590239 /* TrailingCommas.swift */, + 2E2BABCB2C57F6DD00590239 /* TrailingSpace.swift */, + 2E2BABC12C57F6DD00590239 /* TypeSugar.swift */, + 2E2BABFB2C57F6DD00590239 /* UnusedArguments.swift */, + 2E2BABF32C57F6DD00590239 /* UnusedPrivateDeclaration.swift */, + 2E2BABBA2C57F6DD00590239 /* Void.swift */, + 2E2BABD62C57F6DD00590239 /* Wrap.swift */, + 2E2BABF72C57F6DD00590239 /* WrapArguments.swift */, + 2E2BABDD2C57F6DD00590239 /* WrapAttributes.swift */, + 2E2BABDE2C57F6DD00590239 /* WrapConditionalBodies.swift */, + 2E2BABDC2C57F6DD00590239 /* WrapEnumCases.swift */, + 2E2BABE32C57F6DD00590239 /* WrapLoopBodies.swift */, + 2E2BAB952C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift */, + 2E2BABD12C57F6DD00590239 /* WrapMultilineStatementBraces.swift */, + 2E2BABBB2C57F6DD00590239 /* WrapSingleLineComments.swift */, + 2E2BABDF2C57F6DD00590239 /* WrapSwitchCases.swift */, + 2E2BABF92C57F6DD00590239 /* YodaConditions.swift */, + ); + path = Rules; + sourceTree = ""; + }; 9016DEFD1DA5042E008A4E36 /* Resources */ = { isa = PBXGroup; children = ( @@ -678,9 +1352,6 @@ LastUpgradeCheck = 1500; ORGANIZATIONNAME = "Nick Lockwood"; TargetAttributes = { - 015AF2B61DC6A538008F0A8C = { - DevelopmentTeam = 8VQKF583ED; - }; 01A0EAA31D5DB4CF00A0A8E3 = { CreatedOnToolsVersion = 7.3; DevelopmentTeam = 8VQKF583ED; @@ -806,27 +1477,138 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2E2BAD9B2C57F6DD00590239 /* Specifiers.swift in Sources */, D52F6A642A82E04600FE1448 /* GitFileInfo.swift in Sources */, + 2E2BACEB2C57F6DD00590239 /* RedundantObjc.swift in Sources */, + 2E2BAC572C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */, + 2E2BACAB2C57F6DD00590239 /* RedundantLetError.swift in Sources */, + 2E2BADA72C57F6DD00590239 /* UnusedArguments.swift in Sources */, + 2E2BACCB2C57F6DD00590239 /* IsEmpty.swift in Sources */, + 2E2BAD0B2C57F6DD00590239 /* ApplicationMain.swift in Sources */, + 2E2BAD572C57F6DD00590239 /* StrongifiedSelf.swift in Sources */, + 2E2BAC032C57F6DD00590239 /* RedundantBreak.swift in Sources */, + 2E2BAC432C57F6DD00590239 /* RedundantGet.swift in Sources */, 01045A992119979400D2BE3D /* Arguments.swift in Sources */, 01567D2F225B2BFD00B22D41 /* ParsingHelpers.swift in Sources */, + 2E2BACB32C57F6DD00590239 /* RedundantClosure.swift in Sources */, + 2E2BAD032C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, + 2E2BAC7B2C57F6DD00590239 /* RedundantParens.swift in Sources */, + 2E2BAC1B2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, DD9AD39E2999FCC8001C2C0E /* GithubActionsLogReporter.swift in Sources */, + 2E2BAC5F2C57F6DD00590239 /* BlockComments.swift in Sources */, 01045A91211988F100D2BE3D /* Inference.swift in Sources */, + 2E2BAC4F2C57F6DD00590239 /* SortImports.swift in Sources */, + 2E2BAC632C57F6DD00590239 /* StrongOutlets.swift in Sources */, + 2E2BAC532C57F6DD00590239 /* SortedImports.swift in Sources */, + 2E2BADAF2C57F6DD00590239 /* RedundantType.swift in Sources */, + 2E2BAD7B2C57F6DD00590239 /* Acronyms.swift in Sources */, DD9AD3A32999FCC8001C2C0E /* Reporter.swift in Sources */, + 2E2BAC8F2C57F6DD00590239 /* RedundantLet.swift in Sources */, E4FABAD5202FEF060065716E /* OptionDescriptor.swift in Sources */, + 2E2BAD8F2C57F6DD00590239 /* HoistTry.swift in Sources */, + 2E2BAC732C57F6DD00590239 /* EmptyBraces.swift in Sources */, + 2E2BAD6F2C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + 2E2BACD32C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */, + 2E2BACF72C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */, + 2E2BAC4B2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + 2E2BAC372C57F6DD00590239 /* HoistPatternLet.swift in Sources */, + 2E2BAD672C57F6DD00590239 /* HoistAwait.swift in Sources */, + 2E2BAD832C57F6DD00590239 /* DocComments.swift in Sources */, + 2E2BAC2B2C57F6DD00590239 /* Todos.swift in Sources */, + 2E2BAC6F2C57F6DD00590239 /* AssertionFailures.swift in Sources */, + 2E2BAD5B2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 2E2BAC072C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, + 2E2BADA32C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, + 2E2BAD4B2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, + 2E2BAC332C57F6DD00590239 /* Semicolons.swift in Sources */, + 2E2BAC0B2C57F6DD00590239 /* Indent.swift in Sources */, + 2E2BACC32C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */, + 2E2BACF32C57F6DD00590239 /* PreferForLoop.swift in Sources */, + 2E2BAC172C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, + 2E2BACE32C57F6DD00590239 /* GenericExtensions.swift in Sources */, 01BBD85921DAA2A000457380 /* Globs.swift in Sources */, 01ACAE05220CD90F003F3CCF /* Examples.swift in Sources */, 01D3B28624E9C9C700888DE0 /* FormattingHelpers.swift in Sources */, + 2E2BAD532C57F6DD00590239 /* TrailingCommas.swift in Sources */, + 2E2BAD5F2C57F6DD00590239 /* RedundantBackticks.swift in Sources */, + 2E2BAC272C57F6DD00590239 /* RedundantNilInit.swift in Sources */, + 2E2BACDB2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, + 2E2BAC772C57F6DD00590239 /* SpaceAroundComments.swift in Sources */, + 2E2BAD8B2C57F6DD00590239 /* PropertyType.swift in Sources */, + 2E2BAD3F2C57F6DD00590239 /* MarkTypes.swift in Sources */, + 2E2BAC972C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */, + 2E2BAC132C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */, + 2E2BAD0F2C57F6DD00590239 /* RedundantProperty.swift in Sources */, + 2E2BACFF2C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */, E4E4D3C92033F17C000D7CB1 /* EnumAssociable.swift in Sources */, + 2E2BAC472C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */, + 2E2BAD2F2C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 2E2BAC932C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, A3DF48252620E03600F45A5F /* JSONReporter.swift in Sources */, - 01A0EAC11D5DB4F700A0A8E3 /* Rules.swift in Sources */, + 01A0EAC11D5DB4F700A0A8E3 /* FormatRule.swift in Sources */, + 2E2BAC9F2C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, + 2E2BACA72C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, + 2E2BACC72C57F6DD00590239 /* HeaderFileName.swift in Sources */, + 2E2BACD72C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */, + 2E2BAC7F2C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */, + 2E2BAC5B2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */, + 2E2BACBB2C57F6DD00590239 /* FileHeader.swift in Sources */, + 2E2BAD972C57F6DD00590239 /* WrapArguments.swift in Sources */, + 2E2BAD272C57F6DD00590239 /* PreferKeyPath.swift in Sources */, + 2E2BAC6B2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */, + 2E2BACCF2C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, + 2E2BAC9B2C57F6DD00590239 /* RedundantInit.swift in Sources */, + 2E2BAC3B2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */, 01A0EAC51D5DB54A00A0A8E3 /* SwiftFormat.swift in Sources */, + 2E2BAC232C57F6DD00590239 /* RedundantInternal.swift in Sources */, + 2E2BAC872C57F6DD00590239 /* LeadingDelimiters.swift in Sources */, + 2E2BAC8B2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */, + 2E2BAC672C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */, + 2E2BACFB2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */, + 2E2BAD2B2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, + 2E2BAD872C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */, + 2E2BAD1B2C57F6DD00590239 /* ModifierOrder.swift in Sources */, C2FFD1822BD13C9E00774F55 /* XMLReporter.swift in Sources */, + 2E2BAD632C57F6DD00590239 /* SpaceAroundParens.swift in Sources */, 2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */, + 2E2BADB32C57F6DD00590239 /* SortDeclarations.swift in Sources */, + 2E2BACAF2C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */, + 2E2BAB8C2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */, 01B3987D1D763493009ADE61 /* Formatter.swift in Sources */, 2E230CA22C4C1C0700A16E2E /* DeclarationHelpers.swift in Sources */, + 2E2BAD932C57F6DD00590239 /* NumberFormatting.swift in Sources */, 01F17E821E25870700DCD359 /* CommandLine.swift in Sources */, + 2E2BAD7F2C57F6DD00590239 /* SortTypealiases.swift in Sources */, + 2E2BAD772C57F6DD00590239 /* SortedSwitchCases.swift in Sources */, + 2E2BACA32C57F6DD00590239 /* Void.swift in Sources */, + 2E2BAD9F2C57F6DD00590239 /* YodaConditions.swift in Sources */, + 2E2BAD3B2C57F6DD00590239 /* Braces.swift in Sources */, + 2E2BABFF2C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */, + 2E2BACB72C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */, + 2E2BAD132C57F6DD00590239 /* Wrap.swift in Sources */, + 2E2BACDF2C57F6DD00590239 /* RedundantReturn.swift in Sources */, + 2E2BAC0F2C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */, + 2E2BAD072C57F6DD00590239 /* RedundantPattern.swift in Sources */, + 2E2BACE72C57F6DD00590239 /* TrailingSpace.swift in Sources */, + 2E2BACEF2C57F6DD00590239 /* ConditionalAssignment.swift in Sources */, + 2E2BAD172C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, + 2E2BAD332C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */, + 2E2BAC2F2C57F6DD00590239 /* SpaceInsideParens.swift in Sources */, + 2E2BADAB2C57F6DD00590239 /* SortSwitchCases.swift in Sources */, + 2E2BAC832C57F6DD00590239 /* Linebreaks.swift in Sources */, + 2E2BAC3F2C57F6DD00590239 /* DuplicateImports.swift in Sources */, + 2E2BAC1F2C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */, + 2E2BACBF2C57F6DD00590239 /* TypeSugar.swift in Sources */, + 2E2BAD372C57F6DD00590239 /* WrapSwitchCases.swift in Sources */, + 2E2BAD472C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, + 2E2BAD432C57F6DD00590239 /* AndOperator.swift in Sources */, + 2E2BAD4F2C57F6DD00590239 /* RedundantRawValues.swift in Sources */, + 2E2BAD1F2C57F6DD00590239 /* EnumNamespaces.swift in Sources */, + 2E2BAD232C57F6DD00590239 /* RedundantSelf.swift in Sources */, 01F3DF8C1DB9FD3F00454944 /* Options.swift in Sources */, + 2E2BAD6B2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */, 01A0EAC21D5DB4F700A0A8E3 /* Tokenizer.swift in Sources */, + 2E2BAD732C57F6DD00590239 /* TrailingClosures.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -868,28 +1650,139 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 01A0EAD51D5DC08A00A0A8E3 /* Rules.swift in Sources */, + 01A0EAD51D5DC08A00A0A8E3 /* FormatRule.swift in Sources */, + 2E2BAC6C2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */, + 2E2BAD2C2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, 01A0EAD61D5DC08A00A0A8E3 /* Tokenizer.swift in Sources */, + 2E2BACC82C57F6DD00590239 /* HeaderFileName.swift in Sources */, + 2E2BACA42C57F6DD00590239 /* Void.swift in Sources */, + 2E2BACF42C57F6DD00590239 /* PreferForLoop.swift in Sources */, + 2E2BACE42C57F6DD00590239 /* GenericExtensions.swift in Sources */, + 2E2BAC942C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, + 2E2BACDC2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, + 2E2BAD942C57F6DD00590239 /* NumberFormatting.swift in Sources */, + 2E2BAD442C57F6DD00590239 /* AndOperator.swift in Sources */, + 2E2BAC502C57F6DD00590239 /* SortImports.swift in Sources */, + 2E2BAD702C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + 2E2BAC7C2C57F6DD00590239 /* RedundantParens.swift in Sources */, + 2E2BAC002C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */, + 2E2BAC242C57F6DD00590239 /* RedundantInternal.swift in Sources */, + 2E2BAC1C2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, + 2E2BADB42C57F6DD00590239 /* SortDeclarations.swift in Sources */, + 2E2BAC642C57F6DD00590239 /* StrongOutlets.swift in Sources */, 01567D30225B2BFD00B22D41 /* ParsingHelpers.swift in Sources */, 01B3987F1D7634A0009ADE61 /* Formatter.swift in Sources */, + 2E2BAD802C57F6DD00590239 /* SortTypealiases.swift in Sources */, + 2E2BADA02C57F6DD00590239 /* YodaConditions.swift in Sources */, + 2E2BACA82C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, + 2E2BAC742C57F6DD00590239 /* EmptyBraces.swift in Sources */, + 2E2BAD242C57F6DD00590239 /* RedundantSelf.swift in Sources */, + 2E2BAC282C57F6DD00590239 /* RedundantNilInit.swift in Sources */, 01A0EAD41D5DC08A00A0A8E3 /* SwiftFormat.swift in Sources */, + 2E2BADAC2C57F6DD00590239 /* SortSwitchCases.swift in Sources */, + 2E2BAD882C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */, + 2E2BAD642C57F6DD00590239 /* SpaceAroundParens.swift in Sources */, DD9AD39F2999FCC8001C2C0E /* GithubActionsLogReporter.swift in Sources */, + 2E2BAC402C57F6DD00590239 /* DuplicateImports.swift in Sources */, + 2E2BAD782C57F6DD00590239 /* SortedSwitchCases.swift in Sources */, + 2E2BAD4C2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, E4E4D3CA2033F17C000D7CB1 /* EnumAssociable.swift in Sources */, 01BBD85A21DAA2A600457380 /* Globs.swift in Sources */, + 2E2BACBC2C57F6DD00590239 /* FileHeader.swift in Sources */, + 2E2BAC582C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */, + 2E2BADA42C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, + 2E2BAC0C2C57F6DD00590239 /* Indent.swift in Sources */, + 2E2BAD7C2C57F6DD00590239 /* Acronyms.swift in Sources */, + 2E2BAD842C57F6DD00590239 /* DocComments.swift in Sources */, + 2E2BACF02C57F6DD00590239 /* ConditionalAssignment.swift in Sources */, + 2E2BAC2C2C57F6DD00590239 /* Todos.swift in Sources */, 08180DCF2C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */, 01045A92211988F100D2BE3D /* Inference.swift in Sources */, + 2E2BACB02C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */, + 2E2BAC902C57F6DD00590239 /* RedundantLet.swift in Sources */, + 2E2BAC982C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */, + 2E2BAD602C57F6DD00590239 /* RedundantBackticks.swift in Sources */, + 2E2BAC482C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */, + 2E2BACE82C57F6DD00590239 /* TrailingSpace.swift in Sources */, + 2E2BADA82C57F6DD00590239 /* UnusedArguments.swift in Sources */, + 2E2BACF82C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */, + 2E2BAC442C57F6DD00590239 /* RedundantGet.swift in Sources */, 01F3DF8D1DB9FD3F00454944 /* Options.swift in Sources */, E4FABAD6202FEF060065716E /* OptionDescriptor.swift in Sources */, + 2E2BACCC2C57F6DD00590239 /* IsEmpty.swift in Sources */, D52F6A652A82E04600FE1448 /* GitFileInfo.swift in Sources */, + 2E2BAD382C57F6DD00590239 /* WrapSwitchCases.swift in Sources */, + 2E2BAC882C57F6DD00590239 /* LeadingDelimiters.swift in Sources */, + 2E2BAD142C57F6DD00590239 /* Wrap.swift in Sources */, + 2E2BAD682C57F6DD00590239 /* HoistAwait.swift in Sources */, + 2E2BAC842C57F6DD00590239 /* Linebreaks.swift in Sources */, + 2E2BAD182C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, + 2E2BAD402C57F6DD00590239 /* MarkTypes.swift in Sources */, A3DF48262620E03600F45A5F /* JSONReporter.swift in Sources */, + 2E2BACD02C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, 01A8320724EC7F7600A9D0EB /* FormattingHelpers.swift in Sources */, + 2E2BAD582C57F6DD00590239 /* StrongifiedSelf.swift in Sources */, + 2E2BAD302C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 2E2BAD902C57F6DD00590239 /* HoistTry.swift in Sources */, 01F17E831E25870700DCD359 /* CommandLine.swift in Sources */, + 2E2BACB42C57F6DD00590239 /* RedundantClosure.swift in Sources */, + 2E2BAD542C57F6DD00590239 /* TrailingCommas.swift in Sources */, + 2E2BAC042C57F6DD00590239 /* RedundantBreak.swift in Sources */, + 2E2BAD002C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */, + 2E2BACB82C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */, + 2E2BACC42C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */, + 2E2BAD3C2C57F6DD00590239 /* Braces.swift in Sources */, 015243E22B04B0A600F65221 /* Singularize.swift in Sources */, + 2E2BAC3C2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */, + 2E2BAD9C2C57F6DD00590239 /* Specifiers.swift in Sources */, + 2E2BAD742C57F6DD00590239 /* TrailingClosures.swift in Sources */, + 2E2BAC4C2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + 2E2BAC602C57F6DD00590239 /* BlockComments.swift in Sources */, + 2E2BAD0C2C57F6DD00590239 /* ApplicationMain.swift in Sources */, 01ACAE06220CD914003F3CCF /* Examples.swift in Sources */, + 2E2BAD102C57F6DD00590239 /* RedundantProperty.swift in Sources */, + 2E2BAC9C2C57F6DD00590239 /* RedundantInit.swift in Sources */, C2FFD1832BD13C9E00774F55 /* XMLReporter.swift in Sources */, + 2E2BAC5C2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */, + 2E2BACD82C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */, + 2E2BAC382C57F6DD00590239 /* HoistPatternLet.swift in Sources */, + 2E2BAD202C57F6DD00590239 /* EnumNamespaces.swift in Sources */, + 2E2BAD282C57F6DD00590239 /* PreferKeyPath.swift in Sources */, + 2E2BAD5C2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 2E2BAC082C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, + 2E2BAC802C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */, + 2E2BAC182C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, 01A0EACD1D5DB5F500A0A8E3 /* main.swift in Sources */, + 2E2BACA02C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, + 2E2BACFC2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */, + 2E2BAB8D2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */, + 2E2BAC782C57F6DD00590239 /* SpaceAroundComments.swift in Sources */, + 2E2BAC682C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */, + 2E2BAC8C2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */, + 2E2BAC102C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */, + 2E2BACC02C57F6DD00590239 /* TypeSugar.swift in Sources */, + 2E2BACEC2C57F6DD00590239 /* RedundantObjc.swift in Sources */, + 2E2BACE02C57F6DD00590239 /* RedundantReturn.swift in Sources */, + 2E2BAC142C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */, + 2E2BAC702C57F6DD00590239 /* AssertionFailures.swift in Sources */, + 2E2BAD342C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */, DD9AD3A42999FCC8001C2C0E /* Reporter.swift in Sources */, + 2E2BAD1C2C57F6DD00590239 /* ModifierOrder.swift in Sources */, + 2E2BAD982C57F6DD00590239 /* WrapArguments.swift in Sources */, + 2E2BAC342C57F6DD00590239 /* Semicolons.swift in Sources */, + 2E2BAD502C57F6DD00590239 /* RedundantRawValues.swift in Sources */, + 2E2BACAC2C57F6DD00590239 /* RedundantLetError.swift in Sources */, 01045A9A2119979400D2BE3D /* Arguments.swift in Sources */, + 2E2BAD482C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, + 2E2BAD082C57F6DD00590239 /* RedundantPattern.swift in Sources */, + 2E2BAD042C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, + 2E2BADB02C57F6DD00590239 /* RedundantType.swift in Sources */, + 2E2BACD42C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */, + 2E2BAC542C57F6DD00590239 /* SortedImports.swift in Sources */, + 2E2BAD8C2C57F6DD00590239 /* PropertyType.swift in Sources */, + 2E2BAC202C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */, + 2E2BAD6C2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */, + 2E2BAC302C57F6DD00590239 /* SpaceInsideParens.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -897,31 +1790,142 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2E2BAD6D2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */, + 2E2BAC252C57F6DD00590239 /* RedundantInternal.swift in Sources */, + 2E2BADB52C57F6DD00590239 /* SortDeclarations.swift in Sources */, + 2E2BACB92C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */, + 2E2BAD7D2C57F6DD00590239 /* Acronyms.swift in Sources */, + 2E2BAD752C57F6DD00590239 /* TrailingClosures.swift in Sources */, + 2E2BADAD2C57F6DD00590239 /* SortSwitchCases.swift in Sources */, E4083191202C049200CAF11D /* SwiftFormat.swift in Sources */, E4FABAD7202FEF060065716E /* OptionDescriptor.swift in Sources */, + 2E2BAD792C57F6DD00590239 /* SortedSwitchCases.swift in Sources */, + 2E2BAD412C57F6DD00590239 /* MarkTypes.swift in Sources */, + 2E2BAC092C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, + 2E2BAC0D2C57F6DD00590239 /* Indent.swift in Sources */, 015D3A562995A0340065B2D9 /* AboutViewController.swift in Sources */, + 2E2BACAD2C57F6DD00590239 /* RedundantLetError.swift in Sources */, + 2E2BACB12C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */, + 2E2BAD092C57F6DD00590239 /* RedundantPattern.swift in Sources */, E41CB5C52027700100C1BEDE /* FreeTextTableCellView.swift in Sources */, + 2E2BAD992C57F6DD00590239 /* WrapArguments.swift in Sources */, + 2E2BAC852C57F6DD00590239 /* Linebreaks.swift in Sources */, + 2E2BACE52C57F6DD00590239 /* GenericExtensions.swift in Sources */, + 2E2BAC652C57F6DD00590239 /* StrongOutlets.swift in Sources */, E4872125201D980D0014845E /* BinarySelectionTableCellView.swift in Sources */, + 2E2BAD392C57F6DD00590239 /* WrapSwitchCases.swift in Sources */, + 2E2BAC392C57F6DD00590239 /* HoistPatternLet.swift in Sources */, + 2E2BADA52C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, + 2E2BACC12C57F6DD00590239 /* TypeSugar.swift in Sources */, + 2E2BACA12C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, + 2E2BACD92C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */, + 2E2BAC792C57F6DD00590239 /* SpaceAroundComments.swift in Sources */, + 2E2BAC3D2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */, + 2E2BAC612C57F6DD00590239 /* BlockComments.swift in Sources */, + 2E2BACC52C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */, + 2E2BAC8D2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */, + 2E2BACA92C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, + 2E2BAC452C57F6DD00590239 /* RedundantGet.swift in Sources */, + 2E2BAD212C57F6DD00590239 /* EnumNamespaces.swift in Sources */, + 2E2BACED2C57F6DD00590239 /* RedundantObjc.swift in Sources */, + 2E2BAC692C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */, E487211D201D885A0014845E /* RulesViewController.swift in Sources */, + 2E2BAC212C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */, 01045A9B2119979400D2BE3D /* Arguments.swift in Sources */, + 2E2BAC712C57F6DD00590239 /* AssertionFailures.swift in Sources */, E4872114201D3B8C0014845E /* Tokenizer.swift in Sources */, + 2E2BAD612C57F6DD00590239 /* RedundantBackticks.swift in Sources */, + 2E2BAC752C57F6DD00590239 /* EmptyBraces.swift in Sources */, + 2E2BACF92C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */, + 2E2BAC512C57F6DD00590239 /* SortImports.swift in Sources */, + 2E2BAC112C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */, 015243E32B04B0A600F65221 /* Singularize.swift in Sources */, - E4872112201D3B860014845E /* Rules.swift in Sources */, + 2E2BAD552C57F6DD00590239 /* TrailingCommas.swift in Sources */, + 2E2BAD012C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */, + 2E2BAD652C57F6DD00590239 /* SpaceAroundParens.swift in Sources */, + 2E2BAC412C57F6DD00590239 /* DuplicateImports.swift in Sources */, + 2E2BADA12C57F6DD00590239 /* YodaConditions.swift in Sources */, + 2E2BAD8D2C57F6DD00590239 /* PropertyType.swift in Sources */, + E4872112201D3B860014845E /* FormatRule.swift in Sources */, + 2E2BAB8E2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */, + 2E2BAC892C57F6DD00590239 /* LeadingDelimiters.swift in Sources */, + 2E2BACD52C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */, + 2E2BACA52C57F6DD00590239 /* Void.swift in Sources */, + 2E2BAC492C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */, + 2E2BAC812C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */, + 2E2BAD052C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, + 2E2BAD892C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */, E4962DE0203F3CD500A02013 /* OptionsStore.swift in Sources */, + 2E2BAD2D2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, + 2E2BACDD2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, 01ACAE07220CD915003F3CCF /* Examples.swift in Sources */, + 2E2BAD692C57F6DD00590239 /* HoistAwait.swift in Sources */, + 2E2BAD192C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, + 2E2BAC052C57F6DD00590239 /* RedundantBreak.swift in Sources */, + 2E2BAC192C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, + 2E2BAC7D2C57F6DD00590239 /* RedundantParens.swift in Sources */, + 2E2BAC592C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */, E4872113201D3B890014845E /* Formatter.swift in Sources */, 08180DD02C4EB67F00FD60FF /* DeclarationHelpers.swift in Sources */, E4E4D3CB2033F17C000D7CB1 /* EnumAssociable.swift in Sources */, + 2E2BAC992C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */, + 2E2BACE92C57F6DD00590239 /* TrailingSpace.swift in Sources */, + 2E2BAC4D2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + 2E2BAC6D2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */, + 2E2BAD592C57F6DD00590239 /* StrongifiedSelf.swift in Sources */, + 2E2BAD912C57F6DD00590239 /* HoistTry.swift in Sources */, + 2E2BADA92C57F6DD00590239 /* UnusedArguments.swift in Sources */, + 2E2BAD452C57F6DD00590239 /* AndOperator.swift in Sources */, + 2E2BAC312C57F6DD00590239 /* SpaceInsideParens.swift in Sources */, + 2E2BAD5D2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 2E2BAD492C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, + 2E2BACF52C57F6DD00590239 /* PreferForLoop.swift in Sources */, + 2E2BACCD2C57F6DD00590239 /* IsEmpty.swift in Sources */, + 2E2BAD3D2C57F6DD00590239 /* Braces.swift in Sources */, + 2E2BAD712C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + 2E2BAC9D2C57F6DD00590239 /* RedundantInit.swift in Sources */, + 2E2BACB52C57F6DD00590239 /* RedundantClosure.swift in Sources */, + 2E2BAD0D2C57F6DD00590239 /* ApplicationMain.swift in Sources */, + 2E2BAC2D2C57F6DD00590239 /* Todos.swift in Sources */, + 2E2BAD852C57F6DD00590239 /* DocComments.swift in Sources */, + 2E2BAC292C57F6DD00590239 /* RedundantNilInit.swift in Sources */, + 2E2BADB12C57F6DD00590239 /* RedundantType.swift in Sources */, 01BBD85B21DAA2A700457380 /* Globs.swift in Sources */, + 2E2BAD112C57F6DD00590239 /* RedundantProperty.swift in Sources */, 01A8320824EC7F7700A9D0EB /* FormattingHelpers.swift in Sources */, + 2E2BACFD2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */, + 2E2BACF12C57F6DD00590239 /* ConditionalAssignment.swift in Sources */, + 2E2BAD4D2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, 01045A93211988F100D2BE3D /* Inference.swift in Sources */, E41CB5C32026CACD00C1BEDE /* ListSelectionTableCellView.swift in Sources */, + 2E2BAC912C57F6DD00590239 /* RedundantLet.swift in Sources */, + 2E2BAD292C57F6DD00590239 /* PreferKeyPath.swift in Sources */, E4872129201E3DD50014845E /* RulesStore.swift in Sources */, + 2E2BAC152C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */, + 2E2BACBD2C57F6DD00590239 /* FileHeader.swift in Sources */, E41CB5BF2025761D00C1BEDE /* UserSelection.swift in Sources */, + 2E2BAC952C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, + 2E2BAC1D2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, + 2E2BACD12C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, + 2E2BAD312C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 2E2BAD9D2C57F6DD00590239 /* Specifiers.swift in Sources */, E4872111201D3B830014845E /* Options.swift in Sources */, + 2E2BACC92C57F6DD00590239 /* HeaderFileName.swift in Sources */, + 2E2BAD252C57F6DD00590239 /* RedundantSelf.swift in Sources */, 01A95BD3225BEDE400744931 /* ParsingHelpers.swift in Sources */, + 2E2BACE12C57F6DD00590239 /* RedundantReturn.swift in Sources */, D52F6A662A82E04600FE1448 /* GitFileInfo.swift in Sources */, + 2E2BAD952C57F6DD00590239 /* NumberFormatting.swift in Sources */, + 2E2BAD812C57F6DD00590239 /* SortTypealiases.swift in Sources */, 90C4B6CD1DA4B04A009EB000 /* AppDelegate.swift in Sources */, + 2E2BAC552C57F6DD00590239 /* SortedImports.swift in Sources */, + 2E2BAD352C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */, + 2E2BAC012C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */, + 2E2BAC352C57F6DD00590239 /* Semicolons.swift in Sources */, + 2E2BAD512C57F6DD00590239 /* RedundantRawValues.swift in Sources */, + 2E2BAC5D2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */, + 2E2BAD152C57F6DD00590239 /* Wrap.swift in Sources */, + 2E2BAD1D2C57F6DD00590239 /* ModifierOrder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -929,31 +1933,142 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 2E2BAD6E2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift in Sources */, + 2E2BAC262C57F6DD00590239 /* RedundantInternal.swift in Sources */, + 2E2BADB62C57F6DD00590239 /* SortDeclarations.swift in Sources */, + 2E2BACBA2C57F6DD00590239 /* OrganizeDeclarations.swift in Sources */, + 2E2BAD7E2C57F6DD00590239 /* Acronyms.swift in Sources */, + 2E2BAD762C57F6DD00590239 /* TrailingClosures.swift in Sources */, + 2E2BADAE2C57F6DD00590239 /* SortSwitchCases.swift in Sources */, 01045AA0211A1EE300D2BE3D /* Arguments.swift in Sources */, 01BBD85C21DAA2A700457380 /* Globs.swift in Sources */, + 2E2BAD7A2C57F6DD00590239 /* SortedSwitchCases.swift in Sources */, + 2E2BAD422C57F6DD00590239 /* MarkTypes.swift in Sources */, + 2E2BAC0A2C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, + 2E2BAC0E2C57F6DD00590239 /* Indent.swift in Sources */, 01045A9F2119D30D00D2BE3D /* Inference.swift in Sources */, + 2E2BACAE2C57F6DD00590239 /* RedundantLetError.swift in Sources */, + 2E2BACB22C57F6DD00590239 /* BlankLinesBetweenScopes.swift in Sources */, + 2E2BAD0A2C57F6DD00590239 /* RedundantPattern.swift in Sources */, 01A95BD2225BEDE300744931 /* ParsingHelpers.swift in Sources */, + 2E2BAD9A2C57F6DD00590239 /* WrapArguments.swift in Sources */, + 2E2BAC862C57F6DD00590239 /* Linebreaks.swift in Sources */, + 2E2BACE62C57F6DD00590239 /* GenericExtensions.swift in Sources */, + 2E2BAC662C57F6DD00590239 /* StrongOutlets.swift in Sources */, 90C4B6E51DA4B059009EB000 /* SourceEditorExtension.swift in Sources */, + 2E2BAD3A2C57F6DD00590239 /* WrapSwitchCases.swift in Sources */, + 2E2BAC3A2C57F6DD00590239 /* HoistPatternLet.swift in Sources */, + 2E2BADA62C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, + 2E2BACC22C57F6DD00590239 /* TypeSugar.swift in Sources */, + 2E2BACA22C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, + 2E2BACDA2C57F6DD00590239 /* ExtensionAccessControl.swift in Sources */, + 2E2BAC7A2C57F6DD00590239 /* SpaceAroundComments.swift in Sources */, + 2E2BAC3E2C57F6DD00590239 /* ElseOnSameLine.swift in Sources */, + 2E2BAC622C57F6DD00590239 /* BlockComments.swift in Sources */, + 2E2BACC62C57F6DD00590239 /* SpaceInsideBrackets.swift in Sources */, + 2E2BAC8E2C57F6DD00590239 /* SpaceInsideComments.swift in Sources */, + 2E2BACAA2C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, + 2E2BAC462C57F6DD00590239 /* RedundantGet.swift in Sources */, + 2E2BAD222C57F6DD00590239 /* EnumNamespaces.swift in Sources */, + 2E2BACEE2C57F6DD00590239 /* RedundantObjc.swift in Sources */, + 2E2BAC6A2C57F6DD00590239 /* LinebreakAtEndOfFile.swift in Sources */, 0142C77023C3FB6D005D5832 /* LintFileCommand.swift in Sources */, + 2E2BAC222C57F6DD00590239 /* RedundantOptionalBinding.swift in Sources */, E4E4D3CC2033F17C000D7CB1 /* EnumAssociable.swift in Sources */, + 2E2BAC722C57F6DD00590239 /* AssertionFailures.swift in Sources */, E4FABAD8202FEF060065716E /* OptionDescriptor.swift in Sources */, + 2E2BAD622C57F6DD00590239 /* RedundantBackticks.swift in Sources */, + 2E2BAC762C57F6DD00590239 /* EmptyBraces.swift in Sources */, + 2E2BACFA2C57F6DD00590239 /* RedundantStaticSelf.swift in Sources */, + 2E2BAC522C57F6DD00590239 /* SortImports.swift in Sources */, + 2E2BAC122C57F6DD00590239 /* WrapMultilineConditionalAssignment.swift in Sources */, 015243E42B04B0A700F65221 /* Singularize.swift in Sources */, + 2E2BAD562C57F6DD00590239 /* TrailingCommas.swift in Sources */, + 2E2BAD022C57F6DD00590239 /* WrapMultilineStatementBraces.swift in Sources */, + 2E2BAD662C57F6DD00590239 /* SpaceAroundParens.swift in Sources */, + 2E2BAC422C57F6DD00590239 /* DuplicateImports.swift in Sources */, + 2E2BADA22C57F6DD00590239 /* YodaConditions.swift in Sources */, + 2E2BAD8E2C57F6DD00590239 /* PropertyType.swift in Sources */, 9028F7841DA4B435009FE5B4 /* Tokenizer.swift in Sources */, + 2E2BAB8F2C57F6B600590239 /* RuleRegistry.generated.swift in Sources */, + 2E2BAC8A2C57F6DD00590239 /* LeadingDelimiters.swift in Sources */, + 2E2BACD62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift in Sources */, + 2E2BACA62C57F6DD00590239 /* Void.swift in Sources */, + 2E2BAC4A2C57F6DD00590239 /* SpaceAroundOperators.swift in Sources */, + 2E2BAC822C57F6DD00590239 /* SpaceAroundGenerics.swift in Sources */, + 2E2BAD062C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, + 2E2BAD8A2C57F6DD00590239 /* UnusedPrivateDeclaration.swift in Sources */, 90F16AFB1DA5ED9A00EB4EA1 /* CommandErrors.swift in Sources */, + 2E2BAD2E2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, + 2E2BACDE2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, 01A8320924EC7F7800A9D0EB /* FormattingHelpers.swift in Sources */, + 2E2BAD6A2C57F6DD00590239 /* HoistAwait.swift in Sources */, + 2E2BAD1A2C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, + 2E2BAC062C57F6DD00590239 /* RedundantBreak.swift in Sources */, + 2E2BAC1A2C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, + 2E2BAC7E2C57F6DD00590239 /* RedundantParens.swift in Sources */, + 2E2BAC5A2C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift in Sources */, 018541CF1DBA0F17000F82E3 /* XCSourceTextBuffer+SwiftFormat.swift in Sources */, 08180DD12C4EB68000FD60FF /* DeclarationHelpers.swift in Sources */, E4962DE1203F3CD500A02013 /* OptionsStore.swift in Sources */, + 2E2BAC9A2C57F6DD00590239 /* ConsecutiveBlankLines.swift in Sources */, + 2E2BACEA2C57F6DD00590239 /* TrailingSpace.swift in Sources */, + 2E2BAC4E2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + 2E2BAC6E2C57F6DD00590239 /* SpaceInsideGenerics.swift in Sources */, + 2E2BAD5A2C57F6DD00590239 /* StrongifiedSelf.swift in Sources */, + 2E2BAD922C57F6DD00590239 /* HoistTry.swift in Sources */, + 2E2BADAA2C57F6DD00590239 /* UnusedArguments.swift in Sources */, + 2E2BAD462C57F6DD00590239 /* AndOperator.swift in Sources */, + 2E2BAC322C57F6DD00590239 /* SpaceInsideParens.swift in Sources */, + 2E2BAD5E2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 2E2BAD4A2C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, + 2E2BACF62C57F6DD00590239 /* PreferForLoop.swift in Sources */, + 2E2BACCE2C57F6DD00590239 /* IsEmpty.swift in Sources */, + 2E2BAD3E2C57F6DD00590239 /* Braces.swift in Sources */, + 2E2BAD722C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + 2E2BAC9E2C57F6DD00590239 /* RedundantInit.swift in Sources */, + 2E2BACB62C57F6DD00590239 /* RedundantClosure.swift in Sources */, + 2E2BAD0E2C57F6DD00590239 /* ApplicationMain.swift in Sources */, + 2E2BAC2E2C57F6DD00590239 /* Todos.swift in Sources */, + 2E2BAD862C57F6DD00590239 /* DocComments.swift in Sources */, + 2E2BAC2A2C57F6DD00590239 /* RedundantNilInit.swift in Sources */, + 2E2BADB22C57F6DD00590239 /* RedundantType.swift in Sources */, 9028F7851DA4B435009FE5B4 /* Formatter.swift in Sources */, + 2E2BAD122C57F6DD00590239 /* RedundantProperty.swift in Sources */, E487212A201E3DD50014845E /* RulesStore.swift in Sources */, + 2E2BACFE2C57F6DD00590239 /* BlankLinesBetweenImports.swift in Sources */, + 2E2BACF22C57F6DD00590239 /* ConditionalAssignment.swift in Sources */, + 2E2BAD4E2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, 01F3DF8E1DB9FD3F00454944 /* Options.swift in Sources */, 9028F7831DA4B435009FE5B4 /* SwiftFormat.swift in Sources */, + 2E2BAC922C57F6DD00590239 /* RedundantLet.swift in Sources */, + 2E2BAD2A2C57F6DD00590239 /* PreferKeyPath.swift in Sources */, B9C4F55C2387FA3E0088DBEE /* SupportedContentUTIs.swift in Sources */, + 2E2BAC162C57F6DD00590239 /* ConsecutiveSpaces.swift in Sources */, + 2E2BACBE2C57F6DD00590239 /* FileHeader.swift in Sources */, 90C4B6E71DA4B059009EB000 /* FormatSelectionCommand.swift in Sources */, + 2E2BAC962C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, + 2E2BAC1E2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, + 2E2BACD22C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, + 2E2BAD322C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 2E2BAD9E2C57F6DD00590239 /* Specifiers.swift in Sources */, 90F16AF81DA5EB4600EB4EA1 /* FormatFileCommand.swift in Sources */, + 2E2BACCA2C57F6DD00590239 /* HeaderFileName.swift in Sources */, + 2E2BAD262C57F6DD00590239 /* RedundantSelf.swift in Sources */, 01ACAE08220CD916003F3CCF /* Examples.swift in Sources */, + 2E2BACE22C57F6DD00590239 /* RedundantReturn.swift in Sources */, D52F6A672A82E04600FE1448 /* GitFileInfo.swift in Sources */, - 9028F7861DA4B435009FE5B4 /* Rules.swift in Sources */, + 2E2BAD962C57F6DD00590239 /* NumberFormatting.swift in Sources */, + 2E2BAD822C57F6DD00590239 /* SortTypealiases.swift in Sources */, + 9028F7861DA4B435009FE5B4 /* FormatRule.swift in Sources */, + 2E2BAC562C57F6DD00590239 /* SortedImports.swift in Sources */, + 2E2BAD362C57F6DD00590239 /* WrapConditionalBodies.swift in Sources */, + 2E2BAC022C57F6DD00590239 /* InitCoderUnavailable.swift in Sources */, + 2E2BAC362C57F6DD00590239 /* Semicolons.swift in Sources */, + 2E2BAD522C57F6DD00590239 /* RedundantRawValues.swift in Sources */, + 2E2BAC5E2C57F6DD00590239 /* RedundantFileprivate.swift in Sources */, + 2E2BAD162C57F6DD00590239 /* Wrap.swift in Sources */, + 2E2BAD1E2C57F6DD00590239 /* ModifierOrder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -995,6 +2110,7 @@ CLANG_ENABLE_CODE_COVERAGE = NO; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; + "DEVELOPMENT_TEAM[sdk=macosx*]" = ""; ENABLE_TESTABILITY = YES; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -1014,8 +2130,10 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_CODE_COVERAGE = NO; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; + "DEVELOPMENT_TEAM[sdk=macosx*]" = ""; ENABLE_TESTABILITY = YES; INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( diff --git a/Tests/CommandLineTests.swift b/Tests/CommandLineTests.swift index e061ba99b..dee617d14 100644 --- a/Tests/CommandLineTests.swift +++ b/Tests/CommandLineTests.swift @@ -398,12 +398,12 @@ class CommandLineTests: XCTestCase { Package.swift #bar #baz - Sources/Rules.swift + Sources/FormatRule.swift CommandLineTool/*.swift """ XCTAssertEqual(try parseFileList(source, in: projectDirectory.path), [ URL(fileURLWithPath: "\(projectDirectory.path)/Package.swift"), - URL(fileURLWithPath: "\(projectDirectory.path)/Sources/Rules.swift"), + URL(fileURLWithPath: "\(projectDirectory.path)/Sources/FormatRule.swift"), URL(fileURLWithPath: "\(projectDirectory.path)/CommandLineTool/main.swift"), ]) } diff --git a/Tests/FormatterTests.swift b/Tests/FormatterTests.swift index 69fba48bd..a9dc5ffc6 100644 --- a/Tests/FormatterTests.swift +++ b/Tests/FormatterTests.swift @@ -392,7 +392,7 @@ class FormatterTests: XCTestCase { .linebreak("\n", 3), .endOfScope("}"), ]) - FormatRules.blankLinesAtStartOfScope.apply(with: formatter) + FormatRule.blankLinesAtStartOfScope.apply(with: formatter) XCTAssertEqual(formatter.tokens, [ .identifier("foo"), .space(" "), @@ -427,7 +427,7 @@ class FormatterTests: XCTestCase { """) XCTAssertEqual(try format( input, - rules: [FormatRules.consecutiveSpaces], + rules: [.consecutiveSpaces], range: 10 ..< 13 ), output1) let output2 = """ @@ -437,7 +437,7 @@ class FormatterTests: XCTestCase { """ XCTAssertEqual(try sourceCode(for: format( input, - rules: [FormatRules.blankLinesAtStartOfScope], + rules: [.blankLinesAtStartOfScope], range: 6 ..< 9 )), output2) } diff --git a/Tests/GlobsTests.swift b/Tests/GlobsTests.swift index 543461f72..4c4c9bef6 100644 --- a/Tests/GlobsTests.swift +++ b/Tests/GlobsTests.swift @@ -20,14 +20,14 @@ class GlobsTests: XCTestCase { } func testExpandPathWithWildcardInMiddle() { - let path = "Rule*.swift" + let path = "FormatRul*.swift" let directory = URL(fileURLWithPath: #file) .deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Sources") XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 1) } func testExpandPathWithSingleCharacterWildcardInMiddle() { - let path = "Rule?.swift" + let path = "FormatRul?.swift" let directory = URL(fileURLWithPath: #file) .deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Sources") XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 1) diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index 74af2a9b4..4ddc6a819 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -28,6 +28,21 @@ private let rulesURL = private let rulesFile = try! String(contentsOf: rulesURL, encoding: .utf8) +private let ruleRegistryURL = + projectDirectory.appendingPathComponent("Sources/RuleRegistry.generated.swift") + +private let allRuleFiles: [URL] = { + var rulesFiles: [URL] = [] + let rulesDirectory = projectDirectory.appendingPathComponent("Sources/Rules") + _ = enumerateFiles(withInputURL: rulesDirectory) { ruleFileURL, _, _ in + { + guard ruleFileURL.pathExtension == "swift" else { return } + rulesFiles.append(ruleFileURL) + } + } + return rulesFiles +}() + private let swiftFormatVersion: String = { let string = try! String(contentsOf: projectURL) let start = string.range(of: "MARKETING_VERSION = ")!.upperBound @@ -143,119 +158,122 @@ class MetadataTests: XCTestCase { // MARK: options func testRulesOptions() throws { + var allOptions = Set(formattingArguments).subtracting(deprecatedArguments) + var allSharedOptions = allOptions var optionsByProperty = [String: OptionDescriptor]() for descriptor in Descriptors.formatting.reversed() { optionsByProperty[descriptor.propertyName] = descriptor } - let rulesFile = projectDirectory.appendingPathComponent("Sources/Rules.swift") - let rulesSource = try String(contentsOf: rulesFile, encoding: .utf8) - let tokens = tokenize(rulesSource) - let formatter = Formatter(tokens) - var rulesByOption = [String: String]() - var allOptions = Set(formattingArguments).subtracting(deprecatedArguments) - var allSharedOptions = allOptions - formatter.forEach(.identifier("FormatRule")) { i, _ in - guard formatter.next(.nonSpaceOrLinebreak, after: i) == .startOfScope("("), - case let .identifier(name)? = formatter.last(.identifier, before: i), - let scopeStart = formatter.index(of: .startOfScope("{"), after: i), - let scopeEnd = formatter.index(of: .endOfScope("}"), after: scopeStart), - let rule = FormatRules.byName[name] - else { - return - } - for option in rule.options where !rule.isDeprecated { - if let oldName = rulesByOption[option] { - XCTFail("\(option) set as (non-shared) option for both \(name) and \(oldName)") - } - rulesByOption[option] = name - } - let ruleOptions = rule.options + rule.sharedOptions - allOptions.subtract(rule.options) - allSharedOptions.subtract(ruleOptions) - var referencedOptions = [OptionDescriptor]() - for index in scopeStart + 1 ..< scopeEnd { - guard formatter.token(at: index - 1) == .operator(".", .infix), - formatter.token(at: index - 2) == .identifier("formatter") + + for rulesFile in allRuleFiles { + let rulesSource = try String(contentsOf: rulesFile, encoding: .utf8) + let tokens = tokenize(rulesSource) + let formatter = Formatter(tokens) + var rulesByOption = [String: String]() + formatter.forEach(.identifier("FormatRule")) { i, _ in + guard formatter.next(.nonSpaceOrLinebreak, after: i) == .startOfScope("("), + case let .identifier(name)? = formatter.last(.identifier, before: i), + let scopeStart = formatter.index(of: .startOfScope("{"), after: i), + let scopeEnd = formatter.index(of: .endOfScope("}"), after: scopeStart), + let rule = FormatRules.byName[name] else { - continue + return } - switch formatter.tokens[index] { - case .identifier("spaceEquivalentToWidth"), - .identifier("spaceEquivalentToTokens"): - referencedOptions += [ - Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, - ] - case .identifier("tokenLength"): - referencedOptions += [Descriptors.indent, Descriptors.tabWidth] - case .identifier("lineLength"): - referencedOptions += [ - Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, - ] - case .identifier("isCommentedCode"): - referencedOptions.append(Descriptors.indent) - case .identifier("insertLinebreak"), .identifier("linebreakToken"): - referencedOptions.append(Descriptors.linebreak) - case .identifier("wrapCollectionsAndArguments"): - referencedOptions += [ - Descriptors.wrapArguments, Descriptors.wrapParameters, Descriptors.wrapCollections, - Descriptors.closingParenPosition, Descriptors.callSiteClosingParenPosition, - Descriptors.linebreak, Descriptors.truncateBlankLines, - Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, Descriptors.maxWidth, - Descriptors.assetLiteralWidth, Descriptors.wrapReturnType, Descriptors.wrapEffects, - Descriptors.wrapConditions, Descriptors.wrapTypealiases, Descriptors.wrapTernaryOperators, Descriptors.conditionsWrap, - ] - case .identifier("wrapStatementBody"): - referencedOptions += [Descriptors.indent, Descriptors.linebreak] - case .identifier("indexWhereLineShouldWrapInLine"), .identifier("indexWhereLineShouldWrap"): - referencedOptions += [ - Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, - Descriptors.noWrapOperators, - ] - case .identifier("modifierOrder"): - referencedOptions.append(Descriptors.modifierOrder) - case .identifier("options") where formatter.token(at: index + 1) == .operator(".", .infix): - if case let .identifier(property)? = formatter.token(at: index + 2), - let option = optionsByProperty[property] - { - referencedOptions.append(option) + for option in rule.options where !rule.isDeprecated { + if let oldName = rulesByOption[option] { + XCTFail("\(option) set as (non-shared) option for both \(name) and \(oldName)") + } + rulesByOption[option] = name + } + let ruleOptions = rule.options + rule.sharedOptions + allOptions.subtract(rule.options) + allSharedOptions.subtract(ruleOptions) + var referencedOptions = [OptionDescriptor]() + for index in scopeStart + 1 ..< scopeEnd { + guard formatter.token(at: index - 1) == .operator(".", .infix), + formatter.token(at: index - 2) == .identifier("formatter") + else { + continue + } + switch formatter.tokens[index] { + case .identifier("spaceEquivalentToWidth"), + .identifier("spaceEquivalentToTokens"): + referencedOptions += [ + Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, + ] + case .identifier("tokenLength"): + referencedOptions += [Descriptors.indent, Descriptors.tabWidth] + case .identifier("lineLength"): + referencedOptions += [ + Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, + ] + case .identifier("isCommentedCode"): + referencedOptions.append(Descriptors.indent) + case .identifier("insertLinebreak"), .identifier("linebreakToken"): + referencedOptions.append(Descriptors.linebreak) + case .identifier("wrapCollectionsAndArguments"): + referencedOptions += [ + Descriptors.wrapArguments, Descriptors.wrapParameters, Descriptors.wrapCollections, + Descriptors.closingParenPosition, Descriptors.callSiteClosingParenPosition, + Descriptors.linebreak, Descriptors.truncateBlankLines, + Descriptors.indent, Descriptors.tabWidth, Descriptors.smartTabs, Descriptors.maxWidth, + Descriptors.assetLiteralWidth, Descriptors.wrapReturnType, Descriptors.wrapEffects, + Descriptors.wrapConditions, Descriptors.wrapTypealiases, Descriptors.wrapTernaryOperators, Descriptors.conditionsWrap, + ] + case .identifier("wrapStatementBody"): + referencedOptions += [Descriptors.indent, Descriptors.linebreak] + case .identifier("indexWhereLineShouldWrapInLine"), .identifier("indexWhereLineShouldWrap"): + referencedOptions += [ + Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, + Descriptors.noWrapOperators, + ] + case .identifier("modifierOrder"): + referencedOptions.append(Descriptors.modifierOrder) + case .identifier("options") where formatter.token(at: index + 1) == .operator(".", .infix): + if case let .identifier(property)? = formatter.token(at: index + 2), + let option = optionsByProperty[property] + { + referencedOptions.append(option) + } + case .identifier("organizeDeclaration"): + referencedOptions += [ + Descriptors.categoryMarkComment, + Descriptors.markCategories, + Descriptors.beforeMarks, + Descriptors.lifecycleMethods, + Descriptors.organizeTypes, + Descriptors.organizeStructThreshold, + Descriptors.organizeClassThreshold, + Descriptors.organizeEnumThreshold, + Descriptors.organizeExtensionThreshold, + Descriptors.lineAfterMarks, + Descriptors.organizationMode, + Descriptors.alphabeticallySortedDeclarationPatterns, + Descriptors.visibilityOrder, + Descriptors.typeOrder, + Descriptors.customVisibilityMarks, + Descriptors.customTypeMarks, + ] + case .identifier("removeSelf"): + referencedOptions += [ + Descriptors.selfRequired, + ] + default: + continue } - case .identifier("organizeDeclaration"): - referencedOptions += [ - Descriptors.categoryMarkComment, - Descriptors.markCategories, - Descriptors.beforeMarks, - Descriptors.lifecycleMethods, - Descriptors.organizeTypes, - Descriptors.organizeStructThreshold, - Descriptors.organizeClassThreshold, - Descriptors.organizeEnumThreshold, - Descriptors.organizeExtensionThreshold, - Descriptors.lineAfterMarks, - Descriptors.organizationMode, - Descriptors.alphabeticallySortedDeclarationPatterns, - Descriptors.visibilityOrder, - Descriptors.typeOrder, - Descriptors.customVisibilityMarks, - Descriptors.customTypeMarks, - ] - case .identifier("removeSelf"): - referencedOptions += [ - Descriptors.selfRequired, - ] - default: - continue } - } - for option in referencedOptions { - XCTAssert(ruleOptions.contains(option.argumentName) || option.isDeprecated, - "\(option.argumentName) not listed in \(name) rule") - } - for argName in ruleOptions { - XCTAssert(referencedOptions.contains { $0.argumentName == argName }, - "\(argName) not used in \(name) rule") + for option in referencedOptions { + XCTAssert(ruleOptions.contains(option.argumentName) || option.isDeprecated, + "\(option.argumentName) not listed in \(name) rule") + } + for argName in ruleOptions { + XCTAssert(referencedOptions.contains { $0.argumentName == argName }, + "\(argName) not used in \(name) rule") + } } } + XCTAssert(allSharedOptions.isEmpty, "Options \(allSharedOptions.joined(separator: ",")) not shared by any rule)") XCTAssert(allOptions.isEmpty, "Options \(allSharedOptions.joined(separator: ",")) not owned by any rule)") } @@ -299,8 +317,12 @@ class MetadataTests: XCTestCase { // MARK: keywords func testContextualKeywordsReferencedCorrectly() throws { - for file in ["Rules", "ParsingHelpers", "FormattingHelpers"] { - let sourceFile = projectDirectory.appendingPathComponent("Sources/\(file).swift") + let filesToVerify = allRuleFiles + [ + projectDirectory.appendingPathComponent("Sources/ParsingHelpers.swift"), + projectDirectory.appendingPathComponent("Sources/FormattingHelpers.swift"), + ] + + for sourceFile in filesToVerify { let fileSource = try String(contentsOf: sourceFile, encoding: .utf8) let tokens = tokenize(fileSource) let formatter = Formatter(tokens) @@ -323,7 +345,7 @@ class MetadataTests: XCTestCase { } guard keywords.contains(keyword) || keyword.hasPrefix("#") || keyword.hasPrefix("@") else { let line = formatter.originalLine(at: i) - XCTFail("'\(keyword)' referenced on line \(line) of '\(file).swift' is not a valid Swift keyword. " + XCTFail("'\(keyword)' referenced on line \(line) of '\(sourceFile)' is not a valid Swift keyword. " + "Contextual keywords should be referenced with `.identifier(...)`") return } @@ -339,16 +361,6 @@ class MetadataTests: XCTestCase { } } - // MARK: order - - func testRuleOrderNamesAreValid() { - for rule in FormatRules.all { - for name in rule.orderAfter { - XCTAssert(FormatRules.byName[name] != nil, "\(name) rule does not exist") - } - } - } - // MARK: releases func testLatestVersionInChangelog() { @@ -386,3 +398,102 @@ class MetadataTests: XCTestCase { } } } + +/// The cached result from the first run of `generateRuleRegistryIfNecessary()` +private var cachedGenerateRuleRegistryResult: Result? + +extension _FormatRules { + /// Generates `RuleRegistry.generated.swift` if it hasn't been generated yet for this test run. + func generateRuleRegistryIfNecessary() throws { + switch cachedGenerateRuleRegistryResult { + case .success: + break + + case let .failure(error): + throw error + + case .none: + do { + try generateRuleRegistry() + cachedGenerateRuleRegistryResult = .success(()) + } catch { + cachedGenerateRuleRegistryResult = .failure(error) + throw error + } + } + } + + private func generateRuleRegistry() throws { + let validatedRules = try validatedRuleNames() + let ruleRegistryContent = generateRuleRegistryContent(for: validatedRules) + let currentRuleRegistryContent = try String(contentsOf: ruleRegistryURL) + + if ruleRegistryContent != currentRuleRegistryContent { + try ruleRegistryContent.write(to: ruleRegistryURL, atomically: true, encoding: .utf8) + fatalError("Updated rule registry. You can now re-run the test case or test suite.") + } + } + + /// Finds all of the rules defines in `Sources/Rules` and validates that it matches the + /// expected scheme, where each file defines exactly one `FormatRule` with the same name. + private func validatedRuleNames() throws -> [String] { + try allRuleFiles.map { ruleFile in + let titleCaseRuleName = ruleFile.lastPathComponent.replacingOccurrences(of: ".swift", with: "") + let camelCaseRuleName = titleCaseRuleName.first!.lowercased() + titleCaseRuleName.dropFirst() + try validateRuleImplementation(for: camelCaseRuleName, in: ruleFile) + return camelCaseRuleName + } + } + + /// Generates the content of the `RuleRegistry.generated.swift` file + private func generateRuleRegistryContent(for rules: [String]) -> String { + var ruleRegistryContents = """ + // + // RuleRegistry.generated.swift + // SwiftFormat + // + // Created by Cal Stephens on 7/27/24. + // Copyright © 2024 Nick Lockwood. All rights reserved. + // + + /// All of the rules defined in the Rules directory. + /// **Generated automatically when running tests. Do not modify.** + let ruleRegistry: [String: FormatRule] = [\n + """ + + for rule in rules.sorted() { + ruleRegistryContents.append(""" + "\(rule)": .\(rule),\n + """) + } + + ruleRegistryContents.append(""" + ]\n + """) + + return ruleRegistryContents + } + + /// Validates that the given file defines exactly one `FormatRule` with the expected name + private func validateRuleImplementation(for expectedRuleName: String, in file: URL) throws { + let fileContents = try String(contentsOf: file) + let formatter = Formatter(tokenize(fileContents)) + + // Find all of the rules defined in the file, like `let ruleName = FormatRule(`. + var definedRules: [String] = [] + formatter.forEach(.identifier("FormatRule")) { index, _ in + guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: index) == .startOfScope("("), + let declarationKeyword = formatter.indexOfLastSignificantKeyword(at: index), + let ruleNameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: declarationKeyword) + else { return } + + definedRules.append(formatter.tokens[ruleNameIndex].string) + } + + if definedRules != [expectedRuleName] { + fatalError(""" + \(file.lastPathComponent) must define a single FormatRule named \(expectedRuleName). Currently defines rules: \(definedRules). + """) + } + } +} diff --git a/Tests/ReporterTests.swift b/Tests/ReporterTests.swift index 926bba843..2f3e2eb21 100644 --- a/Tests/ReporterTests.swift +++ b/Tests/ReporterTests.swift @@ -35,7 +35,7 @@ import XCTest class ReporterTests: XCTestCase { func testWrite() throws { let reporter = GithubActionsLogReporter(environment: ["GITHUB_WORKSPACE": "/bar"]) - let rule = FormatRules.consecutiveSpaces + let rule = FormatRule.consecutiveSpaces reporter.report([ .init(line: 1, rule: rule, filePath: "/bar/foo.swift"), .init(line: 2, rule: rule, filePath: "/bar/foo.swift"), diff --git a/Tests/RulesTests+Braces.swift b/Tests/RulesTests+Braces.swift index b5b6098d6..fb6036b74 100644 --- a/Tests/RulesTests+Braces.swift +++ b/Tests/RulesTests+Braces.swift @@ -15,7 +15,7 @@ class BracesTests: RulesTests { func testAllmanBracesAreConverted() { let input = "func foo()\n{\n statement\n}" let output = "func foo() {\n statement\n}" - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testNestedAllmanBracesAreConverted() { @@ -35,17 +35,17 @@ class BracesTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testKnRBracesAfterComment() { let input = "func foo() // comment\n{\n statement\n}" - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRBracesAfterMultilineComment() { let input = "func foo() /* comment/ncomment */\n{\n statement\n}" - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRBracesAfterMultilineComment2() { @@ -57,17 +57,17 @@ class BracesTests: RulesTests { // foo } """ - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRExtraSpaceNotAddedBeforeBrace() { let input = "foo({ bar })" - testFormatting(for: input, rule: FormatRules.braces, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .braces, exclude: ["trailingClosures"]) } func testKnRLinebreakNotRemovedBeforeInlineBlockNot() { let input = "func foo() -> Bool\n{ return false }" - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRNoMangleCommentBeforeClosure() { @@ -81,7 +81,7 @@ class BracesTests: RulesTests { }(), ] """ - testFormatting(for: input, rule: FormatRules.braces, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .braces, exclude: ["redundantClosure"]) } func testKnRNoMangleClosureReturningClosure() { @@ -92,7 +92,7 @@ class BracesTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testKnRNoMangleClosureReturningClosure2() { @@ -103,7 +103,7 @@ class BracesTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testAllmanNoMangleClosureReturningClosure() { @@ -116,7 +116,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.braces, options: options) + testFormatting(for: input, rule: .braces, options: options) } func testKnRUnwrapClosure() { @@ -131,7 +131,7 @@ class BracesTests: RulesTests { bar() } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testKnRNoUnwrapClosureIfWidthExceeded() { @@ -142,18 +142,18 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, rule: FormatRules.braces, options: options, exclude: ["indent"]) + testFormatting(for: input, rule: .braces, options: options, exclude: ["indent"]) } func testKnRClosingBraceWrapped() { let input = "func foo() {\n print(bar) }" let output = "func foo() {\n print(bar)\n}" - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testKnRInlineBracesNotWrapped() { let input = "func foo() { print(bar) }" - testFormatting(for: input, rule: FormatRules.braces) + testFormatting(for: input, rule: .braces) } func testAllmanComputedPropertyBracesConverted() { @@ -168,7 +168,7 @@ class BracesTests: RulesTests { return 5 } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testAllmanInitBracesConverted() { @@ -183,7 +183,7 @@ class BracesTests: RulesTests { foo = 5 } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testAllmanSubscriptBracesConverted() { @@ -198,7 +198,7 @@ class BracesTests: RulesTests { foo[i] } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForStructDeclaration() { @@ -213,7 +213,7 @@ class BracesTests: RulesTests { // foo } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForInit() { @@ -228,7 +228,7 @@ class BracesTests: RulesTests { self.foo = foo } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForIfStatement() { @@ -243,7 +243,7 @@ class BracesTests: RulesTests { // foo } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForExtension() { @@ -258,7 +258,7 @@ class BracesTests: RulesTests { // foo } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBracesForOptionalInit() { @@ -273,7 +273,7 @@ class BracesTests: RulesTests { return nil } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } func testBraceUnwrappedIfWrapMultilineStatementBracesRuleDisabled() { @@ -290,7 +290,7 @@ class BracesTests: RulesTests { return nil } """ - testFormatting(for: input, output, rule: FormatRules.braces, + testFormatting(for: input, output, rule: .braces, exclude: ["wrapMultilineStatementBraces"]) } @@ -303,7 +303,7 @@ class BracesTests: RulesTests { } """ testFormatting(for: input, rules: [ - FormatRules.braces, FormatRules.wrapMultilineStatementBraces, + .braces, .wrapMultilineStatementBraces, ]) } @@ -319,7 +319,7 @@ class BracesTests: RulesTests { // } """ - testFormatting(for: input, output, rule: FormatRules.braces) + testFormatting(for: input, output, rule: .braces) } // allman style @@ -328,20 +328,20 @@ class BracesTests: RulesTests { let input = "func foo() {\n statement\n}" let output = "func foo()\n{\n statement\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBlankLineAfterBraceRemoved() { let input = "func foo() {\n \n statement\n}" let output = "func foo()\n{\n statement\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceInsideParensNotConverted() { let input = "foo({\n bar\n})" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.braces, options: options, + testFormatting(for: input, rule: .braces, options: options, exclude: ["trailingClosures"]) } @@ -349,14 +349,14 @@ class BracesTests: RulesTests { let input = "do {\n foo\n}" let output = "do\n{\n foo\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceCatchClauseIndent() { let input = "do {\n try foo\n}\ncatch {\n}" let output = "do\n{\n try foo\n}\ncatch\n{\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options, + testFormatting(for: input, output, rule: .braces, options: options, exclude: ["emptyBraces"]) } @@ -364,7 +364,7 @@ class BracesTests: RulesTests { let input = "do throws(Foo) {\n try foo\n}\ncatch {\n}" let output = "do throws(Foo)\n{\n try foo\n}\ncatch\n{\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options, + testFormatting(for: input, output, rule: .braces, options: options, exclude: ["emptyBraces"]) } @@ -372,42 +372,42 @@ class BracesTests: RulesTests { let input = "repeat {\n foo\n}\nwhile x" let output = "repeat\n{\n foo\n}\nwhile x" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceOptionalComputedPropertyIndent() { let input = "var foo: Int? {\n return 5\n}" let output = "var foo: Int?\n{\n return 5\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceThrowsFunctionIndent() { let input = "func foo() throws {\n bar\n}" let output = "func foo() throws\n{\n bar\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceAsyncFunctionIndent() { let input = "func foo() async {\n bar\n}" let output = "func foo() async\n{\n bar\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceAfterCommentIndent() { let input = "func foo() { // foo\n\n bar\n}" let output = "func foo()\n{ // foo\n bar\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBraceAfterSwitch() { let input = "switch foo {\ncase bar: break\n}" let output = "switch foo\n{\ncase bar: break\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForStructDeclaration() { @@ -425,7 +425,7 @@ class BracesTests: RulesTests { let options = FormatOptions(allmanBraces: true) testFormatting( for: input, output, - rule: FormatRules.braces, + rule: .braces, options: options ) } @@ -443,7 +443,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForOptionalInit() { @@ -459,7 +459,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForIfStatement() { @@ -475,7 +475,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForIfStatement2() { @@ -491,7 +491,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testAllmanBracesForExtension() { @@ -507,7 +507,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.braces, options: options) + testFormatting(for: input, output, rule: .braces, options: options) } func testEmptyAllmanIfElseBraces() { @@ -526,7 +526,7 @@ class BracesTests: RulesTests { """ let options = FormatOptions(allmanBraces: true) testFormatting(for: input, [output], rules: [ - FormatRules.braces, FormatRules.emptyBraces, FormatRules.elseOnSameLine, + .braces, .emptyBraces, .elseOnSameLine, ], options: options) } @@ -543,7 +543,7 @@ class BracesTests: RulesTests { """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, rules: [FormatRules.braces, FormatRules.wrapMultilineStatementBraces], options: options) + testFormatting(for: input, rules: [.braces, .wrapMultilineStatementBraces], options: options) } func testTrailingClosureWrappingAfterMethodWithPartialWrappingAndClosures() { @@ -558,6 +558,6 @@ class BracesTests: RulesTests { """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, rules: [FormatRules.braces, FormatRules.wrapMultilineStatementBraces], options: options) + testFormatting(for: input, rules: [.braces, .wrapMultilineStatementBraces], options: options) } } diff --git a/Tests/RulesTests+General.swift b/Tests/RulesTests+General.swift index 3c7ab5a9a..31f0dc49d 100644 --- a/Tests/RulesTests+General.swift +++ b/Tests/RulesTests+General.swift @@ -41,7 +41,7 @@ class GeneralTests: RulesTests { required init?(coder aDecoder: NSCoder) {} } """ - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable, + testFormatting(for: input, output, rule: .initCoderUnavailable, exclude: ["unusedArguments"]) } @@ -66,7 +66,7 @@ class GeneralTests: RulesTests { } """ let options = FormatOptions(initCoderNil: false) - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable, options: options) + testFormatting(for: input, output, rule: .initCoderUnavailable, options: options) } func testInitCoderUnavailableFatalErrorNilEnabled() { @@ -90,7 +90,7 @@ class GeneralTests: RulesTests { } """ let options = FormatOptions(initCoderNil: true) - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable, options: options) + testFormatting(for: input, output, rule: .initCoderUnavailable, options: options) } func testInitCoderUnavailableAlreadyPresent() { @@ -104,7 +104,7 @@ class GeneralTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.initCoderUnavailable) + testFormatting(for: input, rule: .initCoderUnavailable) } func testInitCoderUnavailableImplemented() { @@ -117,7 +117,7 @@ class GeneralTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.initCoderUnavailable) + testFormatting(for: input, rule: .initCoderUnavailable) } func testPublicInitCoderUnavailable() { @@ -136,7 +136,7 @@ class GeneralTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable) + testFormatting(for: input, output, rule: .initCoderUnavailable) } func testPublicInitCoderUnavailable2() { @@ -156,7 +156,7 @@ class GeneralTests: RulesTests { } """ let options = FormatOptions(initCoderNil: true) - testFormatting(for: input, output, rule: FormatRules.initCoderUnavailable, + testFormatting(for: input, output, rule: .initCoderUnavailable, options: options, exclude: ["modifierOrder"]) } @@ -165,77 +165,77 @@ class GeneralTests: RulesTests { func testCommaAddedToSingleItem() { let input = "[\n foo\n]" let output = "[\n foo,\n]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) + testFormatting(for: input, output, rule: .trailingCommas) } func testCommaAddedToLastItem() { let input = "[\n foo,\n bar\n]" let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) + testFormatting(for: input, output, rule: .trailingCommas) } func testCommaAddedToDictionary() { let input = "[\n foo: bar\n]" let output = "[\n foo: bar,\n]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) + testFormatting(for: input, output, rule: .trailingCommas) } func testCommaNotAddedToInlineArray() { let input = "[foo, bar]" - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testCommaNotAddedToInlineDictionary() { let input = "[foo: bar]" - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testCommaNotAddedToSubscript() { let input = "foo[bar]" - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testCommaAddedBeforeComment() { let input = "[\n foo // comment\n]" let output = "[\n foo, // comment\n]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) + testFormatting(for: input, output, rule: .trailingCommas) } func testCommaNotAddedAfterComment() { let input = "[\n foo, // comment\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testCommaNotAddedInsideEmptyArrayLiteral() { let input = "foo = [\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testCommaNotAddedInsideEmptyDictionaryLiteral() { let input = "foo = [:\n]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, rule: FormatRules.trailingCommas, options: options) + testFormatting(for: input, rule: .trailingCommas, options: options) } func testTrailingCommaRemovedInInlineArray() { let input = "[foo,]" let output = "[foo]" - testFormatting(for: input, output, rule: FormatRules.trailingCommas) + testFormatting(for: input, output, rule: .trailingCommas) } func testTrailingCommaNotAddedToSubscript() { let input = "foo[\n bar\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToSubscript2() { let input = "foo?[\n bar\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToSubscript3() { let input = "foo()[\n bar\n]" - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToSubscriptInsideArrayLiteral() { @@ -248,7 +248,7 @@ class GeneralTests: RulesTests { .baz, ] """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaAddedToArrayLiteralInsideTuple() { @@ -266,7 +266,7 @@ class GeneralTests: RulesTests { bar, ]) """ - testFormatting(for: input, output, rule: FormatRules.trailingCommas) + testFormatting(for: input, output, rule: .trailingCommas) } func testNoTrailingCommaAddedToArrayLiteralInsideTuple() { @@ -277,7 +277,7 @@ class GeneralTests: RulesTests { Int ]).self """ - testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) + testFormatting(for: input, rule: .trailingCommas, exclude: ["propertyType"]) } func testTrailingCommaNotAddedToTypeDeclaration() { @@ -287,7 +287,7 @@ class GeneralTests: RulesTests { String ] """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToTypeDeclaration2() { @@ -297,7 +297,7 @@ class GeneralTests: RulesTests { String ]) """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToTypeDeclaration3() { @@ -306,7 +306,7 @@ class GeneralTests: RulesTests { String: String ] """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToTypeDeclaration4() { @@ -315,7 +315,7 @@ class GeneralTests: RulesTests { String: Int ]] """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToTypeDeclaration5() { @@ -324,7 +324,7 @@ class GeneralTests: RulesTests { String: Int ]]() """ - testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) + testFormatting(for: input, rule: .trailingCommas, exclude: ["propertyType"]) } func testTrailingCommaNotAddedToTypeDeclaration6() { @@ -337,7 +337,7 @@ class GeneralTests: RulesTests { ]) ]]() """ - testFormatting(for: input, rule: FormatRules.trailingCommas, exclude: ["propertyType"]) + testFormatting(for: input, rule: .trailingCommas, exclude: ["propertyType"]) } func testTrailingCommaNotAddedToTypeDeclaration7() { @@ -346,7 +346,7 @@ class GeneralTests: RulesTests { String: Int ]]> """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToTypeDeclaration8() { @@ -359,7 +359,7 @@ class GeneralTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToTypealias() { @@ -368,7 +368,7 @@ class GeneralTests: RulesTests { Int ] """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToCaptureList() { @@ -377,7 +377,7 @@ class GeneralTests: RulesTests { self ] in } """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToCaptureListWithComment() { @@ -386,7 +386,7 @@ class GeneralTests: RulesTests { self // captures self ] in } """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } func testTrailingCommaNotAddedToCaptureListWithMainActor() { @@ -396,7 +396,7 @@ class GeneralTests: RulesTests { baz = state.baz ] _ in } """ - testFormatting(for: input, rule: FormatRules.trailingCommas) + testFormatting(for: input, rule: .trailingCommas) } // trailingCommas = false @@ -404,14 +404,14 @@ class GeneralTests: RulesTests { func testCommaNotAddedToLastItem() { let input = "[\n foo,\n bar\n]" let options = FormatOptions(trailingCommas: false) - testFormatting(for: input, rule: FormatRules.trailingCommas, options: options) + testFormatting(for: input, rule: .trailingCommas, options: options) } func testCommaRemovedFromLastItem() { let input = "[\n foo,\n bar,\n]" let output = "[\n foo,\n bar\n]" let options = FormatOptions(trailingCommas: false) - testFormatting(for: input, output, rule: FormatRules.trailingCommas, options: options) + testFormatting(for: input, output, rule: .trailingCommas, options: options) } // MARK: - fileHeader @@ -420,7 +420,7 @@ class GeneralTests: RulesTests { let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" let output = "/// func\nfunc foo() {}" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testStripHeaderWithWhenHeaderContainsUrl() { @@ -439,110 +439,110 @@ class GeneralTests: RulesTests { """ let output = "/// func\nfunc foo() {}" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testReplaceHeaderWhenFileContainsNoCode() { let input = "// foobar" let options = FormatOptions(fileHeader: "// foobar") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options, + testFormatting(for: input, rule: .fileHeader, options: options, exclude: ["linebreakAtEndOfFile"]) } func testReplaceHeaderWhenFileContainsNoCode2() { let input = "// foobar\n" let options = FormatOptions(fileHeader: "// foobar") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testMultilineCommentHeader() { let input = "/****************************/\n/* Created by Nick Lockwood */\n/****************************/\n\n\n/// func\nfunc foo() {}" let output = "/// func\nfunc foo() {}" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testNoStripHeaderWhenDisabled() { let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" let options = FormatOptions(fileHeader: .ignore) - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testNoStripComment() { let input = "\n/// func\nfunc foo() {}" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testNoStripPackageHeader() { let input = "// swift-tools-version:4.2\n\nimport PackageDescription" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testNoStripFormatDirective() { let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testNoStripFormatDirectiveAfterHeader() { let input = "// header\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testNoReplaceFormatDirective() { let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" let output = "// Hello World\n\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" let options = FormatOptions(fileHeader: "// Hello World") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testSetSingleLineHeader() { let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" let output = "// Hello World\n\n/// func\nfunc foo() {}" let options = FormatOptions(fileHeader: "// Hello World") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testSetMultilineHeader() { let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" let output = "// Hello\n// World\n\n/// func\nfunc foo() {}" let options = FormatOptions(fileHeader: "// Hello\n// World") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testSetMultilineHeaderWithMarkup() { let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" let output = "/*--- Hello ---*/\n/*--- World ---*/\n\n/// func\nfunc foo() {}" let options = FormatOptions(fileHeader: "/*--- Hello ---*/\n/*--- World ---*/") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testNoStripHeaderIfRuleDisabled() { let input = "// swiftformat:disable fileHeader\n// test\n// swiftformat:enable fileHeader\n\nfunc foo() {}" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testNoStripHeaderIfNextRuleDisabled() { let input = "// swiftformat:disable:next fileHeader\n// test\n\nfunc foo() {}" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testNoStripHeaderDocWithNewlineBeforeCode() { let input = "/// Header doc\n\nclass Foo {}" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options, exclude: ["docComments"]) + testFormatting(for: input, rule: .fileHeader, options: options, exclude: ["docComments"]) } func testNoDuplicateHeaderIfMissingTrailingBlankLine() { let input = "// Header comment\nclass Foo {}" let output = "// Header comment\n\nclass Foo {}" let options = FormatOptions(fileHeader: "Header comment") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testNoDuplicateHeaderContainingPossibleCommentDirective() { @@ -561,7 +561,7 @@ class GeneralTests: RulesTests { class Foo {} """ let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// SPDX-License-Identifier: EPL-2.0") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testNoDuplicateHeaderContainingCommentDirective() { @@ -580,7 +580,7 @@ class GeneralTests: RulesTests { class Foo {} """ let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// swiftformat:disable all") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderYearReplacement() { @@ -591,7 +591,7 @@ class GeneralTests: RulesTests { return "// Copyright © \(formatter.string(from: Date()))\n\nlet foo = bar" }() let options = FormatOptions(fileHeader: "// Copyright © {year}") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderCreationYearReplacement() { @@ -604,7 +604,7 @@ class GeneralTests: RulesTests { }() let fileInfo = FileInfo(creationDate: date) let options = FormatOptions(fileHeader: "// Copyright © {created.year}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderAuthorReplacement() { @@ -614,7 +614,7 @@ class GeneralTests: RulesTests { let output = "// Created by \(name) \(email)\n\nlet foo = bar" let fileInfo = FileInfo(replacements: [.authorName: .constant(name), .authorEmail: .constant(email)]) let options = FormatOptions(fileHeader: "// Created by {author.name} {author.email}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderAuthorReplacement2() { @@ -623,7 +623,7 @@ class GeneralTests: RulesTests { let output = "// Created by \(author)\n\nlet foo = bar" let fileInfo = FileInfo(replacements: [.author: .constant(author)]) let options = FormatOptions(fileHeader: "// Created by {author}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderMultipleReplacement() { @@ -632,7 +632,7 @@ class GeneralTests: RulesTests { let output = "// Copyright © \(name)\n// Created by \(name)\n\nlet foo = bar" let fileInfo = FileInfo(replacements: [.authorName: .constant(name)]) let options = FormatOptions(fileHeader: "// Copyright © {author.name}\n// Created by {author.name}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderCreationDateReplacement() { @@ -646,7 +646,7 @@ class GeneralTests: RulesTests { }() let fileInfo = FileInfo(creationDate: date) let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderDateFormattingIso() { @@ -656,7 +656,7 @@ class GeneralTests: RulesTests { let output = "// 2023-08-09\n\nlet foo = bar" let fileInfo = FileInfo(creationDate: date) let options = FormatOptions(fileHeader: "// {created}", dateFormat: .iso, fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderDateFormattingDayMonthYear() { @@ -666,7 +666,7 @@ class GeneralTests: RulesTests { let output = "// 09/08/2023\n\nlet foo = bar" let fileInfo = FileInfo(creationDate: date) let options = FormatOptions(fileHeader: "// {created}", dateFormat: .dayMonthYear, fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderDateFormattingMonthDayYear() { @@ -678,7 +678,7 @@ class GeneralTests: RulesTests { let options = FormatOptions(fileHeader: "// {created}", dateFormat: .monthDayYear, fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderDateFormattingCustom() { @@ -691,7 +691,7 @@ class GeneralTests: RulesTests { dateFormat: .custom("yy.MM.dd-HH.mm.ss.SSS"), timeZone: .identifier("UTC"), fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } private func testTimeZone( @@ -713,7 +713,7 @@ class GeneralTests: RulesTests { ) testFormatting(for: input, output, - rule: FormatRules.fileHeader, + rule: .fileHeader, options: options) } } @@ -768,7 +768,7 @@ class GeneralTests: RulesTests { func testFileHeaderRuleThrowsIfCreationDateUnavailable() { let input = "let foo = bar" let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: FileInfo()) - XCTAssertThrowsError(try format(input, rules: [FormatRules.fileHeader], options: options)) + XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) } func testFileHeaderFileReplacement() { @@ -776,20 +776,20 @@ class GeneralTests: RulesTests { let output = "// MyFile.swift\n\nlet foo = bar" let fileInfo = FileInfo(filePath: "~/MyFile.swift") let options = FormatOptions(fileHeader: "// {file}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderRuleThrowsIfFileNameUnavailable() { let input = "let foo = bar" let options = FormatOptions(fileHeader: "// {file}.", fileInfo: FileInfo()) - XCTAssertThrowsError(try format(input, rules: [FormatRules.fileHeader], options: options)) + XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) } func testEdgeCaseHeaderEndIndexPlusNewHeaderTokensCountEqualsFileTokensEndIndex() { let input = "// Header comment\n\nclass Foo {}" let output = "// Header line1\n// Header line2\n\nclass Foo {}" let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderBlankLineNotRemovedBeforeFollowingComment() { @@ -801,7 +801,7 @@ class GeneralTests: RulesTests { // Something else... """ let options = FormatOptions(fileHeader: "//\n// Header\n//") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testFileHeaderBlankLineNotRemovedBeforeFollowingComment2() { @@ -815,7 +815,7 @@ class GeneralTests: RulesTests { // """ let options = FormatOptions(fileHeader: "//\n// Header\n//") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testFileHeaderRemovedAfterHashbang() { @@ -833,7 +833,7 @@ class GeneralTests: RulesTests { let foo = 5 """ let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testFileHeaderPlacedAfterHashbang() { @@ -851,7 +851,7 @@ class GeneralTests: RulesTests { let foo = 5 """ let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") - testFormatting(for: input, output, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, output, rule: .fileHeader, options: options) } func testBlankLineAfterHashbangNotRemovedByFileHeader() { @@ -861,7 +861,7 @@ class GeneralTests: RulesTests { let foo = 5 """ let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testLineAfterHashbangNotAffectedByFileHeaderRemoval() { @@ -870,7 +870,7 @@ class GeneralTests: RulesTests { let foo = 5 """ let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testDisableFileHeaderCommentRespectedAfterHashbang() { @@ -884,7 +884,7 @@ class GeneralTests: RulesTests { let foo = 5 """ let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } func testDisableFileHeaderCommentRespectedAfterHashbang2() { @@ -898,7 +898,7 @@ class GeneralTests: RulesTests { let foo = 5 """ let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: FormatRules.fileHeader, options: options) + testFormatting(for: input, rule: .fileHeader, options: options) } // MARK: - headerFileName @@ -915,7 +915,7 @@ class GeneralTests: RulesTests { let foo = bar """ let options = FormatOptions(fileInfo: FileInfo(filePath: "~/YourFile.swift")) - testFormatting(for: input, output, rule: FormatRules.headerFileName, options: options) + testFormatting(for: input, output, rule: .headerFileName, options: options) } // MARK: - strongOutlets @@ -923,52 +923,52 @@ class GeneralTests: RulesTests { func testRemoveWeakFromOutlet() { let input = "@IBOutlet weak var label: UILabel!" let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) + testFormatting(for: input, output, rule: .strongOutlets) } func testRemoveWeakFromPrivateOutlet() { let input = "@IBOutlet private weak var label: UILabel!" let output = "@IBOutlet private var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) + testFormatting(for: input, output, rule: .strongOutlets) } func testRemoveWeakFromOutletOnSplitLine() { let input = "@IBOutlet\nweak var label: UILabel!" let output = "@IBOutlet\nvar label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) + testFormatting(for: input, output, rule: .strongOutlets) } func testNoRemoveWeakFromNonOutlet() { let input = "weak var label: UILabel!" - testFormatting(for: input, rule: FormatRules.strongOutlets) + testFormatting(for: input, rule: .strongOutlets) } func testNoRemoveWeakFromNonOutletAfterOutlet() { let input = "@IBOutlet weak var label1: UILabel!\nweak var label2: UILabel!" let output = "@IBOutlet var label1: UILabel!\nweak var label2: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) + testFormatting(for: input, output, rule: .strongOutlets) } func testNoRemoveWeakFromDelegateOutlet() { let input = "@IBOutlet weak var delegate: UITableViewDelegate?" - testFormatting(for: input, rule: FormatRules.strongOutlets) + testFormatting(for: input, rule: .strongOutlets) } func testNoRemoveWeakFromDataSourceOutlet() { let input = "@IBOutlet weak var dataSource: UITableViewDataSource?" - testFormatting(for: input, rule: FormatRules.strongOutlets) + testFormatting(for: input, rule: .strongOutlets) } func testRemoveWeakFromOutletAfterDelegateOutlet() { let input = "@IBOutlet weak var delegate: UITableViewDelegate?\n@IBOutlet weak var label1: UILabel!" let output = "@IBOutlet weak var delegate: UITableViewDelegate?\n@IBOutlet var label1: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) + testFormatting(for: input, output, rule: .strongOutlets) } func testRemoveWeakFromOutletAfterDataSourceOutlet() { let input = "@IBOutlet weak var dataSource: UITableViewDataSource?\n@IBOutlet weak var label1: UILabel!" let output = "@IBOutlet weak var dataSource: UITableViewDataSource?\n@IBOutlet var label1: UILabel!" - testFormatting(for: input, output, rule: FormatRules.strongOutlets) + testFormatting(for: input, output, rule: .strongOutlets) } // MARK: - strongifiedSelf @@ -985,7 +985,7 @@ class GeneralTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, output, rule: FormatRules.strongifiedSelf, options: options, + testFormatting(for: input, output, rule: .strongifiedSelf, options: options, exclude: ["wrapConditionalBodies"]) } @@ -1001,7 +1001,7 @@ class GeneralTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, output, rule: FormatRules.strongifiedSelf, options: options, + testFormatting(for: input, output, rule: .strongifiedSelf, options: options, exclude: ["wrapConditionalBodies"]) } @@ -1012,7 +1012,7 @@ class GeneralTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4.1.5") - testFormatting(for: input, rule: FormatRules.strongifiedSelf, options: options, + testFormatting(for: input, rule: .strongifiedSelf, options: options, exclude: ["wrapConditionalBodies"]) } @@ -1022,14 +1022,14 @@ class GeneralTests: RulesTests { guard let `self` = self else { return } } """ - testFormatting(for: input, rule: FormatRules.strongifiedSelf, + testFormatting(for: input, rule: .strongifiedSelf, exclude: ["wrapConditionalBodies"]) } func testBacktickedSelfNotConvertedIfNotConditional() { let input = "nonisolated(unsafe) let `self` = self" let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, rule: FormatRules.strongifiedSelf, options: options) + testFormatting(for: input, rule: .strongifiedSelf, options: options) } // MARK: - yodaConditions @@ -1037,274 +1037,274 @@ class GeneralTests: RulesTests { func testNumericLiteralEqualYodaCondition() { let input = "5 == foo" let output = "foo == 5" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNumericLiteralGreaterYodaCondition() { let input = "5.1 > foo" let output = "foo < 5.1" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testStringLiteralNotEqualYodaCondition() { let input = "\"foo\" != foo" let output = "foo != \"foo\"" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNilNotEqualYodaCondition() { let input = "nil != foo" let output = "foo != nil" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testTrueNotEqualYodaCondition() { let input = "true != foo" let output = "foo != true" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testEnumCaseNotEqualYodaCondition() { let input = ".foo != foo" let output = "foo != .foo" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testArrayLiteralNotEqualYodaCondition() { let input = "[5, 6] != foo" let output = "foo != [5, 6]" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNestedArrayLiteralNotEqualYodaCondition() { let input = "[5, [6, 7]] != foo" let output = "foo != [5, [6, 7]]" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testDictionaryLiteralNotEqualYodaCondition() { let input = "[foo: 5, bar: 6] != foo" let output = "foo != [foo: 5, bar: 6]" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testSubscriptNotTreatedAsYodaCondition() { let input = "foo[5] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testSubscriptOfParenthesizedExpressionNotTreatedAsYodaCondition() { let input = "(foo + bar)[5] != baz" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testSubscriptOfUnwrappedValueNotTreatedAsYodaCondition() { let input = "foo![5] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testSubscriptOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { let input = "foo /* foo */ [5] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testSubscriptOfCollectionNotTreatedAsYodaCondition() { let input = "[foo][5] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testSubscriptOfTrailingClosureNotTreatedAsYodaCondition() { let input = "foo { [5] }[0] != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testSubscriptOfRhsNotMangledInYodaCondition() { let input = "[1] == foo[0]" let output = "foo[0] == [1]" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testTupleYodaCondition() { let input = "(5, 6) != bar" let output = "bar != (5, 6)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testLabeledTupleYodaCondition() { let input = "(foo: 5, bar: 6) != baz" let output = "baz != (foo: 5, bar: 6)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNestedTupleYodaCondition() { let input = "(5, (6, 7)) != baz" let output = "baz != (5, (6, 7))" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testFunctionCallNotTreatedAsYodaCondition() { let input = "foo(5) != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testCallOfParenthesizedExpressionNotTreatedAsYodaCondition() { let input = "(foo + bar)(5) != baz" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testCallOfUnwrappedValueNotTreatedAsYodaCondition() { let input = "foo!(5) != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testCallOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { let input = "foo /* foo */ (5) != bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testCallOfRhsNotMangledInYodaCondition() { let input = "(1, 2) == foo(0)" let output = "foo(0) == (1, 2)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testTrailingClosureOnRhsNotMangledInYodaCondition() { let input = "(1, 2) == foo { $0 }" let output = "foo { $0 } == (1, 2)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testYodaConditionInIfStatement() { let input = "if 5 != foo {}" let output = "if foo != 5 {}" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testSubscriptYodaConditionInIfStatementWithBraceOnNextLine() { let input = "if [0] == foo.bar[0]\n{ baz() }" let output = "if foo.bar[0] == [0]\n{ baz() }" - testFormatting(for: input, output, rule: FormatRules.yodaConditions, + testFormatting(for: input, output, rule: .yodaConditions, exclude: ["wrapConditionalBodies"]) } func testYodaConditionInSecondClauseOfIfStatement() { let input = "if foo, 5 != bar {}" let output = "if foo, bar != 5 {}" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testYodaConditionInExpression() { let input = "let foo = 5 < bar\nbaz()" let output = "let foo = bar > 5\nbaz()" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testYodaConditionInExpressionWithTrailingClosure() { let input = "let foo = 5 < bar { baz() }" let output = "let foo = bar { baz() } > 5" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testYodaConditionInFunctionCall() { let input = "foo(5 < bar)" let output = "foo(bar > 5)" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testYodaConditionFollowedByExpression() { let input = "5 == foo + 6" let output = "foo + 6 == 5" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testPrefixExpressionYodaCondition() { let input = "!false == foo" let output = "foo == !false" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testPrefixExpressionYodaCondition2() { let input = "true == !foo" let output = "!foo == true" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testPostfixExpressionYodaCondition() { let input = "5<*> == foo" let output = "foo == 5<*>" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testDoublePostfixExpressionYodaCondition() { let input = "5!! == foo" let output = "foo == 5!!" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testPostfixExpressionNonYodaCondition() { let input = "5 == 5<*>" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testPostfixExpressionNonYodaCondition2() { let input = "5<*> == 5" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testStringEqualsStringNonYodaCondition() { let input = "\"foo\" == \"bar\"" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testConstantAfterNullCoalescingNonYodaCondition() { let input = "foo.last ?? -1 < bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testNoMangleYodaConditionFollowedByAndOperator() { let input = "5 <= foo && foo <= 7" let output = "foo >= 5 && foo <= 7" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNoMangleYodaConditionFollowedByOrOperator() { let input = "5 <= foo || foo <= 7" let output = "foo >= 5 || foo <= 7" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNoMangleYodaConditionFollowedByParentheses() { let input = "0 <= (foo + bar)" let output = "(foo + bar) >= 0" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNoMangleYodaConditionInTernary() { let input = "let z = 0 < y ? 3 : 4" let output = "let z = y > 0 ? 3 : 4" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNoMangleYodaConditionInTernary2() { let input = "let z = y > 0 ? 0 < x : 4" let output = "let z = y > 0 ? x > 0 : 4" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testNoMangleYodaConditionInTernary3() { let input = "let z = y > 0 ? 3 : 0 < x" let output = "let z = y > 0 ? 3 : x > 0" - testFormatting(for: input, output, rule: FormatRules.yodaConditions) + testFormatting(for: input, output, rule: .yodaConditions) } func testKeyPathNotMangledAndNotTreatedAsYodaCondition() { let input = "\\.foo == bar" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } func testEnumCaseLessThanEnumCase() { let input = "XCTAssertFalse(.never < .never)" - testFormatting(for: input, rule: FormatRules.yodaConditions) + testFormatting(for: input, rule: .yodaConditions) } // yodaSwap = literalsOnly @@ -1312,7 +1312,7 @@ class GeneralTests: RulesTests { func testNoSwapYodaDotMember() { let input = "foo(where: .bar == baz)" let options = FormatOptions(yodaSwap: .literalsOnly) - testFormatting(for: input, rule: FormatRules.yodaConditions, options: options) + testFormatting(for: input, rule: .yodaConditions, options: options) } // MARK: - leadingDelimiters @@ -1326,7 +1326,7 @@ class GeneralTests: RulesTests { let foo = 5, bar = 6 """ - testFormatting(for: input, output, rule: FormatRules.leadingDelimiters) + testFormatting(for: input, output, rule: .leadingDelimiters) } func testLeadingColonFollowedByCommentMovedToPreviousLine() { @@ -1338,7 +1338,7 @@ class GeneralTests: RulesTests { let foo: /* string */ String """ - testFormatting(for: input, output, rule: FormatRules.leadingDelimiters) + testFormatting(for: input, output, rule: .leadingDelimiters) } func testCommaMovedBeforeCommentIfLineEndsInComment() { @@ -1350,6 +1350,6 @@ class GeneralTests: RulesTests { let foo = 5, // first bar = 6 """ - testFormatting(for: input, output, rule: FormatRules.leadingDelimiters) + testFormatting(for: input, output, rule: .leadingDelimiters) } } diff --git a/Tests/RulesTests+Hoisting.swift b/Tests/RulesTests+Hoisting.swift index 18ba6827b..829e6671e 100644 --- a/Tests/RulesTests+Hoisting.swift +++ b/Tests/RulesTests+Hoisting.swift @@ -15,19 +15,19 @@ class HoistingTests: RulesTests { func testHoistTry() { let input = "greet(try name(), try surname())" let output = "try greet(name(), surname())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryWithOptionalTry() { let input = "greet(try name(), try? surname())" let output = "try greet(name(), try? surname())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideStringInterpolation() { let input = "\"\\(replace(regex: try something()))\"" let output = "try \"\\(replace(regex: something()))\"" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideStringInterpolation2() { @@ -37,7 +37,7 @@ class HoistingTests: RulesTests { let output = """ try "Hello \\(await someValue())" """ - testFormatting(for: input, output, rule: FormatRules.hoistTry, + testFormatting(for: input, output, rule: .hoistTry, options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistAwait"]) } @@ -57,7 +57,7 @@ class HoistingTests: RulesTests { xyz "\"" """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideStringInterpolation4() { @@ -67,7 +67,7 @@ class HoistingTests: RulesTests { let output = """ let str = try "&enrolments[\\(index)][userid]=\\(Foo.tryMe())" """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideStringInterpolation5() { @@ -81,7 +81,7 @@ class HoistingTests: RulesTests { "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + "&enrolments[\\(index)][userid]=\\(user.requireMoodleID())" """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideStringInterpolation6() { @@ -101,7 +101,7 @@ class HoistingTests: RulesTests { \(tripleQuote) """ """# - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideArgument() { @@ -111,29 +111,29 @@ class HoistingTests: RulesTests { let output = """ try array.append(contentsOf: await asyncFunction(param1: param1)) """ - testFormatting(for: input, output, rule: FormatRules.hoistTry, exclude: ["hoistAwait"]) + testFormatting(for: input, output, rule: .hoistTry, exclude: ["hoistAwait"]) } func testNoHoistTryInsideXCTAssert() { let input = "XCTAssertFalse(try foo())" - testFormatting(for: input, rule: FormatRules.hoistTry) + testFormatting(for: input, rule: .hoistTry) } func testNoMergeTrysInsideXCTAssert() { let input = "XCTAssertEqual(try foo(), try bar())" - testFormatting(for: input, rule: FormatRules.hoistTry) + testFormatting(for: input, rule: .hoistTry) } func testNoHoistTryInsideDo() { let input = "do { rg.box.seal(.fulfilled(try body(error))) }" let output = "do { try rg.box.seal(.fulfilled(body(error))) }" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testNoHoistTryInsideDoThrows() { let input = "do throws(Foo) { rg.box.seal(.fulfilled(try body(error))) }" let output = "do throws(Foo) { try rg.box.seal(.fulfilled(body(error))) }" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testNoHoistTryInsideMultilineDo() { @@ -147,84 +147,84 @@ class HoistingTests: RulesTests { try rg.box.seal(.fulfilled(body(error))) } """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistedTryPlacedBeforeAwait() { let input = "let foo = await bar(contentsOf: try baz())" let output = "let foo = try await bar(contentsOf: baz())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInExpressionWithNoSpaces() { let input = "let foo=bar(contentsOf:try baz())" let output = "let foo=try bar(contentsOf:baz())" - testFormatting(for: input, output, rule: FormatRules.hoistTry, + testFormatting(for: input, output, rule: .hoistTry, exclude: ["spaceAroundOperators"]) } func testHoistTryInExpressionWithExcessSpaces() { let input = "let foo = bar ( contentsOf: try baz() )" let output = "let foo = try bar ( contentsOf: baz() )" - testFormatting(for: input, output, rule: FormatRules.hoistTry, + testFormatting(for: input, output, rule: .hoistTry, exclude: ["spaceAroundParens", "spaceInsideParens"]) } func testHoistTryWithReturn() { let input = "return .enumCase(try await service.greet())" let output = "return try .enumCase(await service.greet())" - testFormatting(for: input, output, rule: FormatRules.hoistTry, + testFormatting(for: input, output, rule: .hoistTry, exclude: ["hoistAwait"]) } func testHoistDeeplyNestedTrys() { let input = "let foo = (bar: (5, (try quux(), 6)), baz: (7, quux: try quux()))" let output = "let foo = try (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testTryNotHoistedOutOfClosure() { let input = "let foo = { (try bar(), 5) }" let output = "let foo = { try (bar(), 5) }" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testTryNotHoistedOutOfClosureWithArguments() { let input = "let foo = { bar in (try baz(bar), 5) }" let output = "let foo = { bar in try (baz(bar), 5) }" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testTryNotHoistedOutOfForCondition() { let input = "for foo in bar(try baz()) {}" let output = "for foo in try bar(baz()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryWithInitAssignment() { let input = "let variable = String(try await asyncFunction())" let output = "let variable = try String(await asyncFunction())" - testFormatting(for: input, output, rule: FormatRules.hoistTry, + testFormatting(for: input, output, rule: .hoistTry, exclude: ["hoistAwait"]) } func testHoistTryWithAssignment() { let input = "let variable = (try await asyncFunction())" let output = "let variable = try (await asyncFunction())" - testFormatting(for: input, output, rule: FormatRules.hoistTry, + testFormatting(for: input, output, rule: .hoistTry, exclude: ["hoistAwait"]) } func testHoistTryOnlyOne() { let input = "greet(name, try surname())" let output = "try greet(name, surname())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryRedundantTry() { let input = "try greet(try name(), try surname())" let output = "try greet(name(), surname())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryWithAwaitOnDifferentStatement() { @@ -236,7 +236,7 @@ class HoistingTests: RulesTests { let asyncVariable = try await performSomething() return try Foo(param1: param1()) """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryDoubleParens() { @@ -246,17 +246,17 @@ class HoistingTests: RulesTests { let output = """ try array.append((value: compute())) """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryDoesNothing() { let input = "try greet(name, surname)" - testFormatting(for: input, rule: FormatRules.hoistTry) + testFormatting(for: input, rule: .hoistTry) } func testHoistOptionalTryDoesNothing() { let input = "try? greet(name, surname)" - testFormatting(for: input, rule: FormatRules.hoistTry) + testFormatting(for: input, rule: .hoistTry) } func testHoistedTryOnLineBeginningWithInfixDot() { @@ -268,7 +268,7 @@ class HoistingTests: RulesTests { let foo = try bar() .baz(quux()) """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistedTryOnLineBeginningWithInfixPlus() { @@ -280,7 +280,7 @@ class HoistingTests: RulesTests { let foo = try bar() + baz(quux()) """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistedTryOnLineBeginningWithPrefixOperator() { @@ -292,7 +292,7 @@ class HoistingTests: RulesTests { foo() try !bar(quux()) """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testNoHoistTryIntoPreviousLineEndingWithPostfixOperator() { @@ -304,18 +304,18 @@ class HoistingTests: RulesTests { let foo = bar! try (baz(), quux()).foo() """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testNoHoistTryInCapturingFunction() { let input = "foo(try bar)" - testFormatting(for: input, rule: FormatRules.hoistTry, + testFormatting(for: input, rule: .hoistTry, options: FormatOptions(throwCapturing: ["foo"])) } func testNoHoistSecondArgumentTryInCapturingFunction() { let input = "foo(bar, try baz)" - testFormatting(for: input, rule: FormatRules.hoistTry, + testFormatting(for: input, rule: .hoistTry, options: FormatOptions(throwCapturing: ["foo"])) } @@ -346,42 +346,42 @@ class HoistingTests: RulesTests { i: throwingExample() ) """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideOptionalFunction() { let input = "foo?(try bar())" let output = "try foo?(bar())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testNoHoistTryAfterOptionalTry() { let input = "let foo = try? bar(try baz())" - testFormatting(for: input, rule: FormatRules.hoistTry) + testFormatting(for: input, rule: .hoistTry) } func testHoistTryInsideOptionalSubscript() { let input = "foo?[try bar()]" let output = "try foo?[bar()]" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryAfterGenericType() { let input = "let foo = Tree.Foo(bar: try baz())" let output = "let foo = try Tree.Foo(bar: baz())" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryAfterArrayLiteral() { let input = "if [.first, .second].contains(try foo()) {}" let output = "if try [.first, .second].contains(foo()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryAfterSubscript() { let input = "if foo[5].bar(try baz()) {}" let output = "if try foo[5].bar(baz()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideGenericInit() { @@ -395,13 +395,13 @@ class HoistingTests: RulesTests { file: parseFile(path: $0) ) """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryInsideArrayClosure() { let input = "foo[bar](try parseFile(path: $0))" let output = "try foo[bar](parseFile(path: $0))" - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryAfterString() { @@ -415,7 +415,7 @@ class HoistingTests: RulesTests { try someFunction(parse(json), "someKey") """ - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } func testHoistTryAfterMultilineString() { @@ -437,7 +437,7 @@ class HoistingTests: RulesTests { try someFunction(parse(json), "someKey") """# - testFormatting(for: input, output, rule: FormatRules.hoistTry) + testFormatting(for: input, output, rule: .hoistTry) } // MARK: - hoistAwait @@ -445,14 +445,14 @@ class HoistingTests: RulesTests { func testHoistAwait() { let input = "greet(await name, await surname)" let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testHoistAwaitInsideIf() { let input = "if !(await isSomething()) {}" let output = "if await !(isSomething()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["redundantParens"]) } @@ -464,14 +464,14 @@ class HoistingTests: RulesTests { let output = """ await array.append(contentsOf: try asyncFunction(param1: param1)) """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) } func testHoistAwaitInsideStringInterpolation() { let input = "\"\\(replace(regex: await something()))\"" let output = "await \"\\(replace(regex: something()))\"" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } @@ -482,7 +482,7 @@ class HoistingTests: RulesTests { let output = """ await "Hello \\(try someValue())" """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) } @@ -497,7 +497,7 @@ class HoistingTests: RulesTests { await rg.box.seal(.fulfilled(body(error))) } """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } @@ -512,21 +512,21 @@ class HoistingTests: RulesTests { await rg.box.seal(.fulfilled(body(error))) } """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testHoistAwaitInExpressionWithNoSpaces() { let input = "let foo=bar(contentsOf:await baz())" let output = "let foo=await bar(contentsOf:baz())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["spaceAroundOperators"]) } func testHoistAwaitInExpressionWithExcessSpaces() { let input = "let foo = bar ( contentsOf: await baz() )" let output = "let foo = await bar ( contentsOf: baz() )" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["spaceAroundParens", "spaceInsideParens"]) } @@ -534,55 +534,55 @@ class HoistingTests: RulesTests { func testHoistAwaitWithReturn() { let input = "return .enumCase(try await service.greet())" let output = "return await .enumCase(try service.greet())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) } func testHoistDeeplyNestedAwaits() { let input = "let foo = (bar: (5, (await quux(), 6)), baz: (7, quux: await quux()))" let output = "let foo = await (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testAwaitNotHoistedOutOfClosure() { let input = "let foo = { (await bar(), 5) }" let output = "let foo = { await (bar(), 5) }" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testAwaitNotHoistedOutOfClosureWithArguments() { let input = "let foo = { bar in (await baz(bar), 5) }" let output = "let foo = { bar in await (baz(bar), 5) }" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testAwaitNotHoistedOutOfForCondition() { let input = "for foo in bar(await baz()) {}" let output = "for foo in await bar(baz()) {}" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testAwaitNotHoistedOutOfForIndex() { let input = "for await foo in asyncSequence() {}" - testFormatting(for: input, rule: FormatRules.hoistAwait, + testFormatting(for: input, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testHoistAwaitWithInitAssignment() { let input = "let variable = String(try await asyncFunction())" let output = "let variable = await String(try asyncFunction())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) } func testHoistAwaitWithAssignment() { let input = "let variable = (try await asyncFunction())" let output = "let variable = await (try asyncFunction())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) } @@ -595,72 +595,72 @@ class HoistingTests: RulesTests { let identifiersTypes = 1 await (try? asyncFunction(param1: param1)) """ - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testHoistAwaitOnlyOne() { let input = "greet(name, await surname)" let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testHoistAwaitRedundantAwait() { let input = "await greet(await name, await surname)" let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testHoistAwaitDoesNothing() { let input = "await greet(name, surname)" - testFormatting(for: input, rule: FormatRules.hoistAwait, + testFormatting(for: input, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testNoHoistAwaitBeforeTry() { let input = "try foo(await bar())" let output = "try await foo(bar())" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5")) } func testNoHoistAwaitInCapturingFunction() { let input = "foo(await bar)" - testFormatting(for: input, rule: FormatRules.hoistAwait, + testFormatting(for: input, rule: .hoistAwait, options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) } func testNoHoistSecondArgumentAwaitInCapturingFunction() { let input = "foo(bar, await baz)" - testFormatting(for: input, rule: FormatRules.hoistAwait, + testFormatting(for: input, rule: .hoistAwait, options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) } func testHoistAwaitAfterOrdinaryOperator() { let input = "let foo = bar + (await baz)" let output = "let foo = await bar + (baz)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["redundantParens"]) } func testHoistAwaitAfterUnknownOperator() { let input = "let foo = bar ??? (await baz)" let output = "let foo = await bar ??? (baz)" - testFormatting(for: input, output, rule: FormatRules.hoistAwait, + testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["redundantParens"]) } func testNoHoistAwaitAfterCapturingOperator() { let input = "let foo = await bar ??? (await baz)" - testFormatting(for: input, rule: FormatRules.hoistAwait, + testFormatting(for: input, rule: .hoistAwait, options: FormatOptions(asyncCapturing: ["???"], swiftVersion: "5.5")) } func testNoHoistAwaitInMacroArgument() { let input = "#expect (await monitor.isAvailable == false)" - testFormatting(for: input, rule: FormatRules.hoistAwait, + testFormatting(for: input, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), exclude: ["spaceAroundParens"]) } @@ -671,76 +671,76 @@ class HoistingTests: RulesTests { func testHoistCaseLet() { let input = "if case .foo(let bar, let baz) = quux {}" let output = "if case let .foo(bar, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testHoistLabelledCaseLet() { let input = "if case .foo(bar: let bar, baz: let baz) = quux {}" let output = "if case let .foo(bar: bar, baz: baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testHoistCaseVar() { let input = "if case .foo(var bar, var baz) = quux {}" let output = "if case var .foo(bar, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testNoHoistMixedCaseLetVar() { let input = "if case .foo(let bar, var baz) = quux {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, rule: .hoistPatternLet) } func testNoHoistIfFirstArgSpecified() { let input = "if case .foo(bar, let baz) = quux {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, rule: .hoistPatternLet) } func testNoHoistIfLastArgSpecified() { let input = "if case .foo(let bar, baz) = quux {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, rule: .hoistPatternLet) } func testHoistIfArgIsNumericLiteral() { let input = "if case .foo(5, let baz) = quux {}" let output = "if case let .foo(5, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testHoistIfArgIsEnumCaseLiteral() { let input = "if case .foo(.bar, let baz) = quux {}" let output = "if case let .foo(.bar, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testHoistIfArgIsNamespacedEnumCaseLiteralInParens() { let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" let output = "switch foo {\ncase let (Foo.bar(baz)):\n}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .hoistPatternLet, exclude: ["redundantParens"]) } func testHoistIfFirstArgIsUnderscore() { let input = "if case .foo(_, let baz) = quux {}" let output = "if case let .foo(_, baz) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testHoistIfSecondArgIsUnderscore() { let input = "if case .foo(let baz, _) = quux {}" let output = "if case let .foo(baz, _) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testNestedHoistLet() { let input = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" let output = "if case let (.foo(a, b), .bar(c, d)) = quux {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testHoistCommaSeparatedSwitchCaseLets() { let input = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" let output = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, + testFormatting(for: input, output, rule: .hoistPatternLet, exclude: ["wrapSwitchCases", "sortSwitchCases"]) } @@ -759,51 +759,51 @@ class HoistingTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, + testFormatting(for: input, output, rule: .hoistPatternLet, exclude: ["wrapSwitchCases", "sortSwitchCases"]) } func testHoistCatchLet() { let input = "do {} catch Foo.foo(bar: let bar) {}" let output = "do {} catch let Foo.foo(bar: bar) {}" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testNoNestedHoistLetWithSpecifiedArgs() { let input = "if case (.foo(let a, b), .bar(let c, d)) = quux {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, rule: .hoistPatternLet) } func testNoHoistClosureVariables() { let input = "foo({ let bar = 5 })" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .hoistPatternLet, exclude: ["trailingClosures"]) } // TODO: this should actually hoist the let, but that's tricky to implement without // breaking the `testNoOverHoistSwitchCaseWithNestedParens` case func testHoistSwitchCaseWithNestedParens() { let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), Foo.bar): break\n}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, + testFormatting(for: input, rule: .hoistPatternLet, exclude: ["blankLineAfterImports"]) } // TODO: this could actually hoist the let by one level, but that's tricky to implement func testNoOverHoistSwitchCaseWithNestedParens() { let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), bar): break\n}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, + testFormatting(for: input, rule: .hoistPatternLet, exclude: ["blankLineAfterImports"]) } func testNoHoistLetWithEmptArg() { let input = "if .foo(let _) = bar {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, + testFormatting(for: input, rule: .hoistPatternLet, exclude: ["redundantLet", "redundantPattern"]) } func testHoistLetWithNoSpaceAfterCase() { let input = "switch x { case.some(let y): return y }" let output = "switch x { case let .some(y): return y }" - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testHoistWrappedGuardCaseLet() { @@ -821,14 +821,14 @@ class HoistingTests: RulesTests { return } """ - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet) + testFormatting(for: input, output, rule: .hoistPatternLet) } func testNoHoistCaseLetContainingGenerics() { // Hoisting in this case causes a compilation error as-of Swift 5.3 // See: https://github.com/nicklockwood/SwiftFormat/issues/768 let input = "if case .some(Optional.some(let foo)) = bar else {}" - testFormatting(for: input, rule: FormatRules.hoistPatternLet, exclude: ["typeSugar"]) + testFormatting(for: input, rule: .hoistPatternLet, exclude: ["typeSugar"]) } // hoist = false @@ -837,7 +837,7 @@ class HoistingTests: RulesTests { let input = "if case let .foo(bar, baz) = quux {}" let output = "if case .foo(let bar, let baz) = quux {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testUnhoistCaseLetDictionaryTuple() { @@ -854,21 +854,21 @@ class HoistingTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testUnhoistLabelledCaseLet() { let input = "if case let .foo(bar: bar, baz: baz) = quux {}" let output = "if case .foo(bar: let bar, baz: let baz) = quux {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testUnhoistCaseVar() { let input = "if case var .foo(bar, baz) = quux {}" let output = "if case .foo(var bar, var baz) = quux {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testNoUnhoistGuardCaseLetFollowedByFunction() { @@ -877,7 +877,7 @@ class HoistingTests: RulesTests { foo.bar(foo: bar) """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options, + testFormatting(for: input, rule: .hoistPatternLet, options: options, exclude: ["wrapConditionalBodies"]) } @@ -889,7 +889,7 @@ class HoistingTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, rule: .hoistPatternLet, options: options) } func testNoUnhoistSwitchCaseLetFollowedByAs() { @@ -900,28 +900,28 @@ class HoistingTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, rule: .hoistPatternLet, options: options) } func testUnhoistSingleCaseLet() { let input = "if case let .foo(bar) = quux {}" let output = "if case .foo(let bar) = quux {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testUnhoistIfArgIsEnumCaseLiteral() { let input = "if case let .foo(.bar, baz) = quux {}" let output = "if case .foo(.bar, let baz) = quux {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testUnhoistIfArgIsEnumCaseLiteralInParens() { let input = "switch foo {\ncase let (.bar(baz)):\n}" let output = "switch foo {\ncase (.bar(let baz)):\n}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options, + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, exclude: ["redundantParens"]) } @@ -929,14 +929,14 @@ class HoistingTests: RulesTests { let input = "switch foo {\ncase let Foo.bar(baz):\n}" let output = "switch foo {\ncase Foo.bar(let baz):\n}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { let input = "switch foo {\ncase let (Foo.bar(baz)):\n}" let output = "switch foo {\ncase (Foo.bar(let baz)):\n}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options, + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, exclude: ["redundantParens"]) } @@ -944,21 +944,21 @@ class HoistingTests: RulesTests { let input = "if case let .foo(_, baz) = quux {}" let output = "if case .foo(_, let baz) = quux {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testNestedUnhoistLet() { let input = "if case let (.foo(a, b), .bar(c, d)) = quux {}" let output = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testUnhoistCommaSeparatedSwitchCaseLets() { let input = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" let output = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options, + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, exclude: ["wrapSwitchCases", "sortSwitchCases"]) } @@ -966,7 +966,7 @@ class HoistingTests: RulesTests { let input = "switch foo {\ncase let Foo.foo(bar), let Foo.bar(bar):\n}" let output = "switch foo {\ncase Foo.foo(let bar), Foo.bar(let bar):\n}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options, + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, exclude: ["wrapSwitchCases", "sortSwitchCases"]) } @@ -974,31 +974,31 @@ class HoistingTests: RulesTests { let input = "do {} catch let Foo.foo(bar: bar) {}" let output = "do {} catch Foo.foo(bar: let bar) {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testNoUnhoistTupleLet() { let input = "let (bar, baz) = quux()" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, rule: .hoistPatternLet, options: options) } func testNoUnhoistIfLetTuple() { let input = "if let x = y, let (_, a) = z {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, rule: .hoistPatternLet, options: options) } func testNoUnhoistIfCaseFollowedByLetTuple() { let input = "if case .foo = bar, let (foo, bar) = baz {}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, rule: .hoistPatternLet, options: options) } func testNoUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options, + testFormatting(for: input, rule: .hoistPatternLet, options: options, exclude: ["redundantParens"]) } @@ -1016,7 +1016,7 @@ class HoistingTests: RulesTests { """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, exclude: ["wrapSwitchCases", "sortSwitchCases"]) } @@ -1032,7 +1032,7 @@ class HoistingTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, rule: .hoistPatternLet, options: options) } func testUnhoistCaseWithNilValue() { @@ -1053,7 +1053,7 @@ class HoistingTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } func testUnhoistCaseWithBoolValue() { @@ -1074,6 +1074,6 @@ class HoistingTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: FormatRules.hoistPatternLet, options: options) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) } } diff --git a/Tests/RulesTests+Indentation.swift b/Tests/RulesTests+Indentation.swift index f25f94b36..45c645626 100644 --- a/Tests/RulesTests+Indentation.swift +++ b/Tests/RulesTests+Indentation.swift @@ -15,13 +15,13 @@ class IndentTests: RulesTests { func testReduceIndentAtStartOfFile() { let input = " foo()" let output = "foo()" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testReduceIndentAtEndOfFile() { let input = "foo()\n bar()" let output = "foo()\nbar()" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } // indent parens @@ -29,55 +29,55 @@ class IndentTests: RulesTests { func testSimpleScope() { let input = "foo(\nbar\n)" let output = "foo(\n bar\n)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNestedScope() { let input = "foo(\nbar {\n}\n)" let output = "foo(\n bar {\n }\n)" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: ["emptyBraces"]) } func testNestedScopeOnSameLine() { let input = "foo(bar(\nbaz\n))" let output = "foo(bar(\n baz\n))" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNestedScopeOnSameLine2() { let input = "foo(bar(in:\nbaz))" let output = "foo(bar(in:\n baz))" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentNestedArrayLiteral() { let input = "foo(bar: [\n.baz,\n])" let output = "foo(bar: [\n .baz,\n])" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testClosingScopeAfterContent() { let input = "foo(\nbar\n)" let output = "foo(\n bar\n)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testClosingNestedScopeAfterContent() { let input = "foo(bar(\nbaz\n))" let output = "foo(bar(\n baz\n))" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedFunctionArguments() { let input = "foo(\nbar,\nbaz\n)" let output = "foo(\n bar,\n baz\n)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testFunctionArgumentsWrappedAfterFirst() { let input = "func foo(bar: Int,\nbaz: Int)" let output = "func foo(bar: Int,\n baz: Int)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentPreservedForNestedWrappedParameters() { @@ -88,7 +88,7 @@ class IndentTests: RulesTests { paymentFormURL: .paymentForm) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) } func testIndentPreservedForNestedWrappedParameters2() { @@ -99,7 +99,7 @@ class IndentTests: RulesTests { paymentFormURL: .paymentForm)) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) } func testIndentPreservedForNestedWrappedParameters3() { @@ -112,7 +112,7 @@ class IndentTests: RulesTests { ) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) } func testIndentTrailingClosureInParensContainingUnwrappedArguments() { @@ -121,7 +121,7 @@ class IndentTests: RulesTests { quux(foo, bar) }) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentTrailingClosureInParensContainingWrappedArguments() { @@ -131,7 +131,7 @@ class IndentTests: RulesTests { bar) }) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentTrailingClosureInParensContainingWrappedArguments2() { @@ -143,7 +143,7 @@ class IndentTests: RulesTests { ) }) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentImbalancedNestedClosingParens() { @@ -153,7 +153,7 @@ class IndentTests: RulesTests { baz: quux )) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentChainedCallAfterClosingParen() { @@ -166,7 +166,7 @@ class IndentTests: RulesTests { View() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentChainedCallAfterClosingParen2() { @@ -185,14 +185,14 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent modifiers func testNoIndentWrappedModifiersForProtocol() { let input = "@objc\nprivate\nprotocol Foo {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent braces @@ -200,37 +200,37 @@ class IndentTests: RulesTests { func testElseClauseIndenting() { let input = "if x {\nbar\n} else {\nbaz\n}" let output = "if x {\n bar\n} else {\n baz\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNoIndentBlankLines() { let input = "{\n\n// foo\n}" let output = "{\n\n // foo\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["blankLinesAtStartOfScope"]) + testFormatting(for: input, output, rule: .indent, exclude: ["blankLinesAtStartOfScope"]) } func testNestedBraces() { let input = "({\n// foo\n}, {\n// bar\n})" let output = "({\n // foo\n}, {\n // bar\n})" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testBraceIndentAfterComment() { let input = "if foo { // comment\nbar\n}" let output = "if foo { // comment\n bar\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testBraceIndentAfterClosingScope() { let input = "foo(bar(baz), {\nquux\nbleem\n})" let output = "foo(bar(baz), {\n quux\n bleem\n})" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .indent, exclude: ["trailingClosures"]) } func testBraceIndentAfterLineWithParens() { let input = "({\nfoo()\nbar\n})" let output = "({\n foo()\n bar\n})" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: ["redundantParens"]) } func testUnindentClosingParenAroundBraces() { @@ -244,7 +244,7 @@ class IndentTests: RulesTests { self.bar() }) """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentDoubleParenthesizedClosures() { @@ -255,7 +255,7 @@ class IndentTests: RulesTests { self.baz() })) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentUnbalancedBraces() { @@ -265,7 +265,7 @@ class IndentTests: RulesTests { .baz($0) }) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentClosureArguments() { @@ -285,7 +285,7 @@ class IndentTests: RulesTests { print(baz) }) """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureArguments2() { @@ -298,7 +298,7 @@ class IndentTests: RulesTests { } ) """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["wrapArguments"]) + testFormatting(for: input, rule: .indent, exclude: ["wrapArguments"]) } func testIndentWrappedClosureParameters() { @@ -310,7 +310,7 @@ class IndentTests: RulesTests { print(bar + baz) } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedClosureCaptureList() { @@ -323,7 +323,7 @@ class IndentTests: RulesTests { _ = topView } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // TODO: add `unwrap` rule to improve this case @@ -346,7 +346,7 @@ class IndentTests: RulesTests { return x + y } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: ["propertyType"]) } func testIndentWrappedClosureCaptureListWithUnwrappedParameters() { @@ -359,7 +359,7 @@ class IndentTests: RulesTests { _ = topView } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentTrailingClosureArgumentsAfterFunction() { @@ -373,7 +373,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) } func testIndentAllmanTrailingClosureArguments() { @@ -389,7 +389,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) } func testIndentAllmanTrailingClosureArguments2() { @@ -400,7 +400,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentAllmanTrailingClosureArgumentsAfterFunction() { @@ -422,7 +422,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["redundantReturn"]) } @@ -433,7 +433,7 @@ class IndentTests: RulesTests { { quux } )) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentLineAfterIndentedWrappedClosure() { @@ -447,7 +447,7 @@ class IndentTests: RulesTests { return viewController } """ - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["braces", "wrapMultilineStatementBraces", "redundantProperty"]) } @@ -460,7 +460,7 @@ class IndentTests: RulesTests { return viewController } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .indent, exclude: ["redundantProperty"]) } func testIndentLineAfterNonIndentedClosure() { @@ -473,7 +473,7 @@ class IndentTests: RulesTests { return viewController } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .indent, exclude: ["redundantProperty"]) } func testIndentMultilineStatementDoesntFailToTerminate() { @@ -484,7 +484,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent switch/case @@ -492,43 +492,43 @@ class IndentTests: RulesTests { func testSwitchCaseIndenting() { let input = "switch x {\ncase foo:\nbreak\ncase bar:\nbreak\ndefault:\nbreak\n}" let output = "switch x {\ncase foo:\n break\ncase bar:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testSwitchWrappedCaseIndenting() { let input = "switch x {\ncase foo,\nbar,\n baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase foo,\n bar,\n baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: ["sortSwitchCases"]) } func testSwitchWrappedEnumCaseIndenting() { let input = "switch x {\ncase .foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase .foo,\n .bar,\n .baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: ["sortSwitchCases"]) } func testSwitchWrappedEnumCaseIndentingVariant2() { let input = "switch x {\ncase\n.foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase\n .foo,\n .bar,\n .baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: ["sortSwitchCases"]) } func testSwitchWrappedEnumCaseIsIndenting() { let input = "switch x {\ncase is Foo.Type,\n is Bar.Type:\n break\ndefault:\n break\n}" let output = "switch x {\ncase is Foo.Type,\n is Bar.Type:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: ["sortSwitchCases"]) } func testSwitchCaseIsDictionaryIndenting() { let input = "switch x {\ncase foo is [Key: Value]:\nfallthrough\ndefault:\nbreak\n}" let output = "switch x {\ncase foo is [Key: Value]:\n fallthrough\ndefault:\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testEnumCaseIndenting() { let input = "enum Foo {\ncase Bar\ncase Baz\n}" let output = "enum Foo {\n case Bar\n case Baz\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testEnumCaseIndentingCommas() { @@ -539,29 +539,29 @@ class IndentTests: RulesTests { Baz } """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["wrapEnumCases"]) + testFormatting(for: input, output, rule: .indent, exclude: ["wrapEnumCases"]) } func testGenericEnumCaseIndenting() { let input = "enum Foo {\ncase Bar\ncase Baz\n}" let output = "enum Foo {\n case Bar\n case Baz\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentSwitchAfterRangeCase() { let input = "switch x {\ncase 0 ..< 2:\n switch y {\n default:\n break\n }\ndefault:\n break\n}" - testFormatting(for: input, rule: FormatRules.indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, rule: .indent, exclude: ["blankLineAfterSwitchCase"]) } func testIndentEnumDeclarationInsideSwitchCase() { let input = "switch x {\ncase y:\nenum Foo {\ncase z\n}\nbar()\ndefault: break\n}" let output = "switch x {\ncase y:\n enum Foo {\n case z\n }\n bar()\ndefault: break\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, exclude: ["blankLineAfterSwitchCase"]) } func testIndentEnumCaseBodyAfterWhereClause() { let input = "switch foo {\ncase _ where baz < quux:\n print(1)\n print(2)\ndefault:\n break\n}" - testFormatting(for: input, rule: FormatRules.indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, rule: .indent, exclude: ["blankLineAfterSwitchCase"]) } func testIndentSwitchCaseCommentsCorrectly() { @@ -587,7 +587,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, exclude: ["blankLineAfterSwitchCase"]) } func testIndentMultilineSwitchCaseCommentsCorrectly() { @@ -619,7 +619,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentEnumCaseComment() { @@ -635,25 +635,25 @@ class IndentTests: RulesTests { case bar } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentMultipleSingleLineSwitchCaseCommentsCorrectly() { let input = "switch x {\n// comment 1\n// comment 2\ncase y:\n// comment\nbreak\n}" let output = "switch x {\n// comment 1\n// comment 2\ncase y:\n // comment\n break\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentIfCase() { let input = "{\nif case let .foo(msg) = error {}\n}" let output = "{\n if case let .foo(msg) = error {}\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentGuardCase() { let input = "{\nguard case .Foo = error else {}\n}" let output = "{\n guard case .Foo = error else {}\n}" - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, exclude: ["wrapConditionalBodies"]) } @@ -663,7 +663,7 @@ class IndentTests: RulesTests { } else if let bar = baz, let baz = quux {} """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNestedIndentIfElse() { @@ -676,7 +676,7 @@ class IndentTests: RulesTests { let baz = quux {} } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentIfCaseLet() { @@ -684,7 +684,7 @@ class IndentTests: RulesTests { if case let foo = foo, let bar = bar {} """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultipleIfLet() { @@ -692,7 +692,7 @@ class IndentTests: RulesTests { if let foo = foo, let bar = bar, let baz = baz {} """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedConditionAlignsWithParen() { @@ -706,7 +706,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedConditionAlignsWithParen2() { @@ -720,7 +720,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentUnknownDefault() { @@ -740,7 +740,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentUnknownDefaultOnOwnLine() { @@ -762,7 +762,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentUnknownCase() { @@ -782,7 +782,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentUnknownCaseOnOwnLine() { @@ -804,7 +804,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedClassDeclaration() { @@ -814,7 +814,7 @@ class IndentTests: RulesTests { init() {} } """ - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) } @@ -832,7 +832,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testWrappedClassDeclarationWithBracesOnSameLineLikeXcode() { @@ -841,7 +841,7 @@ class IndentTests: RulesTests { Baz {} """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrappedClassDeclarationWithBraceOnNextLineLikeXcode() { @@ -853,7 +853,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrappedClassWhereDeclarationLikeXcode() { @@ -870,7 +870,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentSwitchCaseDo() { @@ -881,7 +881,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indentCase = true @@ -890,27 +890,27 @@ class IndentTests: RulesTests { let input = "switch x {\ncase foo:\nbreak\ncase bar:\nbreak\ndefault:\nbreak\n}" let output = "switch x {\n case foo:\n break\n case bar:\n break\n default:\n break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchWrappedEnumCaseWithIndentCaseTrue() { let input = "switch x {\ncase .foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\n case .foo,\n .bar,\n .baz:\n break\n default:\n break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["sortSwitchCases"]) } func testIndentMultilineSwitchCaseCommentsWithIndentCaseTrue() { let input = "switch x {\n/*\n * comment\n */\ncase y:\nbreak\n/*\n * comment\n */\ndefault:\nbreak\n}" let output = "switch x {\n /*\n * comment\n */\n case y:\n break\n /*\n * comment\n */\n default:\n break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testNoMangleLabelWhenIndentCaseTrue() { let input = "foo: while true {\n break foo\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentMultipleSingleLineSwitchCaseCommentsWithCommentsIgnoredCorrectlyWhenIndentCaseTrue() { @@ -923,7 +923,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, indentComments: false) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentUnknownDefaultCorrectlyWhenIndentCaseTrue() { @@ -944,7 +944,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentUnknownCaseCorrectlyWhenIndentCaseTrue() { @@ -965,7 +965,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentSwitchCaseDoWhenIndentCaseTrue() { @@ -977,7 +977,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent wrapped lines @@ -985,32 +985,32 @@ class IndentTests: RulesTests { func testWrappedLineAfterOperator() { let input = "if x {\nlet y = foo +\nbar\n}" let output = "if x {\n let y = foo +\n bar\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterComma() { let input = "let a = b,\nb = c" let output = "let a = b,\n b = c" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedBeforeComma() { let input = "let a = b\n, b = c" let output = "let a = b\n , b = c" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["leadingDelimiters"]) + testFormatting(for: input, output, rule: .indent, exclude: ["leadingDelimiters"]) } func testWrappedLineAfterCommaInsideArray() { let input = "[\nfoo,\nbar,\n]" let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeCommaInsideArray() { let input = "[\nfoo\n, bar,\n]" let output = "[\n foo\n , bar,\n]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["leadingDelimiters"]) } @@ -1018,33 +1018,33 @@ class IndentTests: RulesTests { let input = "[foo,\nbar]" let output = "[foo,\n bar]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testWrappedLineBeforeCommaInsideInlineArray() { let input = "[foo\n, bar]" let output = "[foo\n , bar]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["leadingDelimiters"]) } func testWrappedLineAfterColonInFunction() { let input = "func foo(bar:\nbaz)" let output = "func foo(bar:\n baz)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNoDoubleIndentOfWrapAfterAsAfterOpenScope() { let input = "(foo as\nBar)" let output = "(foo as\n Bar)" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: ["redundantParens"]) } func testNoDoubleIndentOfWrapBeforeAsAfterOpenScope() { let input = "(foo\nas Bar)" let output = "(foo\n as Bar)" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: ["redundantParens"]) } func testDoubleIndentWhenScopesSeparatedByWrap() { @@ -1060,93 +1060,93 @@ class IndentTests: RulesTests { baz }) """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: ["redundantParens"]) } func testNoDoubleIndentWhenScopesSeparatedByWrap() { let input = "(foo\nas Bar {\nbaz\n}\n)" let output = "(foo\n as Bar {\n baz\n }\n)" - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, exclude: ["wrapArguments", "redundantParens"]) } func testNoPermanentReductionInScopeAfterWrap() { let input = "{ foo\nas Bar\nlet baz = 5\n}" let output = "{ foo\n as Bar\n let baz = 5\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeOperator() { let input = "if x {\nlet y = foo\n+ bar\n}" let output = "if x {\n let y = foo\n + bar\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeIsOperator() { let input = "if x {\nlet y = foo\nis Bar\n}" let output = "if x {\n let y = foo\n is Bar\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterForKeyword() { let input = "for\ni in range {}" let output = "for\n i in range {}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterInKeyword() { let input = "for i in\nrange {}" let output = "for i in\n range {}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterDot() { let input = "let foo = bar.\nbaz" let output = "let foo = bar.\n baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeDot() { let input = "let foo = bar\n.baz" let output = "let foo = bar\n .baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeWhere() { let input = "let foo = bar\nwhere foo == baz" let output = "let foo = bar\n where foo == baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineAfterWhere() { let input = "let foo = bar where\nfoo == baz" let output = "let foo = bar where\n foo == baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineBeforeGuardElse() { let input = "guard let foo = bar\nelse { return }" - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["wrapConditionalBodies"]) } func testWrappedLineAfterGuardElse() { // Don't indent because this case is handled by braces rule let input = "guard let foo = bar else\n{ return }" - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["elseOnSameLine", "wrapConditionalBodies"]) } func testWrappedLineAfterComment() { let input = "foo = bar && // comment\nbaz" let output = "foo = bar && // comment\n baz" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedLineInClosure() { let input = "forEach { item in\nprint(item)\n}" let output = "forEach { item in\n print(item)\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedGuardInClosure() { @@ -1156,71 +1156,71 @@ class IndentTests: RulesTests { let bar = bar else { break } } """ - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["wrapMultilineStatementBraces", "wrapConditionalBodies"]) } func testConsecutiveWraps() { let input = "let a = b +\nc +\nd" let output = "let a = b +\n c +\n d" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrapReset() { let input = "let a = b +\nc +\nd\nlet a = b +\nc +\nd" let output = "let a = b +\n c +\n d\nlet a = b +\n c +\n d" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentElseAfterComment() { let input = "if x {}\n// comment\nelse {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testWrappedLinesWithComments() { let input = "let foo = bar ||\n // baz||\nquux" let output = "let foo = bar ||\n // baz||\n quux" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNoIndentAfterAssignOperatorToVariable() { let input = "let greaterThan = >\nlet lessThan = <" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentAfterDefaultAsIdentifier() { let input = "let foo = FileManager.default\n/// Comment\nlet bar = 0" - testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: ["propertyType"]) } func testIndentClosureStartingOnIndentedLine() { let input = "foo\n.bar {\nbaz()\n}" let output = "foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureStartingOnIndentedLineInVar() { let input = "var foo = foo\n.bar {\nbaz()\n}" let output = "var foo = foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureStartingOnIndentedLineInLet() { let input = "let foo = foo\n.bar {\nbaz()\n}" let output = "let foo = foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureStartingOnIndentedLineInTypedVar() { let input = "var: Int foo = foo\n.bar {\nbaz()\n}" let output = "var: Int foo = foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentClosureStartingOnIndentedLineInTypedLet() { let input = "let: Int foo = foo\n.bar {\nbaz()\n}" let output = "let: Int foo = foo\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNestedWrappedIfIndents() { @@ -1234,25 +1234,25 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["andOperator", "wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: ["andOperator", "wrapMultilineStatementBraces"]) } func testWrappedEnumThatLooksLikeIf() { let input = "foo &&\n bar.if {\nfoo()\n}" let output = "foo &&\n bar.if {\n foo()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndents() { let input = "foo\n.bar {\nbaz()\n}\n.bar {\nbaz()\n}" let output = "foo\n .bar {\n baz()\n }\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsAfterIfCondition() { let input = "if foo {\nbar()\n.baz()\n}\n\nfoo\n.bar {\nbaz()\n}\n.bar {\nbaz()\n}" let output = "if foo {\n bar()\n .baz()\n}\n\nfoo\n .bar {\n baz()\n }\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsAfterIfCondition2() { @@ -1282,19 +1282,19 @@ class IndentTests: RulesTests { baz() } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsAfterVarDeclaration() { let input = "var foo: Int\nfoo\n.bar {\nbaz()\n}\n.bar {\nbaz()\n}" let output = "var foo: Int\nfoo\n .bar {\n baz()\n }\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsAfterLetDeclaration() { let input = "let foo: Int\nfoo\n.bar {\nbaz()\n}\n.bar {\nbaz()\n}" let output = "let foo: Int\nfoo\n .bar {\n baz()\n }\n .bar {\n baz()\n }" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedClosureIndentsSeparatedByComments() { @@ -1312,7 +1312,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["blankLinesBetweenScopes"]) } @@ -1323,7 +1323,7 @@ class IndentTests: RulesTests { }) .buttonStyle(bar()) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testChainedFunctionIndentWithXcodeIndentation() { @@ -1334,7 +1334,7 @@ class IndentTests: RulesTests { .buttonStyle(bar()) """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrappedClosureIndentAfterAssignment() { @@ -1344,7 +1344,7 @@ class IndentTests: RulesTests { print("baz") } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testChainedFunctionsInPropertySetter() { @@ -1358,7 +1358,7 @@ class IndentTests: RulesTests { .baz()! .quux """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedFunctionsInPropertySetterOnNewLine() { @@ -1374,88 +1374,88 @@ class IndentTests: RulesTests { .baz()! .quux """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedFunctionsInsideIf() { let input = "if foo {\nreturn bar()\n.baz()\n}" let output = "if foo {\n return bar()\n .baz()\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedFunctionsInsideForLoop() { let input = "for x in y {\nfoo\n.bar {\nbaz()\n}\n.quux()\n}" let output = "for x in y {\n foo\n .bar {\n baz()\n }\n .quux()\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testChainedFunctionsAfterAnIfStatement() { let input = "if foo {}\nbar\n.baz {\n}\n.quux()" let output = "if foo {}\nbar\n .baz {\n }\n .quux()" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: ["emptyBraces"]) } func testIndentInsideWrappedIfStatementWithClosureCondition() { let input = "if foo({ 1 }) ||\nbar {\nbaz()\n}" let output = "if foo({ 1 }) ||\n bar {\n baz()\n}" - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) } func testIndentInsideWrappedClassDefinition() { let input = "class Foo\n: Bar {\nbaz()\n}" let output = "class Foo\n : Bar {\n baz()\n}" - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, exclude: ["leadingDelimiters", "wrapMultilineStatementBraces"]) } func testIndentInsideWrappedProtocolDefinition() { let input = "protocol Foo\n: Bar, Baz {\nbaz()\n}" let output = "protocol Foo\n : Bar, Baz {\n baz()\n}" - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, exclude: ["leadingDelimiters", "wrapMultilineStatementBraces"]) } func testIndentInsideWrappedVarStatement() { let input = "var Foo:\nBar {\nreturn 5\n}" let output = "var Foo:\n Bar {\n return 5\n}" - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) } func testNoIndentAfterOperatorDeclaration() { let input = "infix operator ?=\nfunc ?= (lhs _: Int, rhs _: Int) -> Bool {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentAfterChevronOperatorDeclaration() { let input = "infix operator =<<\nfunc =<< (lhs _: T, rhs _: T) -> T {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedStringDictionaryKeysAndValues() { let input = "[\n\"foo\":\n\"bar\",\n\"baz\":\n\"quux\",\n]" let output = "[\n \"foo\":\n \"bar\",\n \"baz\":\n \"quux\",\n]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentWrappedEnumDictionaryKeysAndValues() { let input = "[\n.foo:\n.bar,\n.baz:\n.quux,\n]" let output = "[\n .foo:\n .bar,\n .baz:\n .quux,\n]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentWrappedFunctionArgument() { let input = "foobar(baz: a &&\nb)" let output = "foobar(baz: a &&\n b)" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentWrappedFunctionClosureArgument() { let input = "foobar(baz: { a &&\nb })" let output = "foobar(baz: { a &&\n b })" - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, exclude: ["trailingClosures", "braces"]) } @@ -1467,12 +1467,12 @@ class IndentTests: RulesTests { baz: baz) """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentClassDeclarationContainingComment() { let input = "class Foo: Bar,\n // Comment\n Baz {}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testWrappedLineAfterTypeAttribute() { @@ -1480,7 +1480,7 @@ class IndentTests: RulesTests { let f: @convention(swift) (Int) -> Int = { x in x } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testWrappedLineAfterTypeAttribute2() { @@ -1488,7 +1488,7 @@ class IndentTests: RulesTests { func foo(_: @escaping (Int) -> Int) {} """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testWrappedLineAfterNonTypeAttribute() { @@ -1496,7 +1496,7 @@ class IndentTests: RulesTests { @discardableResult func foo() -> Int { 5 } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentWrappedClosureAfterSwitch() { @@ -1510,7 +1510,7 @@ class IndentTests: RulesTests { // baz } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testSingleIndentTrailingClosureBody() { @@ -1526,7 +1526,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .balanced) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["wrapConditionalBodies"]) } @@ -1541,7 +1541,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["wrapConditionalBodies", "wrapMultilineStatementBraces"]) } @@ -1557,7 +1557,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["wrapConditionalBodies", "wrapMultilineStatementBraces"]) } @@ -1573,7 +1573,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["wrapMultilineStatementBraces"]) } @@ -1586,7 +1586,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) } func testSingleIndentTrailingClosureBodyThatStartsOnFollowingLine() { @@ -1602,7 +1602,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["braces", "wrapConditionalBodies"]) } @@ -1614,7 +1614,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["wrapConditionalBodies"]) } @@ -1625,7 +1625,7 @@ class IndentTests: RulesTests { baz }) """ - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["trailingClosures"]) } @@ -1637,7 +1637,7 @@ class IndentTests: RulesTests { print("and a trailing closure") } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentInInsideClosure3() { @@ -1647,7 +1647,7 @@ class IndentTests: RulesTests { self?.bar() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentInInsideClosure4() { @@ -1657,7 +1657,7 @@ class IndentTests: RulesTests { self?.bar(baz) } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentInInsideClosure5() { @@ -1668,7 +1668,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentInInsideClosure6() { @@ -1679,7 +1679,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoDoubleIndentForInInsideFunction() { @@ -1690,7 +1690,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoUnindentTrailingClosure() { @@ -1717,7 +1717,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["wrapArguments", "wrapMultilineStatementBraces"]) } @@ -1729,7 +1729,7 @@ class IndentTests: RulesTests { .bar .baz """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: ["propertyType"]) } func testIndentChainedPropertiesAfterFunctionCallWithXcodeIndentation() { @@ -1741,7 +1741,7 @@ class IndentTests: RulesTests { .baz """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) } func testIndentChainedPropertiesAfterFunctionCall2() { @@ -1752,7 +1752,7 @@ class IndentTests: RulesTests { .bar .baz """ - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["trailingClosures", "propertyType"]) } @@ -1765,7 +1765,7 @@ class IndentTests: RulesTests { .baz """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["trailingClosures", "propertyType"]) } @@ -1779,7 +1779,7 @@ class IndentTests: RulesTests { .baz() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentChainedMethodsAfterTrailingClosureWithXcodeIndentation() { @@ -1793,7 +1793,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentChainedMethodsAfterWrappedMethodAfterTrailingClosure() { @@ -1807,7 +1807,7 @@ class IndentTests: RulesTests { .baz() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentChainedMethodsAfterWrappedMethodAfterTrailingClosureWithXcodeIndentation() { @@ -1822,7 +1822,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testChainedFunctionOnNewLineWithXcodeIndentation() { @@ -1837,7 +1837,7 @@ class IndentTests: RulesTests { .quux """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testChainedFunctionOnNewLineWithXcodeIndentation2() { @@ -1851,7 +1851,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testCommentSeparatedChainedFunctionAfterBraceWithXcodeIndentation() { @@ -1865,7 +1865,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testChainedFunctionsInPropertySetterOnNewLineWithXcodeIndentation() { @@ -1882,7 +1882,7 @@ class IndentTests: RulesTests { .quux """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testChainedFunctionsInFunctionWithReturnOnNewLineWithXcodeIndentation() { @@ -1903,7 +1903,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testChainedFunctionInGuardIndentation() { @@ -1914,7 +1914,7 @@ class IndentTests: RulesTests { .baz else { return } """ - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["wrapConditionalBodies"]) } @@ -1934,7 +1934,7 @@ class IndentTests: RulesTests { else { return } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["wrapConditionalBodies"]) } @@ -1949,7 +1949,7 @@ class IndentTests: RulesTests { yetAnotherBool else { return } """ - testFormatting(for: input, rule: FormatRules.indent, + testFormatting(for: input, rule: .indent, exclude: ["wrapConditionalBodies"]) } @@ -1976,7 +1976,7 @@ class IndentTests: RulesTests { else { return } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["wrapConditionalBodies"]) } @@ -1994,7 +1994,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testConditionalInitArgumentIndentAfterBrace() { @@ -2019,7 +2019,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testConditionalInitArgumentIndentAfterBraceNoIndent() { @@ -2045,7 +2045,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testConditionalCompiledWrappedChainedFunctionIndent() { @@ -2078,7 +2078,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .indent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testConditionalCompiledWrappedChainedFunctionIndent2() { @@ -2111,7 +2111,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .indent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testConditionalCompiledWrappedChainedFunctionWithIfdefNoIndent() { @@ -2144,7 +2144,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testConditionalCompiledWrappedChainedFunctionWithIfdefOutdent() { @@ -2177,7 +2177,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testChainedOrOperatorsInFunctionWithReturnOnNewLine() { @@ -2197,7 +2197,7 @@ class IndentTests: RulesTests { lhs == rhs } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testWrappedSingleLineClosureOnNewLine() { @@ -2207,7 +2207,7 @@ class IndentTests: RulesTests { { print("foo") } } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: ["braces"]) } func testWrappedMultilineClosureOnNewLine() { @@ -2219,7 +2219,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: ["braces"]) } func testWrappedMultilineClosureOnNewLineWithAllmanBraces() { @@ -2232,7 +2232,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["braces"]) } @@ -2245,7 +2245,7 @@ class IndentTests: RulesTests { .baz """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrappedExpressionIndentAfterTryInClosure() { @@ -2256,7 +2256,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testNoIndentTryAfterCommaInCollection() { @@ -2268,7 +2268,7 @@ class IndentTests: RulesTests { viewModel.snake, ] """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["hoistTry"]) + testFormatting(for: input, rule: .indent, exclude: ["hoistTry"]) } func testIndentChainedFunctionAfterTryInParens() { @@ -2281,7 +2281,7 @@ class IndentTests: RulesTests { ) ?? [] } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentLabelledTrailingClosure() { @@ -2296,7 +2296,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentLinewrappedMultipleTrailingClosures() { @@ -2308,7 +2308,7 @@ class IndentTests: RulesTests { context.completeTransition(finished) } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentLinewrappedMultipleTrailingClosures2() { @@ -2322,7 +2322,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent comments @@ -2330,19 +2330,19 @@ class IndentTests: RulesTests { func testCommentIndenting() { let input = "/* foo\nbar */" let output = "/* foo\n bar */" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testCommentIndentingWithTrailingClose() { let input = "/*\nfoo\n*/" let output = "/*\n foo\n */" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testCommentIndentingWithTrailingClose2() { let input = "/* foo\n*/" let output = "/* foo\n */" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testNestedCommentIndenting() { @@ -2356,7 +2356,7 @@ class IndentTests: RulesTests { } */ """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNestedCommentIndenting2() { @@ -2380,17 +2380,17 @@ class IndentTests: RulesTests { ``` */ """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testCommentedCodeBlocksNotIndented() { let input = "func foo() {\n// var foo: Int\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testBlankCodeCommentBlockLinesNotIndented() { let input = "func foo() {\n//\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testCommentedCodeAfterBracketNotIndented() { @@ -2400,7 +2400,7 @@ class IndentTests: RulesTests { second, ] """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testCommentedCodeAfterBracketNotIndented2() { @@ -2409,7 +2409,7 @@ class IndentTests: RulesTests { // second, third] """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // TODO: maybe need special case handling for this? @@ -2424,25 +2424,25 @@ class IndentTests: RulesTests { // comment // block """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } // indent multiline strings func testSimpleMultilineString() { let input = "\"\"\"\n hello\n world\n\"\"\"" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentIndentedSimpleMultilineString() { let input = "{\n\"\"\"\n hello\n world\n \"\"\"\n}" let output = "{\n \"\"\"\n hello\n world\n \"\"\"\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testMultilineStringWithEscapedLinebreak() { let input = "\"\"\"\n hello \\n world\n\"\"\"" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringWrappedAfter() { @@ -2452,7 +2452,7 @@ class IndentTests: RulesTests { baz \"\"") """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringInNestedCalls() { @@ -2461,7 +2461,7 @@ class IndentTests: RulesTests { baz \"\"")) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringInFunctionWithfollowingArgument() { @@ -2470,7 +2470,7 @@ class IndentTests: RulesTests { baz \"\"", quux: 5)) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testReduceIndentForMultilineString() { @@ -2490,7 +2490,7 @@ class IndentTests: RulesTests { \"\"" } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testReduceIndentForMultilineString2() { @@ -2504,7 +2504,7 @@ class IndentTests: RulesTests { bar \"\"") """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentMultilineStringWithMultilineInterpolation() { @@ -2519,7 +2519,7 @@ class IndentTests: RulesTests { \"\"" } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringWithMultilineNestedInterpolation() { @@ -2536,7 +2536,7 @@ class IndentTests: RulesTests { \"\"" } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineStringWithMultilineNestedInterpolation2() { @@ -2554,7 +2554,7 @@ class IndentTests: RulesTests { \"\"" } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indentStrings = true @@ -2579,7 +2579,7 @@ class IndentTests: RulesTests { } """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testNoIndentMultilineStringWithOmittedReturn() { @@ -2593,7 +2593,7 @@ class IndentTests: RulesTests { } """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testNoIndentMultilineStringOnOwnLineInMethodCall() { @@ -2607,7 +2607,7 @@ class IndentTests: RulesTests { ) """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentMultilineStringInMethodCall() { @@ -2624,7 +2624,7 @@ class IndentTests: RulesTests { """) """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentMultilineStringAtTopLevel() { @@ -2647,7 +2647,7 @@ class IndentTests: RulesTests { """ """# let options = FormatOptions(indent: " ", indentStrings: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentMultilineStringWithBlankLine() { @@ -2667,7 +2667,7 @@ class IndentTests: RulesTests { """ """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentMultilineStringPreservesBlankLines() { @@ -2679,7 +2679,7 @@ class IndentTests: RulesTests { """ """# let options = FormatOptions(indentStrings: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testUnindentMultilineStringAtTopLevel() { @@ -2702,7 +2702,7 @@ class IndentTests: RulesTests { """ """# let options = FormatOptions(indent: " ", indentStrings: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentUnderIndentedMultilineStringPreservesBlankLineIndent() { @@ -2729,7 +2729,7 @@ class IndentTests: RulesTests { } """# let options = FormatOptions(truncateBlankLines: false) - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, options: options) } @@ -2757,7 +2757,7 @@ class IndentTests: RulesTests { } """# let options = FormatOptions(truncateBlankLines: false) - testFormatting(for: input, output, rule: FormatRules.indent, + testFormatting(for: input, output, rule: .indent, options: options) } @@ -2766,7 +2766,7 @@ class IndentTests: RulesTests { func testIndentIndentedSimpleRawMultilineString() { let input = "{\n##\"\"\"\n hello\n world\n \"\"\"##\n}" let output = "{\n ##\"\"\"\n hello\n world\n \"\"\"##\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } // indent multiline regex literals @@ -2779,7 +2779,7 @@ class IndentTests: RulesTests { (baz?) /# """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoMisindentCasePath() { @@ -2789,7 +2789,7 @@ class IndentTests: RulesTests { environment: {} ) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent #if/#else/#elseif/#endif @@ -2889,7 +2889,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } // indent #if/#else/#elseif/#endif (mode: indent) @@ -2897,82 +2897,82 @@ class IndentTests: RulesTests { func testIfEndifIndenting() { let input = "#if x\n// foo\n#endif" let output = "#if x\n // foo\n#endif" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentedIfEndifIndenting() { let input = "{\n#if x\n// foo\nfoo()\n#endif\n}" let output = "{\n #if x\n // foo\n foo()\n #endif\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIfElseEndifIndenting() { let input = "#if x\n // foo\nfoo()\n#else\n // bar\n#endif" let output = "#if x\n // foo\n foo()\n#else\n // bar\n#endif" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testEnumIfCaseEndifIndenting() { let input = "enum Foo {\ncase bar\n#if x\ncase baz\n#endif\n}" let output = "enum Foo {\n case bar\n #if x\n case baz\n #endif\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseEndifIndenting() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let output = "switch foo {\ncase .bar: break\n#if x\n case .baz: break\n#endif\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseEndifIndenting2() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let output = "switch foo {\n case .bar: break\n #if x\n case .baz: break\n #endif\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseEndifIndenting3() { let input = "switch foo {\n#if x\ncase .bar: break\ncase .baz: break\n#endif\n}" let output = "switch foo {\n#if x\n case .bar: break\n case .baz: break\n#endif\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseEndifIndenting4() { let input = "switch foo {\n#if x\ncase .bar:\nbreak\ncase .baz:\nbreak\n#endif\n}" let output = "switch foo {\n #if x\n case .bar:\n break\n case .baz:\n break\n #endif\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseElseCaseEndifIndenting() { let input = "switch foo {\n#if x\ncase .bar: break\n#else\ncase .baz: break\n#endif\n}" let output = "switch foo {\n#if x\n case .bar: break\n#else\n case .baz: break\n#endif\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfCaseElseCaseEndifIndenting2() { let input = "switch foo {\n#if x\ncase .bar: break\n#else\ncase .baz: break\n#endif\n}" let output = "switch foo {\n #if x\n case .bar: break\n #else\n case .baz: break\n #endif\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testSwitchIfEndifInsideCaseIndenting() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\ncase .bar:\n #if x\n bar()\n #endif\n baz()\ncase .baz: break\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLineAfterSwitchCase"]) } func testSwitchIfEndifInsideCaseIndenting2() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\n case .bar:\n #if x\n bar()\n #endif\n baz()\n case .baz: break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLineAfterSwitchCase"]) } func testIfUnknownCaseEndifIndenting() { @@ -2985,7 +2985,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: false, ifdefIndent: .indent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfUnknownCaseEndifIndenting2() { @@ -2998,7 +2998,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, ifdefIndent: .indent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumIndenting() { @@ -3010,7 +3010,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIfEndifInsideEnumWithTrailingCommentIndenting() { @@ -3022,7 +3022,7 @@ class IndentTests: RulesTests { #endif // ends } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentCommentBeforeIfdefAroundCase() { @@ -3043,7 +3043,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentCommentedCodeBeforeIfdefAroundCase() { @@ -3057,7 +3057,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentIfdefFollowedByCommentAroundCase() { @@ -3076,7 +3076,7 @@ class IndentTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentIfDefPostfixMemberSyntax() { @@ -3108,7 +3108,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentIfDefPostfixMemberSyntax2() { @@ -3123,7 +3123,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testNoIndentDotExpressionInsideIfdef() { @@ -3140,7 +3140,7 @@ class IndentTests: RulesTests { #endif }() """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent #if/#else/#elseif/#endif (mode: noindent) @@ -3148,33 +3148,33 @@ class IndentTests: RulesTests { func testIfEndifNoIndenting() { let input = "#if x\n// foo\n#endif" let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentedIfEndifNoIndenting() { let input = "{\n#if x\n// foo\n#endif\n}" let output = "{\n #if x\n // foo\n #endif\n}" let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfElseEndifNoIndenting() { let input = "#if x\n// foo\n#else\n// bar\n#endif" let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfCaseEndifNoIndenting() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let options = FormatOptions(indentCase: false, ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfCaseEndifNoIndenting2() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let output = "switch foo {\n case .bar: break\n #if x\n case .baz: break\n #endif\n}" let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfUnknownCaseEndifNoIndenting() { @@ -3187,7 +3187,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: false, ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfUnknownCaseEndifNoIndenting2() { @@ -3200,21 +3200,21 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideCaseNoIndenting() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\ncase .bar:\n #if x\n bar()\n #endif\n baz()\ncase .baz: break\n}" let options = FormatOptions(indentCase: false, ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLineAfterSwitchCase"]) } func testIfEndifInsideCaseNoIndenting2() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\n case .bar:\n #if x\n bar()\n #endif\n baz()\n case .baz: break\n}" let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLineAfterSwitchCase"]) } func testSwitchCaseInIfEndif() { @@ -3236,7 +3236,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, ifdefIndent: .indent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testSwitchCaseInIfEndifNoIndenting() { @@ -3258,7 +3258,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumNoIndenting() { @@ -3271,7 +3271,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumWithTrailingCommentNoIndenting() { @@ -3284,7 +3284,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxNoIndenting() { @@ -3303,7 +3303,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxNoIndenting2() { @@ -3321,7 +3321,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxNoIndenting3() { @@ -3339,7 +3339,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testNoIndentDotInitInsideIfdef() { @@ -3355,7 +3355,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testNoIndentDotInitInsideIfdef2() { @@ -3369,7 +3369,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .noIndent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent #if/#else/#elseif/#endif (mode: outdent) @@ -3377,60 +3377,60 @@ class IndentTests: RulesTests { func testIfEndifOutdenting() { let input = "#if x\n// foo\n#endif" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentedIfEndifOutdenting() { let input = "{\n#if x\n// foo\n#endif\n}" let output = "{\n#if x\n // foo\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfElseEndifOutdenting() { let input = "#if x\n// foo\n#else\n// bar\n#endif" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentedIfElseEndifOutdenting() { let input = "{\n#if x\n// foo\nfoo()\n#else\n// bar\n#endif\n}" let output = "{\n#if x\n // foo\n foo()\n#else\n // bar\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfElseifEndifOutdenting() { let input = "#if x\n// foo\n#elseif y\n// bar\n#endif" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentedIfElseifEndifOutdenting() { let input = "{\n#if x\n// foo\nfoo()\n#elseif y\n// bar\n#endif\n}" let output = "{\n#if x\n // foo\n foo()\n#elseif y\n // bar\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testNestedIndentedIfElseifEndifOutdenting() { let input = "{\n#if x\n#if y\n// foo\nfoo()\n#elseif y\n// bar\n#endif\n#endif\n}" let output = "{\n#if x\n#if y\n // foo\n foo()\n#elseif y\n // bar\n#endif\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testDoubleNestedIndentedIfElseifEndifOutdenting() { let input = "{\n#if x\n#if y\n#if z\n// foo\nfoo()\n#elseif y\n// bar\n#endif\n#endif\n#endif\n}" let output = "{\n#if x\n#if y\n#if z\n // foo\n foo()\n#elseif y\n // bar\n#endif\n#endif\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIfCaseEndifOutdenting() { let input = "switch foo {\ncase .bar: break\n#if x\ncase .baz: break\n#endif\n}" let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumOutdenting() { @@ -3443,7 +3443,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfEndifInsideEnumWithTrailingCommentOutdenting() { @@ -3456,7 +3456,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxOutdenting() { @@ -3475,7 +3475,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxOutdenting2() { @@ -3493,7 +3493,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIfDefPostfixMemberSyntaxOutdenting3() { @@ -3511,50 +3511,50 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(ifdefIndent: .outdent) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent expression after return func testIndentIdentifierAfterReturn() { let input = "if foo {\n return\n bar\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentEnumValueAfterReturn() { let input = "if foo {\n return\n .bar\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentMultilineExpressionAfterReturn() { let input = "if foo {\n return\n bar +\n baz\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testDontIndentClosingBraceAfterReturn() { let input = "if foo {\n return\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testDontIndentCaseAfterReturn() { let input = "switch foo {\ncase bar:\n return\ncase baz:\n return\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testDontIndentCaseAfterWhere() { let input = "switch foo {\ncase bar\nwhere baz:\nreturn\ndefault:\nreturn\n}" let output = "switch foo {\ncase bar\n where baz:\n return\ndefault:\n return\n}" - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testDontIndentIfAfterReturn() { let input = "if foo {\n return\n if bar {}\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testDontIndentFuncAfterReturn() { let input = "if foo {\n return\n func bar() {}\n}" - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } // indent fragments @@ -3563,21 +3563,21 @@ class IndentTests: RulesTests { let input = " func foo() {\nbar()\n}" let output = " func foo() {\n bar()\n }" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testIndentFragmentAfterBlankLines() { let input = "\n\n func foo() {\nbar()\n}" let output = "\n\n func foo() {\n bar()\n }" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testUnterminatedFragment() { let input = "class Foo {\n\n func foo() {\nbar()\n}" let output = "class Foo {\n\n func foo() {\n bar()\n }" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLinesAtStartOfScope"]) } @@ -3585,19 +3585,19 @@ class IndentTests: RulesTests { let input = " func foo() {\nbar()\n}\n\n}" let output = " func foo() {\n bar()\n }\n\n}" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testDontCorruptPartialFragment() { let input = " } foo {\n bar\n }\n}" let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testDontCorruptPartialFragment2() { let input = " return completionHandler(nil)\n }\n}" let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testDontCorruptPartialFragment3() { @@ -3607,7 +3607,7 @@ class IndentTests: RulesTests { foo2: bar3 """ let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } // indent with tabs @@ -3618,7 +3618,7 @@ class IndentTests: RulesTests { baz: Int) """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: true) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testTabIndentWrappedTupleWithoutSmartTabs() { @@ -3631,7 +3631,7 @@ class IndentTests: RulesTests { \t\t\t\t\t baz: Int) """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options) + testFormatting(for: input, output, rule: .indent, options: options) } func testTabIndentCaseWithSmartTabs() { @@ -3650,7 +3650,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: true) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["sortSwitchCases"]) } func testTabIndentCaseWithoutSmartTabs() { @@ -3669,7 +3669,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["sortSwitchCases"]) } func testTabIndentCaseWithoutSmartTabs2() { @@ -3689,7 +3689,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(indent: "\t", indentCase: true, tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: FormatRules.indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: ["sortSwitchCases"]) } // indent blank lines @@ -3702,10 +3702,10 @@ class IndentTests: RulesTests { \tquux() } """ - let rules = [FormatRules.indent, FormatRules.trailingSpace] + let rules: [FormatRule] = [.indent, .trailingSpace] let options = FormatOptions(indent: "\t", truncateBlankLines: true, tabWidth: 2) XCTAssertEqual(try lint(input, rules: rules, options: options), [ - Formatter.Change(line: 3, rule: FormatRules.trailingSpace, filePath: nil), + Formatter.Change(line: 3, rule: .trailingSpace, filePath: nil), ]) } @@ -3719,7 +3719,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indent: "\t", truncateBlankLines: false, tabWidth: 2) - testFormatting(for: input, rule: FormatRules.indent, options: options, + testFormatting(for: input, rule: .indent, options: options, exclude: ["consecutiveBlankLines", "wrapConditionalBodies"]) } @@ -3733,7 +3733,7 @@ class IndentTests: RulesTests { async throws -> String {} """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testAsyncTypedThrowsNotUnindented() { @@ -3744,7 +3744,7 @@ class IndentTests: RulesTests { async throws(Foo) -> String {} """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentAsyncLet() { @@ -3760,7 +3760,7 @@ class IndentTests: RulesTests { async let baz = quux() } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentAsyncLetAfterLet() { @@ -3770,7 +3770,7 @@ class IndentTests: RulesTests { async let foo = bar() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentAsyncLetAfterBrace() { @@ -3783,7 +3783,7 @@ class IndentTests: RulesTests { async let foo = bar() } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testAsyncFunctionArgumentLabelNotIndented() { @@ -3794,7 +3794,7 @@ class IndentTests: RulesTests { -> String {} """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testIndentIfExpressionAssignmentOnNextLine() { @@ -3836,7 +3836,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) } func testIndentIfExpressionAssignmentOnSameLine() { @@ -3854,7 +3854,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, rule: .indent, exclude: ["wrapMultilineConditionalAssignment"]) } func testIndentSwitchExpressionAssignment() { @@ -3878,7 +3878,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentSwitchExpressionAssignmentInNestedScope() { @@ -3914,7 +3914,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.indent, exclude: ["redundantProperty"]) + testFormatting(for: input, output, rule: .indent, exclude: ["redundantProperty"]) } func testIndentNestedSwitchExpressionAssignment() { @@ -3948,7 +3948,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentSwitchExpressionAssignmentWithComments() { @@ -3982,7 +3982,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, output, rule: FormatRules.indent) + testFormatting(for: input, output, rule: .indent) } func testIndentIfExpressionWithSingleComment() { @@ -3998,7 +3998,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testIndentIfExpressionWithComments() { @@ -4016,7 +4016,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) } func testIndentMultilineIfExpression() { @@ -4037,7 +4037,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: ["braces"]) } func testIndentNestedIfExpressionWithComments() { @@ -4062,7 +4062,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: FormatRules.indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) } func testIndentIfExpressionWithMultilineComments() { @@ -4080,7 +4080,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.indent) + testFormatting(for: input, rule: .indent) } func testSE0380Example() { @@ -4094,6 +4094,6 @@ class IndentTests: RulesTests { print(bullet) """ let options = FormatOptions() - testFormatting(for: input, rule: FormatRules.indent, options: options, exclude: ["wrapConditionalBodies", "andOperator", "redundantParens"]) + testFormatting(for: input, rule: .indent, options: options, exclude: ["wrapConditionalBodies", "andOperator", "redundantParens"]) } } diff --git a/Tests/RulesTests+Linebreaks.swift b/Tests/RulesTests+Linebreaks.swift index 287ccad9c..e8a2df69e 100644 --- a/Tests/RulesTests+Linebreaks.swift +++ b/Tests/RulesTests+Linebreaks.swift @@ -15,25 +15,25 @@ class LinebreakTests: RulesTests { func testCarriageReturn() { let input = "foo\rbar" let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) + testFormatting(for: input, output, rule: .linebreaks) } func testCarriageReturnLinefeed() { let input = "foo\r\nbar" let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) + testFormatting(for: input, output, rule: .linebreaks) } func testVerticalTab() { let input = "foo\u{000B}bar" let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) + testFormatting(for: input, output, rule: .linebreaks) } func testFormfeed() { let input = "foo\u{000C}bar" let output = "foo\nbar" - testFormatting(for: input, output, rule: FormatRules.linebreaks) + testFormatting(for: input, output, rule: .linebreaks) } // MARK: - consecutiveBlankLines @@ -41,42 +41,42 @@ class LinebreakTests: RulesTests { func testConsecutiveBlankLines() { let input = "foo\n\n \nbar" let output = "foo\n\nbar" - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines) + testFormatting(for: input, output, rule: .consecutiveBlankLines) } func testConsecutiveBlankLinesAtEndOfFile() { let input = "foo\n\n" let output = "foo\n" - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines) + testFormatting(for: input, output, rule: .consecutiveBlankLines) } func testConsecutiveBlankLinesAtStartOfFile() { let input = "\n\n\nfoo" let output = "\n\nfoo" - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines) + testFormatting(for: input, output, rule: .consecutiveBlankLines) } func testConsecutiveBlankLinesInsideStringLiteral() { let input = "\"\"\"\nhello\n\n\nworld\n\"\"\"" - testFormatting(for: input, rule: FormatRules.consecutiveBlankLines) + testFormatting(for: input, rule: .consecutiveBlankLines) } func testConsecutiveBlankLinesAtStartOfStringLiteral() { let input = "\"\"\"\n\n\nhello world\n\"\"\"" - testFormatting(for: input, rule: FormatRules.consecutiveBlankLines) + testFormatting(for: input, rule: .consecutiveBlankLines) } func testConsecutiveBlankLinesAfterStringLiteral() { let input = "\"\"\"\nhello world\n\"\"\"\n\n\nfoo()" let output = "\"\"\"\nhello world\n\"\"\"\n\nfoo()" - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines) + testFormatting(for: input, output, rule: .consecutiveBlankLines) } func testFragmentWithTrailingLinebreaks() { let input = "func foo() {}\n\n\n" let output = "func foo() {}\n\n" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.consecutiveBlankLines, options: options) + testFormatting(for: input, output, rule: .consecutiveBlankLines, options: options) } func testConsecutiveBlankLinesNoInterpolation() { @@ -89,7 +89,7 @@ class LinebreakTests: RulesTests { \"\"\" """ - testFormatting(for: input, rule: FormatRules.consecutiveBlankLines) + testFormatting(for: input, rule: .consecutiveBlankLines) } func testConsecutiveBlankLinesAfterInterpolation() { @@ -102,13 +102,13 @@ class LinebreakTests: RulesTests { \"\"\" """ - testFormatting(for: input, rule: FormatRules.consecutiveBlankLines) + testFormatting(for: input, rule: .consecutiveBlankLines) } func testLintingConsecutiveBlankLinesReportsCorrectLine() { let input = "foo\n \n\nbar" - XCTAssertEqual(try lint(input, rules: [FormatRules.consecutiveBlankLines]), [ - .init(line: 3, rule: FormatRules.consecutiveBlankLines, filePath: nil), + XCTAssertEqual(try lint(input, rules: [.consecutiveBlankLines]), [ + .init(line: 3, rule: .consecutiveBlankLines, filePath: nil), ]) } @@ -117,24 +117,24 @@ class LinebreakTests: RulesTests { func testBlankLinesRemovedAtStartOfFunction() { let input = "func foo() {\n\n // code\n}" let output = "func foo() {\n // code\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope) + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) } func testBlankLinesRemovedAtStartOfParens() { let input = "(\n\n foo: Int\n)" let output = "(\n foo: Int\n)" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope) + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) } func testBlankLinesRemovedAtStartOfBrackets() { let input = "[\n\n foo,\n bar,\n]" let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope) + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) } func testBlankLinesNotRemovedBetweenElementsInsideBrackets() { let input = "[foo,\n\n bar]" - testFormatting(for: input, rule: FormatRules.blankLinesAtStartOfScope, exclude: ["wrapArguments"]) + testFormatting(for: input, rule: .blankLinesAtStartOfScope, exclude: ["wrapArguments"]) } func testBlankLineRemovedFromStartOfTypeByDefault() { @@ -150,7 +150,7 @@ class LinebreakTests: RulesTests { func testFoo() {} } """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope) + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) } func testBlankLinesNotRemovedFromStartOfTypeWithOptionEnabled() { @@ -185,7 +185,7 @@ class LinebreakTests: RulesTests { func fooMethod() {} } """ - testFormatting(for: input, rule: FormatRules.blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + testFormatting(for: input, rule: .blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) } func testBlankLineAtStartOfScopeRemovedFromMethodInType() { @@ -205,7 +205,7 @@ class LinebreakTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) } // MARK: - blankLinesAtEndOfScope @@ -213,25 +213,25 @@ class LinebreakTests: RulesTests { func testBlankLinesRemovedAtEndOfFunction() { let input = "func foo() {\n // code\n\n}" let output = "func foo() {\n // code\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope) + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) } func testBlankLinesRemovedAtEndOfParens() { let input = "(\n foo: Int\n\n)" let output = "(\n foo: Int\n)" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope) + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) } func testBlankLinesRemovedAtEndOfBrackets() { let input = "[\n foo,\n bar,\n\n]" let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope) + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) } func testBlankLineNotRemovedBeforeElse() { let input = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n\n}" let output = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope, + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, exclude: ["blankLinesAtStartOfScope"]) } @@ -248,7 +248,7 @@ class LinebreakTests: RulesTests { func testFoo() {} } """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope) + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) } func testBlankLinesNotRemovedFromEndOfTypeWithOptionEnabled() { @@ -282,7 +282,7 @@ class LinebreakTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + testFormatting(for: input, rule: .blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) } func testBlankLineAtEndOfScopeRemovedFromMethodInType() { @@ -302,7 +302,7 @@ class LinebreakTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) } // MARK: - blankLinesBetweenImports @@ -317,7 +317,7 @@ class LinebreakTests: RulesTests { import ModuleA import ModuleB """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenImports) + testFormatting(for: input, output, rule: .blankLinesBetweenImports) } func testBlankLinesBetweenImportsLong() { @@ -344,7 +344,7 @@ class LinebreakTests: RulesTests { import ModuleG import ModuleH """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenImports) + testFormatting(for: input, output, rule: .blankLinesBetweenImports) } func testBlankLinesBetweenImportsWithTestable() { @@ -367,7 +367,7 @@ class LinebreakTests: RulesTests { @testable import ModuleE @testable import ModuleF """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenImports) + testFormatting(for: input, output, rule: .blankLinesBetweenImports) } // MARK: - blankLinesBetweenChainedFunctions @@ -391,7 +391,7 @@ class LinebreakTests: RulesTests { .map { $0 * 2 } .map { $0 * 3 } """ - testFormatting(for: input, [output1, output2], rules: [FormatRules.blankLinesBetweenChainedFunctions]) + testFormatting(for: input, [output1, output2], rules: [.blankLinesBetweenChainedFunctions]) } func testBlankLinesWithCommentsBetweenChainedFunctions() { @@ -409,7 +409,7 @@ class LinebreakTests: RulesTests { // Multiplies by 3 .map { $0 * 3 } """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenChainedFunctions) + testFormatting(for: input, output, rule: .blankLinesBetweenChainedFunctions) } func testBlankLinesWithMarkCommentBetweenChainedFunctions() { @@ -421,7 +421,7 @@ class LinebreakTests: RulesTests { .map { $0 * 3 } """ - testFormatting(for: input, rules: [FormatRules.blankLinesBetweenChainedFunctions, FormatRules.blankLinesAroundMark]) + testFormatting(for: input, rules: [.blankLinesBetweenChainedFunctions, .blankLinesAroundMark]) } // MARK: - blankLineAfterImports @@ -452,7 +452,7 @@ class LinebreakTests: RulesTests { class foo {} """ - testFormatting(for: input, output, rule: FormatRules.blankLineAfterImports) + testFormatting(for: input, output, rule: .blankLineAfterImports) } func testBlankLinesBetweenConditionalImports() { @@ -475,7 +475,7 @@ class LinebreakTests: RulesTests { func foo() {} """ - testFormatting(for: input, output, rule: FormatRules.blankLineAfterImports) + testFormatting(for: input, output, rule: .blankLineAfterImports) } func testBlankLinesBetweenNestedConditionalImports() { @@ -504,7 +504,7 @@ class LinebreakTests: RulesTests { func foo() {} """ - testFormatting(for: input, output, rule: FormatRules.blankLineAfterImports) + testFormatting(for: input, output, rule: .blankLineAfterImports) } func testBlankLineAfterScopedImports() { @@ -521,7 +521,7 @@ class LinebreakTests: RulesTests { public class Foo {} """ - testFormatting(for: input, output, rule: FormatRules.blankLineAfterImports) + testFormatting(for: input, output, rule: .blankLineAfterImports) } // MARK: - blankLinesBetweenScopes @@ -529,107 +529,107 @@ class LinebreakTests: RulesTests { func testBlankLineBetweenFunctions() { let input = "func foo() {\n}\nfunc bar() {\n}" let output = "func foo() {\n}\n\nfunc bar() {\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testNoBlankLineBetweenPropertyAndFunction() { let input = "var foo: Int\nfunc bar() {\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, exclude: ["emptyBraces"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testBlankLineBetweenFunctionsIsBeforeComment() { let input = "func foo() {\n}\n/// headerdoc\nfunc bar() {\n}" let output = "func foo() {\n}\n\n/// headerdoc\nfunc bar() {\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testBlankLineBeforeAtObjcOnLineBeforeProtocol() { let input = "@objc\nprotocol Foo {\n}\n@objc\nprotocol Bar {\n}" let output = "@objc\nprotocol Foo {\n}\n\n@objc\nprotocol Bar {\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testBlankLineBeforeAtAvailabilityOnLineBeforeClass() { let input = "protocol Foo {\n}\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" let output = "protocol Foo {\n}\n\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testNoExtraBlankLineBetweenFunctions() { let input = "func foo() {\n}\n\nfunc bar() {\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, exclude: ["emptyBraces"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testNoBlankLineBetweenFunctionsInProtocol() { let input = "protocol Foo {\n func bar() -> Void\n func baz() -> Int\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testNoBlankLineInsideInitFunction() { let input = "init() {\n super.init()\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testBlankLineAfterProtocolBeforeProperty() { let input = "protocol Foo {\n}\nvar bar: String" let output = "protocol Foo {\n}\n\nvar bar: String" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testNoExtraBlankLineAfterSingleLineComment() { let input = "var foo: Bar? // comment\n\nfunc bar() {}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testNoExtraBlankLineAfterMultilineComment() { let input = "var foo: Bar? /* comment */\n\nfunc bar() {}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testNoBlankLineBeforeFuncAsIdentifier() { let input = "var foo: Bar?\nfoo.func(x) {}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testNoBlankLineBetweenFunctionsWithInlineBody() { let input = "class Foo {\n func foo() { print(\"foo\") }\n func bar() { print(\"bar\") }\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testNoBlankLineBetweenIfStatements() { let input = "func foo() {\n if x {\n }\n if y {\n }\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, exclude: ["emptyBraces"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testNoBlanksInsideClassFunc() { let input = "class func foo {\n if x {\n }\n if y {\n }\n}" let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, options: options, + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, exclude: ["emptyBraces"]) } func testNoBlanksInsideClassVar() { let input = "class var foo: Int {\n if x {\n }\n if y {\n }\n}" let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, options: options, + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, exclude: ["emptyBraces"]) } func testBlankLineBetweenCalledClosures() { let input = "class Foo {\n var foo = {\n }()\n func bar {\n }\n}" let output = "class Foo {\n var foo = {\n }()\n\n func bar {\n }\n}" - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testNoBlankLineAfterCalledClosureAtEndOfScope() { let input = "class Foo {\n var foo = {\n }()\n}" - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, exclude: ["emptyBraces"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } func testNoBlankLineBeforeWhileInRepeatWhile() { @@ -640,14 +640,14 @@ class LinebreakTests: RulesTests { { print("bar") }() """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, options: options, exclude: ["redundantClosure", "wrapLoopBodies"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, exclude: ["redundantClosure", "wrapLoopBodies"]) } func testBlankLineBeforeWhileIfNotRepeatWhile() { let input = "func foo(x)\n{\n}\nwhile true\n{\n}" let output = "func foo(x)\n{\n}\n\nwhile true\n{\n}" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes, options: options, + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, options: options, exclude: ["emptyBraces"]) } @@ -663,7 +663,7 @@ class LinebreakTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes, + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) } @@ -679,7 +679,7 @@ class LinebreakTests: RulesTests { // sourcery:end } """ - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testNoBlankLineBetweenChainedClosures() { @@ -696,7 +696,7 @@ class LinebreakTests: RulesTests { doBaz($0) } """ - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testNoBlankLineBetweenTrailingClosures() { @@ -708,7 +708,7 @@ class LinebreakTests: RulesTests { context.completeTransition(finished) } """ - testFormatting(for: input, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, rule: .blankLinesBetweenScopes) } func testBlankLineBetweenTrailingClosureAndLabelledLoop() { @@ -729,7 +729,7 @@ class LinebreakTests: RulesTests { print(foo) } """ - testFormatting(for: input, output, rule: FormatRules.blankLinesBetweenScopes) + testFormatting(for: input, output, rule: .blankLinesBetweenScopes) } // MARK: - blankLinesAroundMark @@ -747,7 +747,7 @@ class LinebreakTests: RulesTests { let bar = "bar" """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAroundMark) + testFormatting(for: input, output, rule: .blankLinesAroundMark) } func testNoInsertExtraBlankLinesAroundMark() { @@ -758,7 +758,7 @@ class LinebreakTests: RulesTests { let bar = "bar" """ - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark) + testFormatting(for: input, rule: .blankLinesAroundMark) } func testInsertBlankLineAfterMarkAtStartOfFile() { @@ -771,7 +771,7 @@ class LinebreakTests: RulesTests { let bar = "bar" """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAroundMark) + testFormatting(for: input, output, rule: .blankLinesAroundMark) } func testInsertBlankLineBeforeMarkAtEndOfFile() { @@ -784,7 +784,7 @@ class LinebreakTests: RulesTests { // MARK: bar """ - testFormatting(for: input, output, rule: FormatRules.blankLinesAroundMark) + testFormatting(for: input, output, rule: .blankLinesAroundMark) } func testNoInsertBlankLineBeforeMarkAtStartOfScope() { @@ -795,7 +795,7 @@ class LinebreakTests: RulesTests { let foo = "foo" } """ - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark) + testFormatting(for: input, rule: .blankLinesAroundMark) } func testNoInsertBlankLineAfterMarkAtEndOfScope() { @@ -806,7 +806,7 @@ class LinebreakTests: RulesTests { // MARK: foo } """ - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark) + testFormatting(for: input, rule: .blankLinesAroundMark) } func testInsertBlankLinesJustBeforeMarkNotAfter() { @@ -822,7 +822,7 @@ class LinebreakTests: RulesTests { let bar = "bar" """ let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, output, rule: FormatRules.blankLinesAroundMark, options: options) + testFormatting(for: input, output, rule: .blankLinesAroundMark, options: options) } func testNoInsertExtraBlankLinesAroundMarkWithNoBlankLineAfterMark() { @@ -833,7 +833,7 @@ class LinebreakTests: RulesTests { let bar = "bar" """ let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark, options: options) + testFormatting(for: input, rule: .blankLinesAroundMark, options: options) } func testNoInsertBlankLineAfterMarkAtStartOfFile() { @@ -842,7 +842,7 @@ class LinebreakTests: RulesTests { let bar = "bar" """ let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, rule: FormatRules.blankLinesAroundMark, options: options) + testFormatting(for: input, rule: .blankLinesAroundMark, options: options) } // MARK: - linebreakAtEndOfFile @@ -850,12 +850,12 @@ class LinebreakTests: RulesTests { func testLinebreakAtEndOfFile() { let input = "foo\nbar" let output = "foo\nbar\n" - testFormatting(for: input, output, rule: FormatRules.linebreakAtEndOfFile) + testFormatting(for: input, output, rule: .linebreakAtEndOfFile) } func testNoLinebreakAtEndOfFragment() { let input = "foo\nbar" let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.linebreakAtEndOfFile, options: options) + testFormatting(for: input, rule: .linebreakAtEndOfFile, options: options) } } diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index 243be8db6..8c4f3aa89 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -104,7 +104,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) } @@ -206,7 +206,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( visibilityOrder: airbnbVisibilityOrder.components(separatedBy: ","), typeOrder: airbnbTypeOrder.components(separatedBy: ",") @@ -280,7 +280,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -310,7 +310,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, rule: FormatRules.organizeDeclarations, + for: input, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] ) } @@ -370,7 +370,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizationMode: .type), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] ) @@ -396,7 +396,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, rule: FormatRules.organizeDeclarations, + for: input, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] ) } @@ -463,7 +463,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -537,7 +537,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -571,7 +571,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( visibilityOrder: ["private", "internal", "public"], typeOrder: DeclarationType.allCases.map(\.rawValue) @@ -621,7 +621,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( visibilityOrder: ["private", "internal", "public"], typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] @@ -669,7 +669,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizationMode: .type, typeOrder: ["beforeMarks", "instanceLifecycle", "instanceMethod", "nestedType", "instanceProperty", "overriddenMethod"] @@ -714,7 +714,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizationMode: .type, typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] @@ -765,7 +765,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizationMode: .type, visibilityOrder: ["private", "internal", "public"], @@ -798,7 +798,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizationMode: .visibility, visibilityOrder: ["instanceMethod"] + Visibility.allCases.map(\.rawValue), @@ -831,7 +831,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizationMode: .visibility, visibilityOrder: Visibility.allCases.map(\.rawValue), @@ -873,7 +873,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizationMode: .visibility, customVisibilityMarks: ["instanceLifecycle:Init", "public:Public_Group"] @@ -914,7 +914,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizationMode: .type, customTypeMarks: ["instanceLifecycle:Init", "instanceProperty:Bar_Bar", "instanceMethod:Buuuz Lightyeeeaaar"] @@ -957,7 +957,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "enumNamespaces"] ) } @@ -1009,7 +1009,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) } @@ -1039,7 +1039,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "redundantInternal"] ) } @@ -1129,7 +1129,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, exclude: ["blankLinesAtEndOfScope", "redundantType", "redundantClosure"] ) } @@ -1257,7 +1257,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), exclude: ["blankLinesAtEndOfScope", "blankLinesAtStartOfScope", "redundantType", "redundantClosure"] ) @@ -1293,7 +1293,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, exclude: ["blankLinesAtEndOfScope", "unusedArguments"] ) } @@ -1327,7 +1327,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(beforeMarks: ["typealias", "struct"]), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -1380,7 +1380,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(lifecycleMethods: ["viewDidLoad", "viewWillAppear", "viewDidAppear"]), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -1409,7 +1409,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "- %c"), exclude: ["blankLinesAtStartOfScope"] ) @@ -1424,7 +1424,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizeStructThreshold: 2) ) } @@ -1452,7 +1452,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizeStructThreshold: 2), exclude: ["blankLinesAtStartOfScope"] ) @@ -1467,7 +1467,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizeClassThreshold: 2) ) } @@ -1481,7 +1481,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizeEnumThreshold: 2) ) } @@ -1495,7 +1495,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizeTypes: ["class", "struct", "enum", "extension"], organizeExtensionThreshold: 2 @@ -1531,7 +1531,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions( organizeTypes: ["class", "struct", "enum", "extension"], organizeExtensionThreshold: 2 @@ -1556,7 +1556,7 @@ class OrganizationTests: RulesTests { let baz: Int? } """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope"]) } @@ -1611,7 +1611,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, output, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope"]) } @@ -1642,7 +1642,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "docCommentsBeforeAttributes"]) } @@ -1671,7 +1671,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, output, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope"]) } @@ -1693,7 +1693,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations) + testFormatting(for: input, output, rule: .organizeDeclarations) } func testOrganizesTypesWithinConditionalCompilationBlock() { @@ -1737,7 +1737,7 @@ class OrganizationTests: RulesTests { #endif """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, output, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope"]) } @@ -1771,7 +1771,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, output, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope"]) } @@ -1849,7 +1849,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, output, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "propertyType"]) } @@ -1894,7 +1894,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.organizeDeclarations, + for: input, output, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "sortImports"] ) } @@ -1913,7 +1913,7 @@ class OrganizationTests: RulesTests { Foo(bar: 1, baz: 2, quux: 3) """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations) + testFormatting(for: input, rule: .organizeDeclarations) } func testOrganizesStructPropertiesThatDontBreakMemberwiseInitializer() { @@ -1966,7 +1966,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.organizeDeclarations, + for: input, output, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope"] ) } @@ -1992,7 +1992,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, rule: FormatRules.organizeDeclarations, + for: input, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope"] ) } @@ -2014,7 +2014,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, rule: FormatRules.organizeDeclarations, + for: input, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope"] ) } @@ -2062,7 +2062,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.organizeDeclarations, + for: input, output, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) } @@ -2093,7 +2093,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, rule: FormatRules.organizeDeclarations, + for: input, rule: .organizeDeclarations, options: FormatOptions(organizeStructThreshold: 20), exclude: ["blankLinesAtStartOfScope"] ) @@ -2142,7 +2142,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .organizeDeclarations, exclude: ["redundantClosure"]) } func testFuncWithNestedInitNotTreatedAsLifecycle() { @@ -2165,7 +2165,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope"]) } @@ -2198,7 +2198,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, output, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope"]) } @@ -2247,7 +2247,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(lineAfterMarks: false) testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: options, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -2274,7 +2274,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(markCategories: false) ) } @@ -2306,7 +2306,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(markCategories: false) ) } @@ -2331,7 +2331,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) } func testOrganizeConditionalPublicFunction() { @@ -2354,7 +2354,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) } // MARK: extensionAccessControl .onDeclarations @@ -2383,7 +2383,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, + for: input, output, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations), exclude: ["redundantInternal"] ) @@ -2407,7 +2407,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, + for: input, output, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations) ) } @@ -2430,7 +2430,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, + for: input, output, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations) ) } @@ -2455,7 +2455,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, + for: input, output, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations) ) } @@ -2470,7 +2470,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, rule: FormatRules.extensionAccessControl, + for: input, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations) ) } @@ -2507,7 +2507,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, + for: input, output, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations) ) } @@ -2530,7 +2530,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.extensionAccessControl, + for: input, output, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4"), exclude: ["propertyType"] ) @@ -2561,7 +2561,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, output, rule: .extensionAccessControl) } func testUpdatedVisibilityOfExtensionWithDeclarationsInConditionalCompilation() { @@ -2583,7 +2583,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, output, rule: .extensionAccessControl) } func testDoesntUpdateExtensionVisibilityWithoutMajorityBodyVisibility() { @@ -2596,7 +2596,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testUpdateExtensionVisibilityWithMajorityBodyVisibility() { @@ -2618,7 +2618,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, output, rule: .extensionAccessControl) } func testDoesntUpdateExtensionVisibilityWhenMajorityBodyVisibilityIsntMostVisible() { @@ -2630,7 +2630,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testDoesntUpdateExtensionVisibilityWithInternalDeclarations() { @@ -2641,7 +2641,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testDoesntUpdateExtensionThatAlreadyHasCorrectVisibilityKeyword() { @@ -2652,7 +2652,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testUpdatesExtensionThatHasHigherACLThanBodyDeclarations() { @@ -2670,7 +2670,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl, + testFormatting(for: input, output, rule: .extensionAccessControl, exclude: ["redundantFileprivate"]) } @@ -2682,7 +2682,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testDoesntUpdatesExtensionThatHasLowerACLThanBodyDeclarations() { @@ -2693,7 +2693,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testDoesntReduceVisibilityOfImplicitInternalDeclaration() { @@ -2704,7 +2704,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testUpdatesExtensionThatHasRedundantACLOnBodyDeclarations() { @@ -2722,7 +2722,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, output, rule: .extensionAccessControl) } func testNoHoistAccessModifierForOpenMethod() { @@ -2731,7 +2731,7 @@ class OrganizationTests: RulesTests { open func bar() {} } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testDontChangePrivateExtensionToFileprivate() { @@ -2740,7 +2740,7 @@ class OrganizationTests: RulesTests { func bar() {} } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testDontRemoveInternalKeywordFromExtension() { @@ -2749,7 +2749,7 @@ class OrganizationTests: RulesTests { func bar() {} } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl, exclude: ["redundantInternal"]) + testFormatting(for: input, rule: .extensionAccessControl, exclude: ["redundantInternal"]) } func testNoHoistAccessModifierForExtensionThatAddsProtocolConformance() { @@ -2758,7 +2758,7 @@ class OrganizationTests: RulesTests { public func bar() {} } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testProtocolConformanceCheckNotFooledByWhereClause() { @@ -2772,7 +2772,7 @@ class OrganizationTests: RulesTests { func bar() {} } """ - testFormatting(for: input, output, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, output, rule: .extensionAccessControl) } func testAccessNotHoistedIfTypeVisibilityIsLower() { @@ -2783,7 +2783,7 @@ class OrganizationTests: RulesTests { public func bar() {} } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testExtensionAccessControlRuleTerminatesInFileWithConditionalCompilation() { @@ -2793,7 +2793,7 @@ class OrganizationTests: RulesTests { #endif """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } func testExtensionAccessControlRuleTerminatesInFileWithEmptyType() { @@ -2807,7 +2807,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.extensionAccessControl) + testFormatting(for: input, rule: .extensionAccessControl) } // MARK: markTypes @@ -2838,7 +2838,7 @@ class OrganizationTests: RulesTests { protocol Quux {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testDoesntAddMarkBeforeStructWithExistingMark() { @@ -2849,7 +2849,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testCorrectsTypoInTypeMark() { @@ -2867,7 +2867,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testUpdatesMarkAfterTypeIsRenamed() { @@ -2885,7 +2885,7 @@ class OrganizationTests: RulesTests { extension FooBarControllerBuilder {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testAddsMarkBeforeTypeWithDocComment() { @@ -2909,7 +2909,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testCustomTypeMark() { @@ -2926,7 +2926,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.markTypes, + for: input, output, rule: .markTypes, options: FormatOptions(typeMarkComment: "TYPE DEFINITION: %t") ) } @@ -2937,7 +2937,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func preservesExistingCommentForExtensionWithNoConformances() { @@ -2948,7 +2948,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testAddsMarkCommentForExtensionWithConformance() { @@ -2964,7 +2964,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testUpdatesExtensionMarkToCorrectMark() { @@ -2982,7 +2982,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testAddsMarkCommentForExtensionWithMultipleConformances() { @@ -2998,7 +2998,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testUpdatesMarkCommentWithCorrectConformances() { @@ -3016,7 +3016,7 @@ class OrganizationTests: RulesTests { extension Foo {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testCustomExtensionMarkComment() { @@ -3041,7 +3041,7 @@ class OrganizationTests: RulesTests { """ testFormatting( - for: input, output, rule: FormatRules.markTypes, + for: input, output, rule: .markTypes, options: FormatOptions( extensionMarkComment: "EXTENSION: - %t: %c", groupedExtensionMarkComment: "EXTENSION: - %c" @@ -3070,7 +3070,7 @@ class OrganizationTests: RulesTests { extension String: Bar {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testFullyQualifiedTypeNames() { @@ -3086,7 +3086,7 @@ class OrganizationTests: RulesTests { extension MyModule.Foo {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testWhereClauseConformanceWithExactConstraint() { @@ -3102,7 +3102,7 @@ class OrganizationTests: RulesTests { extension Array {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testWhereClauseConformanceWithConformanceConstraint() { @@ -3118,7 +3118,7 @@ class OrganizationTests: RulesTests { extension Array {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testWhereClauseWithExactConstraint() { @@ -3127,7 +3127,7 @@ class OrganizationTests: RulesTests { extension Array {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testWhereClauseWithConformanceConstraint() { @@ -3138,7 +3138,7 @@ class OrganizationTests: RulesTests { extension Rules {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testPlacesMarkAfterImports() { @@ -3162,7 +3162,7 @@ class OrganizationTests: RulesTests { extension Rules {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testPlacesMarkAfterFileHeader() { @@ -3186,7 +3186,7 @@ class OrganizationTests: RulesTests { extension Rules {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testPlacesMarkAfterFileHeaderAndImports() { @@ -3216,7 +3216,7 @@ class OrganizationTests: RulesTests { extension Rules {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testDoesNothingIfOnlyOneDeclaration() { @@ -3231,7 +3231,7 @@ class OrganizationTests: RulesTests { class Rules {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testMultipleExtensionsOfSameType() { @@ -3250,7 +3250,7 @@ class OrganizationTests: RulesTests { extension Foo: QuuxProtocol {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testNeverMarkTypes() { @@ -3267,7 +3267,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(markTypes: .never) testFormatting( - for: input, rule: FormatRules.markTypes, options: options, + for: input, rule: .markTypes, options: options, exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] ) } @@ -3300,7 +3300,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(markTypes: .ifNotEmpty) testFormatting( - for: input, output, rule: FormatRules.markTypes, options: options, + for: input, output, rule: .markTypes, options: options, exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] ) } @@ -3319,7 +3319,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(markExtensions: .never) testFormatting( - for: input, rule: FormatRules.markTypes, options: options, + for: input, rule: .markTypes, options: options, exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] ) } @@ -3352,7 +3352,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(markExtensions: .ifNotEmpty) testFormatting( - for: input, output, rule: FormatRules.markTypes, options: options, + for: input, output, rule: .markTypes, options: options, exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] ) } @@ -3392,7 +3392,7 @@ class OrganizationTests: RulesTests { extension Quux: QuuxProtocol {} """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testExtensionMarkWithImportOfSameName() { @@ -3408,7 +3408,7 @@ class OrganizationTests: RulesTests { extension MagazineLayout: BarProtocol {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testDoesntUseGroupedMarkTemplateWhenSeparatedByOtherType() { @@ -3426,7 +3426,7 @@ class OrganizationTests: RulesTests { extension MyComponent: ContentConfigurableView {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testUsesGroupedMarkTemplateWhenSeparatedByExtensionOfSameType() { @@ -3444,7 +3444,7 @@ class OrganizationTests: RulesTests { extension MyComponent: ContentConfigurableView {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testDoesntUseGroupedMarkTemplateWhenSeparatedByExtensionOfOtherType() { @@ -3462,7 +3462,7 @@ class OrganizationTests: RulesTests { extension MyComponent: ContentConfigurableView {} """ - testFormatting(for: input, rule: FormatRules.markTypes) + testFormatting(for: input, rule: .markTypes) } func testAddsMarkBeforeTypesWithNoBlankLineAfterMark() { @@ -3487,7 +3487,7 @@ class OrganizationTests: RulesTests { protocol Quux {} """ let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, output, rule: FormatRules.markTypes, options: options) + testFormatting(for: input, output, rule: .markTypes, options: options) } func testAddsMarkForTypeInExtension() { @@ -3515,7 +3515,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testDoesntAddsMarkForMultipleTypesInExtension() { @@ -3549,7 +3549,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testAddsMarkForTypeInExtensionNotFollowingTypeBeingExtended() { @@ -3577,7 +3577,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testHandlesMultipleLayersOfExtensionNesting() { @@ -3619,7 +3619,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.markTypes) + testFormatting(for: input, output, rule: .markTypes) } func testMarkTypeLintReturnsErrorAsExpected() throws { @@ -3631,10 +3631,10 @@ class OrganizationTests: RulesTests { // Initialize rule names let _ = FormatRules.byName - let changes = try lint(input, rules: [FormatRules.markTypes]) + let changes = try lint(input, rules: [.markTypes]) XCTAssertEqual(changes, [ - .init(line: 1, rule: FormatRules.markTypes, filePath: nil), - .init(line: 2, rule: FormatRules.markTypes, filePath: nil), + .init(line: 1, rule: .markTypes, filePath: nil), + .init(line: 2, rule: .markTypes, filePath: nil), ]) } @@ -3643,126 +3643,126 @@ class OrganizationTests: RulesTests { func testSortImportsSimpleCase() { let input = "import Foo\nimport Bar" let output = "import Bar\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testSortImportsKeepsPreviousCommentWithImport() { let input = "import Foo\n// important comment\n// (very important)\nimport Bar" let output = "// important comment\n// (very important)\nimport Bar\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports, + testFormatting(for: input, output, rule: .sortImports, exclude: ["blankLineAfterImports"]) } func testSortImportsKeepsPreviousCommentWithImport2() { let input = "// important comment\n// (very important)\nimport Foo\nimport Bar" let output = "import Bar\n// important comment\n// (very important)\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports, + testFormatting(for: input, output, rule: .sortImports, exclude: ["blankLineAfterImports"]) } func testSortImportsDoesntMoveHeaderComment() { let input = "// header comment\n\nimport Foo\nimport Bar" let output = "// header comment\n\nimport Bar\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testSortImportsDoesntMoveHeaderCommentFollowedByImportComment() { let input = "// header comment\n\n// important comment\nimport Foo\nimport Bar" let output = "// header comment\n\nimport Bar\n// important comment\nimport Foo" - testFormatting(for: input, output, rule: FormatRules.sortImports, + testFormatting(for: input, output, rule: .sortImports, exclude: ["blankLineAfterImports"]) } func testSortImportsOnSameLine() { let input = "import Foo; import Bar\nimport Baz" let output = "import Baz\nimport Foo; import Bar" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testSortImportsWithSemicolonAndCommentOnSameLine() { let input = "import Foo; // foobar\nimport Bar\nimport Baz" let output = "import Bar\nimport Baz\nimport Foo; // foobar" - testFormatting(for: input, output, rule: FormatRules.sortImports, exclude: ["semicolons"]) + testFormatting(for: input, output, rule: .sortImports, exclude: ["semicolons"]) } func testSortImportEnum() { let input = "import enum Foo.baz\nimport Foo.bar" let output = "import Foo.bar\nimport enum Foo.baz" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testSortImportFunc() { let input = "import func Foo.baz\nimport Foo.bar" let output = "import Foo.bar\nimport func Foo.baz" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testAlreadySortImportsDoesNothing() { let input = "import Bar\nimport Foo" - testFormatting(for: input, rule: FormatRules.sortImports) + testFormatting(for: input, rule: .sortImports) } func testPreprocessorSortImports() { let input = "#if os(iOS)\n import Foo2\n import Bar2\n#else\n import Foo1\n import Bar1\n#endif\nimport Foo3\nimport Bar3" let output = "#if os(iOS)\n import Bar2\n import Foo2\n#else\n import Bar1\n import Foo1\n#endif\nimport Bar3\nimport Foo3" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testTestableSortImports() { let input = "@testable import Foo3\nimport Bar3" let output = "import Bar3\n@testable import Foo3" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testLengthSortImports() { let input = "import Foo\nimport Module\nimport Bar3" let output = "import Foo\nimport Bar3\nimport Module" let options = FormatOptions(importGrouping: .length) - testFormatting(for: input, output, rule: FormatRules.sortImports, options: options) + testFormatting(for: input, output, rule: .sortImports, options: options) } func testTestableImportsWithTestableOnPreviousLine() { let input = "@testable\nimport Foo3\nimport Bar3" let output = "import Bar3\n@testable\nimport Foo3" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testTestableImportsWithGroupingTestableBottom() { let input = "@testable import Bar\nimport Foo\n@testable import UIKit" let output = "import Foo\n@testable import Bar\n@testable import UIKit" let options = FormatOptions(importGrouping: .testableLast) - testFormatting(for: input, output, rule: FormatRules.sortImports, options: options) + testFormatting(for: input, output, rule: .sortImports, options: options) } func testTestableImportsWithGroupingTestableTop() { let input = "@testable import Bar\nimport Foo\n@testable import UIKit" let output = "@testable import Bar\n@testable import UIKit\nimport Foo" let options = FormatOptions(importGrouping: .testableFirst) - testFormatting(for: input, output, rule: FormatRules.sortImports, options: options) + testFormatting(for: input, output, rule: .sortImports, options: options) } func testCaseInsensitiveSortImports() { let input = "import Zlib\nimport lib" let output = "import lib\nimport Zlib" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testCaseInsensitiveCaseDifferingSortImports() { let input = "import c\nimport B\nimport A.a\nimport A.A" let output = "import A.A\nimport A.a\nimport B\nimport c" - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testNoDeleteCodeBetweenImports() { let input = "import Foo\nfunc bar() {}\nimport Bar" - testFormatting(for: input, rule: FormatRules.sortImports, + testFormatting(for: input, rule: .sortImports, exclude: ["blankLineAfterImports"]) } func testNoDeleteCodeBetweenImports2() { let input = "import Foo\nimport Bar\nfoo = bar\nimport Bar" let output = "import Bar\nimport Foo\nfoo = bar\nimport Bar" - testFormatting(for: input, output, rule: FormatRules.sortImports, + testFormatting(for: input, output, rule: .sortImports, exclude: ["blankLineAfterImports"]) } @@ -3778,13 +3778,13 @@ class OrganizationTests: RulesTests { import A """ - testFormatting(for: input, rule: FormatRules.sortImports) + testFormatting(for: input, rule: .sortImports) } func testSortContiguousImports() { let input = "import Foo\nimport Bar\nfunc bar() {}\nimport Quux\nimport Baz" let output = "import Bar\nimport Foo\nfunc bar() {}\nimport Baz\nimport Quux" - testFormatting(for: input, output, rule: FormatRules.sortImports, + testFormatting(for: input, output, rule: .sortImports, exclude: ["blankLineAfterImports"]) } @@ -3811,7 +3811,7 @@ class OrganizationTests: RulesTests { #endif #endif """ - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } func testNoMangleFileHeaderNotFollowedByLinebreak() { @@ -3839,7 +3839,7 @@ class OrganizationTests: RulesTests { import AModuleUI import SomeOtherModule """ - testFormatting(for: input, output, rule: FormatRules.sortImports) + testFormatting(for: input, output, rule: .sortImports) } // MARK: - sortSwitchCases @@ -3864,7 +3864,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.sortSwitchCases, exclude: ["redundantSelf"]) + testFormatting(for: input, rule: .sortSwitchCases, exclude: ["redundantSelf"]) } func testSortedSwitchCaseMultilineWithOneComment() { @@ -3882,7 +3882,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, output, rule: .sortSwitchCases) } func testSortedSwitchCaseMultilineWithComments() { @@ -3900,7 +3900,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, exclude: ["indent"]) + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["indent"]) } func testSortedSwitchCaseMultilineWithCommentsAndMoreThanOneCasePerLine() { @@ -3921,7 +3921,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, output, rule: .sortSwitchCases) } func testSortedSwitchCaseMultiline() { @@ -3939,7 +3939,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, output, rule: .sortSwitchCases) } func testSortedSwitchCaseMultipleAssociatedValues() { @@ -3955,7 +3955,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["wrapSwitchCases"]) } @@ -3972,7 +3972,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["wrapSwitchCases", "spaceAroundOperators"]) } @@ -3989,7 +3989,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["wrapSwitchCases"]) } @@ -4000,7 +4000,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, rule: .sortSwitchCases) } func testSortedSwitchStrings() { @@ -4016,7 +4016,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["wrapSwitchCases"]) } @@ -4028,7 +4028,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, - rule: FormatRules.sortSwitchCases, + rule: .sortSwitchCases, exclude: ["wrapSwitchCases"]) } @@ -4045,7 +4045,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["wrapSwitchCases"]) } @@ -4062,7 +4062,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["wrapSwitchCases"]) } @@ -4083,7 +4083,7 @@ class OrganizationTests: RulesTests { (.foo, _): } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, output, rule: .sortSwitchCases) } func testSortedSwitchTuples2() { @@ -4105,7 +4105,7 @@ class OrganizationTests: RulesTests { (.quux, .bar): } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, output, rule: .sortSwitchCases) } func testSortSwitchCasesShortestFirst() { @@ -4121,7 +4121,7 @@ class OrganizationTests: RulesTests { let .fooAndBar(baz, quux): } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, output, rule: .sortSwitchCases) } func testSortHexLiteralCasesInAscendingOrder() { @@ -4137,7 +4137,7 @@ class OrganizationTests: RulesTests { return false } """ - testFormatting(for: input, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, rule: .sortSwitchCases) } func testMixedOctalHexIntAndBinaryLiteralCasesInAscendingOrder() { @@ -4152,7 +4152,7 @@ class OrganizationTests: RulesTests { return false } """ - testFormatting(for: input, rule: FormatRules.sortSwitchCases) + testFormatting(for: input, rule: .sortSwitchCases) } func testSortSwitchCasesNoUnwrapReturn() { @@ -4168,7 +4168,7 @@ class OrganizationTests: RulesTests { return nil } """ - testFormatting(for: input, output, rule: FormatRules.sortSwitchCases, + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["wrapSwitchCases"]) } @@ -4178,75 +4178,75 @@ class OrganizationTests: RulesTests { let input = "unowned private static var foo" let output = "private unowned static var foo" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options) + testFormatting(for: input, output, rule: .modifierOrder, options: options) } func testPrivateSetModifierNotMangled() { let input = "private(set) public weak lazy var foo" let output = "public private(set) lazy weak var foo" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) + testFormatting(for: input, output, rule: .modifierOrder) } func testUnownedUnsafeModifierNotMangled() { let input = "unowned(unsafe) lazy var foo" let output = "lazy unowned(unsafe) var foo" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) + testFormatting(for: input, output, rule: .modifierOrder) } func testPrivateRequiredStaticFuncModifiers() { let input = "required static private func foo()" let output = "private required static func foo()" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options) + testFormatting(for: input, output, rule: .modifierOrder, options: options) } func testPrivateConvenienceInit() { let input = "convenience private init()" let output = "private convenience init()" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) + testFormatting(for: input, output, rule: .modifierOrder) } func testSpaceInModifiersLeftIntact() { let input = "weak private(set) /* read-only */\npublic var" let output = "public private(set) /* read-only */\nweak var" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) + testFormatting(for: input, output, rule: .modifierOrder) } func testSpaceInModifiersLeftIntact2() { let input = "nonisolated(unsafe) public var foo: String" let output = "public nonisolated(unsafe) var foo: String" - testFormatting(for: input, output, rule: FormatRules.modifierOrder) + testFormatting(for: input, output, rule: .modifierOrder) } func testPrefixModifier() { let input = "prefix public static func - (rhs: Foo) -> Foo" let output = "public static prefix func - (rhs: Foo) -> Foo" let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options) + testFormatting(for: input, output, rule: .modifierOrder, options: options) } func testModifierOrder() { let input = "override public var foo: Int { 5 }" let output = "public override var foo: Int { 5 }" let options = FormatOptions(modifierOrder: ["public", "override"]) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options) + testFormatting(for: input, output, rule: .modifierOrder, options: options) } func testConsumingModifierOrder() { let input = "consuming public func close()" let output = "public consuming func close()" let options = FormatOptions(modifierOrder: ["public", "consuming"]) - testFormatting(for: input, output, rule: FormatRules.modifierOrder, options: options, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, output, rule: .modifierOrder, options: options, exclude: ["noExplicitOwnership"]) } func testNoConfusePostfixIdentifierWithKeyword() { let input = "var foo = .postfix\noverride init() {}" - testFormatting(for: input, rule: FormatRules.modifierOrder) + testFormatting(for: input, rule: .modifierOrder) } func testNoConfusePostfixIdentifierWithKeyword2() { let input = "var foo = postfix\noverride init() {}" - testFormatting(for: input, rule: FormatRules.modifierOrder) + testFormatting(for: input, rule: .modifierOrder) } func testNoConfuseCaseWithModifier() { @@ -4257,7 +4257,7 @@ class OrganizationTests: RulesTests { public init() {} } """ - testFormatting(for: input, rule: FormatRules.modifierOrder) + testFormatting(for: input, rule: .modifierOrder) } // MARK: - sortDeclarations @@ -4307,7 +4307,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.sortDeclarations) + testFormatting(for: input, output, rule: .sortDeclarations) } func testSortEnumBodyWithOnlyOneCase() { @@ -4318,7 +4318,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.sortDeclarations) + testFormatting(for: input, rule: .sortDeclarations) } func testSortEnumBodyWithoutCase() { @@ -4327,7 +4327,7 @@ class OrganizationTests: RulesTests { enum FeatureFlags {} """ - testFormatting(for: input, rule: FormatRules.sortDeclarations) + testFormatting(for: input, rule: .sortDeclarations) } func testNoSortUnannotatedType() { @@ -4340,7 +4340,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.sortDeclarations) + testFormatting(for: input, rule: .sortDeclarations) } func testPreservesSortedBody() { @@ -4354,7 +4354,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.sortDeclarations) + testFormatting(for: input, rule: .sortDeclarations) } func testSortsTypeBody() { @@ -4378,7 +4378,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.sortDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, output, rule: .sortDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) } func testSortClassWithMixedDeclarationTypes() { @@ -4415,7 +4415,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, [output], - rules: [FormatRules.sortDeclarations, FormatRules.consecutiveBlankLines], + rules: [.sortDeclarations, .consecutiveBlankLines], exclude: ["blankLinesBetweenScopes", "propertyType"]) } @@ -4450,7 +4450,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.sortDeclarations) + testFormatting(for: input, output, rule: .sortDeclarations) } func testSortTopLevelDeclarations() { @@ -4480,7 +4480,7 @@ class OrganizationTests: RulesTests { let anotherUnsortedGlobal = 9 """ - testFormatting(for: input, output, rule: FormatRules.sortDeclarations) + testFormatting(for: input, output, rule: .sortDeclarations) } func testDoesntConflictWithOrganizeDeclarations() { @@ -4504,7 +4504,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations) + testFormatting(for: input, rule: .organizeDeclarations) } func testSortsWithinOrganizeDeclarations() { @@ -4551,7 +4551,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, [output], - rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], exclude: ["blankLinesAtEndOfScope"]) } @@ -4597,7 +4597,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, [output], - rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], options: .init(alphabeticallySortedDeclarationPatterns: ["FeatureFlags"]), exclude: ["blankLinesAtEndOfScope"]) } @@ -4644,7 +4644,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, [output], - rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], options: .init(alphabeticallySortedDeclarationPatterns: ["ureFla"]), exclude: ["blankLinesAtEndOfScope"]) } @@ -4671,7 +4671,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, - rules: [FormatRules.organizeDeclarations, FormatRules.blankLinesBetweenScopes], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], options: .init(alphabeticallySortedDeclarationPatterns: ["Comment"]), exclude: ["blankLinesAtEndOfScope"]) } @@ -4698,7 +4698,7 @@ class OrganizationTests: RulesTests { """ let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Namespace"]) - testFormatting(for: input, [output], rules: [FormatRules.sortDeclarations, FormatRules.blankLinesBetweenScopes], options: options) + testFormatting(for: input, [output], rules: [.sortDeclarations, .blankLinesBetweenScopes], options: options) } func testSortDeclarationsWontSortByNamePatternInComment() { @@ -4715,7 +4715,7 @@ class OrganizationTests: RulesTests { """ let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Constants"]) - testFormatting(for: input, rules: [FormatRules.sortDeclarations, FormatRules.blankLinesBetweenScopes], options: options) + testFormatting(for: input, rules: [.sortDeclarations, .blankLinesBetweenScopes], options: options) } func testSortDeclarationsUsesLocalizedCompare() { @@ -4729,7 +4729,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.sortDeclarations) + testFormatting(for: input, rule: .sortDeclarations) } func testOrganizeDeclarationsSortUsesLocalizedCompare() { @@ -4743,7 +4743,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.organizeDeclarations) + testFormatting(for: input, rule: .organizeDeclarations) } func testSortDeclarationsSortsExtensionBody() { @@ -4773,7 +4773,7 @@ class OrganizationTests: RulesTests { // organizeDeclarations rule is enabled, the extension should be // sorted by the sortDeclarations rule. let options = FormatOptions(organizeTypes: ["class"]) - testFormatting(for: input, [output], rules: [FormatRules.sortDeclarations, FormatRules.organizeDeclarations], options: options) + testFormatting(for: input, [output], rules: [.sortDeclarations, .organizeDeclarations], options: options) } func testOrganizeDeclarationsSortsExtensionBody() { @@ -4806,7 +4806,7 @@ class OrganizationTests: RulesTests { """ let options = FormatOptions(organizeTypes: ["extension"]) - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, options: options, + testFormatting(for: input, output, rule: .organizeDeclarations, options: options, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) } @@ -4843,7 +4843,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, output, rule: .organizeDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) } @@ -4883,7 +4883,7 @@ class OrganizationTests: RulesTests { } """ let options = FormatOptions(indent: " ", organizeTypes: ["struct"]) - testFormatting(for: input, output, rule: FormatRules.organizeDeclarations, + testFormatting(for: input, output, rule: .organizeDeclarations, options: options, exclude: ["blankLinesAtStartOfScope"]) } @@ -4898,7 +4898,7 @@ class OrganizationTests: RulesTests { typealias Placeholders = Baaz & Bar & Foo & Quux """ - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) + testFormatting(for: input, output, rule: .sortTypealiases) } func testSortMultilineTypealias() { @@ -4912,7 +4912,7 @@ class OrganizationTests: RulesTests { & Foo & Quux """ - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) + testFormatting(for: input, output, rule: .sortTypealiases) } func testSortMultilineTypealiasWithComments() { @@ -4930,7 +4930,7 @@ class OrganizationTests: RulesTests { & Quux """ - testFormatting(for: input, [output], rules: [FormatRules.sortTypealiases, FormatRules.indent, FormatRules.trailingSpace]) + testFormatting(for: input, [output], rules: [.sortTypealiases, .indent, .trailingSpace]) } func testSortWrappedMultilineTypealias1() { @@ -4948,7 +4948,7 @@ class OrganizationTests: RulesTests { & QuuxProviding """ - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) + testFormatting(for: input, output, rule: .sortTypealiases) } func testSortWrappedMultilineTypealias2() { @@ -4968,7 +4968,7 @@ class OrganizationTests: RulesTests { & QuuxProviding """ - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) + testFormatting(for: input, output, rule: .sortTypealiases) } func testSortWrappedMultilineTypealiasWithComments() { @@ -4994,7 +4994,7 @@ class OrganizationTests: RulesTests { & QuuxProviding // Comment about QuuxProviding """ - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) + testFormatting(for: input, output, rule: .sortTypealiases) } func testSortTypealiasesWithAssociatedTypes() { @@ -5014,7 +5014,7 @@ class OrganizationTests: RulesTests { & Collection """ - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) + testFormatting(for: input, output, rule: .sortTypealiases) } func testSortTypeAliasesAndRemoveDuplicates() { @@ -5052,7 +5052,7 @@ class OrganizationTests: RulesTests { & QuuxProviding """ - testFormatting(for: input, output, rule: FormatRules.sortTypealiases) + testFormatting(for: input, output, rule: .sortTypealiases) } func testSortSingleSwiftUIPropertyWrapper() { @@ -5107,7 +5107,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -5162,7 +5162,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -5219,7 +5219,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) @@ -5274,7 +5274,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.organizeDeclarations, + rule: .organizeDeclarations, options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] ) diff --git a/Tests/RulesTests+Parens.swift b/Tests/RulesTests+Parens.swift index 032d90fb1..1d141403f 100644 --- a/Tests/RulesTests+Parens.swift +++ b/Tests/RulesTests+Parens.swift @@ -17,190 +17,190 @@ class ParensTests: RulesTests { func testRedundantParensRemoved() { let input = "(x || y)" let output = "x || y" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved2() { let input = "(x) || y" let output = "x || y" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved3() { let input = "x + (5)" let output = "x + 5" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved4() { let input = "(.bar)" let output = ".bar" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved5() { let input = "(Foo.bar)" let output = "Foo.bar" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemoved6() { let input = "(foo())" let output = "foo()" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemoved() { let input = "(x || y) * z" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemoved2() { let input = "(x + y) as Int" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemoved3() { let input = "x+(-5)" - testFormatting(for: input, rule: FormatRules.redundantParens, + testFormatting(for: input, rule: .redundantParens, exclude: ["spaceAroundOperators"]) } func testRedundantParensAroundIsNotRemoved() { let input = "a = (x is Int)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedBeforeSubscript() { let input = "(foo + bar)[baz]" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedBeforeCollectionLiteral() { let input = "(foo + bar)\n[baz]" let output = "foo + bar\n[baz]" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedBeforeFunctionInvocation() { let input = "(foo + bar)(baz)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedBeforeTuple() { let input = "(foo + bar)\n(baz, quux).0" let output = "foo + bar\n(baz, quux).0" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedBeforePostfixOperator() { let input = "(foo + bar)!" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedBeforeInfixOperator() { let input = "(foo + bar) * baz" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensNotRemovedAroundSelectorStringLiteral() { let input = "Selector((\"foo\"))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensRemovedOnLineAfterSelectorIdentifier() { let input = "Selector\n((\"foo\"))" let output = "Selector\n(\"foo\")" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testMeaningfulParensNotRemovedAroundFileLiteral() { let input = "func foo(_ file: String = (#file)) {}" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["unusedArguments"]) } func testMeaningfulParensNotRemovedAroundOperator() { let input = "let foo: (Int, Int) -> Bool = (<)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensNotRemovedAroundOperatorWithSpaces() { let input = "let foo: (Int, Int) -> Bool = ( < )" - testFormatting(for: input, rule: FormatRules.redundantParens, + testFormatting(for: input, rule: .redundantParens, exclude: ["spaceAroundOperators", "spaceInsideParens"]) } func testMeaningfulParensNotRemovedAroundPrefixOperator() { let input = "let foo: (Int) -> Int = ( -)" - testFormatting(for: input, rule: FormatRules.redundantParens, + testFormatting(for: input, rule: .redundantParens, exclude: ["spaceAroundOperators", "spaceInsideParens"]) } func testMeaningfulParensAroundPrefixExpressionFollowedByDotNotRemoved() { let input = "let foo = (!bar).description" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundPrefixExpressionWithSpacesFollowedByDotNotRemoved() { let input = "let foo = ( !bar ).description" - testFormatting(for: input, rule: FormatRules.redundantParens, + testFormatting(for: input, rule: .redundantParens, exclude: ["spaceAroundOperators", "spaceInsideParens"]) } func testMeaningfulParensAroundPrefixExpressionFollowedByPostfixExpressionNotRemoved() { let input = "let foo = (!bar)!" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundPrefixExpressionFollowedBySubscriptNotRemoved() { let input = "let foo = (!bar)[5]" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensAroundPostfixExpressionFollowedByPostfixOperatorRemoved() { let input = "let foo = (bar!)!" let output = "let foo = bar!!" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundPostfixExpressionFollowedByPostfixOperatorRemoved2() { let input = "let foo = ( bar! )!" let output = "let foo = bar!!" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundPostfixExpressionRemoved() { let input = "let foo = foo + (bar!)" let output = "let foo = foo + bar!" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundPostfixExpressionFollowedBySubscriptRemoved() { let input = "let foo = (bar!)[5]" let output = "let foo = bar![5]" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundPrefixExpressionRemoved() { let input = "let foo = foo + (!bar)" let output = "let foo = foo + !bar" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensAroundInfixExpressionNotRemoved() { let input = "let foo = (foo + bar)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensAroundInfixEqualsExpressionNotRemoved() { let input = "let foo = (bar == baz)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensAroundClosureTypeRemoved() { let input = "typealias Foo = ((Int) -> Bool)" let output = "typealias Foo = (Int) -> Bool" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNonRedundantParensAroundClosureTypeNotRemoved() { @@ -210,40 +210,40 @@ class ParensTests: RulesTests { typealias PhotoEnumerationHandler = (PhotoFetchResultEnumeration) -> Void } """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // TODO: future enhancement // func testRedundantParensAroundClosureReturnTypeRemoved() { // let input = "typealias Foo = (Int) -> ((Int) -> Bool)" // let output = "typealias Foo = (Int) -> (Int) -> Bool" -// testFormatting(for: input, output, rule: FormatRules.redundantParens) +// testFormatting(for: input, output, rule: .redundantParens) // } func testRedundantParensAroundNestedClosureTypesNotRemoved() { let input = "typealias Foo = (((Int) -> Bool) -> Int) -> ((String) -> Bool) -> Void" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundClosureTypeNotRemoved() { let input = "let foo = ((Int) -> Bool)?" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundTryExpressionNotRemoved() { let input = "let foo = (try? bar()) != nil" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testMeaningfulParensAroundAwaitExpressionNotRemoved() { let input = "if !(await isSomething()) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensInReturnRemoved() { let input = "return (true)" let output = "return true" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensInMultilineReturnRemovedCleanly() { @@ -259,7 +259,7 @@ class ParensTests: RulesTests { .bar """ - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // around conditions @@ -267,309 +267,309 @@ class ParensTests: RulesTests { func testRedundantParensRemovedInIf() { let input = "if (x || y) {}" let output = "if x || y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf2() { let input = "if (x) || y {}" let output = "if x || y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf3() { let input = "if x + (5) == 6 {}" let output = "if x + 5 == 6 {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf4() { let input = "if (x || y), let foo = bar {}" let output = "if x || y, let foo = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf5() { let input = "if (.bar) {}" let output = "if .bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf6() { let input = "if (Foo.bar) {}" let output = "if Foo.bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf7() { let input = "if (foo()) {}" let output = "if foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIf8() { let input = "if x, (y == 2) {}" let output = "if x, y == 2 {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInIfWithNoSpace() { let input = "if(x) {}" let output = "if x {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedInHashIfWithNoSpace() { let input = "#if(x)\n#endif" let output = "#if x\n#endif" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedInIf() { let input = "if (x || y) * z {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOuterParensRemovedInWhile() { let input = "while ((x || y) && z) {}" let output = "while (x || y) && z {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: ["andOperator"]) } func testOuterParensRemovedInIf() { let input = "if (Foo.bar(baz)) {}" let output = "if Foo.bar(baz) {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testCaseOuterParensRemoved() { let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" let output = "switch foo {\ncase Foo.bar(let baz):\n}" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["hoistPatternLet"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: ["hoistPatternLet"]) } func testCaseLetOuterParensRemoved() { let input = "switch foo {\ncase let (Foo.bar(baz)):\n}" let output = "switch foo {\ncase let Foo.bar(baz):\n}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testCaseVarOuterParensRemoved() { let input = "switch foo {\ncase var (Foo.bar(baz)):\n}" let output = "switch foo {\ncase var Foo.bar(baz):\n}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testGuardParensRemoved() { let input = "guard (x == y) else { return }" let output = "guard x == y else { return }" - testFormatting(for: input, output, rule: FormatRules.redundantParens, + testFormatting(for: input, output, rule: .redundantParens, exclude: ["wrapConditionalBodies"]) } func testForValueParensRemoved() { let input = "for (x) in (y) {}" let output = "for x in y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensForLoopWhereClauseMethodNotRemoved() { let input = "for foo in foos where foo.method() { print(foo) }" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["wrapLoopBodies"]) } func testSpaceInsertedWhenRemovingParens() { let input = "if(x.y) {}" let output = "if x.y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testSpaceInsertedWhenRemovingParens2() { let input = "while(!foo) {}" let output = "while !foo {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNoDoubleSpaceWhenRemovingParens() { let input = "if ( x.y ) {}" let output = "if x.y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNoDoubleSpaceWhenRemovingParens2() { let input = "if (x.y) {}" let output = "if x.y {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // around function and closure arguments func testNestedClosureParensNotRemoved() { let input = "foo { _ in foo(y) {} }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testClosureTypeNotUnwrapped() { let input = "foo = (Bar) -> Baz" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOptionalFunctionCallNotUnwrapped() { let input = "foo?(bar)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOptionalFunctionCallResultNotUnwrapped() { let input = "bar = (foo?()).flatMap(Bar.init)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOptionalSubscriptResultNotUnwrapped() { let input = "bar = (foo?[0]).flatMap(Bar.init)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testOptionalMemberResultNotUnwrapped() { let input = "bar = (foo?.baz).flatMap(Bar.init)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testForceUnwrapFunctionCallNotUnwrapped() { let input = "foo!(bar)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testCurriedFunctionCallNotUnwrapped() { let input = "foo(bar)(baz)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testCurriedFunctionCallNotUnwrapped2() { let input = "foo(bar)(baz) + quux" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testSubscriptFunctionCallNotUnwrapped() { let input = "foo[\"bar\"](baz)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedInsideClosure() { let input = "{ (foo) + bar }" let output = "{ foo + bar }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedAroundFunctionArgument() { let input = "foo(bar: (5))" let output = "foo(bar: 5)" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedAroundOptionalClosureType() { let input = "let foo = (() -> ())?" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["void"]) } func testRequiredParensNotRemovedAroundOptionalRange() { let input = "let foo = (2...)?" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundOptionalUnwrap() { let input = "let foo = (bar!)+5" - testFormatting(for: input, rule: FormatRules.redundantParens, + testFormatting(for: input, rule: .redundantParens, exclude: ["spaceAroundOperators"]) } func testRedundantParensRemovedAroundOptionalOptional() { let input = "let foo: (Int?)?" let output = "let foo: Int??" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedAroundOptionalOptional2() { let input = "let foo: (Int!)?" let output = "let foo: Int!?" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedAroundOptionalOptional3() { let input = "let foo: (Int?)!" let output = "let foo: Int?!" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRequiredParensNotRemovedAroundOptionalAnyType() { let input = "let foo: (any Foo)?" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAroundAnyTypeSelf() { let input = "let foo = (any Foo).self" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAroundAnyTypeType() { let input = "let foo: (any Foo).Type" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAroundAnyComposedMetatype() { let input = "let foo: any (A & B).Type" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundAnyType() { let input = "let foo: (any Foo)" let output = "let foo: any Foo" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedAroundAnyTypeInsideArray() { let input = "let foo: [(any Foo)]" let output = "let foo: [any Foo]" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensAroundParameterPackEachNotRemoved() { let input = "func f(_: repeat ((each V).Type, as: (each V) -> String)) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundOptionalClosureType() { let input = "let foo = ((() -> ()))?" let output = "let foo = (() -> ())?" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: ["void"]) } func testRequiredParensNotRemovedAfterClosureArgument() { let input = "foo({ /* code */ }())" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAfterClosureArgument2() { let input = "foo(bar: { /* code */ }())" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAfterClosureArgument3() { let input = "foo(bar: 5, { /* code */ }())" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAfterClosureInsideArray() { let input = "[{ /* code */ }()]" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAfterClosureInsideArrayWithTrailingComma() { let input = "[{ /* code */ }(),]" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["trailingCommas"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["trailingCommas"]) } func testRequiredParensNotRemovedAfterClosureInWhereClause() { let input = "case foo where { x == y }():" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["redundantClosure"]) } // around closure arguments @@ -577,77 +577,77 @@ class ParensTests: RulesTests { func testSingleClosureArgumentUnwrapped() { let input = "{ (foo) in }" let output = "{ foo in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: ["unusedArguments"]) } func testSingleMainActorClosureArgumentUnwrapped() { let input = "{ @MainActor (foo) in }" let output = "{ @MainActor foo in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: ["unusedArguments"]) } func testSingleClosureArgumentWithReturnValueUnwrapped() { let input = "{ (foo) -> Int in 5 }" let output = "{ foo -> Int in 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: ["unusedArguments"]) } func testSingleAnonymousClosureArgumentUnwrapped() { let input = "{ (_) in }" let output = "{ _ in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testSingleAnonymousClosureArgumentNotUnwrapped() { let input = "{ (_ foo) in }" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["unusedArguments"]) } func testTypedClosureArgumentNotUnwrapped() { let input = "{ (foo: Int) in print(foo) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testSingleClosureArgumentAfterCaptureListUnwrapped() { let input = "{ [weak self] (foo) in self.bar(foo) }" let output = "{ [weak self] foo in self.bar(foo) }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testMultipleClosureArgumentUnwrapped() { let input = "{ (foo, bar) in foo(bar) }" let output = "{ foo, bar in foo(bar) }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testTypedMultipleClosureArgumentNotUnwrapped() { let input = "{ (foo: Int, bar: String) in foo(bar) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testEmptyClosureArgsNotUnwrapped() { let input = "{ () in }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testClosureArgsContainingSelfNotUnwrapped() { let input = "{ (self) in self }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testClosureArgsContainingSelfNotUnwrapped2() { let input = "{ (foo, self) in foo(self) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testClosureArgsContainingSelfNotUnwrapped3() { let input = "{ (self, foo) in foo(self) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNoRemoveParensAroundArrayInitializer() { let input = "let foo = bar { [Int](foo) }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNoRemoveParensAroundForIndexInsideClosure() { @@ -656,12 +656,12 @@ class ParensTests: RulesTests { for (i, token) in bar {} }() """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNoRemoveRequiredParensInsideClosure() { let input = "let foo = { _ in (a + b).c }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // before trailing closure @@ -669,55 +669,55 @@ class ParensTests: RulesTests { func testParensRemovedBeforeTrailingClosure() { let input = "var foo = bar() { /* some code */ }" let output = "var foo = bar { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedBeforeTrailingClosure2() { let input = "let foo = bar() { /* some code */ }" let output = "let foo = bar { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedBeforeTrailingClosure3() { let input = "var foo = bar() { /* some code */ }" let output = "var foo = bar { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedBeforeTrailingClosureInsideHashIf() { let input = "#if baz\n let foo = bar() { /* some code */ }\n#endif" let output = "#if baz\n let foo = bar { /* some code */ }\n#endif" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedBeforeVarBody() { let input = "var foo = bar() { didSet {} }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeFunctionBody() { let input = "func bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeIfBody() { let input = "if let foo = bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeIfBody2() { let input = "if try foo as Bar && baz() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["andOperator"]) } func testParensNotRemovedBeforeIfBody3() { let input = "if #selector(foo(_:)) && bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["andOperator"]) } func testParensNotRemovedBeforeIfBody4() { let input = "if let data = #imageLiteral(resourceName: \"abc.png\").pngData() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeIfBody5() { @@ -726,84 +726,84 @@ class ParensTests: RulesTests { self?.products.accept(newProducts) } """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeIfBodyAfterTry() { let input = "if let foo = try bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeCompoundIfBody() { let input = "if let foo = bar(), let baz = quux() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeForBody() { let input = "for foo in bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeWhileBody() { let input = "while let foo = bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeCaseBody() { let input = "if case foo = bar() { /* some code */ }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedBeforeSwitchBody() { let input = "switch foo() {\ndefault: break\n}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAfterAnonymousClosureInsideIfStatementBody() { let input = "if let foo = bar(), { x == y }() {}" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["redundantClosure"]) } func testParensNotRemovedInGenericInit() { let input = "init(_: T) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedInGenericInit2() { let input = "init() {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedInGenericFunction() { let input = "func foo(_: T) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedInGenericFunction2() { let input = "func foo() {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedInGenericInstantiation() { let input = "let foo = Foo()" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["propertyType"]) } func testParensNotRemovedInGenericInstantiation2() { let input = "let foo = Foo(bar)" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["propertyType"]) } func testRedundantParensRemovedAfterGenerics() { let input = "let foo: Foo\n(a) + b" let output = "let foo: Foo\na + b" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testRedundantParensRemovedAfterGenerics2() { let input = "let foo: Foo\n(foo())" let output = "let foo: Foo\nfoo()" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // closure expression @@ -811,229 +811,229 @@ class ParensTests: RulesTests { func testParensAroundClosureRemoved() { let input = "let foo = ({ /* some code */ })" let output = "let foo = { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensAroundClosureAssignmentBlockRemoved() { let input = "let foo = ({ /* some code */ })()" let output = "let foo = { /* some code */ }()" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensAroundClosureInCompoundExpressionRemoved() { let input = "if foo == ({ /* some code */ }), let bar = baz {}" let output = "if foo == { /* some code */ }, let bar = baz {}" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundClosure() { let input = "if (foo { $0 }) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundClosure2() { let input = "if (foo.filter { $0 > 1 }.isEmpty) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundClosure3() { let input = "if let foo = (bar.filter { $0 > 1 }).first {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // around tuples func testsTupleNotUnwrapped() { let input = "tuple = (1, 2)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testsTupleOfClosuresNotUnwrapped() { let input = "tuple = ({}, {})" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testSwitchTupleNotUnwrapped() { let input = "switch (x, y) {}" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensRemovedAroundTuple() { let input = "let foo = ((bar: Int, baz: String))" let output = "let foo = (bar: Int, baz: String)" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundTupleFunctionArgument() { let input = "let foo = bar((bar: Int, baz: String))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundTupleFunctionArgumentAfterSubscript() { let input = "bar[5]((bar: Int, baz: String))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNestedParensRemovedAroundTupleFunctionArgument() { let input = "let foo = bar(((bar: Int, baz: String)))" let output = "let foo = bar((bar: Int, baz: String))" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNestedParensRemovedAroundTupleFunctionArgument2() { let input = "let foo = bar(foo: ((bar: Int, baz: String)))" let output = "let foo = bar(foo: (bar: Int, baz: String))" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testNestedParensRemovedAroundTupleOperands() { let input = "((1, 2)) == ((1, 2))" let output = "(1, 2) == (1, 2)" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundTupleFunctionTypeDeclaration() { let input = "let foo: ((bar: Int, baz: String)) -> Void" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundUnlabelledTupleFunctionTypeDeclaration() { let input = "let foo: ((Int, String)) -> Void" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundTupleFunctionTypeAssignment() { let input = "foo = ((bar: Int, baz: String)) -> Void { _ in }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundTupleFunctionTypeAssignment() { let input = "foo = ((((bar: Int, baz: String)))) -> Void { _ in }" let output = "foo = ((bar: Int, baz: String)) -> Void { _ in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundUnlabelledTupleFunctionTypeAssignment() { let input = "foo = ((Int, String)) -> Void { _ in }" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRedundantParensRemovedAroundUnlabelledTupleFunctionTypeAssignment() { let input = "foo = ((((Int, String)))) -> Void { _ in }" let output = "foo = ((Int, String)) -> Void { _ in }" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensNotRemovedAroundTupleArgument() { let input = "foo((bar, baz))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundVoidGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["void"]) } func testParensNotRemovedAroundTupleGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["void"]) } func testParensNotRemovedAroundLabeledTupleGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: FormatRules.redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: ["void"]) } // after indexed tuple func testParensNotRemovedAfterTupleIndex() { let input = "foo.1()" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAfterTupleIndex2() { let input = "foo.1(true)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAfterTupleIndex3() { let input = "foo.1((bar: Int, baz: String))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testNestedParensRemovedAfterTupleIndex3() { let input = "foo.1(((bar: Int, baz: String)))" let output = "foo.1((bar: Int, baz: String))" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // inside string interpolation func testParensInStringNotRemoved() { let input = "\"hello \\(world)\"" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // around ranges func testParensAroundRangeNotRemoved() { let input = "(1 ..< 10).reduce(0, combine: +)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensRemovedAroundRangeArguments() { let input = "(a)...(b)" let output = "a...b" - testFormatting(for: input, output, rule: FormatRules.redundantParens, + testFormatting(for: input, output, rule: .redundantParens, exclude: ["spaceAroundOperators"]) } func testParensNotRemovedAroundRangeArgumentBeginningWithDot() { let input = "a...(.b)" - testFormatting(for: input, rule: FormatRules.redundantParens, + testFormatting(for: input, rule: .redundantParens, exclude: ["spaceAroundOperators"]) } func testParensNotRemovedAroundTrailingRangeFollowedByDot() { let input = "(a...).b" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testParensNotRemovedAroundRangeArgumentBeginningWithPrefixOperator() { let input = "a...(-b)" - testFormatting(for: input, rule: FormatRules.redundantParens, + testFormatting(for: input, rule: .redundantParens, exclude: ["spaceAroundOperators"]) } func testParensRemovedAroundRangeArgumentBeginningWithDot() { let input = "a ... (.b)" let output = "a ... .b" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } func testParensRemovedAroundRangeArgumentBeginningWithPrefixOperator() { let input = "a ... (-b)" let output = "a ... -b" - testFormatting(for: input, output, rule: FormatRules.redundantParens) + testFormatting(for: input, output, rule: .redundantParens) } // around ternaries func testParensNotRemovedAroundTernaryCondition() { let input = "let a = (b == c) ? d : e" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedAroundTernaryAssignment() { let input = "a ? (b = c) : (b = d)" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // around parameter repeat each func testRequiredParensNotRemovedAroundRepeat() { let input = "(repeat (each foo, each bar))" - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } // in async expression @@ -1045,7 +1045,7 @@ class ParensTests: RulesTests { async let dataTask2: Void = someTask(request) } """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } func testRequiredParensNotRemovedInAsyncLet2() { @@ -1054,6 +1054,6 @@ class ParensTests: RulesTests { let processURL: (URL) async throws -> Void = { _ in } } """ - testFormatting(for: input, rule: FormatRules.redundantParens) + testFormatting(for: input, rule: .redundantParens) } } diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index a2de691ce..133560aa5 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -36,7 +36,7 @@ class RedundancyTests: RulesTests { print("goodbye") } """ - testFormatting(for: input, output, rule: FormatRules.redundantBreak) + testFormatting(for: input, output, rule: .redundantBreak) } func testBreakInEmptyCaseNotRemoved() { @@ -50,7 +50,7 @@ class RedundancyTests: RulesTests { break } """ - testFormatting(for: input, rule: FormatRules.redundantBreak) + testFormatting(for: input, rule: .redundantBreak) } func testConditionalBreakNotRemoved() { @@ -62,7 +62,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantBreak) + testFormatting(for: input, rule: .redundantBreak) } func testBreakAfterSemicolonNotMangled() { @@ -76,7 +76,7 @@ class RedundancyTests: RulesTests { case 1: print(1); } """ - testFormatting(for: input, output, rule: FormatRules.redundantBreak, exclude: ["semicolons"]) + testFormatting(for: input, output, rule: .redundantBreak, exclude: ["semicolons"]) } // MARK: - redundantExtensionACL @@ -96,7 +96,7 @@ class RedundancyTests: RulesTests { func quux() {} } """ - testFormatting(for: input, output, rule: FormatRules.redundantExtensionACL) + testFormatting(for: input, output, rule: .redundantExtensionACL) } func testPrivateExtensionMemberACLNotStrippedUnlessFileprivate() { @@ -114,7 +114,7 @@ class RedundancyTests: RulesTests { func quux() {} } """ - testFormatting(for: input, output, rule: FormatRules.redundantExtensionACL) + testFormatting(for: input, output, rule: .redundantExtensionACL) } // MARK: - redundantFileprivate @@ -126,7 +126,7 @@ class RedundancyTests: RulesTests { let output = """ private var foo = "foo" """ - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate) + testFormatting(for: input, output, rule: .redundantFileprivate) } func testFileScopeFileprivateVarNotChangedToPrivateIfFragment() { @@ -134,7 +134,7 @@ class RedundancyTests: RulesTests { fileprivate var foo = "foo" """ let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherType() { @@ -149,7 +149,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) } func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherTypeAndFileIncludesImports() { @@ -168,7 +168,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAnotherType() { @@ -184,7 +184,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateVarNotChangedToPrivateIfAccessedFromSubclass() { @@ -200,7 +200,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAFunction() { @@ -214,7 +214,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAConstant() { @@ -226,7 +226,7 @@ class RedundancyTests: RulesTests { let kFoo = Foo().foo """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAVar() { @@ -238,7 +238,7 @@ class RedundancyTests: RulesTests { var kFoo: String { return Foo().foo } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateVarNotChangedToPrivateIfAccessedFromCode() { @@ -250,7 +250,7 @@ class RedundancyTests: RulesTests { print(Foo().foo) """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAClosure() { @@ -262,7 +262,7 @@ class RedundancyTests: RulesTests { print({ Foo().foo }()) """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["redundantClosure"]) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAnExtensionOnAnotherType() { @@ -278,7 +278,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateVarChangedToPrivateIfAccessedFromAnExtensionOnSameType() { @@ -305,7 +305,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) } func testFileprivateVarChangedToPrivateIfAccessedViaSelfFromAnExtensionOnSameType() { @@ -332,7 +332,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options, + testFormatting(for: input, output, rule: .redundantFileprivate, options: options, exclude: ["redundantSelf"]) } @@ -355,7 +355,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateInitChangedToPrivateIfConstructorNotCalledOutsideType() { @@ -370,7 +370,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) } func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType() { @@ -382,7 +382,7 @@ class RedundancyTests: RulesTests { let foo = Foo() """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType2() { @@ -396,7 +396,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateStructMemberNotChangedToPrivateIfConstructorCalledOutsideType() { @@ -408,7 +408,7 @@ class RedundancyTests: RulesTests { let foo = Foo(bar: "test") """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateClassMemberChangedToPrivateEvenIfConstructorCalledOutsideType() { @@ -427,7 +427,7 @@ class RedundancyTests: RulesTests { let foo = Foo() """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateExtensionFuncNotChangedToPrivateIfPartOfProtocolConformance() { @@ -439,7 +439,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateInnerTypeNotChangedToPrivate() { @@ -458,7 +458,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "4") testFormatting(for: input, - rule: FormatRules.redundantFileprivate, + rule: .redundantFileprivate, options: options, exclude: ["wrapEnumCases"]) } @@ -474,7 +474,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testOverriddenFileprivateInitNotChangedToPrivate() { @@ -490,7 +490,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testNonOverriddenFileprivateInitChangedToPrivate() { @@ -517,7 +517,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) } func testFileprivateInitNotChangedToPrivateWhenUsingTypeInferredInits() { @@ -531,7 +531,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) } func testFileprivateInitNotChangedToPrivateWhenUsingTrailingClosureInit() { @@ -547,7 +547,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnContainingType() { @@ -563,7 +563,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnNestedType() { @@ -579,7 +579,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromSubclass() { @@ -595,7 +595,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateInitNotChangedToPrivateWhenAccessedFromSubclass() { @@ -612,7 +612,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromExtensionOnSubclass() { @@ -630,7 +630,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateVarWithPropertWrapperNotChangedToPrivateIfAccessedFromSubclass() { @@ -646,7 +646,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile() { @@ -662,7 +662,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, options: options) + testFormatting(for: input, rule: .redundantFileprivate, options: options) } func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile2() { @@ -678,7 +678,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: FormatRules.redundantFileprivate, + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["typeSugar"]) } @@ -687,34 +687,34 @@ class RedundancyTests: RulesTests { func testRemoveSingleLineIsolatedGet() { let input = "var foo: Int { get { return 5 } }" let output = "var foo: Int { return 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantGet) + testFormatting(for: input, output, rule: .redundantGet) } func testRemoveMultilineIsolatedGet() { let input = "var foo: Int {\n get {\n return 5\n }\n}" let output = "var foo: Int {\n return 5\n}" - testFormatting(for: input, [output], rules: [FormatRules.redundantGet, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.redundantGet, .indent]) } func testNoRemoveMultilineGetSet() { let input = "var foo: Int {\n get { return 5 }\n set { foo = newValue }\n}" - testFormatting(for: input, rule: FormatRules.redundantGet) + testFormatting(for: input, rule: .redundantGet) } func testNoRemoveAttributedGet() { let input = "var enabled: Bool { @objc(isEnabled) get { true } }" - testFormatting(for: input, rule: FormatRules.redundantGet) + testFormatting(for: input, rule: .redundantGet) } func testRemoveSubscriptGet() { let input = "subscript(_ index: Int) {\n get {\n return lookup(index)\n }\n}" let output = "subscript(_ index: Int) {\n return lookup(index)\n}" - testFormatting(for: input, [output], rules: [FormatRules.redundantGet, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.redundantGet, .indent]) } func testGetNotRemovedInFunction() { let input = "func foo() {\n get {\n self.lookup(index)\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantGet) + testFormatting(for: input, rule: .redundantGet) } func testEffectfulGetNotRemoved() { @@ -725,7 +725,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantGet) + testFormatting(for: input, rule: .redundantGet) } // MARK: - redundantInit @@ -733,54 +733,54 @@ class RedundancyTests: RulesTests { func testRemoveRedundantInit() { let input = "[1].flatMap { String.init($0) }" let output = "[1].flatMap { String($0) }" - testFormatting(for: input, output, rule: FormatRules.redundantInit) + testFormatting(for: input, output, rule: .redundantInit) } func testRemoveRedundantInit2() { let input = "[String.self].map { Type in Type.init(foo: 1) }" let output = "[String.self].map { Type in Type(foo: 1) }" - testFormatting(for: input, output, rule: FormatRules.redundantInit) + testFormatting(for: input, output, rule: .redundantInit) } func testRemoveRedundantInit3() { let input = "String.init(\"text\")" let output = "String(\"text\")" - testFormatting(for: input, output, rule: FormatRules.redundantInit) + testFormatting(for: input, output, rule: .redundantInit) } func testDontRemoveInitInSuperCall() { let input = "class C: NSObject { override init() { super.init() } }" - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testDontRemoveInitInSelfCall() { let input = "struct S { let n: Int }; extension S { init() { self.init(n: 1) } }" - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testDontRemoveInitWhenPassedAsFunction() { let input = "[1].flatMap(String.init)" - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testDontRemoveInitWhenUsedOnMetatype() { let input = "[String.self].map { type in type.init(1) }" - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testDontRemoveInitWhenUsedOnImplicitClosureMetatype() { let input = "[String.self].map { $0.init(1) }" - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testDontRemoveInitWhenUsedOnPossibleMetatype() { let input = "let something = Foo.bar.init()" - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testDontRemoveInitWithExplicitSignature() { let input = "[String.self].map(Foo.init(bar:))" - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testRemoveInitWithOpenParenOnFollowingLine() { @@ -801,7 +801,7 @@ class RedundancyTests: RulesTests { ) } """ - testFormatting(for: input, output, rule: FormatRules.redundantInit) + testFormatting(for: input, output, rule: .redundantInit) } func testNoRemoveInitWithOpenParenOnFollowingLineAfterComment() { @@ -814,14 +814,14 @@ class RedundancyTests: RulesTests { ) } """ - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testNoRemoveInitForLowercaseType() { let input = """ let foo = bar.init() """ - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testNoRemoveInitForLocalLetType() { @@ -829,7 +829,7 @@ class RedundancyTests: RulesTests { let Foo = Foo.self let foo = Foo.init() """ - testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantInit, exclude: ["propertyType"]) } func testNoRemoveInitForLocalLetType2() { @@ -841,7 +841,7 @@ class RedundancyTests: RulesTests { return Foo.init(y) } """ - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testNoRemoveInitInsideIfdef() { @@ -854,7 +854,7 @@ class RedundancyTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["indent"]) + testFormatting(for: input, rule: .redundantInit, exclude: ["indent"]) } func testNoRemoveInitInsideIfdef2() { @@ -867,7 +867,7 @@ class RedundancyTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.redundantInit, exclude: ["indent"]) + testFormatting(for: input, rule: .redundantInit, exclude: ["indent"]) } func testRemoveInitAfterCollectionLiterals() { @@ -885,7 +885,7 @@ class RedundancyTests: RulesTests { let tupleArray = [(key: String, value: Int)]() let dictionary = [String: Int]() """ - testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantInit, exclude: ["propertyType"]) } func testPreservesInitAfterTypeOfCall() { @@ -893,7 +893,7 @@ class RedundancyTests: RulesTests { type(of: oldViewController).init() """ - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testRemoveInitAfterOptionalType() { @@ -906,7 +906,7 @@ class RedundancyTests: RulesTests { // (String!.init("Foo") isn't valid Swift code, so we don't test for it) """ - testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantInit, exclude: ["propertyType"]) } func testPreservesTryBeforeInit() { @@ -916,7 +916,7 @@ class RedundancyTests: RulesTests { let throwingOptional2: Foo = try! .init() """ - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } func testRemoveInitAfterGenericType() { @@ -931,14 +931,14 @@ class RedundancyTests: RulesTests { let atomicDictionary = Atomic<[String: Int]>() """ - testFormatting(for: input, output, rule: FormatRules.redundantInit, exclude: ["typeSugar", "propertyType"]) + testFormatting(for: input, output, rule: .redundantInit, exclude: ["typeSugar", "propertyType"]) } func testPreserveNonRedundantInitInTernaryOperator() { let input = """ let bar: Bar = (foo.isBar && bar.isBaaz) ? .init() : nil """ - testFormatting(for: input, rule: FormatRules.redundantInit) + testFormatting(for: input, rule: .redundantInit) } // MARK: - redundantLetError @@ -946,13 +946,13 @@ class RedundancyTests: RulesTests { func testCatchLetError() { let input = "do {} catch let error {}" let output = "do {} catch {}" - testFormatting(for: input, output, rule: FormatRules.redundantLetError) + testFormatting(for: input, output, rule: .redundantLetError) } func testCatchLetErrorWithTypedThrows() { let input = "do throws(Foo) {} catch let error {}" let output = "do throws(Foo) {} catch {}" - testFormatting(for: input, output, rule: FormatRules.redundantLetError) + testFormatting(for: input, output, rule: .redundantLetError) } // MARK: - redundantObjc @@ -960,35 +960,35 @@ class RedundancyTests: RulesTests { func testRedundantObjcRemovedFromBeforeOutlet() { let input = "@objc @IBOutlet var label: UILabel!" let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } func testRedundantObjcRemovedFromAfterOutlet() { let input = "@IBOutlet @objc var label: UILabel!" let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } func testRedundantObjcRemovedFromLineBeforeOutlet() { let input = "@objc\n@IBOutlet var label: UILabel!" let output = "\n@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } func testRedundantObjcCommentNotRemoved() { let input = "@objc /// an outlet\n@IBOutlet var label: UILabel!" let output = "/// an outlet\n@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } func testObjcNotRemovedFromNSCopying() { let input = "@objc @NSCopying var foo: String!" - testFormatting(for: input, rule: FormatRules.redundantObjc) + testFormatting(for: input, rule: .redundantObjc) } func testRenamedObjcNotRemoved() { let input = "@IBOutlet @objc(uiLabel) var label: UILabel!" - testFormatting(for: input, rule: FormatRules.redundantObjc) + testFormatting(for: input, rule: .redundantObjc) } func testObjcRemovedOnObjcMembersClass() { @@ -1002,7 +1002,7 @@ class RedundancyTests: RulesTests { var foo: String } """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } func testObjcRemovedOnRenamedObjcMembersClass() { @@ -1016,7 +1016,7 @@ class RedundancyTests: RulesTests { var foo: String } """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } func testObjcNotRemovedOnNestedClass() { @@ -1025,7 +1025,7 @@ class RedundancyTests: RulesTests { @objc class Bar: NSObject {} } """ - testFormatting(for: input, rule: FormatRules.redundantObjc) + testFormatting(for: input, rule: .redundantObjc) } func testObjcNotRemovedOnRenamedPrivateNestedClass() { @@ -1034,7 +1034,7 @@ class RedundancyTests: RulesTests { @objc private class Bar: NSObject {} } """ - testFormatting(for: input, rule: FormatRules.redundantObjc) + testFormatting(for: input, rule: .redundantObjc) } func testObjcNotRemovedOnNestedEnum() { @@ -1043,7 +1043,7 @@ class RedundancyTests: RulesTests { @objc enum Bar: Int {} } """ - testFormatting(for: input, rule: FormatRules.redundantObjc) + testFormatting(for: input, rule: .redundantObjc) } func testObjcRemovedOnObjcExtensionVar() { @@ -1057,7 +1057,7 @@ class RedundancyTests: RulesTests { var foo: String {} } """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } func testObjcRemovedOnObjcExtensionFunc() { @@ -1071,7 +1071,7 @@ class RedundancyTests: RulesTests { func foo() -> String {} } """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } func testObjcNotRemovedOnPrivateFunc() { @@ -1080,7 +1080,7 @@ class RedundancyTests: RulesTests { @objc private func bar() {} } """ - testFormatting(for: input, rule: FormatRules.redundantObjc) + testFormatting(for: input, rule: .redundantObjc) } func testObjcNotRemovedOnFileprivateFunc() { @@ -1089,7 +1089,7 @@ class RedundancyTests: RulesTests { @objc fileprivate func bar() {} } """ - testFormatting(for: input, rule: FormatRules.redundantObjc) + testFormatting(for: input, rule: .redundantObjc) } func testObjcRemovedOnPrivateSetFunc() { @@ -1103,7 +1103,7 @@ class RedundancyTests: RulesTests { private(set) func bar() {} } """ - testFormatting(for: input, output, rule: FormatRules.redundantObjc) + testFormatting(for: input, output, rule: .redundantObjc) } // MARK: - redundantType @@ -1112,7 +1112,7 @@ class RedundancyTests: RulesTests { let input = "var view: UIView = UIView()" let output = "var view = UIView()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } @@ -1120,7 +1120,7 @@ class RedundancyTests: RulesTests { let input = "var foo: [String] = [String]()" let output = "var foo = [String]()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } @@ -1128,7 +1128,7 @@ class RedundancyTests: RulesTests { let input = "var foo: [String: Int] = [String: Int]()" let output = "var foo = [String: Int]()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } @@ -1136,34 +1136,34 @@ class RedundancyTests: RulesTests { let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" let output = "let relay = BehaviourRelay(value: nil)" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } func testVarNonRedundantTypeDoesNothing() { let input = "var view: UIView = UINavigationBar()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testLetRedundantTypeRemoval() { let input = "let view: UIView = UIView()" let output = "let view = UIView()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } func testLetNonRedundantTypeDoesNothing() { let input = "let view: UIView = UINavigationBar()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testTypeNoRedundancyDoesNothing() { let input = "let foo: Bar = 5" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testClassTwoVariablesNoRedundantTypeDoesNothing() { @@ -1174,7 +1174,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testRedundantTypeRemovedIfValueOnNextLine() { @@ -1187,7 +1187,7 @@ class RedundancyTests: RulesTests { = UIView() """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } @@ -1201,7 +1201,7 @@ class RedundancyTests: RulesTests { UIView() """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } @@ -1209,7 +1209,7 @@ class RedundancyTests: RulesTests { let input = "var foo: Int = 0, bar: Int = 0" let output = "var foo = 0, bar = 0" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } @@ -1217,7 +1217,7 @@ class RedundancyTests: RulesTests { let input = "var view: UIView /* view */ = UIView()" let output = "var view /* view */ = UIView()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } @@ -1225,14 +1225,14 @@ class RedundancyTests: RulesTests { let input = "var view: UIView = /* view */ UIView()" let output = "var view = /* view */ UIView()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } func testNonRedundantTernaryConditionTypeNotRemoved() { let input = "let foo: Bar = Bar.baz() ? .bar1 : .bar2" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testTernaryConditionAfterLetNotTreatedAsPartOfExpression() { @@ -1245,47 +1245,47 @@ class RedundancyTests: RulesTests { baz ? bar2() : bar2() """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } func testNoRemoveRedundantTypeIfVoid() { let input = "let foo: Void = Void()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["void"]) } func testNoRemoveRedundantTypeIfVoid2() { let input = "let foo: () = ()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["void"]) } func testNoRemoveRedundantTypeIfVoid3() { let input = "let foo: [Void] = [Void]()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testNoRemoveRedundantTypeIfVoid4() { let input = "let foo: Array = Array()" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["typeSugar"]) } func testNoRemoveRedundantTypeIfVoid5() { let input = "let foo: Void? = Void?.none" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testNoRemoveRedundantTypeIfVoid6() { let input = "let foo: Optional = Optional.none" let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["typeSugar"]) } @@ -1329,7 +1329,7 @@ class RedundancyTests: RulesTests { let f2: [String: Int?] = ["foo": nil] """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options) } @@ -1343,7 +1343,7 @@ class RedundancyTests: RulesTests { let f: MyDictionaryRepresentable = ["baz": 1] """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testPreservesTypeWithIfExpressionInSwift5_8() { @@ -1356,7 +1356,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testPreservesNonRedundantTypeWithIfExpression() { @@ -1368,7 +1368,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) } func testRedundantTypeWithIfExpression_inferred() { @@ -1387,7 +1387,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) } func testRedundantTypeWithIfExpression_explicit() { @@ -1406,7 +1406,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) } func testRedundantTypeWithNestedIfExpression_inferred() { @@ -1445,7 +1445,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) } func testRedundantTypeWithNestedIfExpression_explicit() { @@ -1484,7 +1484,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) } func testRedundantTypeWithLiteralsInIfExpression() { @@ -1503,7 +1503,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) } // --redundanttype explicit @@ -1512,7 +1512,7 @@ class RedundancyTests: RulesTests { let input = "var view: UIView = UIView()" let output = "var view: UIView = .init()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1520,7 +1520,7 @@ class RedundancyTests: RulesTests { let input = "var view: UIView = UIView /* foo */()" let output = "var view: UIView = .init /* foo */()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["spaceAroundComments", "propertyType"]) } @@ -1528,7 +1528,7 @@ class RedundancyTests: RulesTests { let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" let output = "let relay: BehaviourRelay = .init(value: nil)" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1536,21 +1536,21 @@ class RedundancyTests: RulesTests { let input = "let relay: Foo = Foo\n .default" let output = "let relay: Foo = \n .default" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["trailingSpace", "propertyType"]) } func testVarNonRedundantTypeDoesNothingExplicitType() { let input = "var view: UIView = UINavigationBar()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } func testLetRedundantTypeRemovalExplicitType() { let input = "let view: UIView = UIView()" let output = "let view: UIView = .init()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1564,7 +1564,7 @@ class RedundancyTests: RulesTests { = .init() """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1578,7 +1578,7 @@ class RedundancyTests: RulesTests { .init() """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1586,7 +1586,7 @@ class RedundancyTests: RulesTests { let input = "var view: UIView /* view */ = UIView()" let output = "var view: UIView /* view */ = .init()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1594,7 +1594,7 @@ class RedundancyTests: RulesTests { let input = "var view: UIView = /* view */ UIView()" let output = "var view: UIView = /* view */ .init()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1616,7 +1616,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1638,59 +1638,59 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingWithChainedMember() { let input = "let session: URLSession = URLSession.default.makeCopy()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantRedundantChainedMemberTypeRemovedOnSwift5_4() { let input = "let session: URLSession = URLSession.default.makeCopy()" let output = "let session: URLSession = .default.makeCopy()" let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingWithChainedMember2() { let input = "let color: UIColor = UIColor.red.withAlphaComponent(0.5)" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingWithChainedMember3() { let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeRemovedWithChainedMemberOnSwift5_4() { let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" let output = "let url: URL = .init(fileURLWithPath: #file).deletingLastPathComponent()" let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingIfLet() { let input = "if let foo: Foo = Foo() {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingGuardLet() { let input = "guard let foo: Foo = Foo() else {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeDoesNothingIfLetAfterComma() { let input = "if check == true, let foo: Foo = Foo() {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeWorksAfterIf() { @@ -1703,7 +1703,7 @@ class RedundancyTests: RulesTests { let foo: Foo = .init() """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1711,35 +1711,35 @@ class RedundancyTests: RulesTests { let input = "let foo: [Void] = [Void]()" let output = "let foo: [Void] = .init()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } func testRedundantTypeWithIntegerLiteralNotMangled() { let input = "let foo: Int = 1.toFoo" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, + testFormatting(for: input, rule: .redundantType, options: options) } func testRedundantTypeWithFloatLiteralNotMangled() { let input = "let foo: Double = 1.0.toFoo" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, + testFormatting(for: input, rule: .redundantType, options: options) } func testRedundantTypeWithArrayLiteralNotMangled() { let input = "let foo: [Int] = [1].toFoo" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, + testFormatting(for: input, rule: .redundantType, options: options) } func testRedundantTypeWithBoolLiteralNotMangled() { let input = "let foo: Bool = false.toFoo" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, + testFormatting(for: input, rule: .redundantType, options: options) } @@ -1752,7 +1752,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.redundantType, options: options) + testFormatting(for: input, rule: .redundantType, options: options) } // --redundanttype infer-locals-only @@ -1793,7 +1793,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, output, rule: FormatRules.redundantType, + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) } @@ -1803,21 +1803,21 @@ class RedundancyTests: RulesTests { let input = "var foo: Int? = nil\nlet bar: Int? = nil" let output = "var foo: Int?\nlet bar: Int? = nil" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } func testNoRemoveLetNilInitAfterVar() { let input = "var foo: Int; let bar: Int? = nil" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoRemoveNonNilInit() { let input = "var foo: Int? = 0" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -1825,7 +1825,7 @@ class RedundancyTests: RulesTests { let input = "var foo: Int! = nil" let output = "var foo: Int!" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -1833,56 +1833,56 @@ class RedundancyTests: RulesTests { let input = "var foo: Int? = nil, bar: Int? = nil" let output = "var foo: Int?, bar: Int?" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } func testNoRemoveLazyVarNilInit() { let input = "lazy var foo: Int? = nil" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoRemoveLazyPublicPrivateSetVarNilInit() { let input = "lazy private(set) public var foo: Int? = nil" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, options: options, + testFormatting(for: input, rule: .redundantNilInit, options: options, exclude: ["modifierOrder"]) } func testNoRemoveCodableNilInit() { let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoRemoveNilInitWithPropertyWrapper() { let input = "@Foo var foo: Int? = nil" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoRemoveNilInitWithLowercasePropertyWrapper() { let input = "@foo var foo: Int? = nil" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoRemoveNilInitWithPropertyWrapperWithArgument() { let input = "@Foo(bar: baz) var foo: Int? = nil" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoRemoveNilInitWithLowercasePropertyWrapperWithArgument() { let input = "@foo(bar: baz) var foo: Int? = nil" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -1890,7 +1890,7 @@ class RedundancyTests: RulesTests { let input = "@objc var foo: Int? = nil" let output = "@objc var foo: Int?" let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -1901,7 +1901,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -1917,7 +1917,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .remove, swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -1939,7 +1939,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -1953,7 +1953,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -1971,7 +1971,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -1991,7 +1991,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2001,7 +2001,7 @@ class RedundancyTests: RulesTests { let input = "var foo: Int?\nlet bar: Int? = nil" let output = "var foo: Int? = nil\nlet bar: Int? = nil" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -2009,7 +2009,7 @@ class RedundancyTests: RulesTests { let input = "var foo: Int?; let bar: Int? = nil" let output = "var foo: Int? = nil; let bar: Int? = nil" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -2017,14 +2017,14 @@ class RedundancyTests: RulesTests { let input = "let bar: Int? = nil; var foo: Int?" let output = "let bar: Int? = nil; var foo: Int? = nil" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } func testNoInsertNonNilInit() { let input = "var foo: Int? = 0" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2032,7 +2032,7 @@ class RedundancyTests: RulesTests { let input = "var foo: Int!" let output = "var foo: Int! = nil" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -2040,56 +2040,56 @@ class RedundancyTests: RulesTests { let input = "var foo: Int?, bar: Int?" let output = "var foo: Int? = nil, bar: Int? = nil" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } func testNoInsertLazyVarNilInit() { let input = "lazy var foo: Int?" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoInsertLazyPublicPrivateSetVarNilInit() { let input = "lazy private(set) public var foo: Int?" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, options: options, + testFormatting(for: input, rule: .redundantNilInit, options: options, exclude: ["modifierOrder"]) } func testNoInsertCodableNilInit() { let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoInsertNilInitWithPropertyWrapper() { let input = "@Foo var foo: Int?" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoInsertNilInitWithLowercasePropertyWrapper() { let input = "@foo var foo: Int?" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoInsertNilInitWithPropertyWrapperWithArgument() { let input = "@Foo(bar: baz) var foo: Int?" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } func testNoInsertNilInitWithLowercasePropertyWrapperWithArgument() { let input = "@foo(bar: baz) var foo: Int?" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2097,7 +2097,7 @@ class RedundancyTests: RulesTests { let input = "@objc var foo: Int?" let output = "@objc var foo: Int? = nil" let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -2108,7 +2108,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2126,7 +2126,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert, swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -2152,7 +2152,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -2167,7 +2167,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2186,7 +2186,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2207,7 +2207,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2217,7 +2217,7 @@ class RedundancyTests: RulesTests { var foo: String? { nil } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2232,7 +2232,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2245,7 +2245,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2262,7 +2262,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantNilInit, + testFormatting(for: input, output, rule: .redundantNilInit, options: options) } @@ -2272,7 +2272,7 @@ class RedundancyTests: RulesTests { var jsonObject = json as? [String: Int] """ let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: FormatRules.redundantNilInit, + testFormatting(for: input, rule: .redundantNilInit, options: options) } @@ -2281,45 +2281,45 @@ class RedundancyTests: RulesTests { func testRemoveRedundantLet() { let input = "let _ = bar {}" let output = "_ = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantLet) + testFormatting(for: input, output, rule: .redundantLet) } func testNoRemoveLetWithType() { let input = "let _: String = bar {}" - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testRemoveRedundantLetInCase() { let input = "if case .foo(let _) = bar {}" let output = "if case .foo(_) = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantLet, exclude: ["redundantPattern"]) + testFormatting(for: input, output, rule: .redundantLet, exclude: ["redundantPattern"]) } func testRemoveRedundantVarsInCase() { let input = "if case .foo(var _, var /* unused */ _) = bar {}" let output = "if case .foo(_, /* unused */ _) = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantLet) + testFormatting(for: input, output, rule: .redundantLet) } func testNoRemoveLetInIf() { let input = "if let _ = foo {}" - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveLetInMultiIf() { let input = "if foo == bar, /* comment! */ let _ = baz {}" - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveLetInGuard() { let input = "guard let _ = foo else {}" - testFormatting(for: input, rule: FormatRules.redundantLet, + testFormatting(for: input, rule: .redundantLet, exclude: ["wrapConditionalBodies"]) } func testNoRemoveLetInWhile() { let input = "while let _ = foo {}" - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveLetInViewBuilder() { @@ -2329,7 +2329,7 @@ class RedundancyTests: RulesTests { Text("Some text") } """ - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveLetInViewBuilderModifier() { @@ -2343,7 +2343,7 @@ class RedundancyTests: RulesTests { } ) """ - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveLetInIfStatementInViewBuilder() { @@ -2354,7 +2354,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveLetInSwitchStatementInViewBuilder() { @@ -2376,12 +2376,12 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveAsyncLet() { let input = "async let _ = foo()" - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveLetImmediatelyAfterMainActorAttribute() { @@ -2390,7 +2390,7 @@ class RedundancyTests: RulesTests { let _ = try await baz() } """ - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } func testNoRemoveLetImmediatelyAfterSendableAttribute() { @@ -2399,7 +2399,7 @@ class RedundancyTests: RulesTests { let _ = try await baz() } """ - testFormatting(for: input, rule: FormatRules.redundantLet) + testFormatting(for: input, rule: .redundantLet) } // MARK: - redundantPattern @@ -2407,44 +2407,44 @@ class RedundancyTests: RulesTests { func testRemoveRedundantPatternInIfCase() { let input = "if case let .foo(_, _) = bar {}" let output = "if case .foo = bar {}" - testFormatting(for: input, output, rule: FormatRules.redundantPattern) + testFormatting(for: input, output, rule: .redundantPattern) } func testNoRemoveRequiredPatternInIfCase() { let input = "if case (_, _) = bar {}" - testFormatting(for: input, rule: FormatRules.redundantPattern) + testFormatting(for: input, rule: .redundantPattern) } func testRemoveRedundantPatternInSwitchCase() { let input = "switch foo {\ncase let .bar(_, _): break\ndefault: break\n}" let output = "switch foo {\ncase .bar: break\ndefault: break\n}" - testFormatting(for: input, output, rule: FormatRules.redundantPattern) + testFormatting(for: input, output, rule: .redundantPattern) } func testNoRemoveRequiredPatternLetInSwitchCase() { let input = "switch foo {\ncase let .bar(_, a): break\ndefault: break\n}" - testFormatting(for: input, rule: FormatRules.redundantPattern) + testFormatting(for: input, rule: .redundantPattern) } func testNoRemoveRequiredPatternInSwitchCase() { let input = "switch foo {\ncase (_, _): break\ndefault: break\n}" - testFormatting(for: input, rule: FormatRules.redundantPattern) + testFormatting(for: input, rule: .redundantPattern) } func testSimplifyLetPattern() { let input = "let(_, _) = bar" let output = "let _ = bar" - testFormatting(for: input, output, rule: FormatRules.redundantPattern, exclude: ["redundantLet"]) + testFormatting(for: input, output, rule: .redundantPattern, exclude: ["redundantLet"]) } func testNoRemoveVoidFunctionCall() { let input = "if case .foo() = bar {}" - testFormatting(for: input, rule: FormatRules.redundantPattern) + testFormatting(for: input, rule: .redundantPattern) } func testNoRemoveMethodSignature() { let input = "func foo(_, _) {}" - testFormatting(for: input, rule: FormatRules.redundantPattern) + testFormatting(for: input, rule: .redundantPattern) } // MARK: - redundantRawValues @@ -2452,26 +2452,26 @@ class RedundancyTests: RulesTests { func testRemoveRedundantRawString() { let input = "enum Foo: String {\n case bar = \"bar\"\n case baz = \"baz\"\n}" let output = "enum Foo: String {\n case bar\n case baz\n}" - testFormatting(for: input, output, rule: FormatRules.redundantRawValues) + testFormatting(for: input, output, rule: .redundantRawValues) } func testRemoveCommaDelimitedCaseRawStringCases() { let input = "enum Foo: String { case bar = \"bar\", baz = \"baz\" }" let output = "enum Foo: String { case bar, baz }" - testFormatting(for: input, output, rule: FormatRules.redundantRawValues, + testFormatting(for: input, output, rule: .redundantRawValues, exclude: ["wrapEnumCases"]) } func testRemoveBacktickCaseRawStringCases() { let input = "enum Foo: String { case `as` = \"as\", `let` = \"let\" }" let output = "enum Foo: String { case `as`, `let` }" - testFormatting(for: input, output, rule: FormatRules.redundantRawValues, + testFormatting(for: input, output, rule: .redundantRawValues, exclude: ["wrapEnumCases"]) } func testNoRemoveRawStringIfNameDoesntMatch() { let input = "enum Foo: String {\n case bar = \"foo\"\n}" - testFormatting(for: input, rule: FormatRules.redundantRawValues) + testFormatting(for: input, rule: .redundantRawValues) } // MARK: - redundantVoidReturnType @@ -2479,83 +2479,83 @@ class RedundancyTests: RulesTests { func testRemoveRedundantVoidReturnType() { let input = "func foo() -> Void {}" let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testRemoveRedundantVoidReturnType2() { let input = "func foo() ->\n Void {}" let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testRemoveRedundantSwiftDotVoidReturnType() { let input = "func foo() -> Swift.Void {}" let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testRemoveRedundantSwiftDotVoidReturnType2() { let input = "func foo() -> Swift\n .Void {}" let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testRemoveRedundantEmptyReturnType() { let input = "func foo() -> () {}" let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testRemoveRedundantVoidTupleReturnType() { let input = "func foo() -> (Void) {}" let output = "func foo() {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testNoRemoveCommentFollowingRedundantVoidReturnType() { let input = "func foo() -> Void /* void */ {}" let output = "func foo() /* void */ {}" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testNoRemoveRequiredVoidReturnType() { let input = "typealias Foo = () -> Void" - testFormatting(for: input, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, rule: .redundantVoidReturnType) } func testNoRemoveChainedVoidReturnType() { let input = "func foo() -> () -> Void {}" - testFormatting(for: input, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, rule: .redundantVoidReturnType) } func testRemoveRedundantVoidInClosureArguments() { let input = "{ (foo: Bar) -> Void in foo() }" let output = "{ (foo: Bar) in foo() }" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testRemoveRedundantEmptyReturnTypeInClosureArguments() { let input = "{ (foo: Bar) -> () in foo() }" let output = "{ (foo: Bar) in foo() }" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testRemoveRedundantVoidInClosureArguments2() { let input = "methodWithTrailingClosure { foo -> Void in foo() }" let output = "methodWithTrailingClosure { foo in foo() }" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testRemoveRedundantSwiftDotVoidInClosureArguments2() { let input = "methodWithTrailingClosure { foo -> Swift.Void in foo() }" let output = "methodWithTrailingClosure { foo in foo() }" - testFormatting(for: input, output, rule: FormatRules.redundantVoidReturnType) + testFormatting(for: input, output, rule: .redundantVoidReturnType) } func testNoRemoveRedundantVoidInClosureArgument() { let input = "{ (foo: Bar) -> Void in foo() }" let options = FormatOptions(closureVoidReturn: .preserve) - testFormatting(for: input, rule: FormatRules.redundantVoidReturnType, options: options) + testFormatting(for: input, rule: .redundantVoidReturnType, options: options) } // MARK: - redundantReturn @@ -2563,91 +2563,91 @@ class RedundancyTests: RulesTests { func testRemoveRedundantReturnInClosure() { let input = "foo(with: { return 5 })" let output = "foo(with: { 5 })" - testFormatting(for: input, output, rule: FormatRules.redundantReturn, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .redundantReturn, exclude: ["trailingClosures"]) } func testRemoveRedundantReturnInClosureWithArgs() { let input = "foo(with: { foo in return foo })" let output = "foo(with: { foo in foo })" - testFormatting(for: input, output, rule: FormatRules.redundantReturn, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .redundantReturn, exclude: ["trailingClosures"]) } func testRemoveRedundantReturnInMap() { let input = "let foo = bar.map { return 1 }" let output = "let foo = bar.map { 1 }" - testFormatting(for: input, output, rule: FormatRules.redundantReturn) + testFormatting(for: input, output, rule: .redundantReturn) } func testNoRemoveReturnInComputedVar() { let input = "var foo: Int { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn) + testFormatting(for: input, rule: .redundantReturn) } func testRemoveReturnInComputedVar() { let input = "var foo: Int { return 5 }" let output = "var foo: Int { 5 }" let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, output, rule: .redundantReturn, options: options) } func testNoRemoveReturnInGet() { let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" - testFormatting(for: input, rule: FormatRules.redundantReturn) + testFormatting(for: input, rule: .redundantReturn) } func testRemoveReturnInGet() { let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" let output = "var foo: Int {\n get { 5 }\n set { _foo = newValue }\n}" let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, output, rule: .redundantReturn, options: options) } func testNoRemoveReturnInGetClosure() { let input = "let foo = get { return 5 }" let output = "let foo = get { 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantReturn) + testFormatting(for: input, output, rule: .redundantReturn) } func testRemoveReturnInVarClosure() { let input = "var foo = { return 5 }()" let output = "var foo = { 5 }()" - testFormatting(for: input, output, rule: FormatRules.redundantReturn, exclude: ["redundantClosure"]) + testFormatting(for: input, output, rule: .redundantReturn, exclude: ["redundantClosure"]) } func testRemoveReturnInParenthesizedClosure() { let input = "var foo = ({ return 5 }())" let output = "var foo = ({ 5 }())" - testFormatting(for: input, output, rule: FormatRules.redundantReturn, exclude: ["redundantParens", "redundantClosure"]) + testFormatting(for: input, output, rule: .redundantReturn, exclude: ["redundantParens", "redundantClosure"]) } func testNoRemoveReturnInFunction() { let input = "func foo() -> Int { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn) + testFormatting(for: input, rule: .redundantReturn) } func testRemoveReturnInFunction() { let input = "func foo() -> Int { return 5 }" let output = "func foo() -> Int { 5 }" let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, output, rule: .redundantReturn, options: options) } func testNoRemoveReturnInOperatorFunction() { let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantReturn, exclude: ["unusedArguments"]) } func testRemoveReturnInOperatorFunction() { let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" let output = "func + (lhs: Int, rhs: Int) -> Int { 5 }" let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options, + testFormatting(for: input, output, rule: .redundantReturn, options: options, exclude: ["unusedArguments"]) } func testNoRemoveReturnInFailableInit() { let input = "init?() { return nil }" - testFormatting(for: input, rule: FormatRules.redundantReturn) + testFormatting(for: input, rule: .redundantReturn) } func testNoRemoveReturnInFailableInitWithConditional() { @@ -2661,7 +2661,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testNoRemoveReturnInFailableInitWithNestedConditional() { @@ -2680,26 +2680,26 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRemoveReturnInFailableInit() { let input = "init?() { return nil }" let output = "init?() { nil }" let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, output, rule: .redundantReturn, options: options) } func testNoRemoveReturnInSubscript() { let input = "subscript(index: Int) -> String { return nil }" - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantReturn, exclude: ["unusedArguments"]) } func testRemoveReturnInSubscript() { let input = "subscript(index: Int) -> String { return nil }" let output = "subscript(index: Int) -> String { nil }" let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options, + testFormatting(for: input, output, rule: .redundantReturn, options: options, exclude: ["unusedArguments"]) } @@ -2714,7 +2714,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testNoRemoveReturnInDoThrowsCatch() { @@ -2728,7 +2728,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testNoRemoveReturnInDoCatchLet() { @@ -2742,7 +2742,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testNoRemoveReturnInDoThrowsCatchLet() { @@ -2756,52 +2756,52 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testNoRemoveReturnInForIn() { let input = "for foo in bar { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantReturn, exclude: ["wrapLoopBodies"]) } func testNoRemoveReturnInForWhere() { let input = "for foo in bar where baz { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantReturn, exclude: ["wrapLoopBodies"]) } func testNoRemoveReturnInIfLetTry() { let input = "if let foo = try? bar() { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, exclude: ["wrapConditionalBodies"]) } func testNoRemoveReturnInMultiIfLetTry() { let input = "if let foo = bar, let bar = baz { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, exclude: ["wrapConditionalBodies"]) } func testNoRemoveReturnAfterMultipleAs() { let input = "if foo as? bar as? baz { return 5 }" - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, exclude: ["wrapConditionalBodies"]) } func testRemoveVoidReturn() { let input = "{ _ in return }" let output = "{ _ in }" - testFormatting(for: input, output, rule: FormatRules.redundantReturn) + testFormatting(for: input, output, rule: .redundantReturn) } func testNoRemoveReturnAfterKeyPath() { let input = "func foo() { if bar == #keyPath(baz) { return 5 } }" - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, exclude: ["wrapConditionalBodies"]) } func testNoRemoveReturnAfterParentheses() { let input = "if let foo = (bar as? String) { return foo }" - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, exclude: ["redundantParens", "wrapConditionalBodies"]) } @@ -2809,7 +2809,7 @@ class RedundancyTests: RulesTests { let input = "var foo: (Int, Int) { return (1, 2) }" let output = "var foo: (Int, Int) { (1, 2) }" let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, output, rule: .redundantReturn, options: options) } func testNoRemoveReturnInIfLetWithNoSpaceAfterParen() { @@ -2823,7 +2823,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, + testFormatting(for: input, rule: .redundantReturn, options: options, exclude: ["spaceAroundBraces", "spaceAroundParens"]) } @@ -2833,7 +2833,7 @@ class RedundancyTests: RulesTests { return true } """ - testFormatting(for: input, rule: FormatRules.redundantReturn) + testFormatting(for: input, rule: .redundantReturn) } func testRemoveBlankLineWithReturn() { @@ -2848,7 +2848,7 @@ class RedundancyTests: RulesTests { bar } """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn, + testFormatting(for: input, output, rule: .redundantReturn, exclude: ["indent"]) } @@ -2864,7 +2864,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, + testFormatting(for: input, output, rule: .redundantReturn, options: options) } @@ -2880,7 +2880,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, + testFormatting(for: input, output, rule: .redundantReturn, options: options) } @@ -2892,7 +2892,7 @@ class RedundancyTests: RulesTests { return bar }() """ - testFormatting(for: input, rule: FormatRules.redundantReturn, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantReturn, exclude: ["redundantProperty"]) } func testNoRemoveReturnInForWhereLoop() { @@ -2905,7 +2905,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRedundantReturnInVoidFunction() { @@ -2918,7 +2918,7 @@ class RedundancyTests: RulesTests { func foo() { } """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn, + testFormatting(for: input, output, rule: .redundantReturn, exclude: ["emptyBraces"]) } @@ -2934,7 +2934,7 @@ class RedundancyTests: RulesTests { print("") } """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn) + testFormatting(for: input, output, rule: .redundantReturn) } func testRedundantReturnInVoidFunction3() { @@ -2949,7 +2949,7 @@ class RedundancyTests: RulesTests { // empty } """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn) + testFormatting(for: input, output, rule: .redundantReturn) } func testRedundantReturnInVoidFunction4() { @@ -2963,7 +2963,7 @@ class RedundancyTests: RulesTests { // empty } """ - testFormatting(for: input, output, rule: FormatRules.redundantReturn) + testFormatting(for: input, output, rule: .redundantReturn) } func testNoRemoveVoidReturnInCatch() { @@ -2978,7 +2978,7 @@ class RedundancyTests: RulesTests { print("foo") } """ - testFormatting(for: input, rule: FormatRules.redundantReturn) + testFormatting(for: input, rule: .redundantReturn) } func testNoRemoveReturnInIfCase() { @@ -2988,7 +2988,7 @@ class RedundancyTests: RulesTests { return false } """ - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, options: FormatOptions(swiftVersion: "5.1"), exclude: ["wrapConditionalBodies"]) } @@ -3001,7 +3001,7 @@ class RedundancyTests: RulesTests { return true } """ - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, options: FormatOptions(swiftVersion: "5.1")) } @@ -3014,7 +3014,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, options: FormatOptions(swiftVersion: "5.1"), exclude: ["redundantProperty"]) } @@ -3029,7 +3029,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testNoRemoveRequiredReturnInIfClosure2() { @@ -3043,7 +3043,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRemoveRedundantReturnInIfClosure() { @@ -3066,7 +3066,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, output, rule: .redundantReturn, options: options) } func testDisableNextRedundantReturn() { @@ -3077,7 +3077,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRedundantIfStatementReturnSwift5_8() { @@ -3091,7 +3091,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantReturn, + testFormatting(for: input, rule: .redundantReturn, options: options) } @@ -3107,7 +3107,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRedundantIfStatementReturnInFunction() { @@ -3143,7 +3143,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3164,7 +3164,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, + testFormatting(for: input, rule: .redundantReturn, options: options, exclude: ["conditionalAssignment"]) } @@ -3189,7 +3189,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3204,7 +3204,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, + testFormatting(for: input, rule: .redundantReturn, options: options, exclude: ["conditionalAssignment"]) } @@ -3222,7 +3222,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRedundantIfStatementReturnInRedundantClosure() { @@ -3244,8 +3244,8 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, - FormatRules.redundantClosure, FormatRules.indent], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure, .indent], options: options, exclude: ["wrapMultilineConditionalAssignment"]) } @@ -3272,7 +3272,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3288,7 +3288,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, + testFormatting(for: input, rule: .redundantReturn, options: options, exclude: ["conditionalAssignment"]) } @@ -3305,7 +3305,7 @@ class RedundancyTests: RulesTests { }() """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options, + testFormatting(for: input, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "propertyType"]) } @@ -3321,7 +3321,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRedundantSwitchStatementReturnInFunctionWithDefault() { @@ -3347,7 +3347,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3380,7 +3380,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3396,7 +3396,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testNonRedundantSwitchStatementReturnInFunctionWithFallthrough() { @@ -3411,7 +3411,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testVoidReturnNotStrippedFromSwitch() { @@ -3426,7 +3426,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRedundantNestedSwitchStatementReturnInFunction() { @@ -3472,7 +3472,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3503,7 +3503,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3598,7 +3598,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3614,7 +3614,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options, + testFormatting(for: input, rule: .redundantReturn, options: options, exclude: ["wrapSwitchCases", "sortSwitchCases"]) } @@ -3650,7 +3650,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, rule: .redundantReturn, options: options) } func testRemovseReturnFromIfExpressionNestedConditionalCastInSwift5_9() { @@ -3676,7 +3676,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3703,7 +3703,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(swiftVersion: "5.10") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -3729,7 +3729,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, output, rule: .redundantReturn, options: options) } func testRemovesRedundantReturnBeforeSwitchExpression() { @@ -3756,7 +3756,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantReturn, options: options) + testFormatting(for: input, output, rule: .redundantReturn, options: options) } // MARK: - redundantBackticks @@ -3764,147 +3764,147 @@ class RedundancyTests: RulesTests { func testRemoveRedundantBackticksInLet() { let input = "let `foo` = bar" let output = "let foo = bar" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testNoRemoveBackticksAroundKeyword() { let input = "let `let` = foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testNoRemoveBackticksAroundSelf() { let input = "let `self` = foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testNoRemoveBackticksAroundClassSelfInTypealias() { let input = "typealias `Self` = Foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testRemoveBackticksAroundClassSelfAsReturnType() { let input = "func foo(bar: `Self`) { print(bar) }" let output = "func foo(bar: Self) { print(bar) }" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testRemoveBackticksAroundClassSelfAsParameterType() { let input = "func foo() -> `Self` {}" let output = "func foo() -> Self {}" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testRemoveBackticksAroundClassSelfArgument() { let input = "func foo(`Self`: Foo) { print(Self) }" let output = "func foo(Self: Foo) { print(Self) }" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testNoRemoveBackticksAroundKeywordFollowedByType() { let input = "let `default`: Int = foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testNoRemoveBackticksAroundContextualGet() { let input = "var foo: Int {\n `get`()\n return 5\n}" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testRemoveBackticksAroundGetArgument() { let input = "func foo(`get` value: Int) { print(value) }" let output = "func foo(get value: Int) { print(value) }" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testRemoveBackticksAroundTypeAtRootLevel() { let input = "enum `Type` {}" let output = "enum Type {}" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testNoRemoveBackticksAroundTypeInsideType() { let input = "struct Foo {\n enum `Type` {}\n}" - testFormatting(for: input, rule: FormatRules.redundantBackticks, exclude: ["enumNamespaces"]) + testFormatting(for: input, rule: .redundantBackticks, exclude: ["enumNamespaces"]) } func testNoRemoveBackticksAroundLetArgument() { let input = "func foo(`let`: Foo) { print(`let`) }" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testNoRemoveBackticksAroundTrueArgument() { let input = "func foo(`true`: Foo) { print(`true`) }" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testRemoveBackticksAroundTrueArgument() { let input = "func foo(`true`: Foo) { print(`true`) }" let output = "func foo(true: Foo) { print(`true`) }" let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, options: options) + testFormatting(for: input, output, rule: .redundantBackticks, options: options) } func testNoRemoveBackticksAroundTypeProperty() { let input = "var type: Foo.`Type`" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testNoRemoveBackticksAroundTypePropertyInsideType() { let input = "struct Foo {\n enum `Type` {}\n}" - testFormatting(for: input, rule: FormatRules.redundantBackticks, exclude: ["enumNamespaces"]) + testFormatting(for: input, rule: .redundantBackticks, exclude: ["enumNamespaces"]) } func testNoRemoveBackticksAroundTrueProperty() { let input = "var type = Foo.`true`" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testRemoveBackticksAroundTrueProperty() { let input = "var type = Foo.`true`" let output = "var type = Foo.true" let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantBackticks, options: options, exclude: ["propertyType"]) } func testRemoveBackticksAroundProperty() { let input = "var type = Foo.`bar`" let output = "var type = Foo.bar" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantBackticks, exclude: ["propertyType"]) } func testRemoveBackticksAroundKeywordProperty() { let input = "var type = Foo.`default`" let output = "var type = Foo.default" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantBackticks, exclude: ["propertyType"]) } func testRemoveBackticksAroundKeypathProperty() { let input = "var type = \\.`bar`" let output = "var type = \\.bar" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testNoRemoveBackticksAroundKeypathKeywordProperty() { let input = "var type = \\.`default`" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testRemoveBackticksAroundKeypathKeywordPropertyInSwift5() { let input = "var type = \\.`default`" let output = "var type = \\.default" let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, output, rule: FormatRules.redundantBackticks, options: options) + testFormatting(for: input, output, rule: .redundantBackticks, options: options) } func testNoRemoveBackticksAroundInitPropertyInSwift5() { let input = "let foo: Foo = .`init`" let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, rule: FormatRules.redundantBackticks, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantBackticks, options: options, exclude: ["propertyType"]) } func testNoRemoveBackticksAroundAnyProperty() { let input = "enum Foo {\n case `Any`\n}" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testNoRemoveBackticksAroundGetInSubscript() { @@ -3913,35 +3913,35 @@ class RedundancyTests: RulesTests { `get`(name) } """ - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testNoRemoveBackticksAroundActorProperty() { let input = "let `actor`: Foo" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testRemoveBackticksAroundActorRvalue() { let input = "let foo = `actor`" let output = "let foo = actor" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testRemoveBackticksAroundActorLabel() { let input = "init(`actor`: Foo)" let output = "init(actor: Foo)" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testRemoveBackticksAroundActorLabel2() { let input = "init(`actor` foo: Foo)" let output = "init(actor foo: Foo)" - testFormatting(for: input, output, rule: FormatRules.redundantBackticks) + testFormatting(for: input, output, rule: .redundantBackticks) } func testNoRemoveBackticksAroundUnderscore() { let input = "func `_`(_ foo: T) -> T { foo }" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } func testNoRemoveBackticksAroundShadowedSelf() { @@ -3955,12 +3955,12 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: FormatRules.redundantBackticks, options: options) + testFormatting(for: input, rule: .redundantBackticks, options: options) } func testNoRemoveBackticksAroundDollar() { let input = "@attached(peer, names: prefixed(`$`))" - testFormatting(for: input, rule: FormatRules.redundantBackticks) + testFormatting(for: input, rule: .redundantBackticks) } // MARK: - redundantSelf @@ -3970,54 +3970,54 @@ class RedundancyTests: RulesTests { func testSimpleRemoveRedundantSelf() { let input = "func foo() { self.bar() }" let output = "func foo() { bar() }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfInsideStringInterpolation() { let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNoRemoveSelfForArgument() { let input = "func foo(bar: Int) { self.bar = bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForLocalVariable() { let input = "func foo() { var bar = self.bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRemoveSelfForLocalVariableOn5_4() { let input = "func foo() { var bar = self.bar }" let output = "func foo() { var bar = bar }" let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoRemoveSelfForCommaDelimitedLocalVariables() { let input = "func foo() { let foo = self.foo, bar = self.bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRemoveSelfForCommaDelimitedLocalVariablesOn5_4() { let input = "func foo() { let foo = self.foo, bar = self.bar }" let output = "func foo() { let foo = self.foo, bar = bar }" let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoRemoveSelfForCommaDelimitedLocalVariables2() { let input = "func foo() {\n let foo: Foo, bar: Bar\n foo = self.foo\n bar = self.bar\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForTupleAssignedVariables() { let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } // TODO: make this work @@ -4025,186 +4025,186 @@ class RedundancyTests: RulesTests { // let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" // let output = "func foo() { let (bar, baz) = (bar, baz) }" // let options = FormatOptions(swiftVersion: "5.4") -// testFormatting(for: input, output, rule: FormatRules.redundantSelf, +// testFormatting(for: input, output, rule: .redundantSelf, // options: options) // } func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularVariable() { let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar), baz = self.baz\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularLet() { let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar)\n let baz = self.baz\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveNonRedundantNestedFunctionSelf() { let input = "func foo() { func bar() { self.bar() } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveNonRedundantNestedFunctionSelf2() { let input = "func foo() {\n func bar() {}\n self.bar()\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveNonRedundantNestedFunctionSelf3() { let input = "func foo() { let bar = 5; func bar() { self.bar = bar } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveClosureSelf() { let input = "func foo() { bar { self.bar = 5 } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfAfterOptionalReturn() { let input = "func foo() -> String? {\n var index = startIndex\n if !matching(self[index]) {\n break\n }\n index = self.index(after: index)\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveRequiredSelfInExtensions() { let input = "extension Foo {\n func foo() {\n var index = 5\n if true {\n break\n }\n index = self.index(after: index)\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfBeforeInit() { let input = "convenience init() { self.init(5) }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRemoveSelfInsideSwitch() { let input = "func foo() {\n switch self.bar {\n case .foo:\n self.baz()\n }\n}" let output = "func foo() {\n switch bar {\n case .foo:\n baz()\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfInsideSwitchWhere() { let input = "func foo() {\n switch self.bar {\n case .foo where a == b:\n self.baz()\n }\n}" let output = "func foo() {\n switch bar {\n case .foo where a == b:\n baz()\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfInsideSwitchWhereAs() { let input = "func foo() {\n switch self.bar {\n case .foo where a == b as C:\n self.baz()\n }\n}" let output = "func foo() {\n switch bar {\n case .foo where a == b as C:\n baz()\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfInsideClassInit() { let input = "class Foo {\n var bar = 5\n init() { self.bar = 6 }\n}" let output = "class Foo {\n var bar = 5\n init() { bar = 6 }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNoRemoveSelfInClosureInsideIf() { let input = "if foo { bar { self.baz() } }" - testFormatting(for: input, rule: FormatRules.redundantSelf, + testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapConditionalBodies"]) } func testNoRemoveSelfForErrorInCatch() { let input = "do {} catch { self.error = error }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForErrorInDoThrowsCatch() { let input = "do throws(Foo) {} catch { self.error = error }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForNewValueInSet() { let input = "var foo: Int { set { self.newValue = newValue } get { return 0 } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForCustomNewValueInSet() { let input = "var foo: Int { set(n00b) { self.n00b = n00b } get { return 0 } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForNewValueInWillSet() { let input = "var foo: Int { willSet { self.newValue = newValue } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForCustomNewValueInWillSet() { let input = "var foo: Int { willSet(n00b) { self.n00b = n00b } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForOldValueInDidSet() { let input = "var foo: Int { didSet { self.oldValue = oldValue } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForCustomOldValueInDidSet() { let input = "var foo: Int { didSet(oldz) { self.oldz = oldz } }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForIndexVarInFor() { let input = "for foo in bar { self.foo = foo }" - testFormatting(for: input, rule: FormatRules.redundantSelf, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapLoopBodies"]) } func testNoRemoveSelfForKeyValueTupleInFor() { let input = "for (foo, bar) in baz { self.foo = foo; self.bar = bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapLoopBodies"]) } func testRemoveSelfFromComputedVar() { let input = "var foo: Int { return self.bar }" let output = "var foo: Int { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfFromOptionalComputedVar() { let input = "var foo: Int? { return self.bar }" let output = "var foo: Int? { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfFromNamespacedComputedVar() { let input = "var foo: Swift.String { return self.bar }" let output = "var foo: Swift.String { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfFromGenericComputedVar() { let input = "var foo: Foo { return self.bar }" let output = "var foo: Foo { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfFromComputedArrayVar() { let input = "var foo: [Int] { return self.bar }" let output = "var foo: [Int] { return bar }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfFromVarSetter() { let input = "var foo: Int { didSet { self.bar() } }" let output = "var foo: Int { didSet { bar() } }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNoRemoveSelfFromVarClosure() { let input = "var foo = { self.bar }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfFromLazyVar() { let input = "lazy var foo = self.bar" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRemoveSelfFromLazyVar() { let input = "lazy var foo = self.bar" let output = "lazy var foo = bar" let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { @@ -4212,7 +4212,7 @@ class RedundancyTests: RulesTests { var baz = bar lazy var foo = self.bar """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { @@ -4225,163 +4225,163 @@ class RedundancyTests: RulesTests { lazy var foo = bar """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoRemoveSelfFromLazyVarClosure() { let input = "lazy var foo = { self.bar }()" - testFormatting(for: input, rule: FormatRules.redundantSelf, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantSelf, exclude: ["redundantClosure"]) } func testNoRemoveSelfFromLazyVarClosure2() { let input = "lazy var foo = { let bar = self.baz }()" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfFromLazyVarClosure3() { let input = "lazy var foo = { [unowned self] in let bar = self.baz }()" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRemoveSelfFromVarInFuncWithUnusedArgument() { let input = "func foo(bar _: Int) { self.baz = 5 }" let output = "func foo(bar _: Int) { baz = 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfFromVarMatchingUnusedArgument() { let input = "func foo(bar _: Int) { self.bar = 5 }" let output = "func foo(bar _: Int) { bar = 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNoRemoveSelfFromVarMatchingRenamedArgument() { let input = "func foo(bar baz: Int) { self.baz = baz }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfFromVarRedeclaredInSubscope() { let input = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = self.bar\n}" let output = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = bar\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNoRemoveSelfFromVarDeclaredLaterInScope() { let input = "func foo() {\n let bar = self.baz\n let baz = quux\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfFromVarDeclaredLaterInOuterScope() { let input = "func foo() {\n if quux {\n let bar = self.baz\n }\n let baz = 6\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInWhilePreceededByVarDeclaration() { let input = "var index = start\nwhile index < end {\n index = self.index(after: index)\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInLocalVarPrecededByLocalVarFollowedByIfComma() { let input = "func foo() {\n let bar = Bar()\n let baz = Baz()\n self.baz = baz\n if let bar = bar, bar > 0 {}\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInLocalVarPrecededByIfLetContainingClosure() { let input = "func foo() {\n if let bar = 5 { baz { _ in } }\n let quux = self.quux\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf, + testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapConditionalBodies"]) } func testNoRemoveSelfForVarCreatedInGuardScope() { let input = "func foo() {\n guard let bar = 5 else {}\n let baz = self.bar\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf, + testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapConditionalBodies"]) } func testRemoveSelfForVarCreatedInIfScope() { let input = "func foo() {\n if let bar = bar {}\n let baz = self.bar\n}" let output = "func foo() {\n if let bar = bar {}\n let baz = bar\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNoRemoveSelfForVarDeclaredInWhileCondition() { let input = "while let foo = bar { self.foo = foo }" - testFormatting(for: input, rule: FormatRules.redundantSelf, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapLoopBodies"]) } func testRemoveSelfForVarNotDeclaredInWhileCondition() { let input = "while let foo == bar { self.baz = 5 }" let output = "while let foo == bar { baz = 5 }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, output, rule: .redundantSelf, exclude: ["wrapLoopBodies"]) } func testNoRemoveSelfForVarDeclaredInSwitchCase() { let input = "switch foo {\ncase bar: let baz = self.baz\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfAfterGenericInit() { let input = "init(bar: Int) {\n self = Foo()\n self.bar(bar)\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRemoveSelfInClassFunction() { let input = "class Foo {\n class func foo() {\n func bar() { self.foo() }\n }\n}" let output = "class Foo {\n class func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfInStaticFunction() { let input = "struct Foo {\n static func foo() {\n func bar() { self.foo() }\n }\n}" let output = "struct Foo {\n static func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf, exclude: ["enumNamespaces"]) + testFormatting(for: input, output, rule: .redundantSelf, exclude: ["enumNamespaces"]) } func testRemoveSelfInClassFunctionWithModifiers() { let input = "class Foo {\n class private func foo() {\n func bar() { self.foo() }\n }\n}" let output = "class Foo {\n class private func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, exclude: ["modifierOrder"]) } func testNoRemoveSelfInClassFunction() { let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { self.foo() }\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForVarDeclaredAfterRepeatWhile() { let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n let foo = 6\n self.foo()\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfForVarInClosureAfterRepeatWhile() { let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n ({ self.foo() })()\n }\n}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInClosureAfterVar() { let input = "var foo: String\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInClosureAfterNamespacedVar() { let input = "var foo: Swift.String\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInClosureAfterOptionalVar() { let input = "var foo: String?\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInClosureAfterGenericVar() { let input = "var foo: Foo\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInClosureAfterArray() { let input = "var foo: [Int]\nbar { self.baz() }" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInExpectFunction() { // Special case to support the Nimble framework @@ -4394,7 +4394,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoRemoveNestedSelfInExpectFunction() { @@ -4404,7 +4404,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoRemoveNestedSelfInArrayInExpectFunction() { @@ -4414,7 +4414,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoRemoveNestedSelfInSubscriptInExpectFunction() { @@ -4424,7 +4424,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoRemoveSelfInOSLogFunction() { @@ -4434,7 +4434,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoRemoveSelfInExcludedFunction() { @@ -4447,7 +4447,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoRemoveSelfForExcludedFunction() { @@ -4460,7 +4460,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoRemoveSelfInInterpolatedStringInExcludedFunction() { @@ -4473,7 +4473,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoRemoveSelfInExcludedInitializer() { @@ -4481,12 +4481,12 @@ class RedundancyTests: RulesTests { let vc = UIHostingController(rootView: InspectionView(inspection: self.inspection)) """ let options = FormatOptions(selfRequired: ["InspectionView"]) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["propertyType"]) } func testNoMistakeProtocolClassModifierForClassFunction() { let input = "protocol Foo: class {}\nfunc bar() {}" - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) XCTAssertNoThrow(try format(input, rules: FormatRules.all)) } @@ -4517,7 +4517,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testSwitchCaseLetVarRecognized() { @@ -4529,7 +4529,7 @@ class RedundancyTests: RulesTests { self.baz = baz } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSwitchCaseHoistedLetVarRecognized() { @@ -4541,7 +4541,7 @@ class RedundancyTests: RulesTests { self.baz = baz } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSwitchCaseWhereMemberNotTreatedAsVar() { @@ -4558,7 +4558,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfNotRemovedInClosureAfterSwitch() { @@ -4574,7 +4574,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfNotRemovedInClosureInCaseWithWhereClause() { @@ -4584,7 +4584,7 @@ class RedundancyTests: RulesTests { quux = { self.foo } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfRemovedInDidSet() { @@ -4606,7 +4606,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testSelfNotRemovedInGetter() { @@ -4617,7 +4617,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfNotRemovedInIfdef() { @@ -4628,7 +4628,7 @@ class RedundancyTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRedundantSelfRemovedWhenFollowedBySwitchContainingIfdef() { @@ -4662,7 +4662,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRedundantSelfRemovedInsideConditionalCase() { @@ -4702,7 +4702,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRedundantSelfRemovedAfterConditionalLet() { @@ -4730,7 +4730,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNestedClosureInNotMistakenForForLoop() { @@ -4742,7 +4742,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testTypedThrowingNestedClosureInNotMistakenForForLoop() { @@ -4754,7 +4754,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRedundantSelfPreservesSelfInClosureWithExplicitStrongCaptureBefore5_3() { @@ -4771,7 +4771,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testRedundantSelfRemovesSelfInClosureWithExplicitStrongCapture() { @@ -4799,7 +4799,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantSelf, options: options, exclude: ["unusedArguments"]) } func testRedundantSelfRemovesSelfInClosureWithNestedExplicitStrongCapture() { @@ -4835,7 +4835,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testRedundantSelfKeepsSelfInNestedClosureWithNoExplicitStrongCapture() { @@ -4885,7 +4885,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testRedundantSelfRemovesSelfInClosureCapturingStruct() { @@ -4913,7 +4913,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testRedundantSelfRemovesSelfInClosureCapturingSelfWeakly() { @@ -4995,7 +4995,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, options: options, exclude: ["redundantOptionalBinding"]) } @@ -5012,7 +5012,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testClosureParameterListShadowingPropertyOnSelf() { @@ -5029,7 +5029,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testClosureParameterListShadowingPropertyOnSelfInStruct() { @@ -5046,7 +5046,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testClosureCaptureListShadowingPropertyOnSelf() { @@ -5065,7 +5065,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testRedundantSelfKeepsSelfInClosureCapturingSelfWeaklyBefore5_8() { @@ -5085,7 +5085,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNonRedundantSelfNotRemovedAfterConditionalLet() { @@ -5102,12 +5102,12 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRedundantSelfDoesntGetStuckIfNoParensFound() { let input = "init_ foo: T {}" - testFormatting(for: input, rule: FormatRules.redundantSelf, + testFormatting(for: input, rule: .redundantSelf, exclude: ["spaceAroundOperators"]) } @@ -5128,7 +5128,7 @@ class RedundancyTests: RulesTests { bar() } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNoRemoveSelfInIfLetEscapedSelf() { @@ -5148,7 +5148,7 @@ class RedundancyTests: RulesTests { bar() } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testNoRemoveSelfAfterGuardLetSelf() { @@ -5160,7 +5160,7 @@ class RedundancyTests: RulesTests { self.bar() } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInClosureInIfCondition() { @@ -5171,7 +5171,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInTrailingClosureInVarAssignment() { @@ -5183,7 +5183,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfNotRemovedWhenPropertyIsKeyword() { @@ -5195,7 +5195,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfNotRemovedWhenPropertyIsContextualKeyword() { @@ -5207,7 +5207,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfRemovedForContextualKeywordThatRequiresNoEscaping() { @@ -5227,13 +5227,13 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveSelfForMemberNamedLazy() { let input = "func foo() { self.lazy() }" let output = "func foo() { lazy() }" - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveRedundantSelfInArrayLiteral() { @@ -5251,7 +5251,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveRedundantSelfInArrayLiteralVar() { @@ -5271,7 +5271,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemoveRedundantSelfInGuardLet() { @@ -5293,7 +5293,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testSelfNotRemovedInClosureInIf() { @@ -5303,7 +5303,7 @@ class RedundancyTests: RulesTests { _ = self.myVar }) {} """ - testFormatting(for: input, rule: FormatRules.redundantSelf, + testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapConditionalBodies"]) } @@ -5340,7 +5340,7 @@ class RedundancyTests: RulesTests { func doB() {} } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, options: FormatOptions(swiftVersion: "5.8")) } @@ -5360,7 +5360,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfNotRemovedInDeclarationWithDynamicMemberLookup() { @@ -5378,7 +5378,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfNotRemovedInExtensionOfTypeWithDynamicMemberLookup() { @@ -5399,7 +5399,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfRemovedInNestedExtensionOfTypeWithDynamicMemberLookup() { @@ -5432,7 +5432,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, options: options) } @@ -5448,7 +5448,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf, + testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapConditionalBodies"]) } @@ -5458,7 +5458,7 @@ class RedundancyTests: RulesTests { self.bar = bar } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testNoRemoveSelfInAssignmentInsideIfLetWithPostfixOperator() { @@ -5468,7 +5468,7 @@ class RedundancyTests: RulesTests { self.bar = bar } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRedundantSelfParsingBug() { @@ -5496,7 +5496,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, exclude: ["hoistPatternLet"]) } @@ -5524,7 +5524,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRedundantSelfParsingBug3() { @@ -5550,7 +5550,7 @@ class RedundancyTests: RulesTests { } """ - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) } func testRedundantSelfParsingBug4() { @@ -5562,7 +5562,7 @@ class RedundancyTests: RulesTests { return cell } """ - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) } func testRedundantSelfParsingBug5() { @@ -5576,14 +5576,14 @@ class RedundancyTests: RulesTests { } ) """ - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) } func testRedundantSelfParsingBug6() { let input = """ if let foo = bar, foo.tracking[jsonDict: "something"] != nil {} """ - XCTAssertNoThrow(try format(input, rules: [FormatRules.redundantSelf])) + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) } func testRedundantSelfWithStaticMethodAfterForLoop() { @@ -5607,7 +5607,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRedundantSelfWithStaticMethodAfterForWhereLoop() { @@ -5631,12 +5631,12 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRedundantSelfRuleDoesntErrorInForInTryLoop() { let input = "for foo in try bar() {}" - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRedundantSelfInInitWithActorLabel() { @@ -5648,7 +5648,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRedundantSelfRuleFailsInGuardWithParenthesizedClosureAfterComma() { @@ -5657,7 +5657,7 @@ class RedundancyTests: RulesTests { return nil } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testMinSelfNotRemoved() { @@ -5668,7 +5668,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testMinSelfNotRemovedOnSwift5_4() { @@ -5683,7 +5683,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantProperty"]) } func testDisableRedundantSelfDirective() { @@ -5697,7 +5697,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantProperty"]) } func testDisableRedundantSelfDirective2() { @@ -5712,7 +5712,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantProperty"]) } func testSelfInsertDirective() { @@ -5726,7 +5726,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantProperty"]) } func testNoRemoveVariableShadowedLaterInScopeInOlderSwiftVersions() { @@ -5741,7 +5741,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testStillRemoveVariableShadowedInSameDecalarationInOlderSwiftVersions() { @@ -5762,7 +5762,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.0") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testShadowedSelfRemovedInGuardLet() { @@ -5782,7 +5782,7 @@ class RedundancyTests: RulesTests { print(optional) } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testShadowedStringValueNotRemovedInInit() { @@ -5793,7 +5793,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testShadowedIntValueNotRemovedInInit() { @@ -5804,7 +5804,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testShadowedPropertyValueNotRemovedInInit() { @@ -5815,7 +5815,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testShadowedFuncCallValueNotRemovedInInit() { @@ -5826,7 +5826,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testShadowedFuncParamRemovedInInit() { @@ -5841,7 +5841,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoRemoveSelfInMacro() { @@ -5853,7 +5853,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } // explicitSelf = .insert @@ -5862,95 +5862,95 @@ class RedundancyTests: RulesTests { let input = "class Foo {\n let foo: Int\n init() { foo = 5 }\n}" let output = "class Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testInsertSelfInActor() { let input = "actor Foo {\n let foo: Int\n init() { foo = 5 }\n}" let output = "actor Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testInsertSelfAfterReturn() { let input = "class Foo {\n let foo: Int\n func bar() -> Int { return foo }\n}" let output = "class Foo {\n let foo: Int\n func bar() -> Int { return self.foo }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testInsertSelfInsideStringInterpolation() { let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoInterpretGenericTypesAsMembers() { let input = "class Foo {\n let foo: Bar\n init() { self.foo = Int(5) }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testInsertSelfForStaticMemberInClassFunction() { let input = "class Foo {\n static var foo: Int\n class func bar() { foo = 5 }\n}" let output = "class Foo {\n static var foo: Int\n class func bar() { self.foo = 5 }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoInsertSelfForInstanceMemberInClassFunction() { let input = "class Foo {\n var foo: Int\n class func bar() { foo = 5 }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForStaticMemberInInstanceFunction() { let input = "class Foo {\n static var foo: Int\n func bar() { foo = 5 }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForShadowedClassMemberInClassFunction() { let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { foo = 5 }\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfInForLoopTuple() { let input = "class Foo {\n var bar: Int\n func foo() { for (bar, baz) in quux {} }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForTupleTypeMembers() { let input = "class Foo {\n var foo: (Int, UIColor) {\n let bar = UIColor.red\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForArrayElements() { let input = "class Foo {\n var foo = [1, 2, nil]\n func bar() { baz(nil) }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForNestedVarReference() { let input = "class Foo {\n func bar() {\n var bar = 5\n repeat { bar = 6 } while true\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["wrapLoopBodies"]) } func testNoInsertSelfInSwitchCaseLet() { let input = "class Foo {\n var foo: Bar? {\n switch bar {\n case let .baz(foo, _):\n return nil\n }\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfInFuncAfterImportedClass() { let input = "import class Foo.Bar\nfunc foo() {\n var bar = 5\n if true {\n bar = 6\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["blankLineAfterImports"]) } @@ -5958,38 +5958,38 @@ class RedundancyTests: RulesTests { let input = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return get(key) }\n set { set(key, newValue) }\n }\n}" let output = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return self.get(key) }\n set { self.set(key, newValue) }\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoInsertSelfInIfCaseLet() { let input = "enum Foo {\n case bar(Int)\n var value: Int? {\n if case let .bar(value) = self { return value }\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["wrapConditionalBodies"]) } func testNoInsertSelfForPatternLet() { let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForPatternLet2() { let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case let .foo(baz): print(baz)\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForTypeOf() { let input = "class Foo {\n var type: String?\n func bar() {\n print(\"\\(type(of: self))\")\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForConditionalLocal() { let input = "class Foo {\n func foo() {\n #if os(watchOS)\n var foo: Int\n #else\n var foo: Float\n #endif\n print(foo)\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testInsertSelfInExtension() { @@ -6016,7 +6016,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testGlobalAfterTypeNotTreatedAsMember() { @@ -6034,7 +6034,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testForWhereVarNotTreatedAsMember() { @@ -6050,7 +6050,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSwitchCaseWhereVarNotTreatedAsMember() { @@ -6068,7 +6068,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSwitchCaseVarDoesntLeak() { @@ -6099,7 +6099,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfInsertedInSwitchCaseLet() { @@ -6130,7 +6130,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfInsertedInSwitchCaseWhere() { @@ -6161,7 +6161,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfInsertedInDidSet() { @@ -6184,7 +6184,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfInsertedAfterLet() { @@ -6211,7 +6211,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfNotInsertedInParameterNames() { @@ -6234,7 +6234,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfNotInsertedInCaseLet() { @@ -6249,7 +6249,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfNotInsertedInCaseLet2() { @@ -6264,7 +6264,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfInsertedInTupleAssignment() { @@ -6289,7 +6289,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfNotInsertedInTupleAssignment() { @@ -6304,7 +6304,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testInsertSelfForMemberNamedLazy() { @@ -6325,7 +6325,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoInsertSelfForVarDefinedInIfCaseLet() { @@ -6341,7 +6341,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForVarDefinedInUnhoistedIfCaseLet() { @@ -6357,7 +6357,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["hoistPatternLet"]) } @@ -6374,7 +6374,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfForVarDefinedInWhileLet() { @@ -6390,7 +6390,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfInCaptureList() { @@ -6405,7 +6405,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfInCaptureList2() { @@ -6419,7 +6419,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfInCaptureList3() { @@ -6436,7 +6436,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testBodilessFunctionDoesntBreakParser() { @@ -6451,7 +6451,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfBeforeSet() { @@ -6470,7 +6470,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfInMacro() { @@ -6483,7 +6483,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testNoInsertSelfBeforeBinding() { @@ -6502,7 +6502,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert, swiftVersion: "5.10") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } // explicitSelf = .initOnly @@ -6517,7 +6517,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testRemoveSelfIfNotInsideClassInit() { @@ -6538,7 +6538,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testInsertSelfInsideClassInit() { @@ -6559,7 +6559,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testNoInsertSelfInsideClassInitIfNotLvalue() { @@ -6582,7 +6582,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testRemoveSelfInsideClassInitIfNotLvalue() { @@ -6605,7 +6605,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfDotTypeInsideClassInitEdgeCase() { @@ -6623,7 +6623,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfInsertedInTupleInInit() { @@ -6648,7 +6648,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testSelfInsertedAfterLetInInit() { @@ -6671,7 +6671,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options) } func testRedundantSelfRuleDoesntErrorForStaticFuncInProtocolWithWhere() { @@ -6681,7 +6681,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testRedundantSelfRuleDoesntErrorForStaticFuncInStructWithWhere() { @@ -6691,7 +6691,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testRedundantSelfRuleDoesntErrorForClassFuncInClassWithWhere() { @@ -6701,7 +6701,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testRedundantSelfRuleFailsInInitOnlyMode() { @@ -6717,7 +6717,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantClosure"]) } func testRedundantSelfRuleFailsInInitOnlyMode2() { @@ -6740,7 +6740,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, options: options) } @@ -6752,7 +6752,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly, swiftVersion: "5.4") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testPropertyInitNotInterpretedAsTypeInit() { @@ -6774,7 +6774,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testPropertyInitNotInterpretedAsTypeInit2() { @@ -6796,7 +6796,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } // parsing bugs @@ -6819,7 +6819,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfRemovalParsingBug2() { @@ -6828,7 +6828,7 @@ class RedundancyTests: RulesTests { print("hi") } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfRemovalParsingBug3() { @@ -6841,7 +6841,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfRemovalParsingBug4() { @@ -6855,7 +6855,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfRemovalParsingBug5() { @@ -6876,7 +6876,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfRemovalParsingBug6() { @@ -6887,7 +6887,7 @@ class RedundancyTests: RulesTests { } }) """ - testFormatting(for: input, rule: FormatRules.redundantSelf, + testFormatting(for: input, rule: .redundantSelf, exclude: ["hoistPatternLet"]) } @@ -6909,7 +6909,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfNotRemovedInCaseIfElse() { @@ -6945,7 +6945,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testSelfCallAfterIfStatementInSwitchStatement() { @@ -6971,7 +6971,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testSelfNotRemovedFollowingNestedSwitchStatements() { @@ -7006,7 +7006,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.redundantSelf) + testFormatting(for: input, rule: .redundantSelf) } func testRedundantSelfWithStaticAsyncSendableClosureFunction() { @@ -7034,7 +7034,7 @@ class RedundancyTests: RulesTests { static func bar() {} } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } // enable/disable @@ -7062,7 +7062,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testDisableRemoveSelfCaseInsensitive() { @@ -7088,7 +7088,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testDisableNextRemoveSelf() { @@ -7112,7 +7112,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testMultilineDisableRemoveSelf() { @@ -7134,7 +7134,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testMultilineDisableNextRemoveSelf() { @@ -7158,7 +7158,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRemovesSelfInNestedFunctionInStrongSelfClosure() { @@ -7209,7 +7209,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf, options: FormatOptions(swiftVersion: "5.8")) + testFormatting(for: input, output, rule: .redundantSelf, options: FormatOptions(swiftVersion: "5.8")) } func testPreservesSelfInNestedFunctionInWeakSelfClosure() { @@ -7269,7 +7269,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf, + testFormatting(for: input, output, rule: .redundantSelf, options: FormatOptions(swiftVersion: "5.8")) } @@ -7294,7 +7294,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf) } func testRedundantSelfNotConfusedByParameterPack() { @@ -7304,7 +7304,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } func testRedundantSelfNotConfusedByStaticAfterSwitch() { @@ -7325,7 +7325,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options, exclude: ["enumNamespaces"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["enumNamespaces"]) } func testRedundantSelfNotConfusedByMainActor() { @@ -7343,7 +7343,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: FormatRules.redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options) } // MARK: - redundantStaticSelf @@ -7351,13 +7351,13 @@ class RedundancyTests: RulesTests { func testRedundantStaticSelfInStaticVar() { let input = "enum E { static var x: Int { Self.y } }" let output = "enum E { static var x: Int { y } }" - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, output, rule: .redundantStaticSelf) } func testRedundantStaticSelfInStaticMethod() { let input = "enum E { static func foo() { Self.bar() } }" let output = "enum E { static func foo() { bar() } }" - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, output, rule: .redundantStaticSelf) } func testRedundantStaticSelfOnNextLine() { @@ -7376,13 +7376,13 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, output, rule: .redundantStaticSelf) } func testRedundantStaticSelfWithReturn() { let input = "enum E { static func foo() { return Self.bar() } }" let output = "enum E { static func foo() { return bar() } }" - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, output, rule: .redundantStaticSelf) } func testRedundantStaticSelfInConditional() { @@ -7404,7 +7404,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, output, rule: .redundantStaticSelf) } func testRedundantStaticSelfInNestedFunction() { @@ -7426,7 +7426,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, output, rule: .redundantStaticSelf) } func testRedundantStaticSelfInNestedType() { @@ -7446,17 +7446,17 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, output, rule: .redundantStaticSelf) } func testStaticSelfNotRemovedWhenUsedAsImplicitInitializer() { let input = "enum E { static func foo() { Self().bar() } }" - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, rule: .redundantStaticSelf) } func testStaticSelfNotRemovedWhenUsedAsExplicitInitializer() { let input = "enum E { static func foo() { Self.init().bar() } }" - testFormatting(for: input, rule: FormatRules.redundantStaticSelf, exclude: ["redundantInit"]) + testFormatting(for: input, rule: .redundantStaticSelf, exclude: ["redundantInit"]) } func testPreservesStaticSelfInFunctionAfterStaticVar() { @@ -7477,7 +7477,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantStaticSelf, exclude: ["propertyType"]) } func testPreserveStaticSelfInInstanceFunction() { @@ -7490,7 +7490,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, rule: .redundantStaticSelf) } func testPreserveStaticSelfForShadowedProperty() { @@ -7503,7 +7503,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, rule: .redundantStaticSelf) } func testPreserveStaticSelfInGetter() { @@ -7516,7 +7516,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, rule: .redundantStaticSelf) } func testRemoveStaticSelfInStaticGetter() { @@ -7538,7 +7538,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, output, rule: .redundantStaticSelf) } func testPreserveStaticSelfInGuardLet() { @@ -7552,14 +7552,14 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, rule: .redundantStaticSelf) } func testPreserveStaticSelfInSingleLineClassInit() { let input = """ class A { static let defaultName = "A"; let name: String; init() { name = Self.defaultName }} """ - testFormatting(for: input, rule: FormatRules.redundantStaticSelf) + testFormatting(for: input, rule: .redundantStaticSelf) } // MARK: - semicolons @@ -7567,56 +7567,56 @@ class RedundancyTests: RulesTests { func testSemicolonRemovedAtEndOfLine() { let input = "print(\"hello\");\n" let output = "print(\"hello\")\n" - testFormatting(for: input, output, rule: FormatRules.semicolons) + testFormatting(for: input, output, rule: .semicolons) } func testSemicolonRemovedAtStartOfLine() { let input = "\n;print(\"hello\")" let output = "\nprint(\"hello\")" - testFormatting(for: input, output, rule: FormatRules.semicolons) + testFormatting(for: input, output, rule: .semicolons) } func testSemicolonRemovedAtEndOfProgram() { let input = "print(\"hello\");" let output = "print(\"hello\")" - testFormatting(for: input, output, rule: FormatRules.semicolons) + testFormatting(for: input, output, rule: .semicolons) } func testSemicolonRemovedAtStartOfProgram() { let input = ";print(\"hello\")" let output = "print(\"hello\")" - testFormatting(for: input, output, rule: FormatRules.semicolons) + testFormatting(for: input, output, rule: .semicolons) } func testIgnoreInlineSemicolon() { let input = "print(\"hello\"); print(\"goodbye\")" let options = FormatOptions(allowInlineSemicolons: true) - testFormatting(for: input, rule: FormatRules.semicolons, options: options) + testFormatting(for: input, rule: .semicolons, options: options) } func testReplaceInlineSemicolon() { let input = "print(\"hello\"); print(\"goodbye\")" let output = "print(\"hello\")\nprint(\"goodbye\")" let options = FormatOptions(allowInlineSemicolons: false) - testFormatting(for: input, output, rule: FormatRules.semicolons, options: options) + testFormatting(for: input, output, rule: .semicolons, options: options) } func testReplaceSemicolonFollowedByComment() { let input = "print(\"hello\"); // comment\nprint(\"goodbye\")" let output = "print(\"hello\") // comment\nprint(\"goodbye\")" let options = FormatOptions(allowInlineSemicolons: true) - testFormatting(for: input, output, rule: FormatRules.semicolons, options: options) + testFormatting(for: input, output, rule: .semicolons, options: options) } func testSemicolonNotReplacedAfterReturn() { let input = "return;\nfoo()" - testFormatting(for: input, rule: FormatRules.semicolons) + testFormatting(for: input, rule: .semicolons) } func testSemicolonReplacedAfterReturnIfEndOfScope() { let input = "do { return; }" let output = "do { return }" - testFormatting(for: input, output, rule: FormatRules.semicolons) + testFormatting(for: input, output, rule: .semicolons) } func testRequiredSemicolonNotRemovedAfterInferredVar() { @@ -7626,7 +7626,7 @@ class RedundancyTests: RulesTests { print(colorScheme) } """ - testFormatting(for: input, rule: FormatRules.semicolons) + testFormatting(for: input, rule: .semicolons) } // MARK: - duplicateImports @@ -7634,53 +7634,53 @@ class RedundancyTests: RulesTests { func testRemoveDuplicateImport() { let input = "import Foundation\nimport Foundation" let output = "import Foundation" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) + testFormatting(for: input, output, rule: .duplicateImports) } func testRemoveDuplicateConditionalImport() { let input = "#if os(iOS)\n import Foo\n import Foo\n#else\n import Bar\n import Bar\n#endif" let output = "#if os(iOS)\n import Foo\n#else\n import Bar\n#endif" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) + testFormatting(for: input, output, rule: .duplicateImports) } func testNoRemoveOverlappingImports() { let input = "import MyModule\nimport MyModule.Private" - testFormatting(for: input, rule: FormatRules.duplicateImports) + testFormatting(for: input, rule: .duplicateImports) } func testNoRemoveCaseDifferingImports() { let input = "import Auth0.Authentication\nimport Auth0.authentication" - testFormatting(for: input, rule: FormatRules.duplicateImports) + testFormatting(for: input, rule: .duplicateImports) } func testRemoveDuplicateImportFunc() { let input = "import func Foo.bar\nimport func Foo.bar" let output = "import func Foo.bar" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) + testFormatting(for: input, output, rule: .duplicateImports) } func testNoRemoveTestableDuplicateImport() { let input = "import Foo\n@testable import Foo" let output = "\n@testable import Foo" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) + testFormatting(for: input, output, rule: .duplicateImports) } func testNoRemoveTestableDuplicateImport2() { let input = "@testable import Foo\nimport Foo" let output = "@testable import Foo" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) + testFormatting(for: input, output, rule: .duplicateImports) } func testNoRemoveExportedDuplicateImport() { let input = "import Foo\n@_exported import Foo" let output = "\n@_exported import Foo" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) + testFormatting(for: input, output, rule: .duplicateImports) } func testNoRemoveExportedDuplicateImport2() { let input = "@_exported import Foo\nimport Foo" let output = "@_exported import Foo" - testFormatting(for: input, output, rule: FormatRules.duplicateImports) + testFormatting(for: input, output, rule: .duplicateImports) } // MARK: - unusedArguments @@ -7690,71 +7690,71 @@ class RedundancyTests: RulesTests { func testUnusedTypedClosureArguments() { let input = "let foo = { (bar: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" let output = "let foo = { (_: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedUntypedClosureArguments() { let input = "let foo = { bar, baz in\n print(\"Hello \\(baz)\")\n}" let output = "let foo = { _, baz in\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testNoRemoveClosureReturnType() { let input = "let foo = { () -> Foo.Bar in baz() }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testNoRemoveClosureThrows() { let input = "let foo = { () throws in }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testNoRemoveClosureTypedThrows() { let input = "let foo = { () throws(Foo) in }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testNoRemoveClosureGenericReturnTypes() { let input = "let foo = { () -> Promise in bar }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testNoRemoveClosureTupleReturnTypes() { let input = "let foo = { () -> (Int, Int) in (5, 6) }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testNoRemoveClosureGenericArgumentTypes() { let input = "let foo = { (_: Foo) in }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testNoRemoveFunctionNameBeforeForLoop() { let input = "{\n func foo() -> Int {}\n for a in b {}\n}" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testClosureTypeInClosureArgumentsIsNotMangled() { let input = "{ (foo: (Int) -> Void) in }" let output = "{ (_: (Int) -> Void) in }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedUnnamedClosureArguments() { let input = "{ (_ foo: Int, _ bar: Int) in }" let output = "{ (_: Int, _: Int) in }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedInoutClosureArgumentsNotMangled() { let input = "{ (foo: inout Foo, bar: inout Bar) in }" let output = "{ (_: inout Foo, _: inout Bar) in }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testMalformedFunctionNotMisidentifiedAsClosure() { let input = "func foo() { bar(5) {} in }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedUsedArguments() { @@ -7765,7 +7765,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedPartUsedArguments() { @@ -7783,7 +7783,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testShadowedParameterUsedInSameGuard() { @@ -7801,7 +7801,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testParameterUsedInForIn() { @@ -7812,7 +7812,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testParameterUsedInWhereClause() { @@ -7823,7 +7823,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testParameterUsedInSwitchCase() { @@ -7835,7 +7835,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testParameterUsedInStringInterpolation() { @@ -7844,7 +7844,7 @@ class RedundancyTests: RulesTests { print("\\(foo)") } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedClosureArgument() { @@ -7854,7 +7854,7 @@ class RedundancyTests: RulesTests { return parser } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty", "propertyType"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty", "propertyType"]) } func testShadowedClosureArgument2() { @@ -7864,7 +7864,7 @@ class RedundancyTests: RulesTests { return input } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) } func testUnusedPropertyWrapperArgument() { @@ -7873,29 +7873,29 @@ class RedundancyTests: RulesTests { Text(note.foobar) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUnusedThrowingClosureArgument() { let input = "foo = { bar throws in \"\" }" let output = "foo = { _ throws in \"\" }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedTypedThrowingClosureArgument() { let input = "foo = { bar throws(Foo) in \"\" }" let output = "foo = { _ throws(Foo) in \"\" }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUsedThrowingClosureArgument() { let input = "let foo = { bar throws in bar + \"\" }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUsedTypedThrowingClosureArgument() { let input = "let foo = { bar throws(Foo) in bar + \"\" }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUnusedTrailingAsyncClosureArgument() { @@ -7909,7 +7909,7 @@ class RedundancyTests: RulesTests { print("No foo") } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedTrailingAsyncClosureArgument2() { @@ -7923,7 +7923,7 @@ class RedundancyTests: RulesTests { "No foo" } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedTrailingAsyncClosureArgument3() { @@ -7937,7 +7937,7 @@ class RedundancyTests: RulesTests { "No foo" } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUsedTrailingAsyncClosureArgument() { @@ -7946,12 +7946,12 @@ class RedundancyTests: RulesTests { "\\(foo)" } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testTrailingAsyncClosureArgumentAlreadyMarkedUnused() { let input = "app.get { _ async in 5 }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUnusedTrailingClosureArgumentCalledAsync() { @@ -7965,7 +7965,7 @@ class RedundancyTests: RulesTests { "No async" } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testClosureArgumentUsedInGuardNotRemoved() { @@ -7979,7 +7979,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testClosureArgumentUsedInIfNotRemoved() { @@ -7993,7 +7993,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } // init @@ -8004,7 +8004,7 @@ class RedundancyTests: RulesTests { let x = sqrt(max(0, m)) / 2 } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUnusedParametersShadowedInTupleAssignment() { @@ -8018,7 +8018,7 @@ class RedundancyTests: RulesTests { let (x, y) = v } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUsedParametersShadowedInAssignmentFromFunctionCall() { @@ -8027,7 +8027,7 @@ class RedundancyTests: RulesTests { let r = max(abs(r), epsilon) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedUsedArgumentInSwitch() { @@ -8039,7 +8039,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testParameterUsedInSwitchCaseAfterShadowing() { @@ -8051,7 +8051,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, + testFormatting(for: input, rule: .unusedArguments, exclude: ["hoistPatternLet"]) } @@ -8060,99 +8060,99 @@ class RedundancyTests: RulesTests { func testMarkUnusedFunctionArgument() { let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" let output = "func foo(bar _: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testMarkUnusedArgumentsInNonVoidFunction() { let input = "func foo(bar: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" let output = "func foo(bar _: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testMarkUnusedArgumentsInThrowsFunction() { let input = "func foo(bar: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" let output = "func foo(bar _: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testMarkUnusedArgumentsInOptionalReturningFunction() { let input = "func foo(bar: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" let output = "func foo(bar _: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testNoMarkUnusedArgumentsInProtocolFunction() { let input = "protocol Foo {\n func foo(bar: Int) -> Int\n var bar: Int { get }\n}" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUnusedUnnamedFunctionArgument() { let input = "func foo(_ foo: Int) {}" let output = "func foo(_: Int) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedInoutFunctionArgumentIsNotMangled() { let input = "func foo(_ foo: inout Foo) {}" let output = "func foo(_: inout Foo) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedInternallyRenamedFunctionArgument() { let input = "func foo(foo bar: Int) {}" let output = "func foo(foo _: Int) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testNoMarkProtocolFunctionArgument() { let input = "func foo(foo bar: Int)\nvar bar: Bool { get }" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testMembersAreNotArguments() { let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(bar.baz)\")\n}" let output = "func foo(bar: Int, baz _: String) {\n print(\"Hello \\(bar.baz)\")\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testLabelsAreNotArguments() { let input = "func foo(bar: Int, baz: String) {\n bar: while true { print(baz) }\n}" let output = "func foo(bar _: Int, baz: String) {\n bar: while true { print(baz) }\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, output, rule: .unusedArguments, exclude: ["wrapLoopBodies"]) } func testDictionaryLiteralsRuinEverything() { let input = "func foo(bar: Int, baz: Int) {\n let quux = [bar: 1, baz: 2]\n}" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testOperatorArgumentsAreUnnamed() { let input = "func == (lhs: Int, rhs: Int) { false }" let output = "func == (_: Int, _: Int) { false }" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedtFailableInitArgumentsAreNotMangled() { let input = "init?(foo: Bar) {}" let output = "init?(foo _: Bar) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testTreatEscapedArgumentsAsUsed() { let input = "func foo(default: Int) -> Int {\n return `default`\n}" - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testPartiallyMarkedUnusedArguments() { let input = "func foo(bar: Bar, baz _: Baz) {}" let output = "func foo(bar _: Bar, baz _: Baz) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testPartiallyMarkedUnusedArguments2() { let input = "func foo(bar _: Bar, baz: Baz) {}" let output = "func foo(bar _: Bar, baz _: Baz) {}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnownedUnsafeNotStripped() { @@ -8165,7 +8165,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedUnusedArguments() { @@ -8181,7 +8181,7 @@ class RedundancyTests: RulesTests { print(bar, baz) } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testShadowedUsedArguments2() { @@ -8194,7 +8194,7 @@ class RedundancyTests: RulesTests { print(form) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedUsedArguments3() { @@ -8207,7 +8207,7 @@ class RedundancyTests: RulesTests { print(locations) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedUsedArguments4() { @@ -8219,7 +8219,7 @@ class RedundancyTests: RulesTests { print(bar) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedUsedArguments5() { @@ -8236,7 +8236,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedUsedArgumentInSwitchCase() { @@ -8253,7 +8253,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, + testFormatting(for: input, rule: .unusedArguments, exclude: ["sortSwitchCases"]) } @@ -8265,7 +8265,7 @@ class RedundancyTests: RulesTests { return bar } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) } func testTryAwaitArgumentNotMarkedUnused() { @@ -8276,7 +8276,7 @@ class RedundancyTests: RulesTests { return bar } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) } func testTypedTryAwaitArgumentNotMarkedUnused() { @@ -8287,7 +8287,7 @@ class RedundancyTests: RulesTests { return bar } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) } func testConditionalIfLetMarkedAsUnused() { @@ -8305,7 +8305,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testConditionAfterIfCaseHoistedLetNotMarkedUnused() { @@ -8321,7 +8321,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: true) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, rule: .unusedArguments, options: options) } func testConditionAfterIfCaseInlineLetNotMarkedUnused2() { @@ -8337,7 +8337,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, rule: .unusedArguments, options: options) } func testConditionAfterIfCaseInlineLetNotMarkedUnused3() { @@ -8354,7 +8354,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, rule: .unusedArguments, options: options) } func testConditionAfterIfCaseInlineLetNotMarkedUnused4() { @@ -8369,7 +8369,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, rule: .unusedArguments, options: options) } func testConditionAfterIfCaseInlineLetNotMarkedUnused5() { @@ -8383,7 +8383,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, rule: .unusedArguments, options: options) } func testSecondConditionAfterTupleMarkedUnused() { @@ -8399,7 +8399,7 @@ class RedundancyTests: RulesTests { print(foo, bar, baz) } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedParamsInTupleAssignment() { @@ -8415,7 +8415,7 @@ class RedundancyTests: RulesTests { print(foo, bar, baz, quux) } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testShadowedIfLetNotMarkedAsUnused() { @@ -8424,7 +8424,7 @@ class RedundancyTests: RulesTests { if let foo = foo, let bar = bar {} } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShorthandIfLetNotMarkedAsUnused() { @@ -8433,7 +8433,7 @@ class RedundancyTests: RulesTests { if let foo, let bar {} } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShorthandLetMarkedAsUnused() { @@ -8447,7 +8447,7 @@ class RedundancyTests: RulesTests { var foo, bar: Int? } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testShadowedClosureNotMarkedUnused() { @@ -8460,7 +8460,7 @@ class RedundancyTests: RulesTests { bar() } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testShadowedClosureMarkedUnused() { @@ -8480,7 +8480,7 @@ class RedundancyTests: RulesTests { bar() } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testViewBuilderAnnotationDoesntBreakUnusedArgDetection() { @@ -8508,7 +8508,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments, + testFormatting(for: input, output, rule: .unusedArguments, exclude: ["braces", "wrapArguments"]) } @@ -8523,7 +8523,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, + testFormatting(for: input, rule: .unusedArguments, exclude: ["trailingCommas"]) } @@ -8542,7 +8542,7 @@ class RedundancyTests: RulesTests { print(string) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUsedConsumingArgument() { @@ -8551,7 +8551,7 @@ class RedundancyTests: RulesTests { file.close() } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["noExplicitOwnership"]) } func testUsedConsumingBorrowingArguments() { @@ -8561,7 +8561,7 @@ class RedundancyTests: RulesTests { borrow(b) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["noExplicitOwnership"]) } func testUnusedConsumingArgument() { @@ -8575,7 +8575,7 @@ class RedundancyTests: RulesTests { print("no-op") } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, output, rule: .unusedArguments, exclude: ["noExplicitOwnership"]) } func testUnusedConsumingBorrowingArguments() { @@ -8589,7 +8589,7 @@ class RedundancyTests: RulesTests { print("no-op") } """ - testFormatting(for: input, output, rule: FormatRules.unusedArguments, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, output, rule: .unusedArguments, exclude: ["noExplicitOwnership"]) } func testFunctionArgumentUsedInGuardNotRemoved() { @@ -8606,7 +8606,7 @@ class RedundancyTests: RulesTests { store.handle(.loadNext) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testFunctionArgumentUsedInGuardNotRemoved2() { @@ -8626,7 +8626,7 @@ class RedundancyTests: RulesTests { return History(firstParameter, secondParameter) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testFunctionArgumentUsedInGuardNotRemoved3() { @@ -8642,7 +8642,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, + testFormatting(for: input, rule: .unusedArguments, exclude: ["wrapArguments", "wrapConditionalBodies", "indent"]) } @@ -8651,7 +8651,7 @@ class RedundancyTests: RulesTests { func testNoMarkFunctionArgument() { let input = "func foo(_ bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" let options = FormatOptions(stripUnusedArguments: .closureOnly) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, rule: .unusedArguments, options: options) } // functions (unnamed-only) @@ -8659,20 +8659,20 @@ class RedundancyTests: RulesTests { func testNoMarkNamedFunctionArgument() { let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, rule: .unusedArguments, options: options) } func testRemoveUnnamedFunctionArgument() { let input = "func foo(_ foo: Int) {}" let output = "func foo(_: Int) {}" let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, output, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, output, rule: .unusedArguments, options: options) } func testNoRemoveInternalFunctionArgumentName() { let input = "func foo(foo bar: Int) {}" let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, rule: FormatRules.unusedArguments, options: options) + testFormatting(for: input, rule: .unusedArguments, options: options) } // init @@ -8680,7 +8680,7 @@ class RedundancyTests: RulesTests { func testMarkUnusedInitArgument() { let input = "init(bar: Int, baz: String) {\n self.baz = baz\n}" let output = "init(bar _: Int, baz: String) {\n self.baz = baz\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } // subscript @@ -8688,19 +8688,19 @@ class RedundancyTests: RulesTests { func testMarkUnusedSubscriptArgument() { let input = "subscript(foo: Int, baz: String) -> String {\n return get(baz)\n}" let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testMarkUnusedUnnamedSubscriptArgument() { let input = "subscript(_ foo: Int, baz: String) -> String {\n return get(baz)\n}" let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testMarkUnusedNamedSubscriptArgument() { let input = "subscript(foo foo: Int, baz: String) -> String {\n return get(baz)\n}" let output = "subscript(foo _: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: FormatRules.unusedArguments) + testFormatting(for: input, output, rule: .unusedArguments) } func testUnusedArgumentWithClosureShadowingParamName() { @@ -8716,7 +8716,7 @@ class RedundancyTests: RulesTests { print(foo) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUnusedArgumentWithConditionalAssignmentShadowingParamName() { @@ -8731,7 +8731,7 @@ class RedundancyTests: RulesTests { print(foo) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUnusedArgumentWithSwitchAssignmentShadowingParamName() { @@ -8747,7 +8747,7 @@ class RedundancyTests: RulesTests { print(foo) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testUnusedArgumentWithConditionalAssignmentNotShadowingParamName() { @@ -8762,7 +8762,7 @@ class RedundancyTests: RulesTests { print(quux) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments) + testFormatting(for: input, rule: .unusedArguments) } func testIssue1694() { @@ -8774,7 +8774,7 @@ class RedundancyTests: RulesTests { self?.configure(update) } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantParens"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantParens"]) } func testIssue1696() { @@ -8787,7 +8787,7 @@ class RedundancyTests: RulesTests { return parameter } """ - testFormatting(for: input, rule: FormatRules.unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) } // MARK: redundantClosure @@ -8811,7 +8811,7 @@ class RedundancyTests: RulesTests { let quux = "quux" """ - testFormatting(for: input, output, rule: FormatRules.redundantClosure) + testFormatting(for: input, output, rule: .redundantClosure) } func testRedundantClosureWithExplicitReturn() { @@ -8838,7 +8838,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.redundantReturn, FormatRules.redundantClosure], + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure], options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) } @@ -8875,7 +8875,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.redundantReturn, FormatRules.redundantClosure]) + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure]) } func testKeepsClosureThatIsNotCalled() { @@ -8883,7 +8883,7 @@ class RedundancyTests: RulesTests { let foo = { "Foo" } """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testKeepsEmptyClosures() { @@ -8892,7 +8892,7 @@ class RedundancyTests: RulesTests { let bar = { /* comment */ }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testRemoveRedundantClosureInMultiLinePropertyDeclaration() { @@ -8906,7 +8906,7 @@ class RedundancyTests: RulesTests { lazy var bar = Bar() """ - testFormatting(for: input, output, rule: FormatRules.redundantClosure, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantClosure, exclude: ["propertyType"]) } func testRemoveRedundantClosureInMultiLinePropertyDeclarationWithString() { @@ -8924,7 +8924,7 @@ class RedundancyTests: RulesTests { """ """# - testFormatting(for: input, [output], rules: [FormatRules.redundantClosure, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.redundantClosure, .indent]) } func testRemoveRedundantClosureInMultiLinePropertyDeclarationInClass() { @@ -8942,8 +8942,8 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.redundantReturn, FormatRules.redundantClosure, - FormatRules.semicolons], exclude: ["propertyType"]) + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure, + .semicolons], exclude: ["propertyType"]) } func testRemoveRedundantClosureInWrappedPropertyDeclaration_beforeFirst() { @@ -8963,7 +8963,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) testFormatting(for: input, [output], - rules: [FormatRules.redundantClosure, FormatRules.wrapArguments], + rules: [.redundantClosure, .wrapArguments], options: options, exclude: ["propertyType"]) } @@ -8982,7 +8982,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) testFormatting(for: input, [output], - rules: [FormatRules.redundantClosure, FormatRules.wrapArguments], + rules: [.redundantClosure, .wrapArguments], options: options, exclude: ["propertyType"]) } @@ -8995,7 +8995,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testRedundantClosureKeepsMultiStatementClosureWithMultipleStatements() { @@ -9006,7 +9006,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testRedundantClosureKeepsClosureWithInToken() { @@ -9016,7 +9016,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testRedundantClosureKeepsMultiStatementClosureOnSameLine() { @@ -9026,7 +9026,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testRedundantClosureRemovesComplexMultilineClosure() { @@ -9046,7 +9046,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.redundantClosure, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.redundantClosure, .indent]) } func testKeepsClosureWithIfStatement() { @@ -9060,7 +9060,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testKeepsClosureWithIfStatementOnSingleLine() { @@ -9070,7 +9070,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure, + testFormatting(for: input, rule: .redundantClosure, exclude: ["wrapConditionalBodies"]) } @@ -9098,7 +9098,7 @@ class RedundancyTests: RulesTests { """ testFormatting(for: input, [output], - rules: [FormatRules.redundantClosure, FormatRules.indent]) + rules: [.redundantClosure, .indent]) } func testKeepsClosureWithSwitchStatement() { @@ -9113,7 +9113,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testKeepsClosureWithIfDirective() { @@ -9127,7 +9127,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testKeepsClosureThatCallsMethodThatReturnsNever() { @@ -9136,7 +9136,7 @@ class RedundancyTests: RulesTests { lazy var bar: String = { return preconditionFailure("no default value has been set") }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure, + testFormatting(for: input, rule: .redundantClosure, exclude: ["redundantReturn"]) } @@ -9151,7 +9151,7 @@ class RedundancyTests: RulesTests { lazy var foo = Foo(handle: { fatalError() }) """ - testFormatting(for: input, output, rule: FormatRules.redundantClosure, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantClosure, exclude: ["propertyType"]) } func testPreservesClosureWithMultipleVoidMethodCalls() { @@ -9163,7 +9163,7 @@ class RedundancyTests: RulesTests { }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testRemovesClosureWithMultipleNestedVoidMethodCalls() { @@ -9185,12 +9185,12 @@ class RedundancyTests: RulesTests { }) """ - testFormatting(for: input, [output], rules: [FormatRules.redundantClosure, FormatRules.indent], exclude: ["redundantType"]) + testFormatting(for: input, [output], rules: [.redundantClosure, .indent], exclude: ["redundantType"]) } func testKeepsClosureThatThrowsError() { let input = "let foo = try bar ?? { throw NSError() }()" - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testKeepsDiscardableResultClosure() { @@ -9202,7 +9202,7 @@ class RedundancyTests: RulesTests { /// would return a String instead. let void: Void = { discardableResult() }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testKeepsDiscardableResultClosure2() { @@ -9214,7 +9214,7 @@ class RedundancyTests: RulesTests { /// would return a String instead. let void: () = { discardableResult() }() """ - testFormatting(for: input, rule: FormatRules.redundantClosure) + testFormatting(for: input, rule: .redundantClosure) } func testRedundantClosureDoesntLeaveStrayTry() { @@ -9236,8 +9236,8 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, - FormatRules.redundantClosure], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) } @@ -9260,8 +9260,8 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, - FormatRules.redundantClosure], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) } @@ -9283,7 +9283,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain() { @@ -9305,7 +9305,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain2() { @@ -9327,7 +9327,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain3() { @@ -9347,7 +9347,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testRedundantClosureDoesRemoveRedundantIfStatementClosureInAssignmentPosition() { @@ -9380,7 +9380,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) } func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInArray() { @@ -9401,7 +9401,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testRedundantClosureRemovesClosureAsReturnTryStatement() { @@ -9428,7 +9428,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) } func testRedundantClosureRemovesClosureAsReturnTryStatement2() { @@ -9455,7 +9455,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) } func testRedundantClosureRemovesClosureAsReturnTryStatement3() { @@ -9482,7 +9482,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) } func testRedundantClosureRemovesClosureAsReturnTryStatement4() { @@ -9509,7 +9509,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) } func testRedundantClosureRemovesClosureAsReturnStatement() { @@ -9536,7 +9536,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.redundantClosure], + testFormatting(for: input, [output], rules: [.redundantClosure], options: options, exclude: ["redundantReturn", "indent"]) } @@ -9564,7 +9564,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["indent"]) } func testClosureNotRemovedAroundIfExpressionInGuard() { @@ -9579,7 +9579,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testClosureNotRemovedInMethodCall() { @@ -9594,7 +9594,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testClosureNotRemovedInMethodCall2() { @@ -9609,7 +9609,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testClosureNotRemovedInMethodCall3() { @@ -9624,7 +9624,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testClosureNotRemovedInMethodCall4() { @@ -9643,7 +9643,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testDoesntRemoveClosureWithIfExpressionConditionalCastInSwift5_9() { @@ -9676,7 +9676,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.redundantClosure, options: options) + testFormatting(for: input, rule: .redundantClosure, options: options) } func testDoesRemoveClosureWithIfExpressionConditionalCastInSwift5_10() { @@ -9715,7 +9715,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) } func testRedundantClosureDoesntBreakBuildWithRedundantReturnRuleDisabled() { @@ -9740,7 +9740,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.redundantClosure, options: options, + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "blankLinesBetweenScopes", "propertyType"]) } @@ -9778,8 +9778,8 @@ class RedundancyTests: RulesTests { let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment, - FormatRules.redundantClosure], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], options: options, exclude: ["indent", "blankLinesBetweenScopes", "wrapMultilineConditionalAssignment", "propertyType"]) @@ -9798,7 +9798,7 @@ class RedundancyTests: RulesTests { let foo: any Foo = DefaultFoo() """ - testFormatting(for: input, output, rule: FormatRules.redundantClosure) + testFormatting(for: input, output, rule: .redundantClosure) } func testRedundantSwitchStatementReturnInFunctionWithMultipleWhereClauses() { @@ -9833,7 +9833,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -9867,7 +9867,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], - rules: [FormatRules.redundantReturn, FormatRules.conditionalAssignment], + rules: [.redundantReturn, .conditionalAssignment], options: options) } @@ -9911,7 +9911,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.redundantOptionalBinding, options: options, exclude: ["elseOnSameLine"]) + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options, exclude: ["elseOnSameLine"]) } func testRemovesMultipleOptionalBindings() { @@ -9936,7 +9936,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.redundantOptionalBinding, options: options) + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options) } func testRemovesMultipleOptionalBindingsOnSeparateLines() { @@ -9977,7 +9977,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(indent: " ", swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.redundantOptionalBinding, options: options) + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options) } func testKeepsRedundantOptionalBeforeSwift5_7() { @@ -9988,7 +9988,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: FormatRules.redundantOptionalBinding, options: options) + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) } func testKeepsNonRedundantOptional() { @@ -9999,7 +9999,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.redundantOptionalBinding, options: options) + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) } func testKeepsOptionalNotEligibleForShorthand() { @@ -10010,7 +10010,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.redundantOptionalBinding, options: options, exclude: ["redundantSelf"]) + testFormatting(for: input, rule: .redundantOptionalBinding, options: options, exclude: ["redundantSelf"]) } func testRedundantSelfAndRedundantOptionalTogether() { @@ -10027,7 +10027,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, [output], rules: [FormatRules.redundantOptionalBinding, FormatRules.redundantSelf], options: options) + testFormatting(for: input, [output], rules: [.redundantOptionalBinding, .redundantSelf], options: options) } func testDoesntRemoveShadowingOutsideOfOptionalBinding() { @@ -10041,7 +10041,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.redundantOptionalBinding, options: options) + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) } // MARK: - redundantInternal @@ -10071,7 +10071,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.redundantInternal) + testFormatting(for: input, output, rule: .redundantInternal) } func testPreserveInternalInNonInternalExtensionExtension() { @@ -10119,12 +10119,12 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.redundantInternal, exclude: ["redundantExtensionACL"]) + testFormatting(for: input, output, rule: .redundantInternal, exclude: ["redundantExtensionACL"]) } func testPreserveInternalImport() { let input = "internal import MyPackage" - testFormatting(for: input, rule: FormatRules.redundantInternal) + testFormatting(for: input, rule: .redundantInternal) } func testPreservesInternalInPublicExtensionWithWhereClause() { @@ -10139,7 +10139,7 @@ class RedundancyTests: RulesTests { func fun2() {} } """ - testFormatting(for: input, rule: FormatRules.redundantInternal) + testFormatting(for: input, rule: .redundantInternal) } // MARK: - noExplicitOwnership @@ -10155,7 +10155,7 @@ class RedundancyTests: RulesTests { func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} """ - testFormatting(for: input, output, rule: FormatRules.noExplicitOwnership, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: ["unusedArguments"]) } func testRemovesOwnershipKeywordsFromClosure() { @@ -10179,7 +10179,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.noExplicitOwnership, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: ["unusedArguments"]) } func testRemovesOwnershipKeywordsFromType() { @@ -10193,7 +10193,7 @@ class RedundancyTests: RulesTests { let borrowing: (Foo) -> Bar """ - testFormatting(for: input, output, rule: FormatRules.noExplicitOwnership) + testFormatting(for: input, output, rule: .noExplicitOwnership) } // MARK: - redundantProperty @@ -10212,7 +10212,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.redundantProperty, exclude: ["redundantReturn"]) + testFormatting(for: input, output, rule: .redundantProperty, exclude: ["redundantReturn"]) } func testRemovesRedundantPropertyWithIfExpression() { @@ -10240,7 +10240,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.redundantProperty, FormatRules.redundantReturn, FormatRules.indent], options: options) + testFormatting(for: input, [output], rules: [.redundantProperty, .redundantReturn, .indent], options: options) } func testRemovesRedundantPropertyWithSwitchExpression() { @@ -10270,7 +10270,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.redundantProperty, FormatRules.redundantReturn, FormatRules.indent], options: options) + testFormatting(for: input, [output], rules: [.conditionalAssignment, .redundantProperty, .redundantReturn, .indent], options: options) } func testRemovesRedundantPropertyWithPreferInferredType() { @@ -10287,7 +10287,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantProperty, FormatRules.redundantInit], exclude: ["redundantReturn"]) + testFormatting(for: input, [output], rules: [.propertyType, .redundantProperty, .redundantInit], exclude: ["redundantReturn"]) } func testRemovesRedundantPropertyWithComments() { @@ -10308,7 +10308,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.redundantProperty, exclude: ["redundantReturn"]) + testFormatting(for: input, output, rule: .redundantProperty, exclude: ["redundantReturn"]) } func testRemovesRedundantPropertyFollowingOtherProperty() { @@ -10327,7 +10327,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.redundantProperty) + testFormatting(for: input, output, rule: .redundantProperty) } func testPreservesPropertyWhereReturnIsNotRedundant() { @@ -10349,7 +10349,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.redundantProperty) + testFormatting(for: input, rule: .redundantProperty) } func testPreservesUnwrapConditionInIfStatement() { @@ -10365,7 +10365,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.redundantProperty) + testFormatting(for: input, rule: .redundantProperty) } // MARK: - redundantTypedThrows @@ -10384,7 +10384,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, output, rule: FormatRules.redundantTypedThrows, options: options) + testFormatting(for: input, output, rule: .redundantTypedThrows, options: options) } func testRemovesRedundantAnyErrorTypeThrows() { @@ -10401,7 +10401,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, output, rule: FormatRules.redundantTypedThrows, options: options) + testFormatting(for: input, output, rule: .redundantTypedThrows, options: options) } func testDontRemovesNonRedundantErrorTypeThrows() { @@ -10416,7 +10416,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, rule: FormatRules.redundantTypedThrows, options: options) + testFormatting(for: input, rule: .redundantTypedThrows, options: options) } // MARK: - unusedPrivateDeclaration @@ -10433,7 +10433,7 @@ class RedundancyTests: RulesTests { var bar = "bar" } """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) } func testRemoveUnusedFilePrivate() { @@ -10448,7 +10448,7 @@ class RedundancyTests: RulesTests { var bar = "bar" } """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) } func testDoNotRemoveUsedFilePrivate() { @@ -10462,7 +10462,7 @@ class RedundancyTests: RulesTests { let localFoo = Foo().foo } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testRemoveMultipleUnusedFilePrivate() { @@ -10478,7 +10478,7 @@ class RedundancyTests: RulesTests { var bar = "bar" } """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) } func testRemoveMixedUsedAndUnusedFilePrivate() { @@ -10503,7 +10503,7 @@ class RedundancyTests: RulesTests { let localFoo = Foo().foo } """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) } func testDoNotRemoveFilePrivateUsedInSameStruct() { @@ -10517,7 +10517,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testRemoveUnusedFilePrivateInNestedStruct() { @@ -10538,7 +10538,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: ["emptyBraces"]) } func testDoNotRemoveFilePrivateUsedInNestedStruct() { @@ -10554,7 +10554,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testRemoveUnusedFileprivateFunction() { @@ -10572,7 +10572,7 @@ class RedundancyTests: RulesTests { var bar = "bar" } """ - testFormatting(for: input, [output], rules: [FormatRules.unusedPrivateDeclaration, FormatRules.blankLinesAtEndOfScope]) + testFormatting(for: input, [output], rules: [.unusedPrivateDeclaration, .blankLinesAtEndOfScope]) } func testDoNotRemoveUnusedFileprivateOperatorDefinition() { @@ -10583,7 +10583,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testRemovePrivateDeclarationButDoNotRemoveUnusedPrivateType() { @@ -10599,7 +10599,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: ["emptyBraces"]) } func testRemovePrivateDeclarationButDoNotRemovePrivateExtension() { @@ -10615,7 +10615,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) } func testRemovesPrivateTypealias() { @@ -10630,7 +10630,7 @@ class RedundancyTests: RulesTests { struct Bar {} } """ - testFormatting(for: input, output, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) } func testDoesntRemoveFileprivateInit() { @@ -10640,7 +10640,7 @@ class RedundancyTests: RulesTests { static let foo = Foo() } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, exclude: ["propertyType"]) + testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: ["propertyType"]) } func testCanDisableUnusedPrivateDeclarationRule() { @@ -10651,7 +10651,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testDoesNotRemovePropertyWrapperPrefixesIfUsed() { @@ -10664,7 +10664,7 @@ class RedundancyTests: RulesTests { @State private var showButton: Bool } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testDoesNotRemoveUnderscoredDeclarationIfUsed() { @@ -10674,7 +10674,7 @@ class RedundancyTests: RulesTests { print(_showButton) } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testDoesNotRemoveBacktickDeclarationIfUsed() { @@ -10686,7 +10686,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testDoesNotRemoveBacktickUsage() { @@ -10698,7 +10698,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, exclude: ["redundantBackticks"]) + testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: ["redundantBackticks"]) } func testDoNotRemovePreservedPrivateDeclarations() { @@ -10708,7 +10708,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(preservedPrivateDeclarations: ["registryAssociation", "hello"]) - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration, options: options) + testFormatting(for: input, rule: .unusedPrivateDeclaration, options: options) } func testDoNotRemoveOverridePrivateMethodDeclarations() { @@ -10719,7 +10719,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testDoNotRemoveOverridePrivatePropertyDeclarations() { @@ -10730,7 +10730,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testDoNotRemoveObjcPrivatePropertyDeclaration() { @@ -10740,7 +10740,7 @@ class RedundancyTests: RulesTests { private var bar = "bar" } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testDoNotRemoveObjcPrivateFunctionDeclaration() { @@ -10750,7 +10750,7 @@ class RedundancyTests: RulesTests { private func doSomething() {} } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } func testDoNotRemoveIBActionPrivateFunctionDeclaration() { @@ -10761,6 +10761,6 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.unusedPrivateDeclaration) + testFormatting(for: input, rule: .unusedPrivateDeclaration) } } diff --git a/Tests/RulesTests+Spacing.swift b/Tests/RulesTests+Spacing.swift index ff55d7b45..0cd384574 100644 --- a/Tests/RulesTests+Spacing.swift +++ b/Tests/RulesTests+Spacing.swift @@ -15,51 +15,51 @@ class SpacingTests: RulesTests { func testSpaceAfterSet() { let input = "private(set)var foo: Int" let output = "private(set) var foo: Int" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenParenAndClass() { let input = "@objc(XYZFoo)class foo" let output = "@objc(XYZFoo) class foo" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenConventionAndBlock() { let input = "@convention(block)() -> Void" let output = "@convention(block) () -> Void" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenConventionAndEscaping() { let input = "@convention(block)@escaping () -> Void" let output = "@convention(block) @escaping () -> Void" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenAutoclosureEscapingAndBlock() { // Swift 2.3 only let input = "@autoclosure(escaping)() -> Void" let output = "@autoclosure(escaping) () -> Void" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenSendableAndBlock() { let input = "@Sendable (Action) -> Void" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testAddSpaceBetweenMainActorAndBlock() { let input = "@MainActor (Action) -> Void" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testAddSpaceBetweenMainActorAndBlock2() { let input = "@MainActor (@MainActor (Action) -> Void) async -> Void" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testAddSpaceBetweenMainActorAndClosureParams() { let input = "{ @MainActor (foo: Int) in foo }" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testSpaceBetweenUncheckedAndSendable() { @@ -68,248 +68,248 @@ class SpacingTests: RulesTests { case bar } """ - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testSpaceBetweenParenAndAs() { let input = "(foo.bar) as? String" - testFormatting(for: input, rule: FormatRules.spaceAroundParens, exclude: ["redundantParens"]) + testFormatting(for: input, rule: .spaceAroundParens, exclude: ["redundantParens"]) } func testNoSpaceAfterParenAtEndOfFile() { let input = "(foo.bar)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens, exclude: ["redundantParens"]) + testFormatting(for: input, rule: .spaceAroundParens, exclude: ["redundantParens"]) } func testSpaceBetweenParenAndFoo() { let input = "func foo ()" let output = "func foo()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testSpaceBetweenParenAndAny() { let input = "func any ()" let output = "func any()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testSpaceBetweenParenAndAnyType() { let input = "let foo: any(A & B).Type" let output = "let foo: any (A & B).Type" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testSpaceBetweenParenAndSomeType() { let input = "func foo() -> some(A & B).Type" let output = "func foo() -> some (A & B).Type" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testNoSpaceBetweenParenAndInit() { let input = "init ()" let output = "init()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testNoSpaceBetweenObjcAndSelector() { let input = "@objc (XYZFoo) class foo" let output = "@objc(XYZFoo) class foo" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testNoSpaceBetweenHashSelectorAndBrace() { let input = "#selector(foo)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testNoSpaceBetweenHashKeyPathAndBrace() { let input = "#keyPath (foo.bar)" let output = "#keyPath(foo.bar)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testNoSpaceBetweenHashAvailableAndBrace() { let input = "#available (iOS 9.0, *)" let output = "#available(iOS 9.0, *)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testNoSpaceBetweenPrivateAndSet() { let input = "private (set) var foo: Int" let output = "private(set) var foo: Int" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testSpaceBetweenLetAndTuple() { let input = "if let (foo, bar) = baz {}" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testSpaceBetweenIfAndCondition() { let input = "if(a || b) == true {}" let output = "if (a || b) == true {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testNoSpaceBetweenArrayLiteralAndParen() { let input = "[String] ()" let output = "[String]()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenCaptureListAndArguments() { let input = "{ [weak self](foo) in print(foo) }" let output = "{ [weak self] (foo) in print(foo) }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantParens"]) } func testAddSpaceBetweenCaptureListAndArguments2() { let input = "{ [weak self]() -> Void in }" let output = "{ [weak self] () -> Void in }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantVoidReturnType"]) } func testAddSpaceBetweenCaptureListAndArguments3() { let input = "{ [weak self]() throws -> Void in }" let output = "{ [weak self] () throws -> Void in }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantVoidReturnType"]) } func testAddSpaceBetweenCaptureListAndArguments4() { let input = "{ [weak self](foo: @escaping(Bar?) -> Void) -> Baz? in foo }" let output = "{ [weak self] (foo: @escaping (Bar?) -> Void) -> Baz? in foo }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenCaptureListAndArguments5() { let input = "{ [weak self](foo: @autoclosure() -> String) -> Baz? in foo() }" let output = "{ [weak self] (foo: @autoclosure () -> String) -> Baz? in foo() }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenCaptureListAndArguments6() { let input = "{ [weak self](foo: @Sendable() -> String) -> Baz? in foo() }" let output = "{ [weak self] (foo: @Sendable () -> String) -> Baz? in foo() }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenCaptureListAndArguments7() { let input = "Foo(0) { [weak self]() -> Void in }" let output = "Foo(0) { [weak self] () -> Void in }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantVoidReturnType"]) } func testAddSpaceBetweenCaptureListAndArguments8() { let input = "{ [weak self]() throws(Foo) -> Void in }" let output = "{ [weak self] () throws(Foo) -> Void in }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantVoidReturnType"]) } func testAddSpaceBetweenEscapingAndParenthesizedClosure() { let input = "@escaping(() -> Void)" let output = "@escaping (() -> Void)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenAutoclosureAndParenthesizedClosure() { let input = "@autoclosure(() -> String)" let output = "@autoclosure (() -> String)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testSpaceBetweenClosingParenAndOpenBrace() { let input = "func foo(){ foo }" let output = "func foo() { foo }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testNoSpaceBetweenClosingBraceAndParens() { let input = "{ block } ()" let output = "{ block }()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, exclude: ["redundantClosure"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantClosure"]) } func testDontRemoveSpaceBetweenOpeningBraceAndParens() { let input = "a = (b + c)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens, + testFormatting(for: input, rule: .spaceAroundParens, exclude: ["redundantParens"]) } func testKeywordAsIdentifierParensSpacing() { let input = "if foo.let (foo, bar) {}" let output = "if foo.let(foo, bar) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testSpaceAfterInoutParam() { let input = "func foo(bar _: inout(Int, String)) {}" let output = "func foo(bar _: inout (Int, String)) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testSpaceAfterEscapingAttribute() { let input = "func foo(bar: @escaping() -> Void)" let output = "func foo(bar: @escaping () -> Void)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testSpaceAfterAutoclosureAttribute() { let input = "func foo(bar: @autoclosure () -> Void)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testSpaceAfterSendableAttribute() { let input = "func foo(bar: @Sendable () -> Void)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testSpaceBeforeTupleIndexArgument() { let input = "foo.1 (true)" let output = "foo.1(true)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testRemoveSpaceBetweenParenAndBracket() { let input = "let foo = bar[5] ()" let output = "let foo = bar[5]()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testRemoveSpaceBetweenParenAndBracketInsideClosure() { let input = "let foo = bar { [Int] () }" let output = "let foo = bar { [Int]() }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenParenAndCaptureList() { let input = "let foo = bar { [self](foo: Int) in foo }" let output = "let foo = bar { [self] (foo: Int) in foo }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenParenAndAwait() { let input = "let foo = await(bar: 5)" let output = "let foo = await (bar: 5)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenParenAndAwaitForSwift5_5() { let input = "let foo = await(bar: 5)" let output = "let foo = await (bar: 5)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, + testFormatting(for: input, output, rule: .spaceAroundParens, options: FormatOptions(swiftVersion: "5.5")) } func testNoAddSpaceBetweenParenAndAwaitForSwiftLessThan5_5() { let input = "let foo = await(bar: 5)" - testFormatting(for: input, rule: FormatRules.spaceAroundParens, + testFormatting(for: input, rule: .spaceAroundParens, options: FormatOptions(swiftVersion: "5.4.9")) } func testRemoveSpaceBetweenParenAndConsume() { let input = "let foo = consume (bar)" let output = "let foo = consume(bar)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testNoAddSpaceBetweenParenAndAvailableAfterFunc() { @@ -319,32 +319,32 @@ class SpacingTests: RulesTests { @available(macOS 10.13, *) func bar() """ - testFormatting(for: input, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, rule: .spaceAroundParens) } func testNoAddSpaceAroundTypedThrowsFunctionType() { let input = "func foo() throws (Bar) -> Baz {}" let output = "func foo() throws(Bar) -> Baz {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenParenAndBorrowing() { let input = "func foo(_: borrowing(any Foo)) {}" let output = "func foo(_: borrowing (any Foo)) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens, + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["noExplicitOwnership"]) } func testAddSpaceBetweenParenAndIsolated() { let input = "func foo(isolation _: isolated(any Actor)) {}" let output = "func foo(isolation _: isolated (any Actor)) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } func testAddSpaceBetweenParenAndSending() { let input = "func foo(_: sending(any Foo)) {}" let output = "func foo(_: sending (any Foo)) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundParens) + testFormatting(for: input, output, rule: .spaceAroundParens) } // MARK: - spaceInsideParens @@ -352,103 +352,103 @@ class SpacingTests: RulesTests { func testSpaceInsideParens() { let input = "( 1, ( 2, 3 ) )" let output = "(1, (2, 3))" - testFormatting(for: input, output, rule: FormatRules.spaceInsideParens) + testFormatting(for: input, output, rule: .spaceInsideParens) } func testSpaceBeforeCommentInsideParens() { let input = "( /* foo */ 1, 2 )" let output = "( /* foo */ 1, 2)" - testFormatting(for: input, output, rule: FormatRules.spaceInsideParens) + testFormatting(for: input, output, rule: .spaceInsideParens) } // MARK: - spaceAroundBrackets func testSubscriptNoAddSpacing() { let input = "foo[bar] = baz" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, rule: .spaceAroundBrackets) } func testSubscriptRemoveSpacing() { let input = "foo [bar] = baz" let output = "foo[bar] = baz" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } func testArrayLiteralSpacing() { let input = "foo = [bar, baz]" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, rule: .spaceAroundBrackets) } func testAsArrayCastingSpacing() { let input = "foo as[String]" let output = "foo as [String]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } func testAsOptionalArrayCastingSpacing() { let input = "foo as? [String]" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, rule: .spaceAroundBrackets) } func testIsArrayTestingSpacing() { let input = "if foo is[String] {}" let output = "if foo is [String] {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } func testKeywordAsIdentifierBracketSpacing() { let input = "if foo.is[String] {}" - testFormatting(for: input, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, rule: .spaceAroundBrackets) } func testSpaceBeforeTupleIndexSubscript() { let input = "foo.1 [2]" let output = "foo.1[2]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } func testRemoveSpaceBetweenBracketAndParen() { let input = "let foo = bar[5] ()" let output = "let foo = bar[5]()" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } func testRemoveSpaceBetweenBracketAndParenInsideClosure() { let input = "let foo = bar { [Int] () }" let output = "let foo = bar { [Int]() }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } func testAddSpaceBetweenCaptureListAndParen() { let input = "let foo = bar { [self](foo: Int) in foo }" let output = "let foo = bar { [self] (foo: Int) in foo }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } func testAddSpaceBetweenInoutAndStringArray() { let input = "func foo(arg _: inout[String]) {}" let output = "func foo(arg _: inout [String]) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } func testAddSpaceBetweenConsumingAndStringArray() { let input = "func foo(arg _: consuming[String]) {}" let output = "func foo(arg _: consuming [String]) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets, + testFormatting(for: input, output, rule: .spaceAroundBrackets, exclude: ["noExplicitOwnership"]) } func testAddSpaceBetweenBorrowingAndStringArray() { let input = "func foo(arg _: borrowing[String]) {}" let output = "func foo(arg _: borrowing [String]) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets, + testFormatting(for: input, output, rule: .spaceAroundBrackets, exclude: ["noExplicitOwnership"]) } func testAddSpaceBetweenSendingAndStringArray() { let input = "func foo(arg _: sending[String]) {}" let output = "func foo(arg _: sending [String]) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBrackets) + testFormatting(for: input, output, rule: .spaceAroundBrackets) } // MARK: - spaceInsideBrackets @@ -456,20 +456,20 @@ class SpacingTests: RulesTests { func testSpaceInsideBrackets() { let input = "foo[ 5 ]" let output = "foo[5]" - testFormatting(for: input, output, rule: FormatRules.spaceInsideBrackets) + testFormatting(for: input, output, rule: .spaceInsideBrackets) } func testSpaceInsideWrappedArray() { let input = "[ foo,\n bar ]" let output = "[foo,\n bar]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: FormatRules.spaceInsideBrackets, options: options) + testFormatting(for: input, output, rule: .spaceInsideBrackets, options: options) } func testSpaceBeforeCommentInsideWrappedArray() { let input = "[ // foo\n bar,\n]" let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, rule: FormatRules.spaceInsideBrackets, options: options) + testFormatting(for: input, rule: .spaceInsideBrackets, options: options) } // MARK: - spaceAroundBraces @@ -477,53 +477,53 @@ class SpacingTests: RulesTests { func testSpaceAroundTrailingClosure() { let input = "if x{ y }else{ z }" let output = "if x { y } else { z }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces, + testFormatting(for: input, output, rule: .spaceAroundBraces, exclude: ["wrapConditionalBodies"]) } func testNoSpaceAroundClosureInsiderParens() { let input = "foo({ $0 == 5 })" - testFormatting(for: input, rule: FormatRules.spaceAroundBraces, + testFormatting(for: input, rule: .spaceAroundBraces, exclude: ["trailingClosures"]) } func testNoExtraSpaceAroundBracesAtStartOrEndOfFile() { let input = "{ foo }" - testFormatting(for: input, rule: FormatRules.spaceAroundBraces) + testFormatting(for: input, rule: .spaceAroundBraces) } func testNoSpaceAfterPrefixOperator() { let input = "let foo = ..{ bar }" - testFormatting(for: input, rule: FormatRules.spaceAroundBraces) + testFormatting(for: input, rule: .spaceAroundBraces) } func testNoSpaceBeforePostfixOperator() { let input = "let foo = { bar }.." - testFormatting(for: input, rule: FormatRules.spaceAroundBraces) + testFormatting(for: input, rule: .spaceAroundBraces) } func testSpaceAroundBracesAfterOptionalProperty() { let input = "var: Foo?{}" let output = "var: Foo? {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces) + testFormatting(for: input, output, rule: .spaceAroundBraces) } func testSpaceAroundBracesAfterImplicitlyUnwrappedProperty() { let input = "var: Foo!{}" let output = "var: Foo! {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces) + testFormatting(for: input, output, rule: .spaceAroundBraces) } func testSpaceAroundBracesAfterNumber() { let input = "if x = 5{}" let output = "if x = 5 {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces) + testFormatting(for: input, output, rule: .spaceAroundBraces) } func testSpaceAroundBracesAfterString() { let input = "if x = \"\"{}" let output = "if x = \"\" {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundBraces) + testFormatting(for: input, output, rule: .spaceAroundBraces) } // MARK: - spaceInsideBraces @@ -531,22 +531,22 @@ class SpacingTests: RulesTests { func testSpaceInsideBraces() { let input = "foo({bar})" let output = "foo({ bar })" - testFormatting(for: input, output, rule: FormatRules.spaceInsideBraces, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .spaceInsideBraces, exclude: ["trailingClosures"]) } func testNoExtraSpaceInsidebraces() { let input = "{ foo }" - testFormatting(for: input, rule: FormatRules.spaceInsideBraces, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .spaceInsideBraces, exclude: ["trailingClosures"]) } func testNoSpaceAddedInsideEmptybraces() { let input = "foo({})" - testFormatting(for: input, rule: FormatRules.spaceInsideBraces, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .spaceInsideBraces, exclude: ["trailingClosures"]) } func testNoSpaceAddedBetweenDoublebraces() { let input = "func foo() -> () -> Void {{ bar() }}" - testFormatting(for: input, rule: FormatRules.spaceInsideBraces) + testFormatting(for: input, rule: .spaceInsideBraces) } // MARK: - spaceAroundGenerics @@ -554,17 +554,17 @@ class SpacingTests: RulesTests { func testSpaceAroundGenerics() { let input = "Foo >" let output = "Foo>" - testFormatting(for: input, output, rule: FormatRules.spaceAroundGenerics) + testFormatting(for: input, output, rule: .spaceAroundGenerics) } func testSpaceAroundGenericsFollowedByAndOperator() { let input = "if foo is Foo && baz {}" - testFormatting(for: input, rule: FormatRules.spaceAroundGenerics, exclude: ["andOperator"]) + testFormatting(for: input, rule: .spaceAroundGenerics, exclude: ["andOperator"]) } func testSpaceAroundGenericResultBuilder() { let input = "func foo(@SomeResultBuilder builder: () -> Void) {}" - testFormatting(for: input, rule: FormatRules.spaceAroundGenerics) + testFormatting(for: input, rule: .spaceAroundGenerics) } // MARK: - spaceInsideGenerics @@ -572,7 +572,7 @@ class SpacingTests: RulesTests { func testSpaceInsideGenerics() { let input = "Foo< Bar< Baz > >" let output = "Foo>" - testFormatting(for: input, output, rule: FormatRules.spaceInsideGenerics) + testFormatting(for: input, output, rule: .spaceInsideGenerics) } // MARK: - spaceAroundOperators @@ -580,417 +580,417 @@ class SpacingTests: RulesTests { func testSpaceAfterColon() { let input = "let foo:Bar = 5" let output = "let foo: Bar = 5" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpaceBetweenOptionalAndDefaultValue() { let input = "let foo: String?=nil" let output = "let foo: String? = nil" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpaceBetweenImplictlyUnwrappedOptionalAndDefaultValue() { let input = "let foo: String!=nil" let output = "let foo: String! = nil" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpacePreservedBetweenOptionalTryAndDot() { let input = "let foo: Int = try? .init()" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, rule: .spaceAroundOperators) } func testSpacePreservedBetweenForceTryAndDot() { let input = "let foo: Int = try! .init()" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, rule: .spaceAroundOperators) } func testSpaceBetweenOptionalAndDefaultValueInFunction() { let input = "func foo(bar _: String?=nil) {}" let output = "func foo(bar _: String? = nil) {}" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testNoSpaceAddedAfterColonInSelector() { let input = "@objc(foo:bar:)" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, rule: .spaceAroundOperators) } func testSpaceAfterColonInSwitchCase() { let input = "switch x { case .y:break }" let output = "switch x { case .y: break }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpaceAfterColonInSwitchDefault() { let input = "switch x { default:break }" let output = "switch x { default: break }" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpaceAfterComma() { let input = "let foo = [1,2,3]" let output = "let foo = [1, 2, 3]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpaceBetweenColonAndEnumValue() { let input = "[.Foo:.Bar]" let output = "[.Foo: .Bar]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpaceBetweenCommaAndEnumValue() { let input = "[.Foo,.Bar]" let output = "[.Foo, .Bar]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testNoRemoveSpaceAroundEnumInBrackets() { let input = "[ .red ]" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators, + testFormatting(for: input, rule: .spaceAroundOperators, exclude: ["spaceInsideBrackets"]) } func testSpaceBetweenSemicolonAndEnumValue() { let input = "statement;.Bar" let output = "statement; .Bar" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpacePreservedBetweenEqualsAndEnumValue() { let input = "foo = .Bar" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, rule: .spaceAroundOperators) } func testNoSpaceBeforeColon() { let input = "let foo : Bar = 5" let output = "let foo: Bar = 5" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpacePreservedBeforeColonInTernary() { let input = "foo ? bar : baz" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, rule: .spaceAroundOperators) } func testSpacePreservedAroundEnumValuesInTernary() { let input = "foo ? .Bar : .Baz" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, rule: .spaceAroundOperators) } func testSpaceBeforeColonInNestedTernary() { let input = "foo ? (hello + a ? b: c) : baz" let output = "foo ? (hello + a ? b : c) : baz" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testNoSpaceBeforeComma() { let input = "let foo = [1 , 2 , 3]" let output = "let foo = [1, 2, 3]" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testSpaceAtStartOfLine() { let input = "print(foo\n ,bar)" let output = "print(foo\n , bar)" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators, + testFormatting(for: input, output, rule: .spaceAroundOperators, exclude: ["leadingDelimiters"]) } func testSpaceAroundInfixMinus() { let input = "foo-bar" let output = "foo - bar" - testFormatting(for: input, output, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, output, rule: .spaceAroundOperators) } func testNoSpaceAroundPrefixMinus() { let input = "foo + -bar" - testFormatting(for: input, rule: FormatRules.spaceAroundOperators) + testFormatting(for: input, rule: .spaceAroundOperators) } func testSpaceAroundLessThan() { let input = "foo () print(foo) """ - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testParensRemovedAroundVoid() { let input = "() -> (Void)" let output = "() -> Void" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testVoidArgumentConvertedToEmptyParens() { let input = "Void -> Void" let output = "() -> Void" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testVoidArgumentInParensNotConvertedToEmptyParens() { let input = "(Void) -> Void" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testAnonymousVoidArgumentNotConvertedToEmptyParens() { let input = "{ (_: Void) -> Void in }" - testFormatting(for: input, rule: FormatRules.void, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, rule: .void, exclude: ["redundantVoidReturnType"]) } func testFuncWithAnonymousVoidArgumentNotStripped() { let input = "func foo(_: Void) -> Void" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testFunctionThatReturnsAFunction() { let input = "(Void) -> Void -> ()" let output = "(Void) -> () -> Void" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testFunctionThatReturnsAFunctionThatThrows() { let input = "(Void) -> Void throws -> ()" let output = "(Void) -> () throws -> Void" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testFunctionThatReturnsAFunctionThatHasTypedThrows() { let input = "(Void) -> Void throws(Foo) -> ()" let output = "(Void) -> () throws(Foo) -> Void" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testChainOfFunctionsIsNotChanged() { let input = "() -> () -> () -> Void" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testChainOfFunctionsWithThrowsIsNotChanged() { let input = "() -> () throws -> () throws -> Void" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testChainOfFunctionsWithTypedThrowsIsNotChanged() { let input = "() -> () throws(Foo) -> () throws(Foo) -> Void" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testVoidThrowsIsNotMangled() { let input = "(Void) throws -> Void" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testVoidTypedThrowsIsNotMangled() { let input = "(Void) throws(Foo) -> Void" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testEmptyClosureArgsNotMangled() { let input = "{ () in }" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testEmptyClosureReturnValueConvertedToVoid() { let input = "{ () -> () in }" let output = "{ () -> Void in }" - testFormatting(for: input, output, rule: FormatRules.void, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .void, exclude: ["redundantVoidReturnType"]) } func testAnonymousVoidClosureNotChanged() { let input = "{ (_: Void) in }" - testFormatting(for: input, rule: FormatRules.void, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .void, exclude: ["unusedArguments"]) } func testVoidLiteralConvertedToParens() { let input = "foo(Void())" let output = "foo(())" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testVoidLiteralConvertedToParens2() { let input = "let foo = Void()" let output = "let foo = ()" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testVoidLiteralReturnValueConvertedToParens() { @@ -287,35 +287,35 @@ class SyntaxTests: RulesTests { return () } """ - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testVoidLiteralReturnValueConvertedToParens2() { let input = "{ _ in Void() }" let output = "{ _ in () }" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testNamespacedVoidLiteralNotConverted() { // TODO: it should actually be safe to convert Swift.Void - only unsafe for other namespaces let input = "let foo = Swift.Void()" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testMalformedFuncDoesNotCauseInvalidOutput() throws { let input = "func baz(Void) {}" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testEmptyParensInGenericsConvertedToVoid() { let input = "Foo<(), ()>" let output = "Foo" - testFormatting(for: input, output, rule: FormatRules.void) + testFormatting(for: input, output, rule: .void) } func testCaseVoidNotUnwrapped() { let input = "case some(Void)" - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testLocalVoidTypeNotConverted() { @@ -324,7 +324,7 @@ class SyntaxTests: RulesTests { let foo = Void() print(foo) """ - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testLocalVoidTypeForwardReferenceNotConverted() { @@ -333,7 +333,7 @@ class SyntaxTests: RulesTests { print(foo) struct Void {} """ - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testLocalVoidTypealiasNotConverted() { @@ -342,7 +342,7 @@ class SyntaxTests: RulesTests { let foo = Void() print(foo) """ - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } func testLocalVoidTypealiasForwardReferenceNotConverted() { @@ -351,7 +351,7 @@ class SyntaxTests: RulesTests { print(foo) typealias Void = MyVoid """ - testFormatting(for: input, rule: FormatRules.void) + testFormatting(for: input, rule: .void) } // useVoid = false @@ -360,45 +360,45 @@ class SyntaxTests: RulesTests { let input = "(Void) -> Void" let output = "(()) -> ()" let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: FormatRules.void, options: options) + testFormatting(for: input, output, rule: .void, options: options) } func testNamespacedVoidNotConverted() { let input = "() -> Swift.Void" let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: FormatRules.void, options: options) + testFormatting(for: input, rule: .void, options: options) } func testTypealiasVoidNotConverted() { let input = "public typealias Void = ()" let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: FormatRules.void, options: options) + testFormatting(for: input, rule: .void, options: options) } func testVoidClosureReturnValueConvertedToEmptyTuple() { let input = "{ () -> Void in }" let output = "{ () -> () in }" let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: FormatRules.void, options: options, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .void, options: options, exclude: ["redundantVoidReturnType"]) } func testNoConvertVoidSelfToTuple() { let input = "Void.self" let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: FormatRules.void, options: options) + testFormatting(for: input, rule: .void, options: options) } func testNoConvertVoidTypeToTuple() { let input = "Void.Type" let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: FormatRules.void, options: options) + testFormatting(for: input, rule: .void, options: options) } func testCaseVoidConvertedToTuple() { let input = "case some(Void)" let output = "case some(())" let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: FormatRules.void, options: options) + testFormatting(for: input, output, rule: .void, options: options) } // MARK: - trailingClosures @@ -406,55 +406,55 @@ class SyntaxTests: RulesTests { func testAnonymousClosureArgumentMadeTrailing() { let input = "foo(foo: 5, { /* some code */ })" let output = "foo(foo: 5) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testNamedClosureArgumentNotMadeTrailing() { let input = "foo(foo: 5, bar: { /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testClosureArgumentPassedToFunctionInArgumentsNotMadeTrailing() { let input = "foo(bar { /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testClosureArgumentInFunctionWithOtherClosureArgumentsNotMadeTrailing() { let input = "foo(foo: { /* some code */ }, { /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testClosureArgumentInExpressionNotMadeTrailing() { let input = "if let foo = foo(foo: 5, { /* some code */ }) {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testClosureArgumentInCompoundExpressionNotMadeTrailing() { let input = "if let foo = foo(foo: 5, { /* some code */ }), let bar = bar(bar: 2, { /* some code */ }) {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testClosureArgumentAfterLinebreakInGuardNotMadeTrailing() { let input = "guard let foo =\n bar({ /* some code */ })\nelse { return }" - testFormatting(for: input, rule: FormatRules.trailingClosures, + testFormatting(for: input, rule: .trailingClosures, exclude: ["wrapConditionalBodies"]) } func testClosureMadeTrailingForNumericTupleMember() { let input = "foo.1(5, { bar })" let output = "foo.1(5) { bar }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testNoRemoveParensAroundClosureFollowedByOpeningBrace() { let input = "foo({ bar }) { baz }" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testRemoveParensAroundClosureWithInnerSpacesFollowedByUnwrapOperator() { let input = "foo( { bar } )?.baz" let output = "foo { bar }?.baz" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } // solitary argument @@ -462,67 +462,67 @@ class SyntaxTests: RulesTests { func testParensAroundSolitaryClosureArgumentRemoved() { let input = "foo({ /* some code */ })" let output = "foo { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testParensAroundNamedSolitaryClosureArgumentNotRemoved() { let input = "foo(foo: { /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testParensAroundSolitaryClosureArgumentInExpressionNotRemoved() { let input = "if let foo = foo({ /* some code */ }) {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testParensAroundSolitaryClosureArgumentInCompoundExpressionNotRemoved() { let input = "if let foo = foo({ /* some code */ }), let bar = bar({ /* some code */ }) {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testParensAroundOptionalTrailingClosureInForLoopNotRemoved() { let input = "for foo in bar?.map({ $0.baz }) ?? [] {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testParensAroundTrailingClosureInGuardCaseLetNotRemoved() { let input = "guard case let .foo(bar) = baz.filter({ $0 == quux }).isEmpty else {}" - testFormatting(for: input, rule: FormatRules.trailingClosures, + testFormatting(for: input, rule: .trailingClosures, exclude: ["wrapConditionalBodies"]) } func testParensAroundTrailingClosureInWhereClauseLetNotRemoved() { let input = "for foo in bar where baz.filter({ $0 == quux }).isEmpty {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testParensAroundTrailingClosureInSwitchNotRemoved() { let input = "switch foo({ $0 == bar }).count {}" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testSolitaryClosureMadeTrailingInChain() { let input = "foo.map({ $0.path }).joined()" let output = "foo.map { $0.path }.joined()" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testSpaceNotInsertedAfterClosureBeforeUnwrap() { let input = "let foo = bar.map({ foo($0) })?.baz" let output = "let foo = bar.map { foo($0) }?.baz" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testSpaceNotInsertedAfterClosureBeforeForceUnwrap() { let input = "let foo = bar.map({ foo($0) })!.baz" let output = "let foo = bar.map { foo($0) }!.baz" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testSolitaryClosureMadeTrailingForNumericTupleMember() { let input = "foo.1({ bar })" let output = "foo.1 { bar }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } // dispatch methods @@ -530,38 +530,38 @@ class SyntaxTests: RulesTests { func testDispatchAsyncClosureArgumentMadeTrailing() { let input = "queue.async(execute: { /* some code */ })" let output = "queue.async { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testDispatchAsyncGroupClosureArgumentMadeTrailing() { // TODO: async(group: , qos: , flags: , execute: ) let input = "queue.async(group: g, execute: { /* some code */ })" let output = "queue.async(group: g) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testDispatchAsyncAfterClosureArgumentMadeTrailing() { let input = "queue.asyncAfter(deadline: t, execute: { /* some code */ })" let output = "queue.asyncAfter(deadline: t) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testDispatchAsyncAfterWallClosureArgumentMadeTrailing() { let input = "queue.asyncAfter(wallDeadline: t, execute: { /* some code */ })" let output = "queue.asyncAfter(wallDeadline: t) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testDispatchSyncClosureArgumentMadeTrailing() { let input = "queue.sync(execute: { /* some code */ })" let output = "queue.sync { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } func testDispatchSyncFlagsClosureArgumentMadeTrailing() { let input = "queue.sync(flags: f, execute: { /* some code */ })" let output = "queue.sync(flags: f) { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } // autoreleasepool @@ -569,7 +569,7 @@ class SyntaxTests: RulesTests { func testAutoreleasepoolMadeTrailing() { let input = "autoreleasepool(invoking: { /* some code */ })" let output = "autoreleasepool { /* some code */ }" - testFormatting(for: input, output, rule: FormatRules.trailingClosures) + testFormatting(for: input, output, rule: .trailingClosures) } // explicit trailing closure methods @@ -578,25 +578,25 @@ class SyntaxTests: RulesTests { let input = "foo(bar: 1, baz: { /* some code */ })" let output = "foo(bar: 1) { /* some code */ }" let options = FormatOptions(trailingClosures: ["foo"]) - testFormatting(for: input, output, rule: FormatRules.trailingClosures, options: options) + testFormatting(for: input, output, rule: .trailingClosures, options: options) } // explicit non-trailing closure methods func testPerformBatchUpdatesNotMadeTrailing() { let input = "collectionView.performBatchUpdates({ /* some code */ })" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testNimbleExpectNotMadeTrailing() { let input = "expect({ bar }).to(beNil())" - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } func testCustomMethodNotMadeTrailing() { let input = "foo({ /* some code */ })" let options = FormatOptions(neverTrailing: ["foo"]) - testFormatting(for: input, rule: FormatRules.trailingClosures, options: options) + testFormatting(for: input, rule: .trailingClosures, options: options) } // multiple closures @@ -617,7 +617,7 @@ class SyntaxTests: RulesTests { """, count: repeatCount)) } } """ - testFormatting(for: input, rule: FormatRules.trailingClosures) + testFormatting(for: input, rule: .trailingClosures) } // MARK: - enumNamespaces @@ -628,12 +628,12 @@ class SyntaxTests: RulesTests { @objc static var expressionTypes: [String: RuntimeType] { get } } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesConformingOtherType() { let input = "private final class CustomUITableViewCell: UITableViewCell {}" - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesImportClass() { @@ -644,7 +644,7 @@ class SyntaxTests: RulesTests { static var bar: String } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesImportStruct() { @@ -655,7 +655,7 @@ class SyntaxTests: RulesTests { static var bar: String } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesClassFunction() { @@ -664,7 +664,7 @@ class SyntaxTests: RulesTests { class func bar() {} } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesRemovingExtraKeywords() { @@ -678,7 +678,7 @@ class SyntaxTests: RulesTests { static let bar = "bar" } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesNestedTypes() { @@ -698,7 +698,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesNestedTypes2() { @@ -718,7 +718,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesNestedTypes3() { @@ -738,7 +738,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesNestedTypes4() { @@ -760,7 +760,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesNestedTypes5() { @@ -778,7 +778,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesStaticVariable() { @@ -792,7 +792,7 @@ class SyntaxTests: RulesTests { static let β = 0, 5 } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesStaticAndInstanceVariable() { @@ -802,7 +802,7 @@ class SyntaxTests: RulesTests { let Ɣ = 0, 3 } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesStaticFunction() { @@ -820,7 +820,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesStaticAndInstanceFunction() { @@ -836,7 +836,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespaceDoesNothing() { @@ -849,14 +849,14 @@ class SyntaxTests: RulesTests { #endif } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespaceDoesNothingForEmptyDeclaration() { let input = """ struct Foo {} """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesDoesNothingIfTypeInitializedInternally() { @@ -867,7 +867,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesDoesNothingIfSelfInitializedInternally() { @@ -878,7 +878,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesDoesNothingIfSelfInitializedInternally2() { @@ -889,7 +889,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesDoesNothingIfSelfAssignedInternally() { @@ -900,7 +900,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesDoesNothingIfSelfAssignedInternally2() { @@ -911,7 +911,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesDoesNothingIfSelfAssignedInternally3() { @@ -922,7 +922,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testClassFuncNotReplacedByEnum() { @@ -933,7 +933,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces, + testFormatting(for: input, rule: .enumNamespaces, exclude: ["modifierOrder"]) } @@ -943,7 +943,7 @@ class SyntaxTests: RulesTests { public static let bar = "bar" } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testClassNotReplacedByEnum() { @@ -953,7 +953,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(enumNamespaces: .structsOnly) - testFormatting(for: input, rule: FormatRules.enumNamespaces, options: options) + testFormatting(for: input, rule: .enumNamespaces, options: options) } func testEnumNamespacesAfterImport() { @@ -974,7 +974,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesAfterImport2() { @@ -1003,7 +1003,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.enumNamespaces) + testFormatting(for: input, output, rule: .enumNamespaces) } func testEnumNamespacesNotAppliedToNonFinalClass() { @@ -1012,7 +1012,7 @@ class SyntaxTests: RulesTests { static let = "A" } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesNotAppliedIfObjC() { @@ -1022,7 +1022,7 @@ class SyntaxTests: RulesTests { static let = "A" } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesNotAppliedIfMacro() { @@ -1032,7 +1032,7 @@ class SyntaxTests: RulesTests { static let = "A" } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesNotAppliedIfParameterizedMacro() { @@ -1042,7 +1042,7 @@ class SyntaxTests: RulesTests { static let = "A" } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesNotAppliedIfGenericMacro() { @@ -1052,7 +1052,7 @@ class SyntaxTests: RulesTests { static let = "A" } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } func testEnumNamespacesNotAppliedIfGenericParameterizedMacro() { @@ -1062,7 +1062,7 @@ class SyntaxTests: RulesTests { static let = "A" } """ - testFormatting(for: input, rule: FormatRules.enumNamespaces) + testFormatting(for: input, rule: .enumNamespaces) } // MARK: - numberFormatting @@ -1072,33 +1072,33 @@ class SyntaxTests: RulesTests { func testLowercaseLiteralConvertedToUpper() { let input = "let foo = 0xabcd" let output = "let foo = 0xABCD" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) + testFormatting(for: input, output, rule: .numberFormatting) } func testMixedCaseLiteralConvertedToUpper() { let input = "let foo = 0xaBcD" let output = "let foo = 0xABCD" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) + testFormatting(for: input, output, rule: .numberFormatting) } func testUppercaseLiteralConvertedToLower() { let input = "let foo = 0xABCD" let output = "let foo = 0xabcd" let options = FormatOptions(uppercaseHex: false) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testPInExponentialNotConvertedToUpper() { let input = "let foo = 0xaBcDp5" let output = "let foo = 0xABCDp5" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) + testFormatting(for: input, output, rule: .numberFormatting) } func testPInExponentialNotConvertedToLower() { let input = "let foo = 0xaBcDP5" let output = "let foo = 0xabcdP5" let options = FormatOptions(uppercaseHex: false, uppercaseExponent: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } // exponent case @@ -1106,28 +1106,28 @@ class SyntaxTests: RulesTests { func testLowercaseExponent() { let input = "let foo = 0.456E-5" let output = "let foo = 0.456e-5" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) + testFormatting(for: input, output, rule: .numberFormatting) } func testUppercaseExponent() { let input = "let foo = 0.456e-5" let output = "let foo = 0.456E-5" let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testUppercaseHexExponent() { let input = "let foo = 0xFF00p54" let output = "let foo = 0xFF00P54" let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testUppercaseGroupedHexExponent() { let input = "let foo = 0xFF00_AABB_CCDDp54" let output = "let foo = 0xFF00_AABB_CCDDP54" let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } // decimal grouping @@ -1135,40 +1135,40 @@ class SyntaxTests: RulesTests { func testDefaultDecimalGrouping() { let input = "let foo = 1234_56_78" let output = "let foo = 12_345_678" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) + testFormatting(for: input, output, rule: .numberFormatting) } func testIgnoreDecimalGrouping() { let input = "let foo = 1234_5_678" let options = FormatOptions(decimalGrouping: .ignore) - testFormatting(for: input, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, rule: .numberFormatting, options: options) } func testNoDecimalGrouping() { let input = "let foo = 1234_5_678" let output = "let foo = 12345678" let options = FormatOptions(decimalGrouping: .none) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testDecimalGroupingThousands() { let input = "let foo = 1234" let output = "let foo = 1_234" let options = FormatOptions(decimalGrouping: .group(3, 3)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testExponentialGrouping() { let input = "let foo = 1234e5678" let output = "let foo = 1_234e5678" let options = FormatOptions(decimalGrouping: .group(3, 3)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testZeroGrouping() { let input = "let foo = 1234" let options = FormatOptions(decimalGrouping: .group(0, 0)) - testFormatting(for: input, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, rule: .numberFormatting, options: options) } // binary grouping @@ -1176,27 +1176,27 @@ class SyntaxTests: RulesTests { func testDefaultBinaryGrouping() { let input = "let foo = 0b11101000_00111111" let output = "let foo = 0b1110_1000_0011_1111" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) + testFormatting(for: input, output, rule: .numberFormatting) } func testIgnoreBinaryGrouping() { let input = "let foo = 0b1110_10_00" let options = FormatOptions(binaryGrouping: .ignore) - testFormatting(for: input, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, rule: .numberFormatting, options: options) } func testNoBinaryGrouping() { let input = "let foo = 0b1110_10_00" let output = "let foo = 0b11101000" let options = FormatOptions(binaryGrouping: .none) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testBinaryGroupingCustom() { let input = "let foo = 0b110011" let output = "let foo = 0b11_00_11" let options = FormatOptions(binaryGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } // hex grouping @@ -1204,14 +1204,14 @@ class SyntaxTests: RulesTests { func testDefaultHexGrouping() { let input = "let foo = 0xFF01FF01AE45" let output = "let foo = 0xFF01_FF01_AE45" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) + testFormatting(for: input, output, rule: .numberFormatting) } func testCustomHexGrouping() { let input = "let foo = 0xFF00p54" let output = "let foo = 0xFF_00p54" let options = FormatOptions(hexGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } // octal grouping @@ -1219,14 +1219,14 @@ class SyntaxTests: RulesTests { func testDefaultOctalGrouping() { let input = "let foo = 0o123456701234" let output = "let foo = 0o1234_5670_1234" - testFormatting(for: input, output, rule: FormatRules.numberFormatting) + testFormatting(for: input, output, rule: .numberFormatting) } func testCustomOctalGrouping() { let input = "let foo = 0o12345670" let output = "let foo = 0o12_34_56_70" let options = FormatOptions(octalGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } // fraction grouping @@ -1234,28 +1234,28 @@ class SyntaxTests: RulesTests { func testIgnoreFractionGrouping() { let input = "let foo = 1.234_5_678" let options = FormatOptions(decimalGrouping: .ignore, fractionGrouping: true) - testFormatting(for: input, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, rule: .numberFormatting, options: options) } func testNoFractionGrouping() { let input = "let foo = 1.234_5_678" let output = "let foo = 1.2345678" let options = FormatOptions(decimalGrouping: .none, fractionGrouping: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testFractionGroupingThousands() { let input = "let foo = 12.34_56_78" let output = "let foo = 12.345_678" let options = FormatOptions(decimalGrouping: .group(3, 3), fractionGrouping: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } func testHexFractionGrouping() { let input = "let foo = 0x12.34_56_78p56" let output = "let foo = 0x12.34_5678p56" let options = FormatOptions(hexGrouping: .group(4, 4), fractionGrouping: true) - testFormatting(for: input, output, rule: FormatRules.numberFormatting, options: options) + testFormatting(for: input, output, rule: .numberFormatting, options: options) } // MARK: - andOperator @@ -1263,65 +1263,65 @@ class SyntaxTests: RulesTests { func testIfAndReplaced() { let input = "if true && true {}" let output = "if true, true {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) + testFormatting(for: input, output, rule: .andOperator) } func testGuardAndReplaced() { let input = "guard true && true\nelse { return }" let output = "guard true, true\nelse { return }" - testFormatting(for: input, output, rule: FormatRules.andOperator, + testFormatting(for: input, output, rule: .andOperator, exclude: ["wrapConditionalBodies"]) } func testWhileAndReplaced() { let input = "while true && true {}" let output = "while true, true {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) + testFormatting(for: input, output, rule: .andOperator) } func testIfDoubleAndReplaced() { let input = "if true && true && true {}" let output = "if true, true, true {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) + testFormatting(for: input, output, rule: .andOperator) } func testIfAndParensReplaced() { let input = "if true && (true && true) {}" let output = "if true, (true && true) {}" - testFormatting(for: input, output, rule: FormatRules.andOperator, + testFormatting(for: input, output, rule: .andOperator, exclude: ["redundantParens"]) } func testIfFunctionAndReplaced() { let input = "if functionReturnsBool() && true {}" let output = "if functionReturnsBool(), true {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) + testFormatting(for: input, output, rule: .andOperator) } func testNoReplaceIfOrAnd() { let input = "if foo || bar && baz {}" - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testNoReplaceIfAndOr() { let input = "if foo && bar || baz {}" - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testIfAndReplacedInFunction() { let input = "func someFunc() { if bar && baz {} }" let output = "func someFunc() { if bar, baz {} }" - testFormatting(for: input, output, rule: FormatRules.andOperator) + testFormatting(for: input, output, rule: .andOperator) } func testNoReplaceIfCaseLetAnd() { let input = "if case let a = foo && bar {}" - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testNoReplaceWhileCaseLetAnd() { let input = "while case let a = foo && bar {}" - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testNoReplaceRepeatWhileAnd() { @@ -1329,40 +1329,40 @@ class SyntaxTests: RulesTests { repeat {} while true && !false foo {} """ - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testNoReplaceIfLetAndLetAnd() { let input = "if let a = b && c, let d = e && f {}" - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testNoReplaceIfTryAnd() { let input = "if try true && explode() {}" - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testHandleAndAtStartOfLine() { let input = "if a == b\n && b == c {}" let output = "if a == b,\n b == c {}" - testFormatting(for: input, output, rule: FormatRules.andOperator, exclude: ["indent"]) + testFormatting(for: input, output, rule: .andOperator, exclude: ["indent"]) } func testHandleAndAtStartOfLineAfterComment() { let input = "if a == b // foo\n && b == c {}" let output = "if a == b, // foo\n b == c {}" - testFormatting(for: input, output, rule: FormatRules.andOperator, exclude: ["indent"]) + testFormatting(for: input, output, rule: .andOperator, exclude: ["indent"]) } func testNoReplaceAndOperatorWhereGenericsAmbiguous() { let input = "if x < y && z > (a * b) {}" - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testNoReplaceAndOperatorWhereGenericsAmbiguous2() { let input = "if x < y && z && w > (a * b) {}" let output = "if x < y, z && w > (a * b) {}" - testFormatting(for: input, output, rule: FormatRules.andOperator) + testFormatting(for: input, output, rule: .andOperator) } func testAndOperatorCrash() { @@ -1380,7 +1380,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.andOperator) + testFormatting(for: input, output, rule: .andOperator) } func testNoReplaceAndInViewBuilder() { @@ -1393,7 +1393,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testNoReplaceAndInViewBuilder2() { @@ -1406,7 +1406,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.andOperator) + testFormatting(for: input, rule: .andOperator) } func testReplaceAndInViewBuilderInSwift5_3() { @@ -1429,7 +1429,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.andOperator, options: options) + testFormatting(for: input, output, rule: .andOperator, options: options) } // MARK: - isEmpty @@ -1439,49 +1439,49 @@ class SyntaxTests: RulesTests { func testCountEqualsZero() { let input = "if foo.count == 0 {}" let output = "if foo.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testFunctionCountEqualsZero() { let input = "if foo().count == 0 {}" let output = "if foo().isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testExpressionCountEqualsZero() { let input = "if foo || bar.count == 0 {}" let output = "if foo || bar.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCompoundIfCountEqualsZero() { let input = "if foo, bar.count == 0 {}" let output = "if foo, bar.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testOptionalCountEqualsZero() { let input = "if foo?.count == 0 {}" let output = "if foo?.isEmpty == true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testOptionalChainCountEqualsZero() { let input = "if foo?.bar.count == 0 {}" let output = "if foo?.bar.isEmpty == true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCompoundIfOptionalCountEqualsZero() { let input = "if foo, bar?.count == 0 {}" let output = "if foo, bar?.isEmpty == true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testTernaryCountEqualsZero() { let input = "foo ? bar.count == 0 : baz.count == 0" let output = "foo ? bar.isEmpty : baz.isEmpty" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } // count != 0 @@ -1489,25 +1489,25 @@ class SyntaxTests: RulesTests { func testCountNotEqualToZero() { let input = "if foo.count != 0 {}" let output = "if !foo.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testFunctionCountNotEqualToZero() { let input = "if foo().count != 0 {}" let output = "if !foo().isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testExpressionCountNotEqualToZero() { let input = "if foo || bar.count != 0 {}" let output = "if foo || !bar.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCompoundIfCountNotEqualToZero() { let input = "if foo, bar.count != 0 {}" let output = "if foo, !bar.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } // count > 0 @@ -1515,12 +1515,12 @@ class SyntaxTests: RulesTests { func testCountGreaterThanZero() { let input = "if foo.count > 0 {}" let output = "if !foo.isEmpty {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCountExpressionGreaterThanZero() { let input = "if a.count - b.count > 0 {}" - testFormatting(for: input, rule: FormatRules.isEmpty) + testFormatting(for: input, rule: .isEmpty) } // optional count @@ -1528,19 +1528,19 @@ class SyntaxTests: RulesTests { func testOptionalCountNotEqualToZero() { let input = "if foo?.count != 0 {}" // nil evaluates to true let output = "if foo?.isEmpty != true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testOptionalChainCountNotEqualToZero() { let input = "if foo?.bar.count != 0 {}" // nil evaluates to true let output = "if foo?.bar.isEmpty != true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCompoundIfOptionalCountNotEqualToZero() { let input = "if foo, bar?.count != 0 {}" let output = "if foo, bar?.isEmpty != true {}" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } // edge cases @@ -1548,37 +1548,37 @@ class SyntaxTests: RulesTests { func testTernaryCountNotEqualToZero() { let input = "foo ? bar.count != 0 : baz.count != 0" let output = "foo ? !bar.isEmpty : !baz.isEmpty" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCountEqualsZeroAfterOptionalOnPreviousLine() { let input = "_ = foo?.bar\nbar.count == 0 ? baz() : quux()" let output = "_ = foo?.bar\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCountEqualsZeroAfterOptionalCallOnPreviousLine() { let input = "foo?.bar()\nbar.count == 0 ? baz() : quux()" let output = "foo?.bar()\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCountEqualsZeroAfterTrailingCommentOnPreviousLine() { let input = "foo?.bar() // foobar\nbar.count == 0 ? baz() : quux()" let output = "foo?.bar() // foobar\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCountGreaterThanZeroAfterOpenParen() { let input = "foo(bar.count > 0)" let output = "foo(!bar.isEmpty)" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } func testCountGreaterThanZeroAfterArgumentLabel() { let input = "foo(bar: baz.count > 0)" let output = "foo(bar: !baz.isEmpty)" - testFormatting(for: input, output, rule: FormatRules.isEmpty) + testFormatting(for: input, output, rule: .isEmpty) } // MARK: - anyObjectProtocol @@ -1587,40 +1587,40 @@ class SyntaxTests: RulesTests { let input = "protocol Foo: class {}" let output = "protocol Foo: AnyObject {}" let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: FormatRules.anyObjectProtocol, options: options) + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options) } func testClassReplacedByAnyObjectWithOtherProtocols() { let input = "protocol Foo: class, Codable {}" let output = "protocol Foo: AnyObject, Codable {}" let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: FormatRules.anyObjectProtocol, options: options) + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options) } func testClassReplacedByAnyObjectImmediatelyAfterImport() { let input = "import Foundation\nprotocol Foo: class {}" let output = "import Foundation\nprotocol Foo: AnyObject {}" let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: FormatRules.anyObjectProtocol, options: options, + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options, exclude: ["blankLineAfterImports"]) } func testClassDeclarationNotReplacedByAnyObject() { let input = "class Foo: Codable {}" let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: FormatRules.anyObjectProtocol, options: options) + testFormatting(for: input, rule: .anyObjectProtocol, options: options) } func testClassImportNotReplacedByAnyObject() { let input = "import class Foo.Bar" let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: FormatRules.anyObjectProtocol, options: options) + testFormatting(for: input, rule: .anyObjectProtocol, options: options) } func testClassNotReplacedByAnyObjectIfSwiftVersionLessThan4_1() { let input = "protocol Foo: class {}" let options = FormatOptions(swiftVersion: "4.0") - testFormatting(for: input, rule: FormatRules.anyObjectProtocol, options: options) + testFormatting(for: input, rule: .anyObjectProtocol, options: options) } // MARK: - applicationMain @@ -1635,7 +1635,7 @@ class SyntaxTests: RulesTests { class AppDelegate: UIResponder, UIApplicationDelegate {} """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.applicationMain, options: options) + testFormatting(for: input, output, rule: .applicationMain, options: options) } func testNSApplicationMainReplacedByMain() { @@ -1648,7 +1648,7 @@ class SyntaxTests: RulesTests { class AppDelegate: NSObject, NSApplicationDelegate {} """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: FormatRules.applicationMain, options: options) + testFormatting(for: input, output, rule: .applicationMain, options: options) } func testNSApplicationMainNotReplacedInSwift5_2() { @@ -1657,7 +1657,7 @@ class SyntaxTests: RulesTests { class AppDelegate: NSObject, NSApplicationDelegate {} """ let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.applicationMain, options: options) + testFormatting(for: input, rule: .applicationMain, options: options) } // MARK: - typeSugar @@ -1667,47 +1667,47 @@ class SyntaxTests: RulesTests { func testArrayTypeConvertedToSugar() { let input = "var foo: Array" let output = "var foo: [String]" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } func testSwiftArrayTypeConvertedToSugar() { let input = "var foo: Swift.Array" let output = "var foo: [String]" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } func testArrayNestedTypeAliasNotConvertedToSugar() { let input = "typealias Indices = Array.Indices" - testFormatting(for: input, rule: FormatRules.typeSugar) + testFormatting(for: input, rule: .typeSugar) } func testArrayTypeReferenceConvertedToSugar() { let input = "let type = Array.Type" let output = "let type = [Foo].Type" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } func testSwiftArrayTypeReferenceConvertedToSugar() { let input = "let type = Swift.Array.Type" let output = "let type = [Foo].Type" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } func testArraySelfReferenceConvertedToSugar() { let input = "let type = Array.self" let output = "let type = [Foo].self" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } func testSwiftArraySelfReferenceConvertedToSugar() { let input = "let type = Swift.Array.self" let output = "let type = [Foo].self" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } func testArrayDeclarationNotConvertedToSugar() { let input = "struct Array {}" - testFormatting(for: input, rule: FormatRules.typeSugar) + testFormatting(for: input, rule: .typeSugar) } func testExtensionTypeSugar() { @@ -1724,7 +1724,7 @@ class SyntaxTests: RulesTests { extension [Foo: Bar] {} extension [[Foo: [Bar]]]? {} """ - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } // dictionaries @@ -1732,81 +1732,81 @@ class SyntaxTests: RulesTests { func testDictionaryTypeConvertedToSugar() { let input = "var foo: Dictionary" let output = "var foo: [String: Int]" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } func testSwiftDictionaryTypeConvertedToSugar() { let input = "var foo: Swift.Dictionary" let output = "var foo: [String: Int]" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } // optionals func testOptionalPropertyTypeNotConvertedToSugarByDefault() { let input = "var bar: Optional" - testFormatting(for: input, rule: FormatRules.typeSugar) + testFormatting(for: input, rule: .typeSugar) } func testOptionalTypeConvertedToSugar() { let input = "var foo: Optional" let output = "var foo: String?" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testSwiftOptionalTypeConvertedToSugar() { let input = "var foo: Swift.Optional" let output = "var foo: String?" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testOptionalClosureParenthesizedConvertedToSugar() { let input = "var foo: Optional<(Int) -> String>" let output = "var foo: ((Int) -> String)?" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testOptionalTupleWrappedInParensConvertedToSugar() { let input = "let foo: Optional<(foo: Int, bar: String)>" let output = "let foo: (foo: Int, bar: String)?" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testOptionalComposedProtocolWrappedInParensConvertedToSugar() { let input = "let foo: Optional" let output = "let foo: (UIView & Foo)?" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testSwiftOptionalClosureParenthesizedConvertedToSugar() { let input = "var foo: Swift.Optional<(Int) -> String>" let output = "var foo: ((Int) -> String)?" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testStrippingSwiftNamespaceInOptionalTypeWhenConvertedToSugar() { let input = "Swift.Optional" let output = "String?" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } func testStrippingSwiftNamespaceDoesNotStripPreviousSwiftNamespaceReferences() { let input = "let a: Swift.String = Optional" let output = "let a: Swift.String = String?" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testOptionalTypeInsideCaseConvertedToSugar() { let input = "if case .some(Optional.some(let foo)) = bar else {}" let output = "if case .some(Any?.some(let foo)) = bar else {}" - testFormatting(for: input, output, rule: FormatRules.typeSugar, exclude: ["hoistPatternLet"]) + testFormatting(for: input, output, rule: .typeSugar, exclude: ["hoistPatternLet"]) } func testSwitchCaseOptionalNotReplaced() { @@ -1815,77 +1815,77 @@ class SyntaxTests: RulesTests { case Optional.none: } """ - testFormatting(for: input, rule: FormatRules.typeSugar) + testFormatting(for: input, rule: .typeSugar) } func testCaseOptionalNotReplaced2() { let input = "if case Optional.none = foo {}" - testFormatting(for: input, rule: FormatRules.typeSugar) + testFormatting(for: input, rule: .typeSugar) } func testUnwrappedOptionalSomeParenthesized() { let input = "func foo() -> Optional> {}" let output = "func foo() -> (some Publisher)? {}" - testFormatting(for: input, output, rule: FormatRules.typeSugar) + testFormatting(for: input, output, rule: .typeSugar) } // swift parser bug func testAvoidSwiftParserBugWithClosuresInsideArrays() { let input = "var foo = Array<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) } func testAvoidSwiftParserBugWithClosuresInsideDictionaries() { let input = "var foo = Dictionary Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) } func testAvoidSwiftParserBugWithClosuresInsideOptionals() { let input = "var foo = Optional<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: FormatRules.typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) } func testDontOverApplyBugWorkaround() { let input = "var foo: Array<(_ image: Data?) -> Void>" let output = "var foo: [(_ image: Data?) -> Void]" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testDontOverApplyBugWorkaround2() { let input = "var foo: Dictionary Void>" let output = "var foo: [String: (_ image: Data?) -> Void]" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testDontOverApplyBugWorkaround3() { let input = "var foo: Optional<(_ image: Data?) -> Void>" let output = "var foo: ((_ image: Data?) -> Void)?" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options) + testFormatting(for: input, output, rule: .typeSugar, options: options) } func testDontOverApplyBugWorkaround4() { let input = "var foo = Array<(image: Data?) -> Void>()" let output = "var foo = [(image: Data?) -> Void]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: ["propertyType"]) } func testDontOverApplyBugWorkaround5() { let input = "var foo = Array<(Data?) -> Void>()" let output = "var foo = [(Data?) -> Void]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: ["propertyType"]) } func testDontOverApplyBugWorkaround6() { let input = "var foo = Dictionary Void>>()" let output = "var foo = [Int: Array<(_ image: Data?) -> Void>]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: FormatRules.typeSugar, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: ["propertyType"]) } // MARK: - preferKeyPath @@ -1894,7 +1894,7 @@ class SyntaxTests: RulesTests { let input = "let foo = bar.map { $0.foo }" let output = "let foo = bar.map(\\.foo)" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, + testFormatting(for: input, output, rule: .preferKeyPath, options: options) } @@ -1902,7 +1902,7 @@ class SyntaxTests: RulesTests { let input = "let foo = bar.compactMap { $0.foo }" let output = "let foo = bar.compactMap(\\.foo)" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, + testFormatting(for: input, output, rule: .preferKeyPath, options: options) } @@ -1910,7 +1910,7 @@ class SyntaxTests: RulesTests { let input = "let foo = bar.flatMap { $0.foo }" let output = "let foo = bar.flatMap(\\.foo)" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, + testFormatting(for: input, output, rule: .preferKeyPath, options: options) } @@ -1918,7 +1918,7 @@ class SyntaxTests: RulesTests { let input = "let foo = bar.map { $0 . foo . bar }" let output = "let foo = bar.map(\\ . foo . bar)" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, + testFormatting(for: input, output, rule: .preferKeyPath, options: options, exclude: ["spaceAroundOperators"]) } @@ -1930,7 +1930,7 @@ class SyntaxTests: RulesTests { """ let output = "let foo = bar.map(\\.foo)" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, + testFormatting(for: input, output, rule: .preferKeyPath, options: options) } @@ -1938,57 +1938,57 @@ class SyntaxTests: RulesTests { let input = "let foo = bar.map({ $0.foo })" let output = "let foo = bar.map(\\.foo)" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, + testFormatting(for: input, output, rule: .preferKeyPath, options: options) } func testNoMapSelfToKeyPath() { let input = "let foo = bar.map { $0 }" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) + testFormatting(for: input, rule: .preferKeyPath, options: options) } func testNoMapPropertyToKeyPathForSwiftLessThan5_2() { let input = "let foo = bar.map { $0.foo }" let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) + testFormatting(for: input, rule: .preferKeyPath, options: options) } func testNoMapPropertyToKeyPathForFunctionCalls() { let input = "let foo = bar.map { $0.foo() }" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) + testFormatting(for: input, rule: .preferKeyPath, options: options) } func testNoMapPropertyToKeyPathForCompoundExpressions() { let input = "let foo = bar.map { $0.foo || baz }" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) + testFormatting(for: input, rule: .preferKeyPath, options: options) } func testNoMapPropertyToKeyPathForOptionalChaining() { let input = "let foo = bar.map { $0?.foo }" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) + testFormatting(for: input, rule: .preferKeyPath, options: options) } func testNoMapPropertyToKeyPathForTrailingContains() { let input = "let foo = bar.contains { $0.foo }" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) + testFormatting(for: input, rule: .preferKeyPath, options: options) } func testMapPropertyToKeyPathForContainsWhere() { let input = "let foo = bar.contains(where: { $0.foo })" let output = "let foo = bar.contains(where: \\.foo)" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: FormatRules.preferKeyPath, options: options) + testFormatting(for: input, output, rule: .preferKeyPath, options: options) } func testMultipleTrailingClosuresNotConvertedToKeyPath() { let input = "foo.map { $0.bar } reverse: { $0.bar }" let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: FormatRules.preferKeyPath, options: options) + testFormatting(for: input, rule: .preferKeyPath, options: options) } // MARK: - assertionFailures @@ -1996,13 +1996,13 @@ class SyntaxTests: RulesTests { func testAssertionFailuresForAssertFalse() { let input = "assert(false)" let output = "assertionFailure()" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) + testFormatting(for: input, output, rule: .assertionFailures) } func testAssertionFailuresForAssertFalseWithSpaces() { let input = "assert ( false )" let output = "assertionFailure()" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) + testFormatting(for: input, output, rule: .assertionFailures) } func testAssertionFailuresForAssertFalseWithLinebreaks() { @@ -2012,35 +2012,35 @@ class SyntaxTests: RulesTests { ) """ let output = "assertionFailure()" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) + testFormatting(for: input, output, rule: .assertionFailures) } func testAssertionFailuresForAssertTrue() { let input = "assert(true)" - testFormatting(for: input, rule: FormatRules.assertionFailures) + testFormatting(for: input, rule: .assertionFailures) } func testAssertionFailuresForAssertFalseWithArgs() { let input = "assert(false, msg, 20, 21)" let output = "assertionFailure(msg, 20, 21)" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) + testFormatting(for: input, output, rule: .assertionFailures) } func testAssertionFailuresForPreconditionFalse() { let input = "precondition(false)" let output = "preconditionFailure()" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) + testFormatting(for: input, output, rule: .assertionFailures) } func testAssertionFailuresForPreconditionTrue() { let input = "precondition(true)" - testFormatting(for: input, rule: FormatRules.assertionFailures) + testFormatting(for: input, rule: .assertionFailures) } func testAssertionFailuresForPreconditionFalseWithArgs() { let input = "precondition(false, msg, 0, 1)" let output = "preconditionFailure(msg, 0, 1)" - testFormatting(for: input, output, rule: FormatRules.assertionFailures) + testFormatting(for: input, output, rule: .assertionFailures) } // MARK: - acronyms @@ -2080,7 +2080,7 @@ class SyntaxTests: RulesTests { struct ScreenID {} """ - testFormatting(for: input, output, rule: FormatRules.acronyms, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .acronyms, exclude: ["propertyType"]) } func testUppercaseCustomAcronym() { @@ -2098,7 +2098,7 @@ class SyntaxTests: RulesTests { let imageInPNGFormat: UIImage """ - testFormatting(for: input, output, rule: FormatRules.acronyms, options: FormatOptions(acronyms: ["png"])) + testFormatting(for: input, output, rule: .acronyms, options: FormatOptions(acronyms: ["png"])) } func testDisableUppercaseAcronym() { @@ -2108,7 +2108,7 @@ class SyntaxTests: RulesTests { typeOwnedByAuthor.destinationURL = URL() """ - testFormatting(for: input, rule: FormatRules.acronyms) + testFormatting(for: input, rule: .acronyms) } // MARK: - blockComments @@ -2116,18 +2116,18 @@ class SyntaxTests: RulesTests { func testBlockCommentsOneLine() { let input = "foo = bar /* comment */" let output = "foo = bar // comment" - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testDocBlockCommentsOneLine() { let input = "foo = bar /** doc comment */" let output = "foo = bar /// doc comment" - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testPreservesBlockCommentInSingleLineScope() { let input = "if foo { /* code */ }" - testFormatting(for: input, rule: FormatRules.blockComments) + testFormatting(for: input, rule: .blockComments) } func testBlockCommentsMultiLine() { @@ -2141,7 +2141,7 @@ class SyntaxTests: RulesTests { // foo // bar """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockCommentsWithoutBlankFirstLine() { @@ -2154,7 +2154,7 @@ class SyntaxTests: RulesTests { // foo // bar """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockCommentsWithBlankLine() { @@ -2170,7 +2170,7 @@ class SyntaxTests: RulesTests { // // bar """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockDocCommentsWithAsterisksOnEachLine() { @@ -2184,7 +2184,7 @@ class SyntaxTests: RulesTests { /// This is a documentation comment, /// not a standard comment. """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) } func testBlockDocCommentsWithoutAsterisksOnEachLine() { @@ -2198,7 +2198,7 @@ class SyntaxTests: RulesTests { /// This is a documentation comment, /// not a standard comment. """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) } func testBlockCommentWithBulletPoints() { @@ -2228,7 +2228,7 @@ class SyntaxTests: RulesTests { // Another comment. """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockCommentsNested() { @@ -2244,7 +2244,7 @@ class SyntaxTests: RulesTests { // inside // a comment """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockCommentsIndentPreserved() { @@ -2262,7 +2262,7 @@ class SyntaxTests: RulesTests { // bar. } """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockCommentsIndentPreserved2() { @@ -2280,7 +2280,7 @@ class SyntaxTests: RulesTests { // bar. } """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockDocCommentsIndentPreserved() { @@ -2298,7 +2298,7 @@ class SyntaxTests: RulesTests { /// bar. } """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) } func testLongBlockCommentsWithoutPerLineMarkersFullyConverted() { @@ -2322,7 +2322,7 @@ class SyntaxTests: RulesTests { // // The comment must have at least this many lines to trigger the bug. """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockCommentImmediatelyFollowedByCode() { @@ -2340,7 +2340,7 @@ class SyntaxTests: RulesTests { /// bar func foo() {} """ - testFormatting(for: input, output, rule: FormatRules.blockComments) + testFormatting(for: input, output, rule: .blockComments) } func testBlockCommentImmediatelyFollowedByCode2() { @@ -2362,7 +2362,7 @@ class SyntaxTests: RulesTests { /// Line 3. foo(bar) """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) } func testBlockCommentImmediatelyFollowedByCode3() { @@ -2376,7 +2376,7 @@ class SyntaxTests: RulesTests { // bar func foo() {} """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) } func testBlockCommentFollowedByBlankLine() { @@ -2396,7 +2396,7 @@ class SyntaxTests: RulesTests { func foo() {} """ - testFormatting(for: input, output, rule: FormatRules.blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) } // MARK: - opaqueGenericParameters @@ -2409,7 +2409,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterWithNoConstraint() { @@ -2460,7 +2460,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testDisableSomeAnyGenericType() { @@ -2471,7 +2471,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(useSomeAny: false, swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterWithConstraintInBracket() { @@ -2504,7 +2504,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterWithConstraintsInWhereClause() { @@ -2529,7 +2529,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterCanRemoveOneButNotOthers_onOneLine() { @@ -2546,7 +2546,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterCanRemoveOneButNotOthers_onMultipleLines() { @@ -2577,7 +2577,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterWithUnknownAssociatedTypeConstraint() { @@ -2593,7 +2593,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterWithAssociatedTypeConformance() { @@ -2605,7 +2605,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterWithKnownAssociatedTypeConstraint() { @@ -2624,7 +2624,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParameterWithAssociatedTypeConstraint() { @@ -2641,7 +2641,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testGenericTypeUsedInMultipleParameters() { @@ -2652,7 +2652,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericTypeUsedInClosureMultipleTimes() { @@ -2663,7 +2663,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericTypeUsedAsReturnType() { @@ -2686,7 +2686,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericTypeUsedAsReturnTypeAndParameter() { @@ -2702,7 +2702,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericTypeWithClosureInWhereClauseDoesntCrash() { @@ -2713,7 +2713,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericExtensionSameTypeConstraint() { @@ -2730,7 +2730,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testGenericExtensionSameTypeGenericConstraint() { @@ -2755,7 +2755,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testIssue1269() { @@ -2772,7 +2772,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testVariadicParameterNotConvertedToOpaqueGeneric() { @@ -2783,7 +2783,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testNonGenericVariadicParametersDoesntPreventUsingOpaqueGenerics() { @@ -2800,7 +2800,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testIssue1275() { @@ -2813,7 +2813,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testIssue1278() { @@ -2837,7 +2837,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testIssue1392() { @@ -2853,7 +2853,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testIssue1684() { @@ -2864,7 +2864,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericSimplifiedInMethodWithAttributeOrMacro() { @@ -2895,7 +2895,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } // MARK: - genericExtensions @@ -2904,7 +2904,7 @@ class SyntaxTests: RulesTests { let input = "extension Array where Element == Foo {}" let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testUpdatesArrayGenericExtensionToAngleBracketSyntax() { @@ -2912,7 +2912,7 @@ class SyntaxTests: RulesTests { let output = "extension Array {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) } func testUpdatesOptionalGenericExtensionToAngleBracketSyntax() { @@ -2920,7 +2920,7 @@ class SyntaxTests: RulesTests { let output = "extension Optional {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) } func testUpdatesArrayGenericExtensionToAngleBracketSyntaxWithSelf() { @@ -2928,7 +2928,7 @@ class SyntaxTests: RulesTests { let output = "extension Array {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) } func testUpdatesArrayWithGenericElement() { @@ -2936,7 +2936,7 @@ class SyntaxTests: RulesTests { let output = "extension Array> {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) } func testUpdatesDictionaryGenericExtensionToAngleBracketSyntax() { @@ -2944,7 +2944,7 @@ class SyntaxTests: RulesTests { let output = "extension Dictionary {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) } func testRequiresAllGenericTypesToBeProvided() { @@ -2952,7 +2952,7 @@ class SyntaxTests: RulesTests { let input = "extension Dictionary where Key == Foo {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.genericExtensions, options: options) + testFormatting(for: input, rule: .genericExtensions, options: options) } func testHandlesNestedCollectionTypes() { @@ -2960,7 +2960,7 @@ class SyntaxTests: RulesTests { let output = "extension Array<[[Foo: Bar]]> {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) } func testDoesntUpdateIneligibleConstraints() { @@ -2969,7 +2969,7 @@ class SyntaxTests: RulesTests { let input = "extension Optional where Wrapped: Fooable {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.genericExtensions, options: options) + testFormatting(for: input, rule: .genericExtensions, options: options) } func testPreservesOtherConstraintsInWhereClause() { @@ -2977,7 +2977,7 @@ class SyntaxTests: RulesTests { let output = "extension Collection where Index == Int {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options) + testFormatting(for: input, output, rule: .genericExtensions, options: options) } func testSupportsUserProvidedGenericTypes() { @@ -2994,7 +2994,7 @@ class SyntaxTests: RulesTests { genericTypes: "LinkedList;StateStore", swiftVersion: "5.7" ) - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options) + testFormatting(for: input, output, rule: .genericExtensions, options: options) } func testSupportsMultilineUserProvidedGenericTypes() { @@ -3013,7 +3013,7 @@ class SyntaxTests: RulesTests { genericTypes: "Reducer", swiftVersion: "5.7" ) - testFormatting(for: input, output, rule: FormatRules.genericExtensions, options: options) + testFormatting(for: input, output, rule: .genericExtensions, options: options) } func testOpaqueGenericParametersRuleSuccessfullyTerminatesInSampleCode() { @@ -3034,7 +3034,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericParameterUsedInConstraintOfOtherTypeNotChanged() { @@ -3049,7 +3049,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericParameterInheritedFromContextNotRemoved() { @@ -3062,7 +3062,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericParameterUsedInBodyNotRemoved() { @@ -3075,7 +3075,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testGenericParameterUsedAsClosureParameterNotRemoved() { @@ -3091,7 +3091,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testFinalGenericParamRemovedProperlyWithoutHangingComma() { @@ -3110,7 +3110,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testAddsParensAroundTypeIfNecessary() { @@ -3125,7 +3125,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testHandlesSingleExactTypeGenericConstraint() { @@ -3138,7 +3138,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testGenericConstraintThatIsGeneric() { @@ -3157,7 +3157,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) } func testDoesntChangeTypeWithConstraintThatReferencesItself() { @@ -3170,14 +3170,14 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: FormatRules.opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) } func testOpaqueGenericParametersDoesntleaveTrailingComma() { let input = "func f(x: U) -> T where T: A, U: B {}" let output = "func f(x: some B) -> T where T: A {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: FormatRules.opaqueGenericParameters, + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options, exclude: ["unusedArguments"]) } @@ -3266,7 +3266,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.docComments, + testFormatting(for: input, output, rule: .docComments, exclude: ["spaceInsideComments", "propertyType"]) } @@ -3341,7 +3341,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.docComments, + testFormatting(for: input, output, rule: .docComments, exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) } @@ -3419,7 +3419,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(preserveDocComments: true) - testFormatting(for: input, output, rule: FormatRules.docComments, options: options, exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) + testFormatting(for: input, output, rule: .docComments, options: options, exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) } func testDoesntConvertCommentBeforeConsecutivePropertiesToDocComment() { @@ -3465,7 +3465,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.docComments) + testFormatting(for: input, output, rule: .docComments) } func testConvertsCommentsToDocCommentsInConsecutiveDeclarations() { @@ -3521,7 +3521,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.docComments) + testFormatting(for: input, output, rule: .docComments) } func testDoesntConvertCommentBeforeConsecutiveEnumCasesToDocComment() { @@ -3567,7 +3567,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.docComments) + testFormatting(for: input, output, rule: .docComments) } func testDoesntConvertAnnotationCommentsToDocComments() { @@ -3582,7 +3582,7 @@ class SyntaxTests: RulesTests { let testSourcery: Foo """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testDoesntConvertTODOCommentsToDocComments() { @@ -3591,7 +3591,7 @@ class SyntaxTests: RulesTests { func doSomething() {} """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testDoesntConvertCommentAfterTODOToDocComments() { @@ -3600,7 +3600,7 @@ class SyntaxTests: RulesTests { // because it's bothering me func doSomething() {} """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testDoesntConvertCommentBeforeTODOToDocComments() { @@ -3609,7 +3609,7 @@ class SyntaxTests: RulesTests { // TODO: Clean up this mess func doSomething() {} """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testConvertNoteCommentsToDocComments() { @@ -3623,7 +3623,7 @@ class SyntaxTests: RulesTests { /// Note: not really func doSomething() {} """ - testFormatting(for: input, output, rule: FormatRules.docComments) + testFormatting(for: input, output, rule: .docComments) } func testConvertURLCommentsToDocComments() { @@ -3637,7 +3637,7 @@ class SyntaxTests: RulesTests { /// http://example.com func doSomething() {} """ - testFormatting(for: input, output, rule: FormatRules.docComments) + testFormatting(for: input, output, rule: .docComments) } func testMultilineDocCommentReplaced() { @@ -3651,7 +3651,7 @@ class SyntaxTests: RulesTests { /// With some other details class Foo {} """ - testFormatting(for: input, output, rule: FormatRules.docComments) + testFormatting(for: input, output, rule: .docComments) } func testCommentWithBlankLineNotReplaced() { @@ -3661,7 +3661,7 @@ class SyntaxTests: RulesTests { class Foo {} """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testDocCommentsAssociatedTypeNotReplaced() { @@ -3669,7 +3669,7 @@ class SyntaxTests: RulesTests { /// An interesting comment about Foo. associatedtype Foo """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testNonDocCommentsAssociatedTypeReplaced() { @@ -3681,7 +3681,7 @@ class SyntaxTests: RulesTests { /// An interesting comment about Foo. associatedtype Foo """ - testFormatting(for: input, output, rule: FormatRules.docComments) + testFormatting(for: input, output, rule: .docComments) } func testConditionalDeclarationCommentNotReplaced() { @@ -3691,7 +3691,7 @@ class SyntaxTests: RulesTests { let baz = bar {} """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testCommentInsideSwitchCaseNotReplaced() { @@ -3706,7 +3706,7 @@ class SyntaxTests: RulesTests { let baz = quux() } """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testDocCommentInsideIfdef() { @@ -3722,7 +3722,7 @@ class SyntaxTests: RulesTests { func returnNumber() { 3 } #endif """ - testFormatting(for: input, output, rule: FormatRules.docComments) + testFormatting(for: input, output, rule: .docComments) } func testDocCommentInsideIfdefElse() { @@ -3736,7 +3736,7 @@ class SyntaxTests: RulesTests { func returnNumber() { 3 } #endif """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } func testDocCommentForMacro() { @@ -3747,7 +3747,7 @@ class SyntaxTests: RulesTests { category: String? = nil ) = #externalMacro(module: "StaticLoggerMacros", type: "StaticLogger") """ - testFormatting(for: input, rule: FormatRules.docComments) + testFormatting(for: input, rule: .docComments) } // MARK: - conditionalAssignment @@ -3762,7 +3762,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testConvertsIfStatementAssignment() { @@ -3782,7 +3782,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) } func testConvertsSimpleSwitchStatementAssignment() { @@ -3804,7 +3804,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) } func testConvertsTrivialSwitchStatementAssignment() { @@ -3822,7 +3822,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) } func testConvertsNestedIfAndStatementAssignments() { @@ -3874,7 +3874,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) } func testConvertsIfStatementAssignmentPreservingComment() { @@ -3897,7 +3897,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["indent", "redundantType", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["indent", "redundantType", "wrapMultilineConditionalAssignment"]) } func testDoesntConvertsIfStatementAssigningMultipleProperties() { @@ -3913,7 +3913,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertsIfStatementAssigningDifferentProperties() { @@ -3927,7 +3927,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertNonExhaustiveIfStatementAssignment1() { @@ -3940,7 +3940,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertNonExhaustiveIfStatementAssignment2() { @@ -3955,7 +3955,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementAssignment1() { @@ -3969,7 +3969,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementAssignment2() { @@ -3985,7 +3985,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementAssignment3() { @@ -4003,7 +4003,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementAssignment4() { @@ -4023,7 +4023,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementWithStringLiteral() { @@ -4038,7 +4038,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementWithCollectionLiteral() { @@ -4053,7 +4053,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementWithIntLiteral() { @@ -4068,7 +4068,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementWithNilLiteral() { @@ -4083,7 +4083,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertMultiStatementIfStatementWithOtherProperty() { @@ -4098,7 +4098,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertConditionalCastInSwift5_9() { @@ -4129,7 +4129,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testAllowsAsWithinInnerScope() { @@ -4153,7 +4153,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) } // TODO: update branches parser to handle this case properly @@ -4174,7 +4174,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } // TODO: update branches parser to handle this scenario properly @@ -4195,7 +4195,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testConvertsConditionalCastInSwift5_10() { @@ -4232,7 +4232,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) } func testConvertsSwitchWithDefaultCase() { @@ -4260,7 +4260,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment", "redundantType"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment", "redundantType"]) } func testConvertsSwitchWithUnknownDefaultCase() { @@ -4288,7 +4288,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: FormatRules.conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment", "redundantType"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment", "redundantType"]) } func testPreservesSwitchWithReturnInDefaultCase() { @@ -4305,7 +4305,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testPreservesSwitchWithReturnInUnknownDefaultCase() { @@ -4322,7 +4322,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testDoesntConvertIfStatementWithForLoopInBranch() { @@ -4338,7 +4338,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: FormatRules.conditionalAssignment, options: options) + testFormatting(for: input, rule: .conditionalAssignment, options: options) } func testConvertsIfStatementNotFollowingPropertyDefinition() { @@ -4360,7 +4360,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } func testPreservesIfStatementNotFollowingPropertyDefinitionWithInvalidBranch() { @@ -4382,7 +4382,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } func testPreservesNonExhaustiveIfStatementNotFollowingPropertyDefinition() { @@ -4399,7 +4399,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } func testConvertsSwitchStatementNotFollowingPropertyDefinition() { @@ -4423,7 +4423,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } func testConvertsSwitchStatementWithComplexLValueNotFollowingPropertyDefinition() { @@ -4447,7 +4447,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } func testDoesntMergePropertyWithUnrelatedCondition() { @@ -4473,7 +4473,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } func testConvertsNestedIfSwitchStatementNotFollowingPropertyDefinition() { @@ -4515,7 +4515,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } func testPreservesSwitchConditionWithIneligibleBranch() { @@ -4541,7 +4541,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } func testPreservesIfConditionWithIneligibleBranch() { @@ -4565,7 +4565,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rules: [FormatRules.conditionalAssignment, FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent], options: options) + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) } // MARK: - preferForLoop @@ -4595,7 +4595,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testConvertAnonymousForEachToForLoop() { @@ -4617,7 +4617,7 @@ class SyntaxTests: RulesTests { potatoes.forEach({ $0.bake() }) """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .preferForLoop, exclude: ["trailingClosures"]) } func testNoConvertAnonymousForEachToForLoop() { @@ -4631,7 +4631,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(preserveAnonymousForEach: true, preserveSingleLineForEach: false) - testFormatting(for: input, rule: FormatRules.preferForLoop, options: options, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .preferForLoop, options: options, exclude: ["trailingClosures"]) } func testConvertSingleLineForEachToForLoop() { @@ -4639,7 +4639,7 @@ class SyntaxTests: RulesTests { let output = "for item in potatoes { item.bake() }" let options = FormatOptions(preserveSingleLineForEach: false) - testFormatting(for: input, output, rule: FormatRules.preferForLoop, options: options, + testFormatting(for: input, output, rule: .preferForLoop, options: options, exclude: ["wrapLoopBodies"]) } @@ -4648,7 +4648,7 @@ class SyntaxTests: RulesTests { let output = "for potato in potatoes { potato.bake() }" let options = FormatOptions(preserveSingleLineForEach: false) - testFormatting(for: input, output, rule: FormatRules.preferForLoop, options: options, + testFormatting(for: input, output, rule: .preferForLoop, options: options, exclude: ["wrapLoopBodies"]) } @@ -4675,7 +4675,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testDefaultNameAlreadyUsedInLoopBody() { @@ -4695,7 +4695,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testIgnoreLoopsWithCaptureListForNow() { @@ -4705,7 +4705,7 @@ class SyntaxTests: RulesTests { print($0, someCapturedValue) } """ - testFormatting(for: input, rule: FormatRules.preferForLoop) + testFormatting(for: input, rule: .preferForLoop) } func testRemoveAllPrefixFromLoopIdentifier() { @@ -4721,7 +4721,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testConvertsReturnToContinue() { @@ -4754,7 +4754,7 @@ class SyntaxTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testHandlesForEachOnChainedProperties() { @@ -4771,7 +4771,7 @@ class SyntaxTests: RulesTests { print(string) } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testHandlesForEachOnFunctionCallResult() { @@ -4788,7 +4788,7 @@ class SyntaxTests: RulesTests { print(baazValue) } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testHandlesForEachOnSubscriptResult() { @@ -4805,7 +4805,7 @@ class SyntaxTests: RulesTests { print(item) } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testHandlesForEachOnArrayLiteral() { @@ -4822,7 +4822,7 @@ class SyntaxTests: RulesTests { print(item) } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testHandlesForEachOnCurriedFunctionWithSubscript() { @@ -4839,7 +4839,7 @@ class SyntaxTests: RulesTests { print(item) } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testHandlesForEachOnArrayLiteralInParens() { @@ -4856,7 +4856,7 @@ class SyntaxTests: RulesTests { print(item) } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .preferForLoop, exclude: ["redundantParens"]) } func testPreservesForEachAfterMultilineChain() { @@ -4871,7 +4871,7 @@ class SyntaxTests: RulesTests { .map({ $0.uppercased() }) .forEach({ print($0) }) """ - testFormatting(for: input, rule: FormatRules.preferForLoop, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .preferForLoop, exclude: ["trailingClosures"]) } func testPreservesChainWithClosure() { @@ -4883,7 +4883,7 @@ class SyntaxTests: RulesTests { // to silence this warning". strings.map { $0.uppercased() }.forEach { print($0) } """ - testFormatting(for: input, rule: FormatRules.preferForLoop) + testFormatting(for: input, rule: .preferForLoop) } func testForLoopVariableNotUsedIfClashesWithKeyword() { @@ -4897,7 +4897,7 @@ class SyntaxTests: RulesTests { print(item) } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } func testTryNotRemovedInThrowingForEach() { @@ -4906,7 +4906,7 @@ class SyntaxTests: RulesTests { print($0) } """ - testFormatting(for: input, rule: FormatRules.preferForLoop) + testFormatting(for: input, rule: .preferForLoop) } func testOptionalTryNotRemovedInThrowingForEach() { @@ -4915,7 +4915,7 @@ class SyntaxTests: RulesTests { print($0) } """ - testFormatting(for: input, rule: FormatRules.preferForLoop) + testFormatting(for: input, rule: .preferForLoop) } func testAwaitNotRemovedInAsyncForEach() { @@ -4924,7 +4924,7 @@ class SyntaxTests: RulesTests { print($0) } """ - testFormatting(for: input, rule: FormatRules.preferForLoop) + testFormatting(for: input, rule: .preferForLoop) } func testForEachOverDictionary() { @@ -4946,7 +4946,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.preferForLoop) + testFormatting(for: input, output, rule: .preferForLoop) } // MARK: propertyType @@ -4975,7 +4975,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) } func testConvertsInferredTypeToExplicitType() { @@ -5000,7 +5000,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, output, rule: .propertyType, options: options) } func testConvertsTypeMembersToExplicitType() { @@ -5029,7 +5029,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, output, rule: .propertyType, options: options) } func testConvertsLocalsToImplicitType() { @@ -5064,7 +5064,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) } func testPreservesInferredTypeFollowingTypeWithDots() { @@ -5074,7 +5074,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPreservesExplicitTypeIfNoRHS() { @@ -5084,7 +5084,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPreservesImplicitTypeIfNoRHSType() { @@ -5096,7 +5096,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPreservesImplicitForVoidAndTuples() { @@ -5111,7 +5111,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.propertyType, options: options, exclude: ["void"]) + testFormatting(for: input, rule: .propertyType, options: options, exclude: ["void"]) } func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { @@ -5126,7 +5126,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options, exclude: ["redundantType"]) + testFormatting(for: input, rule: .propertyType, options: options, exclude: ["redundantType"]) } func testCompatibleWithRedundantTypeInferred() { @@ -5139,7 +5139,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType], options: options) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType], options: options) } func testCompatibleWithRedundantTypeExplicit() { @@ -5152,7 +5152,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType], options: options) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType], options: options) } func testCompatibleWithRedundantTypeInferLocalsOnly() { @@ -5177,7 +5177,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [FormatRules.redundantType, FormatRules.propertyType, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType, .redundantInit], options: options) } func testPropertyTypeWithIfExpressionDisabledByDefault() { @@ -5191,7 +5191,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPropertyTypeWithIfExpression() { @@ -5214,7 +5214,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) } func testPropertyTypeWithSwitchExpression() { @@ -5239,7 +5239,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) } func testPreservesNonMatchingIfExpression() { @@ -5253,7 +5253,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPreservesExplicitOptionalType() { @@ -5266,7 +5266,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPreservesTypeWithSeparateDeclarationAndProperty() { @@ -5278,7 +5278,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPreservesTypeWithExistentialAny() { @@ -5298,7 +5298,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPreservesExplicitRightHandSideWithOperator() { @@ -5317,7 +5317,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, output, rule: .propertyType, options: options) } func testPreservesInferredRightHandSideWithOperators() { @@ -5328,7 +5328,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPreservesUserProvidedSymbolTypes() { @@ -5361,7 +5361,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["Foo", "Baaz", "quux"]) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, output, rule: .propertyType, options: options) } func testPreserveInitIfExplicitlyExcluded() { @@ -5394,7 +5394,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["init"]) - testFormatting(for: input, output, rule: FormatRules.propertyType, options: options, exclude: ["redundantInit"]) + testFormatting(for: input, output, rule: .propertyType, options: options, exclude: ["redundantInit"]) } func testClosureBodyIsConsideredLocal() { @@ -5453,7 +5453,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [FormatRules.propertyType, FormatRules.redundantInit], options: options) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) } func testIfGuardConditionsPreserved() { @@ -5472,7 +5472,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } func testPropertyObserversConsideredLocal() { @@ -5496,7 +5496,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, rule: FormatRules.propertyType, options: options) + testFormatting(for: input, rule: .propertyType, options: options) } // MARK: - docCommentsBeforeAttributes @@ -5536,7 +5536,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes) + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes) } func testDocCommentsBeforeMultipleAttributes() { @@ -5564,7 +5564,7 @@ class SyntaxTests: RulesTests { public func bar() {} """ - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes) + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes) } func testUpdatesCommentsAfterMark() { @@ -5618,7 +5618,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) } func testPreservesCommentsBetweenAttributes() { @@ -5648,7 +5648,7 @@ class SyntaxTests: RulesTests { func bar() {} """ - testFormatting(for: input, output, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: ["docComments"]) } func testPreservesCommentOnSameLineAsAttribute() { @@ -5657,7 +5657,7 @@ class SyntaxTests: RulesTests { func foo() {} """ - testFormatting(for: input, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: ["docComments"]) } func testPreservesRegularComments() { @@ -5667,7 +5667,7 @@ class SyntaxTests: RulesTests { func foo() {} """ - testFormatting(for: input, rule: FormatRules.docCommentsBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: ["docComments"]) } func testCombinesWithDocCommentsRule() { @@ -5683,6 +5683,6 @@ class SyntaxTests: RulesTests { func foo() {} """ - testFormatting(for: input, [output], rules: [FormatRules.docComments, FormatRules.docCommentsBeforeAttributes]) + testFormatting(for: input, [output], rules: [.docComments, .docCommentsBeforeAttributes]) } } diff --git a/Tests/RulesTests+Wrapping.swift b/Tests/RulesTests+Wrapping.swift index edb136651..270be9da7 100644 --- a/Tests/RulesTests+Wrapping.swift +++ b/Tests/RulesTests+Wrapping.swift @@ -15,25 +15,25 @@ class WrappingTests: RulesTests { func testElseOnSameLine() { let input = "if true {\n 1\n}\nelse { 2 }" let output = "if true {\n 1\n} else { 2 }" - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, exclude: ["wrapConditionalBodies"]) } func testElseOnSameLineOnlyAppliedToDanglingBrace() { let input = "if true { 1 }\nelse { 2 }" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, exclude: ["wrapConditionalBodies"]) } func testGuardNotAffectedByElseOnSameLine() { let input = "guard true\nelse { return }" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, exclude: ["wrapConditionalBodies"]) } func testElseOnSameLineDoesntEatPreviousStatement() { let input = "if true {}\nguard true else { return }" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, exclude: ["wrapConditionalBodies"]) } @@ -41,7 +41,7 @@ class WrappingTests: RulesTests { let input = "if true\n{\n 1\n} else { 2 }" let output = "if true\n{\n 1\n}\nelse { 2 }" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, options: options, exclude: ["wrapConditionalBodies"]) } @@ -49,14 +49,14 @@ class WrappingTests: RulesTests { let input = "if true {\n 1\n} else { 2 }" let output = "if true {\n 1\n}\nelse { 2 }" let options = FormatOptions(elseOnNextLine: true) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, options: options, exclude: ["wrapConditionalBodies"]) } func testGuardNotAffectedByElseOnSameLineForAllman() { let input = "guard true else { return }" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, options: options, exclude: ["wrapConditionalBodies"]) } @@ -64,17 +64,17 @@ class WrappingTests: RulesTests { let input = "repeat\n{\n foo\n} while x" let output = "repeat\n{\n foo\n}\nwhile x" let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, options: options) + testFormatting(for: input, output, rule: .elseOnSameLine, options: options) } func testWhileNotAffectedByElseOnSameLineIfNotRepeatWhile() { let input = "func foo(x) {}\n\nwhile true {}" - testFormatting(for: input, rule: FormatRules.elseOnSameLine) + testFormatting(for: input, rule: .elseOnSameLine) } func testCommentsNotDiscardedByElseOnSameLineRule() { let input = "if true {\n 1\n}\n\n// comment\nelse {}" - testFormatting(for: input, rule: FormatRules.elseOnSameLine) + testFormatting(for: input, rule: .elseOnSameLine) } func testElseOnSameLineInferenceEdgeCase() { @@ -112,7 +112,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(elseOnNextLine: false) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, options: options, + testFormatting(for: input, rule: .elseOnSameLine, options: options, exclude: ["braces"]) } @@ -120,20 +120,20 @@ class WrappingTests: RulesTests { func testSingleLineGuardElseNotWrappedByDefault() { let input = "guard foo = bar else {}" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, exclude: ["wrapConditionalBodies"]) } func testSingleLineGuardElseNotUnwrappedByDefault() { let input = "guard foo = bar\nelse {}" - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, exclude: ["wrapConditionalBodies"]) } func testSingleLineGuardElseWrappedByDefaultIfBracesOnNextLine() { let input = "guard foo = bar else\n{}" let output = "guard foo = bar\nelse {}" - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, exclude: ["wrapConditionalBodies"]) } @@ -144,7 +144,7 @@ class WrappingTests: RulesTests { return } """ - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, exclude: ["wrapMultilineStatementBraces"]) } @@ -163,7 +163,7 @@ class WrappingTests: RulesTests { return } """ - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine) + testFormatting(for: input, output, rule: .elseOnSameLine) } func testWrappedMultilineGuardElseCorrectlyIndented() { @@ -185,7 +185,7 @@ class WrappingTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine) + testFormatting(for: input, output, rule: .elseOnSameLine) } // guardelse = nextLine @@ -193,14 +193,14 @@ class WrappingTests: RulesTests { func testSingleLineGuardElseNotWrapped() { let input = "guard foo = bar else {}" let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, options: options, exclude: ["wrapConditionalBodies"]) } func testSingleLineGuardElseNotUnwrapped() { let input = "guard foo = bar\nelse {}" let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, rule: .elseOnSameLine, options: options, exclude: ["wrapConditionalBodies"]) } @@ -208,7 +208,7 @@ class WrappingTests: RulesTests { let input = "guard foo = bar else\n{}" let output = "guard foo = bar\nelse {}" let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, options: options, exclude: ["wrapConditionalBodies"]) } @@ -227,7 +227,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, options: options, exclude: ["wrapMultilineStatementBraces"]) } @@ -247,7 +247,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(guardElsePosition: .auto) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, options: options) } @@ -268,7 +268,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(guardElsePosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, options: options, exclude: ["wrapMultilineStatementBraces"]) } @@ -276,7 +276,7 @@ class WrappingTests: RulesTests { let input = "guard foo = bar\nelse {}" let output = "guard foo = bar else {}" let options = FormatOptions(guardElsePosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, + testFormatting(for: input, output, rule: .elseOnSameLine, options: options) } @@ -295,7 +295,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.elseOnSameLine) + testFormatting(for: input, rule: .elseOnSameLine) } func testPreserveBlankLineBeforeElseOnSameLine() { @@ -314,7 +314,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(elseOnNextLine: false) - testFormatting(for: input, rule: FormatRules.elseOnSameLine, options: options) + testFormatting(for: input, rule: .elseOnSameLine, options: options) } func testPreserveBlankLineBeforeElseWithComments() { @@ -333,7 +333,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, rule: FormatRules.elseOnSameLine) + testFormatting(for: input, rule: .elseOnSameLine) } func testPreserveBlankLineBeforeElseDoesntAffectOtherCases() { @@ -379,7 +379,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(elseOnNextLine: false, guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: FormatRules.elseOnSameLine, options: options) + testFormatting(for: input, output, rule: .elseOnSameLine, options: options) } // MARK: - wrapConditionalBodies @@ -391,18 +391,18 @@ class WrappingTests: RulesTests { return } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testEmptyGuardReturnWithSpaceDoesNothing() { let input = "guard let foo = bar else { }" - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies, + testFormatting(for: input, rule: .wrapConditionalBodies, exclude: ["emptyBraces"]) } func testEmptyGuardReturnWithoutSpaceDoesNothing() { let input = "guard let foo = bar else {}" - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies, + testFormatting(for: input, rule: .wrapConditionalBodies, exclude: ["emptyBraces"]) } @@ -413,7 +413,7 @@ class WrappingTests: RulesTests { return baz } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testGuardBodyWithClosingBraceAlreadyOnNewlineWraps() { @@ -426,7 +426,7 @@ class WrappingTests: RulesTests { return } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testGuardContinueWithNoSpacesToCleanupWraps() { @@ -436,7 +436,7 @@ class WrappingTests: RulesTests { continue } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testGuardReturnWrapsSemicolonDelimitedStatements() { @@ -446,7 +446,7 @@ class WrappingTests: RulesTests { var baz = 0; let boo = 1; fatalError() } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testGuardReturnWrapsSemicolonDelimitedStatementsWithNoSpaces() { @@ -456,7 +456,7 @@ class WrappingTests: RulesTests { var baz=0;let boo=1;fatalError() } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies, + testFormatting(for: input, output, rule: .wrapConditionalBodies, exclude: ["spaceAroundOperators"]) } @@ -466,7 +466,7 @@ class WrappingTests: RulesTests { return } """ - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, rule: .wrapConditionalBodies) } func testGuardCommentSameLineUnchanged() { @@ -475,7 +475,7 @@ class WrappingTests: RulesTests { return } """ - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, rule: .wrapConditionalBodies) } func testGuardMultilineCommentSameLineUnchanged() { @@ -485,7 +485,7 @@ class WrappingTests: RulesTests { return } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testGuardTwoMultilineCommentsSameLine() { @@ -495,7 +495,7 @@ class WrappingTests: RulesTests { return /* Test comment 2 */ } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testNestedGuardElseIfStatementsPutOnNewline() { @@ -509,7 +509,7 @@ class WrappingTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testNestedGuardElseGuardStatementPutOnNewline() { @@ -521,7 +521,7 @@ class WrappingTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testGuardWithClosureOnlyWrapsElseBody() { @@ -531,7 +531,7 @@ class WrappingTests: RulesTests { return true } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testIfElseReturnsWrap() { @@ -545,7 +545,7 @@ class WrappingTests: RulesTests { return quux } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testIfElseBodiesWrap() { @@ -559,7 +559,7 @@ class WrappingTests: RulesTests { quux } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testIfElsesWithClosuresDontWrapClosures() { @@ -573,18 +573,18 @@ class WrappingTests: RulesTests { corge } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies) } func testEmptyIfElseBodiesWithSpaceDoNothing() { let input = "if foo { } else if baz { } else { }" - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies, + testFormatting(for: input, rule: .wrapConditionalBodies, exclude: ["emptyBraces"]) } func testEmptyIfElseBodiesWithoutSpaceDoNothing() { let input = "if foo {} else if baz {} else {}" - testFormatting(for: input, rule: FormatRules.wrapConditionalBodies, + testFormatting(for: input, rule: .wrapConditionalBodies, exclude: ["emptyBraces"]) } @@ -600,7 +600,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies, + testFormatting(for: input, output, rule: .wrapConditionalBodies, exclude: ["braces", "indent", "elseOnSameLine"]) } @@ -627,7 +627,7 @@ class WrappingTests: RulesTests { return quux } """ - testFormatting(for: input, output, rule: FormatRules.wrapConditionalBodies, + testFormatting(for: input, output, rule: .wrapConditionalBodies, exclude: ["braces", "indent", "elseOnSameLine"]) } @@ -640,7 +640,7 @@ class WrappingTests: RulesTests { print(foo) } """ - testFormatting(for: input, output, rule: FormatRules.wrapLoopBodies) + testFormatting(for: input, output, rule: .wrapLoopBodies) } func testWrapWhileLoop() { @@ -650,7 +650,7 @@ class WrappingTests: RulesTests { print(foo) } """ - testFormatting(for: input, output, rule: FormatRules.wrapLoopBodies) + testFormatting(for: input, output, rule: .wrapLoopBodies) } func testWrapRepeatWhileLoop() { @@ -660,7 +660,7 @@ class WrappingTests: RulesTests { print(foo) } while condition() """ - testFormatting(for: input, output, rule: FormatRules.wrapLoopBodies) + testFormatting(for: input, output, rule: .wrapLoopBodies) } // MARK: - wrap @@ -675,7 +675,7 @@ class WrappingTests: RulesTests { let baz = baz {} """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrapIfElseStatement() { @@ -693,7 +693,7 @@ class WrappingTests: RulesTests { bar {} """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) } func testWrapGuardStatement() { @@ -717,7 +717,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapClosure() { @@ -736,7 +736,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) } func testWrapClosure2() { @@ -755,7 +755,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) } func testWrapClosureWithAllmanBraces() { @@ -774,7 +774,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true, maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) } func testWrapClosure3() { @@ -789,7 +789,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) } func testWrapFunctionIfReturnTypeExceedsMaxWidth() { @@ -807,7 +807,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 25) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapFunctionIfReturnTypeExceedsMaxWidthWithXcodeIndentation() { @@ -832,7 +832,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 25) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth2() { @@ -848,7 +848,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation() { @@ -870,7 +870,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation2() { @@ -892,7 +892,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth3() { @@ -908,7 +908,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth3WithXcodeIndentation() { @@ -930,7 +930,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth4() { @@ -946,7 +946,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth4WithXcodeIndentation() { @@ -968,7 +968,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [FormatRules.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapChainedFunctionAfterSubscriptCollection() { @@ -980,7 +980,7 @@ class WrappingTests: RulesTests { .quuz() """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrapChainedFunctionInSubscriptCollection() { @@ -992,7 +992,7 @@ class WrappingTests: RulesTests { bar[baz.quuz()] """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrapThrowingFunctionIfReturnTypeExceedsMaxWidth() { @@ -1008,7 +1008,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 42) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) } func testWrapTypedThrowingFunctionIfReturnTypeExceedsMaxWidth() { @@ -1024,7 +1024,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 42) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) } func testNoWrapInterpolatedStringLiteral() { @@ -1032,21 +1032,21 @@ class WrappingTests: RulesTests { "a very long \\(string) literal" """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, rule: FormatRules.wrap, options: options) + testFormatting(for: input, rule: .wrap, options: options) } func testNoWrapAtUnspacedOperator() { let input = "let foo = bar+baz+quux" let output = "let foo =\n bar+baz+quux" let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["spaceAroundOperators"]) } func testNoWrapAtUnspacedEquals() { let input = "let foo=bar+baz+quux" let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, rule: FormatRules.wrap, options: options, + testFormatting(for: input, rule: .wrap, options: options, exclude: ["spaceAroundOperators"]) } @@ -1057,7 +1057,7 @@ class WrappingTests: RulesTests { .decode(FooBar.self) """ let options = FormatOptions(maxWidth: 50) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrapSingleParameter() { @@ -1068,7 +1068,7 @@ class WrappingTests: RulesTests { ) """ let options = FormatOptions(maxWidth: 50, noWrapOperators: [".", "="]) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrapFunctionArrow() { @@ -1078,7 +1078,7 @@ class WrappingTests: RulesTests { -> Int {} """ let options = FormatOptions(maxWidth: 14) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testNoWrapFunctionArrow() { @@ -1088,7 +1088,7 @@ class WrappingTests: RulesTests { ) -> Int {} """ let options = FormatOptions(maxWidth: 14, noWrapOperators: ["->"]) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testNoCrashWrap() { @@ -1108,7 +1108,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 10) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["unusedArguments"]) } @@ -1136,7 +1136,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapParameters: .preserve, maxWidth: 80) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options, + testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["indent", "wrapArguments"]) } @@ -1149,7 +1149,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 100) - let rules = [FormatRules.wrap, FormatRules.wrapArguments] + let rules: [FormatRule] = [.wrap, .wrapArguments] XCTAssertNoThrow(try format(input, rules: rules, options: options)) } @@ -1158,13 +1158,13 @@ class WrappingTests: RulesTests { button.setTitleColor(#colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1), for: .normal) """ let options = FormatOptions(maxWidth: 80, assetLiteralWidth: .visualWidth) - testFormatting(for: input, rule: FormatRules.wrap, options: options) + testFormatting(for: input, rule: .wrap, options: options) } func testWrapImageLiteral() { let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" let options = FormatOptions(maxWidth: 40, assetLiteralWidth: .visualWidth) - testFormatting(for: input, rule: FormatRules.wrap, options: options) + testFormatting(for: input, rule: .wrap, options: options) } func testNoWrapBeforeFirstArgumentInSingleLineStringInterpolation() { @@ -1172,7 +1172,7 @@ class WrappingTests: RulesTests { "a very long string literal with \\(interpolation) inside" """ let options = FormatOptions(maxWidth: 40) - testFormatting(for: input, rule: FormatRules.wrap, options: options) + testFormatting(for: input, rule: .wrap, options: options) } func testWrapBeforeFirstArgumentInMultineStringInterpolation() { @@ -1189,7 +1189,7 @@ class WrappingTests: RulesTests { \""" """ let options = FormatOptions(maxWidth: 40) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } // ternary expressions @@ -1206,7 +1206,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testRewrapsSimpleTernaryOperator() { @@ -1222,7 +1222,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrapComplexTernaryOperator() { @@ -1237,7 +1237,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testRewrapsComplexTernaryOperator() { @@ -1253,7 +1253,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrappedTernaryOperatorIndentsChainedCalls() { @@ -1270,7 +1270,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, rule: FormatRules.indent, options: options) + testFormatting(for: input, rule: .indent, options: options) } func testWrapsSimpleNestedTernaryOperator() { @@ -1285,7 +1285,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrapsDoubleNestedTernaryOperation() { @@ -1304,7 +1304,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testWrapsTripleNestedTernaryOperation() { @@ -1327,7 +1327,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testNoWrapTernaryWrappedWithinChildExpression() { @@ -1340,7 +1340,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) - testFormatting(for: input, rule: FormatRules.wrap, options: options) + testFormatting(for: input, rule: .wrap, options: options) } func testNoWrapTernaryWrappedWithinChildExpression2() { @@ -1354,7 +1354,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) - testFormatting(for: input, rule: FormatRules.wrap, options: options) + testFormatting(for: input, rule: .wrap, options: options) } func testNoWrapTernaryInsideStringLiteral() { @@ -1362,7 +1362,7 @@ class WrappingTests: RulesTests { "\\(true ? "Some string literal" : "Some other string")" """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) - testFormatting(for: input, rule: FormatRules.wrap, options: options) + testFormatting(for: input, rule: .wrap, options: options) } func testWrapTernaryInsideMultilineStringLiteral() { @@ -1379,7 +1379,7 @@ class WrappingTests: RulesTests { \""" """ let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) - testFormatting(for: input, output, rule: FormatRules.wrap, options: options) + testFormatting(for: input, output, rule: .wrap, options: options) } func testErrorNotReportedOnBlankLineAfterWrap() throws { @@ -1391,8 +1391,8 @@ class WrappingTests: RulesTests { ] """ let options = FormatOptions(truncateBlankLines: false, maxWidth: 40) - let changes = try lint(input, rules: [FormatRules.wrap, FormatRules.indent], options: options) - XCTAssertEqual(changes, [.init(line: 2, rule: FormatRules.wrap, filePath: nil)]) + let changes = try lint(input, rules: [.wrap, .indent], options: options) + XCTAssertEqual(changes, [.init(line: 2, rule: .wrap, filePath: nil)]) } // MARK: - wrapArguments @@ -1410,7 +1410,7 @@ class WrappingTests: RulesTests { Thing(), ]) """ - testFormatting(for: input, output, rule: FormatRules.wrapArguments, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapArguments, exclude: ["propertyType"]) } func testWrapArgumentsDoesntIndentTrailingComment() { @@ -1424,7 +1424,7 @@ class WrappingTests: RulesTests { bar: Int ) """ - testFormatting(for: input, output, rule: FormatRules.wrapArguments) + testFormatting(for: input, output, rule: .wrapArguments) } func testWrapArgumentsDoesntIndentClosingBracket() { @@ -1434,13 +1434,13 @@ class WrappingTests: RulesTests { ], ] """ - testFormatting(for: input, rule: FormatRules.wrapArguments) + testFormatting(for: input, rule: .wrapArguments) } func testWrapParametersDoesNotAffectFunctionDeclaration() { let input = "foo(\n bar _: Int,\n baz _: String\n)" let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapParametersClosureAfterParameterListDoesNotWrapClosureArguments() { @@ -1450,28 +1450,28 @@ class WrappingTests: RulesTests { quuz: 10) """ let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapParametersNotSetWrapArgumentsAfterFirstDefaultsToAfterFirst() { let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" let output = "func foo(bar _: Int,\n baz _: String) {}" let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersNotSetWrapArgumentsBeforeFirstDefaultsToBeforeFirst() { let input = "func foo(bar _: Int,\n baz _: String) {}" let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersNotSetWrapArgumentsPreserveDefaultsToPreserve() { let input = "func foo(\n bar _: Int,\n baz _: String) {}" let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapArguments: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersFunctionDeclarationClosingParenOnSameLine() { @@ -1487,7 +1487,7 @@ class WrappingTests: RulesTests { baz _: String) {} """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersFunctionDeclarationClosingParenOnNextLine() { @@ -1503,7 +1503,7 @@ class WrappingTests: RulesTests { ) {} """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersFunctionDeclarationClosingParenOnSameLineAndForce() { @@ -1519,7 +1519,7 @@ class WrappingTests: RulesTests { baz _: String) {} """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersFunctionDeclarationClosingParenOnNextLineAndForce() { @@ -1535,7 +1535,7 @@ class WrappingTests: RulesTests { ) {} """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersFunctionCallClosingParenOnNextLineAndForce() { @@ -1551,7 +1551,7 @@ class WrappingTests: RulesTests { baz: "foo") """ let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testIndentMultilineStringWhenWrappingArguments() { @@ -1564,7 +1564,7 @@ class WrappingTests: RulesTests { \"\"") """ let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testHandleXcodeTokenApplyingWrap() { @@ -1579,7 +1579,7 @@ class WrappingTests: RulesTests { ) """ let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testIssue1530() { @@ -1593,7 +1593,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["propertyType"]) } // MARK: wrapParameters @@ -1603,41 +1603,41 @@ class WrappingTests: RulesTests { func testAfterFirstPreserved() { let input = "func foo(bar _: Int,\n baz _: String) {}" let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testAfterFirstPreservedIndentFixed() { let input = "func foo(bar _: Int,\n baz _: String) {}" let output = "func foo(bar _: Int,\n baz _: String) {}" let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testAfterFirstPreservedNewlineRemoved() { let input = "func foo(bar _: Int,\n baz _: String\n) {}" let output = "func foo(bar _: Int,\n baz _: String) {}" let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testBeforeFirstPreserved() { let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testBeforeFirstPreservedIndentFixed() { let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testBeforeFirstPreservedNewlineAdded() { let input = "func foo(\n bar _: Int,\n baz _: String) {}" let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersAfterMultilineComment() { @@ -1651,7 +1651,7 @@ class WrappingTests: RulesTests { ) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } // MARK: afterFirst @@ -1660,14 +1660,14 @@ class WrappingTests: RulesTests { let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" let output = "func foo(bar _: Int,\n baz _: String) {}" let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testNoWrapInnerArguments() { let input = "func foo(\n bar _: Int,\n baz _: foo(bar, baz)\n) {}" let output = "func foo(bar _: Int,\n baz _: foo(bar, baz)) {}" let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } // MARK: afterFirst, maxWidth @@ -1681,7 +1681,7 @@ class WrappingTests: RulesTests { baz: String) -> Bool {} """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments", "wrap"]) } @@ -1695,7 +1695,7 @@ class WrappingTests: RulesTests { quux: Bool) -> Bool {} """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments", "wrap"]) } @@ -1708,7 +1708,7 @@ class WrappingTests: RulesTests { aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments", "wrap"]) } @@ -1728,7 +1728,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) testFormatting(for: input, [output, output2], - rules: [FormatRules.wrapArguments, FormatRules.wrap], + rules: [.wrapArguments, .wrap], options: options, exclude: ["unusedArguments"]) } @@ -1743,7 +1743,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.wrap], + rules: [.wrapArguments, .wrap], options: options, exclude: ["unusedArguments"]) } @@ -1771,7 +1771,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) testFormatting(for: input, [output, output2], - rules: [FormatRules.wrapArguments, FormatRules.wrap], + rules: [.wrapArguments, .wrap], options: options, exclude: ["unusedArguments"]) } @@ -1799,7 +1799,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 30) testFormatting(for: input, [output, output2], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options) } @@ -1812,7 +1812,7 @@ class WrappingTests: RulesTests { quux: Bool) -> LongReturnType {} """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 50) - testFormatting(for: input, [input, output2], rules: [FormatRules.wrapArguments], + testFormatting(for: input, [input, output2], rules: [.wrapArguments], options: options, exclude: ["unusedArguments"]) } @@ -1830,7 +1830,7 @@ class WrappingTests: RulesTests { and quux: Bool) -> LongReturnType {} """ let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } @@ -1840,21 +1840,21 @@ class WrappingTests: RulesTests { let input = "func foo(bar _: Int,\n baz _: String) {}" let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testLinebreakInsertedAtEndOfWrappedFunction() { let input = "func foo(\n bar _: Int,\n baz _: String) {}" let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testAfterFirstConvertedToBeforeFirst() { let input = "func foo(bar _: Int,\n baz _: String) {}" let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapParametersListBeforeFirstInClosureType() { @@ -1875,7 +1875,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options) } @@ -1897,7 +1897,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options) } @@ -1919,7 +1919,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options) } @@ -1941,7 +1941,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options) } @@ -1959,7 +1959,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options, exclude: ["unusedArguments"]) } @@ -1978,7 +1978,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options, exclude: ["unusedArguments"]) } @@ -1997,7 +1997,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options, exclude: ["unusedArguments"]) } @@ -2016,7 +2016,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options, exclude: ["unusedArguments"]) } @@ -2035,7 +2035,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options, exclude: ["unusedArguments"]) } @@ -2054,7 +2054,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: options, exclude: ["unusedArguments"]) } @@ -2072,7 +2072,7 @@ class WrappingTests: RulesTests { ) -> Bool {} """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } @@ -2081,7 +2081,7 @@ class WrappingTests: RulesTests { func foo(bar: Int, baz: String) -> Bool {} """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 42) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } @@ -2096,7 +2096,7 @@ class WrappingTests: RulesTests { ) -> Bool {} """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } @@ -2113,7 +2113,7 @@ class WrappingTests: RulesTests { ) -> Bool {} """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 26) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } @@ -2129,7 +2129,7 @@ class WrappingTests: RulesTests { ) -> LongReturnType {} """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 50) - testFormatting(for: input, [input, output2], rules: [FormatRules.wrapArguments], + testFormatting(for: input, [input, output2], rules: [.wrapArguments], options: options, exclude: ["unusedArguments"]) } @@ -2147,7 +2147,7 @@ class WrappingTests: RulesTests { ) -> LongReturnType {} """ let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } @@ -2167,7 +2167,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments], + testFormatting(for: input, [output], rules: [.wrapArguments], options: options) } @@ -2184,35 +2184,35 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 37) - testFormatting(for: input, rule: FormatRules.wrapArguments, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } func testNoWrapSubscriptWithSingleElement() { let input = "guard let foo = bar[0] {}" let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 20) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrap"]) } func testNoWrapArrayWithSingleElement() { let input = "let foo = [0]" let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 11) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrap"]) } func testNoWrapDictionaryWithSingleElement() { let input = "let foo = [bar: baz]" let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 15) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrap"]) } func testNoWrapImageLiteral() { let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrap"]) } @@ -2221,7 +2221,7 @@ class WrappingTests: RulesTests { if let color = #colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1) {} """ let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrap"]) } @@ -2234,7 +2234,7 @@ class WrappingTests: RulesTests { ] """ let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrap", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) } @@ -2244,21 +2244,21 @@ class WrappingTests: RulesTests { let input = "func foo(bar _: Int,\n baz _: String) {}" let output = "func foo(\n bar _: Int,\n baz _: String) {}" let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testParenOnSameLineWhenWrapBeforeFirstUnchanged() { let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" let output = "func foo(\n bar _: Int,\n baz _: String) {}" let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testParenOnSameLineWhenWrapBeforeFirstPreserved() { let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" let output = "func foo(\n bar _: Int,\n baz _: String) {}" let options = FormatOptions(wrapParameters: .preserve, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } // MARK: indent with tabs @@ -2269,7 +2269,7 @@ class WrappingTests: RulesTests { baz: Int) {} """ let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, tabWidth: 2) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } @@ -2284,7 +2284,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments"]) } @@ -2293,19 +2293,19 @@ class WrappingTests: RulesTests { func testWrapArgumentsDoesNotAffectFunctionDeclaration() { let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapArgumentsDoesNotAffectInit() { let input = "init(\n bar _: Int,\n baz _: String\n) {}" let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapArgumentsDoesNotAffectSubscript() { let input = "subscript(\n bar _: Int,\n baz _: String\n) -> Int {}" let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } // MARK: afterFirst @@ -2322,20 +2322,20 @@ class WrappingTests: RulesTests { baz _: String) """ let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testCorrectWrapIndentForNestedArguments() { let input = "foo(\nbar: (\nx: 0,\ny: 0\n),\nbaz: (\nx: 0,\ny: 0\n)\n)" let output = "foo(bar: (x: 0,\n y: 0),\n baz: (x: 0,\n y: 0))" let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testNoRemoveLinebreakAfterCommentInArguments() { let input = "a(b // comment\n)" let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testNoRemoveLinebreakAfterCommentInArguments2() { @@ -2346,7 +2346,7 @@ class WrappingTests: RulesTests { ) {} """ let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["indent"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["indent"]) } func testConsecutiveCodeCommentsNotIndented() { @@ -2357,7 +2357,7 @@ class WrappingTests: RulesTests { quux) """ let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } // MARK: afterFirst maxWidth @@ -2372,7 +2372,7 @@ class WrappingTests: RulesTests { quux: Bool) """ let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["unusedArguments", "wrap"]) } @@ -2381,7 +2381,7 @@ class WrappingTests: RulesTests { func testClosureInsideParensNotWrappedOntoNextLine() { let input = "foo({\n bar()\n})" let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["trailingClosures"]) } @@ -2400,7 +2400,7 @@ class WrappingTests: RulesTests { ) {} """ let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testNoMangleCommentedLinesWhenWrappingArgumentsWithNoCommas() { @@ -2416,7 +2416,7 @@ class WrappingTests: RulesTests { ) {} """ let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } // MARK: preserve @@ -2428,7 +2428,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .preserve) - testFormatting(for: input, rule: FormatRules.wrapArguments, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrapConditionalBodies"]) } @@ -2443,7 +2443,7 @@ class WrappingTests: RulesTests { baz) """ let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["redundantParens"]) } @@ -2456,7 +2456,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst, maxWidth: 40) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testNoWrapBeforeFirstArgumentInStringInterpolation2() { @@ -2466,7 +2466,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst, maxWidth: 50) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testNoWrapBeforeFirstArgumentInStringInterpolation3() { @@ -2476,7 +2476,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst, maxWidth: 40) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testNoWrapBeforeNestedFirstArgumentInStringInterpolation() { @@ -2486,7 +2486,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst, maxWidth: 45) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testNoWrapBeforeNestedFirstArgumentInStringInterpolation2() { @@ -2496,7 +2496,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst, maxWidth: 45) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapProtocolFuncParametersBeforeFirst() { @@ -2514,7 +2514,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, + testFormatting(for: input, output, rule: .wrapArguments, options: options) } @@ -2527,7 +2527,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .afterFirst, maxWidth: 46) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testNoWrapAfterFirstArgumentInStringInterpolation2() { @@ -2537,7 +2537,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .afterFirst, maxWidth: 50) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testNoWrapAfterNestedFirstArgumentInStringInterpolation() { @@ -2547,7 +2547,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .afterFirst, maxWidth: 55) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } // macros @@ -2565,7 +2565,7 @@ class WrappingTests: RulesTests { ) -> (T, String) """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, + testFormatting(for: input, output, rule: .wrapArguments, options: options) } @@ -2577,7 +2577,7 @@ class WrappingTests: RulesTests { let input = "[ foo,\n bar ]" let output = "[\n foo,\n bar\n]" let options = FormatOptions(trailingCommas: false, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.spaceInsideBrackets], + testFormatting(for: input, [output], rules: [.wrapArguments, .spaceInsideBrackets], options: options) } @@ -2585,7 +2585,7 @@ class WrappingTests: RulesTests { let input = "[foo,\n bar]" let output = "[\n foo,\n bar,\n]" let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], options: options) } @@ -2593,7 +2593,7 @@ class WrappingTests: RulesTests { let input = "[foo: [bar: baz,\n bar2: baz2]]" let output = "[foo: [\n bar: baz,\n bar2: baz2,\n]]" let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], options: options) } @@ -2601,7 +2601,7 @@ class WrappingTests: RulesTests { let input = "[\n foo: [bar: baz, bar2: baz2]]" let output = "[\n foo: [bar: baz, bar2: baz2],\n]" let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], options: options) } @@ -2609,14 +2609,14 @@ class WrappingTests: RulesTests { let input = "[foo: [bar: baz,\n bar2: baz2],\n foo2: [bar: baz,\n bar2: baz2]]" let output = "[\n foo: [\n bar: baz,\n bar2: baz2,\n ],\n foo2: [\n bar: baz,\n bar2: baz2,\n ],\n]" let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], options: options) } func testSpaceAroundEnumValuesInArray() { let input = "[\n .foo,\n .bar, .baz,\n]" let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } // MARK: beforeFirst maxWidth @@ -2631,7 +2631,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 26) testFormatting(for: input, [input, output2], - rules: [FormatRules.wrapArguments], options: options) + rules: [.wrapArguments], options: options) } // MARK: afterFirst @@ -2640,13 +2640,13 @@ class WrappingTests: RulesTests { let input = "[\n .foo,\n .bar,\n .baz,\n]" let output = "[.foo,\n .bar,\n .baz]" let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testNoRemoveLinebreakAfterCommentInElements() { let input = "[a, // comment\n]" let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapCollectionsConsecutiveCodeCommentsNotIndented() { @@ -2657,7 +2657,7 @@ class WrappingTests: RulesTests { quux] """ let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapCollectionsConsecutiveCodeCommentsNotIndentedInWrapBeforeFirst() { @@ -2670,7 +2670,7 @@ class WrappingTests: RulesTests { ] """ let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } // MARK: preserve @@ -2679,7 +2679,7 @@ class WrappingTests: RulesTests { let input = "[foo: [bar: baz,\n bar2: baz2]]" let output = "[foo: [bar: baz,\n bar2: baz2]]" let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], options: options) } @@ -2687,7 +2687,7 @@ class WrappingTests: RulesTests { let input = "[\n foo: [bar: baz, bar2: baz2]]" let output = "[\n foo: [bar: baz, bar2: baz2],\n]" let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], options: options) } @@ -2695,7 +2695,7 @@ class WrappingTests: RulesTests { let input = "[\n foo: [bar: baz]]" let output = "[\n foo: [bar: baz],\n]" let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.trailingCommas], + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], options: options) } @@ -2717,7 +2717,7 @@ class WrappingTests: RulesTests { wrapCollections: .beforeFirst, maxWidth: 26) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], options: options) + rules: [.wrapArguments], options: options) } // MARK: afterFirst maxWidth @@ -2734,7 +2734,7 @@ class WrappingTests: RulesTests { wrapCollections: .beforeFirst, maxWidth: 26) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments], options: options) + rules: [.wrapArguments], options: options) } // MARK: - wrapArguments Multiple Wraps On Same Line @@ -2752,7 +2752,7 @@ class WrappingTests: RulesTests { wrapCollections: .afterFirst, maxWidth: 28) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.wrap], options: options) + rules: [.wrapArguments, .wrap], options: options) } func testWrapAfterFirstWrapCollectionsBeforeFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { @@ -2768,7 +2768,7 @@ class WrappingTests: RulesTests { wrapCollections: .beforeFirst, maxWidth: 28) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.wrap], options: options) + rules: [.wrapArguments, .wrap], options: options) } func testNoMangleNestedFunctionCalls() { @@ -2796,7 +2796,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 40) testFormatting(for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.wrap], options: options) + rules: [.wrapArguments, .wrap], options: options) } func testWrapArguments_typealias_beforeFirst() { @@ -2813,7 +2813,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 40) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_multipleTypealiases_beforeFirst() { @@ -2836,7 +2836,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 45) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_afterFirst() { @@ -2852,7 +2852,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 40) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_multipleTypealiases_afterFirst() { @@ -2873,7 +2873,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 45) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_shorterThanMaxWidth() { @@ -2882,7 +2882,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 100) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently() { @@ -2899,7 +2899,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently2() { @@ -2921,7 +2921,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently3() { @@ -2939,7 +2939,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently4() { @@ -2959,7 +2959,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistentlyWithComment() { @@ -2979,7 +2979,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_singleTypePreserved() { @@ -2988,7 +2988,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 10) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["wrap"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrap"]) } func testWrapArguments_typealias_preservesCommentsBetweenTypes() { @@ -3003,7 +3003,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_preservesCommentsAfterTypes() { @@ -3015,7 +3015,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } func testWrapArguments_typealias_withAssociatedType() { @@ -3032,7 +3032,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 50) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) } // MARK: - -return wrap-if-multiline @@ -3057,7 +3057,7 @@ class WrappingTests: RulesTests { wrapReturnType: .ifMultiline ) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapReturnAndEffectOnMultilineFunctionDeclaration() { @@ -3081,7 +3081,7 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testDoesntWrapReturnAndEffectOnSingleLineFunctionDeclaration() { @@ -3096,7 +3096,7 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testDoesntWrapReturnAndTypedEffectOnSingleLineFunctionDeclaration() { @@ -3111,7 +3111,7 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapEffectOnMultilineFunctionDeclaration() { @@ -3136,7 +3136,7 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testUnwrapEffectOnMultilineFunctionDeclaration() { @@ -3161,7 +3161,7 @@ class WrappingTests: RulesTests { wrapEffects: .never ) - testFormatting(for: input, output, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, output, rule: .wrapArguments, options: options) } func testWrapArgumentsDoesntBreakFunctionDeclaration_issue_1776() { @@ -3181,7 +3181,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapEffects: .never) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["propertyType"]) } func testWrapEffectsNeverPreservesComments() { @@ -3194,7 +3194,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(closingParenPosition: .sameLine, wrapEffects: .never) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testWrapReturnOnMultilineFunctionDeclarationWithAfterFirst() { @@ -3216,7 +3216,7 @@ class WrappingTests: RulesTests { ) testFormatting( - for: input, output, rule: FormatRules.wrapArguments, options: options, + for: input, output, rule: .wrapArguments, options: options, exclude: ["indent"] ) } @@ -3240,7 +3240,7 @@ class WrappingTests: RulesTests { ) testFormatting( - for: input, output, rule: FormatRules.wrapArguments, options: options, + for: input, output, rule: .wrapArguments, options: options, exclude: ["indent"] ) } @@ -3265,7 +3265,7 @@ class WrappingTests: RulesTests { ) testFormatting( - for: input, output, rule: FormatRules.wrapArguments, options: options, + for: input, output, rule: .wrapArguments, options: options, exclude: ["indent"] ) } @@ -3284,7 +3284,7 @@ class WrappingTests: RulesTests { ) testFormatting( - for: input, rule: FormatRules.wrapArguments, options: options, + for: input, rule: .wrapArguments, options: options, exclude: ["indent"] ) } @@ -3300,7 +3300,7 @@ class WrappingTests: RulesTests { wrapReturnType: .ifMultiline ) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineArray() { @@ -3320,7 +3320,7 @@ class WrappingTests: RulesTests { wrapReturnType: .ifMultiline ) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineMethodCall() { @@ -3342,7 +3342,7 @@ class WrappingTests: RulesTests { wrapReturnType: .ifMultiline ) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["propertyType"]) } func testPreserveReturnOnMultilineFunctionDeclarationByDefault() { @@ -3358,7 +3358,7 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) - testFormatting(for: input, rule: FormatRules.wrapArguments, options: options) + testFormatting(for: input, rule: .wrapArguments, options: options) } // MARK: wrapMultilineStatementBraces @@ -3377,7 +3377,7 @@ class WrappingTests: RulesTests { print("statement body") } """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) } func testMultilineFuncBraceOnNextLine() { @@ -3396,7 +3396,7 @@ class WrappingTests: RulesTests { print("function body") } """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, exclude: ["wrapArguments", "unusedArguments"]) } @@ -3414,7 +3414,7 @@ class WrappingTests: RulesTests { print("function body") } """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, exclude: ["wrapArguments", "unusedArguments"]) } @@ -3432,7 +3432,7 @@ class WrappingTests: RulesTests { print(foo) } """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) } func testMultilineForLoopBraceOnNextLine2() { @@ -3444,7 +3444,7 @@ class WrappingTests: RulesTests { print(foo) } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, rule: .wrapMultilineStatementBraces) } func testMultilineForWhereLoopBraceOnNextLine() { @@ -3461,7 +3461,7 @@ class WrappingTests: RulesTests { print(foo) } """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) } func testMultilineGuardBraceOnNextLine() { @@ -3478,7 +3478,7 @@ class WrappingTests: RulesTests { print("statement body") } """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, exclude: ["braces", "elseOnSameLine"]) } @@ -3500,7 +3500,7 @@ class WrappingTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) } func testMultilineIfBraceOnSameLine() { @@ -3512,7 +3512,7 @@ class WrappingTests: RulesTests { print("statement body") } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, exclude: ["propertyType"]) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, exclude: ["propertyType"]) } func testSingleLineIfBraceOnSameLine() { @@ -3521,7 +3521,7 @@ class WrappingTests: RulesTests { print("statement body") } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, rule: .wrapMultilineStatementBraces) } func testSingleLineGuardBrace() { @@ -3530,7 +3530,7 @@ class WrappingTests: RulesTests { print("statement body") } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, rule: .wrapMultilineStatementBraces) } func testGuardElseOnOwnLineBraceNotWrapped() { @@ -3541,7 +3541,7 @@ class WrappingTests: RulesTests { print("statement body") } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, rule: .wrapMultilineStatementBraces) } func testMultilineGuardClosingBraceOnSameLine() { @@ -3549,7 +3549,7 @@ class WrappingTests: RulesTests { guard let foo = bar, let baz = quux else { return } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, + testFormatting(for: input, rule: .wrapMultilineStatementBraces, exclude: ["wrapConditionalBodies"]) } @@ -3561,7 +3561,7 @@ class WrappingTests: RulesTests { return } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, rule: .wrapMultilineStatementBraces) } func testMultilineClassBrace() { @@ -3572,7 +3572,7 @@ class WrappingTests: RulesTests { init() {} } """ - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces) + testFormatting(for: input, rule: .wrapMultilineStatementBraces) } func testMultilineClassBraceNotAppliedForXcodeIndentationMode() { @@ -3583,7 +3583,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, options: options) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, options: options) } func testMultilineBraceAppliedToTrailingClosure_wrapBeforeFirst() { @@ -3608,7 +3608,7 @@ class WrappingTests: RulesTests { wrapArguments: .beforeFirst, closingParenPosition: .sameLine ) - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, options: options, exclude: ["indent"]) } @@ -3637,8 +3637,8 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.indent, FormatRules.braces, + .wrapMultilineStatementBraces, + .indent, .braces, ], options: options) } @@ -3665,8 +3665,8 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.indent, + .wrapMultilineStatementBraces, + .indent, ], options: options, exclude: ["propertyType"]) } @@ -3690,7 +3690,7 @@ class WrappingTests: RulesTests { wrapArguments: .afterFirst, closingParenPosition: .sameLine ) - testFormatting(for: input, output, rule: FormatRules.wrapMultilineStatementBraces, + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, options: options, exclude: ["indent"]) } @@ -3708,8 +3708,8 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, [], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options, exclude: ["propertyType"]) } @@ -3727,7 +3727,7 @@ class WrappingTests: RulesTests { wrapArguments: .beforeFirst, closingParenPosition: .sameLine ) - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, + testFormatting(for: input, rule: .wrapMultilineStatementBraces, options: options, exclude: ["trailingClosures"]) } @@ -3757,8 +3757,8 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -3788,8 +3788,8 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -3819,8 +3819,8 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -3850,8 +3850,8 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -3881,8 +3881,8 @@ class WrappingTests: RulesTests { wrapEffects: .ifMultiline ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -3909,8 +3909,8 @@ class WrappingTests: RulesTests { closingParenPosition: .balanced ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -3938,8 +3938,8 @@ class WrappingTests: RulesTests { wrapEffects: .never ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -3967,8 +3967,8 @@ class WrappingTests: RulesTests { wrapEffects: .never ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -3989,8 +3989,8 @@ class WrappingTests: RulesTests { ) testFormatting(for: input, [], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -4018,8 +4018,8 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, [output], rules: [ - FormatRules.wrapMultilineStatementBraces, - FormatRules.wrapArguments, + .wrapMultilineStatementBraces, + .wrapArguments, ], options: options) } @@ -4035,7 +4035,7 @@ class WrappingTests: RulesTests { wrapArguments: .beforeFirst, closingParenPosition: .sameLine ) - testFormatting(for: input, rule: FormatRules.wrapMultilineStatementBraces, + testFormatting(for: input, rule: .wrapMultilineStatementBraces, options: options, exclude: ["trailingClosures"]) } @@ -4053,7 +4053,7 @@ class WrappingTests: RulesTests { wrapArguments: .beforeFirst, closingParenPosition: .sameLine ) - testFormatting(for: input, rules: [FormatRules.wrapMultilineStatementBraces, FormatRules.wrap], + testFormatting(for: input, rules: [.wrapMultilineStatementBraces, .wrap], options: options, exclude: ["indent", "redundantClosure", "wrapConditionalBodies"]) } @@ -4084,7 +4084,7 @@ class WrappingTests: RulesTests { {} """ testFormatting( - for: input, rules: [FormatRules.wrapArguments, FormatRules.indent], + for: input, rules: [.wrapArguments, .indent], options: FormatOptions(closingParenPosition: .sameLine, wrapConditions: .beforeFirst), exclude: ["propertyType"] ) @@ -4128,7 +4128,7 @@ class WrappingTests: RulesTests { let bar = bar {} """ testFormatting( - for: input, output, rule: FormatRules.wrapArguments, + for: input, output, rule: .wrapArguments, options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), exclude: ["wrapConditionalBodies"] ) @@ -4151,7 +4151,7 @@ class WrappingTests: RulesTests { else {} """ testFormatting( - for: input, rule: FormatRules.wrapArguments, + for: input, rule: .wrapArguments, options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), exclude: ["elseOnSameLine", "wrapConditionalBodies"] ) @@ -4199,7 +4199,7 @@ class WrappingTests: RulesTests { let bar = bar {} """ testFormatting( - for: input, output, rule: FormatRules.wrapArguments, + for: input, output, rule: .wrapArguments, options: FormatOptions(indent: " ", wrapConditions: .afterFirst), exclude: ["wrapConditionalBodies"] ) @@ -4242,7 +4242,7 @@ class WrappingTests: RulesTests { else { return } """ testFormatting( - for: input, [output], rules: [FormatRules.wrapArguments, FormatRules.indent], + for: input, [output], rules: [.wrapArguments, .indent], options: FormatOptions(wrapConditions: .afterFirst), exclude: ["wrapConditionalBodies"] ) @@ -4265,7 +4265,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) ) } @@ -4276,7 +4276,7 @@ class WrappingTests: RulesTests { """ testFormatting( for: input, - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120) ) } @@ -4297,7 +4297,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.indent], + rules: [.wrapArguments, .indent], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) ) } @@ -4332,7 +4332,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) ) } @@ -4353,7 +4353,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) ) } @@ -4383,7 +4383,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) ) } @@ -4423,7 +4423,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120) ) } @@ -4461,7 +4461,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120), exclude: ["wrapMultilineStatementBraces"] ) @@ -4481,7 +4481,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.indent], + rules: [.wrapArguments, .indent], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 25) ) } @@ -4501,7 +4501,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments, FormatRules.indent], + rules: [.wrapArguments, .indent], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 25) ) } @@ -4523,7 +4523,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], - rules: [FormatRules.wrapArguments], + rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .always, maxWidth: 120) ) } @@ -4535,14 +4535,14 @@ class WrappingTests: RulesTests { @objc func foo() {} """ - testFormatting(for: input, rule: FormatRules.wrapAttributes) + testFormatting(for: input, rule: .wrapAttributes) } func testPreserveUnwrappedFuncAttributeByDefault() { let input = """ @objc func foo() {} """ - testFormatting(for: input, rule: FormatRules.wrapAttributes) + testFormatting(for: input, rule: .wrapAttributes) } func testWrapFuncAttribute() { @@ -4554,7 +4554,7 @@ class WrappingTests: RulesTests { func foo() {} """ let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testWrapInitAttribute() { @@ -4566,7 +4566,7 @@ class WrappingTests: RulesTests { init() {} """ let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testMultipleAttributesNotSeparated() { @@ -4578,7 +4578,7 @@ class WrappingTests: RulesTests { func foo {} """ let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["redundantObjc"]) } @@ -4588,7 +4588,7 @@ class WrappingTests: RulesTests { func foo() {} """ let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testUnwrapFuncAttribute() { @@ -4600,7 +4600,7 @@ class WrappingTests: RulesTests { @available(iOS 14.0, *) func foo() {} """ let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testUnwrapFuncAttribute2() { @@ -4620,7 +4620,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testFuncAttributeStaysUnwrapped() { @@ -4628,7 +4628,7 @@ class WrappingTests: RulesTests { @objc func foo() {} """ let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testVarAttributeIsNotWrapped() { @@ -4639,7 +4639,7 @@ class WrappingTests: RulesTests { func foo() {} """ let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testWrapTypeAttribute() { @@ -4654,7 +4654,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.wrapAttributes, + rule: .wrapAttributes, options: options ) } @@ -4671,7 +4671,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.wrapAttributes, + rule: .wrapAttributes, options: options ) } @@ -4682,7 +4682,7 @@ class WrappingTests: RulesTests { struct Foo {} """ let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testUnwrapTypeAttribute() { @@ -4694,7 +4694,7 @@ class WrappingTests: RulesTests { @available(iOS 14.0, *) enum Foo {} """ let options = FormatOptions(typeAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testTypeAttributeStaysUnwrapped() { @@ -4702,7 +4702,7 @@ class WrappingTests: RulesTests { @objc class Foo {} """ let options = FormatOptions(typeAttributes: .sameLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testTestableImportIsNotWrapped() { @@ -4713,7 +4713,7 @@ class WrappingTests: RulesTests { class Foo {} """ let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testModifiersDontAffectAttributeWrapping() { @@ -4725,7 +4725,7 @@ class WrappingTests: RulesTests { override public func foo {} """ let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testClassFuncAttributeTreatedAsFunction() { @@ -4737,7 +4737,7 @@ class WrappingTests: RulesTests { class func foo {} """ let options = FormatOptions(funcAttributes: .prevLine, fragment: true) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testClassFuncAttributeNotTreatedAsType() { @@ -4745,7 +4745,7 @@ class WrappingTests: RulesTests { @objc class func foo {} """ let options = FormatOptions(typeAttributes: .prevLine, fragment: true) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testClassAttributeNotMistakenForClassLet() { @@ -4759,7 +4759,7 @@ class WrappingTests: RulesTests { let myClass = MyClass() """ let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) } func testClassImportAttributeNotTreatedAsType() { @@ -4767,7 +4767,7 @@ class WrappingTests: RulesTests { @testable import class Framework.Foo """ let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testWrapPrivateSetComputedVarAttributes() { @@ -4779,7 +4779,7 @@ class WrappingTests: RulesTests { private(set) dynamic var foo = Foo() """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) } func testWrapPrivateSetVarAttributes() { @@ -4791,7 +4791,7 @@ class WrappingTests: RulesTests { private(set) dynamic var foo = Foo() """ let options = FormatOptions(varAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) } func testDontWrapPrivateSetVarAttributes() { @@ -4803,7 +4803,7 @@ class WrappingTests: RulesTests { @objc private(set) dynamic var foo = Foo() """ let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) } func testWrapConvenienceInitAttribute() { @@ -4815,7 +4815,7 @@ class WrappingTests: RulesTests { public convenience init() {} """ let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testWrapPropertyWrapperAttributeVarAttributes() { @@ -4827,7 +4827,7 @@ class WrappingTests: RulesTests { var foo: Int """ let options = FormatOptions(varAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testWrapPropertyWrapperAttribute() { @@ -4839,7 +4839,7 @@ class WrappingTests: RulesTests { var foo: Int """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testDontWrapPropertyWrapperAttribute() { @@ -4851,7 +4851,7 @@ class WrappingTests: RulesTests { @OuterType.Wrapper var foo: Int """ let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testWrapGenericPropertyWrapperAttribute() { @@ -4863,7 +4863,7 @@ class WrappingTests: RulesTests { var foo: WrappedType """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testWrapGenericPropertyWrapperAttribute2() { @@ -4875,7 +4875,7 @@ class WrappingTests: RulesTests { var foo: WrappedType """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testAttributeOnComputedProperty() { @@ -4889,7 +4889,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testWrapAvailableAttributeUnderMaxWidth() { @@ -4901,7 +4901,7 @@ class WrappingTests: RulesTests { @available(*, unavailable, message: "This property is deprecated.") var foo: WrappedType """ let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testDoesntWrapAvailableAttributeWithLongMessage() { @@ -4919,7 +4919,7 @@ class WrappingTests: RulesTests { var foo: WrappedType """ let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testDoesntWrapComplexAttribute() { @@ -4930,7 +4930,7 @@ class WrappingTests: RulesTests { var foo: WrappedType """ let options = FormatOptions(closingParenPosition: .sameLine, varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testDoesntWrapComplexMultilineAttribute() { @@ -4939,7 +4939,7 @@ class WrappingTests: RulesTests { var foo: WrappedType """ let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testWrapsComplexAttribute() { @@ -4952,7 +4952,7 @@ class WrappingTests: RulesTests { var foo: WrappedType """ let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testWrapAttributesIndentsLineCorrectly() { @@ -4968,7 +4968,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) } func testComplexAttributesException() { @@ -4990,7 +4990,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine, complexAttributesExceptions: ["@SomeCustomAttr"]) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) } func testMixedComplexAndSimpleAttributes() { @@ -5039,7 +5039,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) } func testEscapingClosureNotMistakenForComplexAttribute() { @@ -5050,7 +5050,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testEscapingTypedThrowClosureNotMistakenForComplexAttribute() { @@ -5061,7 +5061,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testWrapOrDontWrapMultipleDeclarationsInClass() { @@ -5111,7 +5111,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: FormatRules.wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) } func testWrapOrDontAttributesInSwiftUIView() { @@ -5131,7 +5131,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testWrapAttributesInSwiftUIView() { @@ -5151,7 +5151,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(varAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } func testInlineMainActorAttributeNotWrapped() { @@ -5160,7 +5160,7 @@ class WrappingTests: RulesTests { var bar: @MainActor (Bar) -> Void """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, rule: FormatRules.wrapAttributes, options: options) + testFormatting(for: input, rule: .wrapAttributes, options: options) } // MARK: wrapEnumCases @@ -5184,7 +5184,7 @@ class WrappingTests: RulesTests { case m(String, String) } """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases) + testFormatting(for: input, output, rule: .wrapEnumCases) } func testMultilineEnumCasesWithNestedEnumsDoesNothing() { @@ -5201,7 +5201,7 @@ class WrappingTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.wrapEnumCases) + testFormatting(for: input, rule: .wrapEnumCases) } func testEnumCaseSplitOverMultipleLines() { @@ -5222,7 +5222,7 @@ class WrappingTests: RulesTests { case baz } """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases) + testFormatting(for: input, output, rule: .wrapEnumCases) } func testEnumCasesAlreadyWrappedOntoMultipleLines() { @@ -5240,7 +5240,7 @@ class WrappingTests: RulesTests { case quux } """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases) + testFormatting(for: input, output, rule: .wrapEnumCases) } func testEnumCasesIfValuesWithoutValuesDoesNothing() { @@ -5249,7 +5249,7 @@ class WrappingTests: RulesTests { case bar, baz, quux } """ - testFormatting(for: input, rule: FormatRules.wrapEnumCases, + testFormatting(for: input, rule: .wrapEnumCases, options: FormatOptions(wrapEnumCases: .withValues)) } @@ -5277,7 +5277,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.wrapEnumCases, + rule: .wrapEnumCases, options: FormatOptions(wrapEnumCases: .withValues) ) } @@ -5298,7 +5298,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, - rule: FormatRules.wrapEnumCases, + rule: .wrapEnumCases, options: FormatOptions(wrapEnumCases: .withValues) ) } @@ -5318,12 +5318,12 @@ class WrappingTests: RulesTests { case quux // quux } """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases) + testFormatting(for: input, output, rule: .wrapEnumCases) } func testNoWrapEnumStatementAllOnOneLine() { let input = "enum Foo { bar, baz }" - testFormatting(for: input, rule: FormatRules.wrapEnumCases) + testFormatting(for: input, rule: .wrapEnumCases) } func testNoConfuseIfCaseWithEnum() { @@ -5342,7 +5342,7 @@ class WrappingTests: RulesTests { } } """ - testFormatting(for: input, rule: FormatRules.wrapEnumCases, + testFormatting(for: input, rule: .wrapEnumCases, exclude: ["hoistPatternLet"]) } @@ -5358,7 +5358,7 @@ class WrappingTests: RulesTests { case bar } """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases, exclude: ["indent"]) + testFormatting(for: input, output, rule: .wrapEnumCases, exclude: ["indent"]) } func testNoMangleEnumCaseOnOpeningLine() { @@ -5374,12 +5374,12 @@ class WrappingTests: RulesTests { case desc(String) } """ - testFormatting(for: input, output, rule: FormatRules.wrapEnumCases, exclude: ["indent"]) + testFormatting(for: input, output, rule: .wrapEnumCases, exclude: ["indent"]) } func testNoWrapSingleLineEnumCases() { let input = "enum Foo { case foo, bar }" - testFormatting(for: input, rule: FormatRules.wrapEnumCases) + testFormatting(for: input, rule: .wrapEnumCases) } // MARK: wrapSwitchCases @@ -5407,7 +5407,7 @@ class WrappingTests: RulesTests { } } """ - testFormatting(for: input, output, rule: FormatRules.wrapSwitchCases) + testFormatting(for: input, output, rule: .wrapSwitchCases) } func testIfAfterSwitchCaseNotWrapped() { @@ -5422,7 +5422,7 @@ class WrappingTests: RulesTests { throw error } """ - testFormatting(for: input, rule: FormatRules.wrapSwitchCases) + testFormatting(for: input, rule: .wrapSwitchCases) } // MARK: wrapSingleLineComments @@ -5437,7 +5437,7 @@ class WrappingTests: RulesTests { // fgh """ - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 6)) } @@ -5450,7 +5450,7 @@ class WrappingTests: RulesTests { // h """ - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 14)) } @@ -5459,7 +5459,7 @@ class WrappingTests: RulesTests { // a b cde fg h """ - testFormatting(for: input, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 15)) } @@ -5473,7 +5473,7 @@ class WrappingTests: RulesTests { //fgh """ - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 6), exclude: ["spaceInsideComments"]) } @@ -5488,7 +5488,7 @@ class WrappingTests: RulesTests { /// fgh """ - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 7), exclude: ["docComments"]) } @@ -5502,7 +5502,7 @@ class WrappingTests: RulesTests { ///fgh """ - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 6), exclude: ["spaceInsideComments", "docComments"]) } @@ -5522,7 +5522,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 14), exclude: ["docComments"]) } @@ -5540,7 +5540,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 29), exclude: ["wrap"]) } @@ -5549,7 +5549,7 @@ class WrappingTests: RulesTests { /// See [Link](https://www.domain.com/pathextension/pathextension/pathextension/pathextension/pathextension/pathextension). """ - testFormatting(for: input, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 100), exclude: ["docComments"]) } @@ -5558,7 +5558,7 @@ class WrappingTests: RulesTests { /// Link to SDK documentation - https://docs.adyen.com/checkout/3d-secure/native-3ds2/api-integration#collect-the-3d-secure-2-device-fingerprint-from-an-ios-app """ - testFormatting(for: input, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 80)) } @@ -5571,7 +5571,7 @@ class WrappingTests: RulesTests { /// http://another-very-long-url-that-wont-fit-on-one-line """ - testFormatting(for: input, output, rule: FormatRules.wrapSingleLineComments, + testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 40), exclude: ["docComments"]) } @@ -5595,7 +5595,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) } func testUnwrapsAssignmentOperatorInIfExpressionAssignment() { @@ -5617,7 +5617,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) } func testUnwrapsAssignmentOperatorInIfExpressionFollowingComment() { @@ -5643,7 +5643,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) } func testWrapIfAssignmentWithoutIntroducer() { @@ -5664,7 +5664,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) } func testWrapSwitchAssignmentWithoutIntroducer() { @@ -5687,7 +5687,7 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) } func testWrapSwitchAssignmentWithComplexLValue() { @@ -5710,6 +5710,6 @@ class WrappingTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [FormatRules.wrapMultilineConditionalAssignment, FormatRules.indent]) + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) } } diff --git a/Tests/RulesTests.swift b/Tests/RulesTests.swift index 3844b5d4d..7498c70d6 100644 --- a/Tests/RulesTests.swift +++ b/Tests/RulesTests.swift @@ -47,6 +47,13 @@ class RulesTests: XCTestCase { options: FormatOptions = .default, exclude: [String] = [], file: StaticString = #file, line: UInt = #line) { + // Always make sure the rule registry is up-to-date before running the tests + do { + try FormatRules.generateRuleRegistryIfNecessary() + } catch { + XCTFail("Encountered error generating rule registry: \(error.localizedDescription)", file: file, line: line) + } + var options = options if options.timeout == FormatOptions.default.timeout { // Make breakpoint debugging easier by increasing timeout diff --git a/Tests/SwiftFormatTests.swift b/Tests/SwiftFormatTests.swift index c3a7c479d..7d8635d0c 100644 --- a/Tests/SwiftFormatTests.swift +++ b/Tests/SwiftFormatTests.swift @@ -67,7 +67,7 @@ class SwiftFormatTests: XCTestCase { return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 73) + XCTAssertGreaterThanOrEqual(files.count, 180) } func testInputFilesMatchOutputFilesForSameOutput() { @@ -78,7 +78,7 @@ class SwiftFormatTests: XCTestCase { return { files.append(inputURL) } } XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 73) + XCTAssertGreaterThanOrEqual(files.count, 180) } func testInputFileNotEnumeratedWhenExcluded() { @@ -92,8 +92,16 @@ class SwiftFormatTests: XCTestCase { XCTAssertEqual(inputURL, outputURL) return { files.append(inputURL) } } + + var allFiles = [URL]() + let allFilesInputURL = URL(fileURLWithPath: #file).deletingLastPathComponent().deletingLastPathComponent() + _ = enumerateFiles(withInputURL: allFilesInputURL, outputURL: allFilesInputURL) { inputURL, outputURL, _ in + XCTAssertEqual(inputURL, outputURL) + return { allFiles.append(inputURL) } + } + XCTAssertEqual(errors.count, 0) - XCTAssertEqual(files.count, 46) + XCTAssertLessThan(files.count, allFiles.count) } // MARK: format function @@ -119,16 +127,16 @@ class SwiftFormatTests: XCTestCase { func testLintWithDefaultRules() { let input = "foo () " XCTAssertEqual(try lint(input), [ - .init(line: 1, rule: FormatRules.linebreakAtEndOfFile, filePath: nil), - .init(line: 1, rule: FormatRules.spaceAroundParens, filePath: nil), - .init(line: 1, rule: FormatRules.trailingSpace, filePath: nil), + .init(line: 1, rule: .linebreakAtEndOfFile, filePath: nil), + .init(line: 1, rule: .spaceAroundParens, filePath: nil), + .init(line: 1, rule: .trailingSpace, filePath: nil), ]) } func testLintConsecutiveBlankLinesAtEndOfFile() { let input = "foo\n\n" XCTAssertEqual(try lint(input), [ - .init(line: 2, rule: FormatRules.consecutiveBlankLines, filePath: nil), + .init(line: 2, rule: .consecutiveBlankLines, filePath: nil), ]) } @@ -277,7 +285,7 @@ class SwiftFormatTests: XCTestCase { let offset2 = SourceOffset(line: 2, column: 1) let offset3 = SourceOffset(line: 5, column: 1) let offset4 = SourceOffset(line: 6, column: 1) - let output = try format(input, rules: [FormatRules.consecutiveBlankLines]) + let output = try format(input, rules: [.consecutiveBlankLines]) let expected3 = SourceOffset(line: 4, column: 1) let expected4 = SourceOffset(line: 5, column: 1) XCTAssertEqual(newOffset(for: offset1, in: output, tabWidth: 1), offset1) @@ -319,6 +327,6 @@ class SwiftFormatTests: XCTestCase { func testLinebreakInferredForBlankLinesBetweenScopes() { let input = "class Foo {\r func bar() {\r }\r func baz() {\r }\r}" let output = "class Foo {\r func bar() {\r }\r\r func baz() {\r }\r}" - XCTAssertEqual(try format(input, rules: [FormatRules.blankLinesBetweenScopes]), output) + XCTAssertEqual(try format(input, rules: [.blankLinesBetweenScopes]), output) } } From 8ae2da99ff3556a36fcc1059814d44cb1c5e8b28 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 29 Jul 2024 09:36:18 -0700 Subject: [PATCH 27/52] Update `exclude` to reference rules using static member lookup (#1787) --- Tests/RulesTests+Braces.swift | 14 +- Tests/RulesTests+General.swift | 24 +- Tests/RulesTests+Hoisting.swift | 66 +++--- Tests/RulesTests+Indentation.swift | 166 +++++++------- Tests/RulesTests+Linebreaks.swift | 34 +-- Tests/RulesTests+Organization.swift | 172 +++++++------- Tests/RulesTests+Parens.swift | 58 ++--- Tests/RulesTests+Redundancy.swift | 340 ++++++++++++++-------------- Tests/RulesTests+Spacing.swift | 88 +++---- Tests/RulesTests+Syntax.swift | 124 +++++----- Tests/RulesTests+Wrapping.swift | 264 ++++++++++----------- Tests/RulesTests.swift | 8 +- 12 files changed, 679 insertions(+), 679 deletions(-) diff --git a/Tests/RulesTests+Braces.swift b/Tests/RulesTests+Braces.swift index fb6036b74..21f13171f 100644 --- a/Tests/RulesTests+Braces.swift +++ b/Tests/RulesTests+Braces.swift @@ -62,7 +62,7 @@ class BracesTests: RulesTests { func testKnRExtraSpaceNotAddedBeforeBrace() { let input = "foo({ bar })" - testFormatting(for: input, rule: .braces, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .braces, exclude: [.trailingClosures]) } func testKnRLinebreakNotRemovedBeforeInlineBlockNot() { @@ -81,7 +81,7 @@ class BracesTests: RulesTests { }(), ] """ - testFormatting(for: input, rule: .braces, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .braces, exclude: [.redundantClosure]) } func testKnRNoMangleClosureReturningClosure() { @@ -142,7 +142,7 @@ class BracesTests: RulesTests { } """ let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, rule: .braces, options: options, exclude: ["indent"]) + testFormatting(for: input, rule: .braces, options: options, exclude: [.indent]) } func testKnRClosingBraceWrapped() { @@ -291,7 +291,7 @@ class BracesTests: RulesTests { } """ testFormatting(for: input, output, rule: .braces, - exclude: ["wrapMultilineStatementBraces"]) + exclude: [.wrapMultilineStatementBraces]) } func testBraceNotUnwrappedIfWrapMultilineStatementBracesRuleDisabled() { @@ -342,7 +342,7 @@ class BracesTests: RulesTests { let input = "foo({\n bar\n})" let options = FormatOptions(allmanBraces: true) testFormatting(for: input, rule: .braces, options: options, - exclude: ["trailingClosures"]) + exclude: [.trailingClosures]) } func testAllmanBraceDoClauseIndent() { @@ -357,7 +357,7 @@ class BracesTests: RulesTests { let output = "do\n{\n try foo\n}\ncatch\n{\n}" let options = FormatOptions(allmanBraces: true) testFormatting(for: input, output, rule: .braces, options: options, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testAllmanBraceDoThrowsCatchClauseIndent() { @@ -365,7 +365,7 @@ class BracesTests: RulesTests { let output = "do throws(Foo)\n{\n try foo\n}\ncatch\n{\n}" let options = FormatOptions(allmanBraces: true) testFormatting(for: input, output, rule: .braces, options: options, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testAllmanBraceRepeatWhileIndent() { diff --git a/Tests/RulesTests+General.swift b/Tests/RulesTests+General.swift index 31f0dc49d..76805a9d6 100644 --- a/Tests/RulesTests+General.swift +++ b/Tests/RulesTests+General.swift @@ -42,7 +42,7 @@ class GeneralTests: RulesTests { } """ testFormatting(for: input, output, rule: .initCoderUnavailable, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testInitCoderUnavailableFatalErrorNilDisabled() { @@ -157,7 +157,7 @@ class GeneralTests: RulesTests { """ let options = FormatOptions(initCoderNil: true) testFormatting(for: input, output, rule: .initCoderUnavailable, - options: options, exclude: ["modifierOrder"]) + options: options, exclude: [.modifierOrder]) } // MARK: - trailingCommas @@ -277,7 +277,7 @@ class GeneralTests: RulesTests { Int ]).self """ - testFormatting(for: input, rule: .trailingCommas, exclude: ["propertyType"]) + testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) } func testTrailingCommaNotAddedToTypeDeclaration() { @@ -324,7 +324,7 @@ class GeneralTests: RulesTests { String: Int ]]() """ - testFormatting(for: input, rule: .trailingCommas, exclude: ["propertyType"]) + testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) } func testTrailingCommaNotAddedToTypeDeclaration6() { @@ -337,7 +337,7 @@ class GeneralTests: RulesTests { ]) ]]() """ - testFormatting(for: input, rule: .trailingCommas, exclude: ["propertyType"]) + testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) } func testTrailingCommaNotAddedToTypeDeclaration7() { @@ -446,7 +446,7 @@ class GeneralTests: RulesTests { let input = "// foobar" let options = FormatOptions(fileHeader: "// foobar") testFormatting(for: input, rule: .fileHeader, options: options, - exclude: ["linebreakAtEndOfFile"]) + exclude: [.linebreakAtEndOfFile]) } func testReplaceHeaderWhenFileContainsNoCode2() { @@ -535,7 +535,7 @@ class GeneralTests: RulesTests { func testNoStripHeaderDocWithNewlineBeforeCode() { let input = "/// Header doc\n\nclass Foo {}" let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options, exclude: ["docComments"]) + testFormatting(for: input, rule: .fileHeader, options: options, exclude: [.docComments]) } func testNoDuplicateHeaderIfMissingTrailingBlankLine() { @@ -986,7 +986,7 @@ class GeneralTests: RulesTests { """ let options = FormatOptions(swiftVersion: "4.2") testFormatting(for: input, output, rule: .strongifiedSelf, options: options, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testBacktickedSelfConvertedToSelfInIf() { @@ -1002,7 +1002,7 @@ class GeneralTests: RulesTests { """ let options = FormatOptions(swiftVersion: "4.2") testFormatting(for: input, output, rule: .strongifiedSelf, options: options, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testBacktickedSelfNotConvertedIfVersionLessThan4_2() { @@ -1013,7 +1013,7 @@ class GeneralTests: RulesTests { """ let options = FormatOptions(swiftVersion: "4.1.5") testFormatting(for: input, rule: .strongifiedSelf, options: options, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testBacktickedSelfNotConvertedIfVersionUnspecified() { @@ -1023,7 +1023,7 @@ class GeneralTests: RulesTests { } """ testFormatting(for: input, rule: .strongifiedSelf, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testBacktickedSelfNotConvertedIfNotConditional() { @@ -1184,7 +1184,7 @@ class GeneralTests: RulesTests { let input = "if [0] == foo.bar[0]\n{ baz() }" let output = "if foo.bar[0] == [0]\n{ baz() }" testFormatting(for: input, output, rule: .yodaConditions, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testYodaConditionInSecondClauseOfIfStatement() { diff --git a/Tests/RulesTests+Hoisting.swift b/Tests/RulesTests+Hoisting.swift index 829e6671e..bb7c8ed63 100644 --- a/Tests/RulesTests+Hoisting.swift +++ b/Tests/RulesTests+Hoisting.swift @@ -39,7 +39,7 @@ class HoistingTests: RulesTests { """ testFormatting(for: input, output, rule: .hoistTry, options: FormatOptions(swiftVersion: "5.5"), - exclude: ["hoistAwait"]) + exclude: [.hoistAwait]) } func testHoistTryInsideStringInterpolation3() { @@ -111,7 +111,7 @@ class HoistingTests: RulesTests { let output = """ try array.append(contentsOf: await asyncFunction(param1: param1)) """ - testFormatting(for: input, output, rule: .hoistTry, exclude: ["hoistAwait"]) + testFormatting(for: input, output, rule: .hoistTry, exclude: [.hoistAwait]) } func testNoHoistTryInsideXCTAssert() { @@ -160,21 +160,21 @@ class HoistingTests: RulesTests { let input = "let foo=bar(contentsOf:try baz())" let output = "let foo=try bar(contentsOf:baz())" testFormatting(for: input, output, rule: .hoistTry, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testHoistTryInExpressionWithExcessSpaces() { let input = "let foo = bar ( contentsOf: try baz() )" let output = "let foo = try bar ( contentsOf: baz() )" testFormatting(for: input, output, rule: .hoistTry, - exclude: ["spaceAroundParens", "spaceInsideParens"]) + exclude: [.spaceAroundParens, .spaceInsideParens]) } func testHoistTryWithReturn() { let input = "return .enumCase(try await service.greet())" let output = "return try .enumCase(await service.greet())" testFormatting(for: input, output, rule: .hoistTry, - exclude: ["hoistAwait"]) + exclude: [.hoistAwait]) } func testHoistDeeplyNestedTrys() { @@ -205,14 +205,14 @@ class HoistingTests: RulesTests { let input = "let variable = String(try await asyncFunction())" let output = "let variable = try String(await asyncFunction())" testFormatting(for: input, output, rule: .hoistTry, - exclude: ["hoistAwait"]) + exclude: [.hoistAwait]) } func testHoistTryWithAssignment() { let input = "let variable = (try await asyncFunction())" let output = "let variable = try (await asyncFunction())" testFormatting(for: input, output, rule: .hoistTry, - exclude: ["hoistAwait"]) + exclude: [.hoistAwait]) } func testHoistTryOnlyOne() { @@ -454,7 +454,7 @@ class HoistingTests: RulesTests { let output = "if await !(isSomething()) {}" testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), - exclude: ["redundantParens"]) + exclude: [.redundantParens]) } func testHoistAwaitInsideArgument() { @@ -465,7 +465,7 @@ class HoistingTests: RulesTests { await array.append(contentsOf: try asyncFunction(param1: param1)) """ testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) } func testHoistAwaitInsideStringInterpolation() { @@ -483,7 +483,7 @@ class HoistingTests: RulesTests { await "Hello \\(try someValue())" """ testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) } func testNoHoistAwaitInsideDo() { @@ -520,7 +520,7 @@ class HoistingTests: RulesTests { let input = "let foo=bar(contentsOf:await baz())" let output = "let foo=await bar(contentsOf:baz())" testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["spaceAroundOperators"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.spaceAroundOperators]) } func testHoistAwaitInExpressionWithExcessSpaces() { @@ -528,14 +528,14 @@ class HoistingTests: RulesTests { let output = "let foo = await bar ( contentsOf: baz() )" testFormatting(for: input, output, rule: .hoistAwait, options: FormatOptions(swiftVersion: "5.5"), - exclude: ["spaceAroundParens", "spaceInsideParens"]) + exclude: [.spaceAroundParens, .spaceInsideParens]) } func testHoistAwaitWithReturn() { let input = "return .enumCase(try await service.greet())" let output = "return await .enumCase(try service.greet())" testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) } func testHoistDeeplyNestedAwaits() { @@ -576,14 +576,14 @@ class HoistingTests: RulesTests { let input = "let variable = String(try await asyncFunction())" let output = "let variable = await String(try asyncFunction())" testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) } func testHoistAwaitWithAssignment() { let input = "let variable = (try await asyncFunction())" let output = "let variable = await (try asyncFunction())" testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["hoistTry"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) } func testHoistAwaitInRedundantScopePriorToNumber() { @@ -642,14 +642,14 @@ class HoistingTests: RulesTests { let input = "let foo = bar + (await baz)" let output = "let foo = await bar + (baz)" testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["redundantParens"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.redundantParens]) } func testHoistAwaitAfterUnknownOperator() { let input = "let foo = bar ??? (await baz)" let output = "let foo = await bar ??? (baz)" testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["redundantParens"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.redundantParens]) } func testNoHoistAwaitAfterCapturingOperator() { @@ -661,7 +661,7 @@ class HoistingTests: RulesTests { func testNoHoistAwaitInMacroArgument() { let input = "#expect (await monitor.isAvailable == false)" testFormatting(for: input, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: ["spaceAroundParens"]) + options: FormatOptions(swiftVersion: "5.5"), exclude: [.spaceAroundParens]) } // MARK: - hoistPatternLet @@ -716,7 +716,7 @@ class HoistingTests: RulesTests { func testHoistIfArgIsNamespacedEnumCaseLiteralInParens() { let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" let output = "switch foo {\ncase let (Foo.bar(baz)):\n}" - testFormatting(for: input, output, rule: .hoistPatternLet, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .hoistPatternLet, exclude: [.redundantParens]) } func testHoistIfFirstArgIsUnderscore() { @@ -741,7 +741,7 @@ class HoistingTests: RulesTests { let input = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" let output = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" testFormatting(for: input, output, rule: .hoistPatternLet, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) + exclude: [.wrapSwitchCases, .sortSwitchCases]) } func testHoistNewlineSeparatedSwitchCaseLets() { @@ -760,7 +760,7 @@ class HoistingTests: RulesTests { """ testFormatting(for: input, output, rule: .hoistPatternLet, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) + exclude: [.wrapSwitchCases, .sortSwitchCases]) } func testHoistCatchLet() { @@ -776,7 +776,7 @@ class HoistingTests: RulesTests { func testNoHoistClosureVariables() { let input = "foo({ let bar = 5 })" - testFormatting(for: input, rule: .hoistPatternLet, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .hoistPatternLet, exclude: [.trailingClosures]) } // TODO: this should actually hoist the let, but that's tricky to implement without @@ -784,20 +784,20 @@ class HoistingTests: RulesTests { func testHoistSwitchCaseWithNestedParens() { let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), Foo.bar): break\n}" testFormatting(for: input, rule: .hoistPatternLet, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } // TODO: this could actually hoist the let by one level, but that's tricky to implement func testNoOverHoistSwitchCaseWithNestedParens() { let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), bar): break\n}" testFormatting(for: input, rule: .hoistPatternLet, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testNoHoistLetWithEmptArg() { let input = "if .foo(let _) = bar {}" testFormatting(for: input, rule: .hoistPatternLet, - exclude: ["redundantLet", "redundantPattern"]) + exclude: [.redundantLet, .redundantPattern]) } func testHoistLetWithNoSpaceAfterCase() { @@ -828,7 +828,7 @@ class HoistingTests: RulesTests { // Hoisting in this case causes a compilation error as-of Swift 5.3 // See: https://github.com/nicklockwood/SwiftFormat/issues/768 let input = "if case .some(Optional.some(let foo)) = bar else {}" - testFormatting(for: input, rule: .hoistPatternLet, exclude: ["typeSugar"]) + testFormatting(for: input, rule: .hoistPatternLet, exclude: [.typeSugar]) } // hoist = false @@ -878,7 +878,7 @@ class HoistingTests: RulesTests { """ let options = FormatOptions(hoistPatternLet: false) testFormatting(for: input, rule: .hoistPatternLet, options: options, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoUnhoistSwitchCaseLetFollowedByWhere() { @@ -922,7 +922,7 @@ class HoistingTests: RulesTests { let output = "switch foo {\ncase (.bar(let baz)):\n}" let options = FormatOptions(hoistPatternLet: false) testFormatting(for: input, output, rule: .hoistPatternLet, options: options, - exclude: ["redundantParens"]) + exclude: [.redundantParens]) } func testUnhoistIfArgIsNamespacedEnumCaseLiteral() { @@ -937,7 +937,7 @@ class HoistingTests: RulesTests { let output = "switch foo {\ncase (Foo.bar(let baz)):\n}" let options = FormatOptions(hoistPatternLet: false) testFormatting(for: input, output, rule: .hoistPatternLet, options: options, - exclude: ["redundantParens"]) + exclude: [.redundantParens]) } func testUnhoistIfArgIsUnderscore() { @@ -959,7 +959,7 @@ class HoistingTests: RulesTests { let output = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" let options = FormatOptions(hoistPatternLet: false) testFormatting(for: input, output, rule: .hoistPatternLet, options: options, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) + exclude: [.wrapSwitchCases, .sortSwitchCases]) } func testUnhoistCommaSeparatedSwitchCaseLets2() { @@ -967,7 +967,7 @@ class HoistingTests: RulesTests { let output = "switch foo {\ncase Foo.foo(let bar), Foo.bar(let bar):\n}" let options = FormatOptions(hoistPatternLet: false) testFormatting(for: input, output, rule: .hoistPatternLet, options: options, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) + exclude: [.wrapSwitchCases, .sortSwitchCases]) } func testUnhoistCatchLet() { @@ -999,7 +999,7 @@ class HoistingTests: RulesTests { let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" let options = FormatOptions(hoistPatternLet: false) testFormatting(for: input, rule: .hoistPatternLet, options: options, - exclude: ["redundantParens"]) + exclude: [.redundantParens]) } func testNoDeleteCommentWhenUnhoistingWrappedLet() { @@ -1017,7 +1017,7 @@ class HoistingTests: RulesTests { let options = FormatOptions(hoistPatternLet: false) testFormatting(for: input, output, rule: .hoistPatternLet, - options: options, exclude: ["wrapSwitchCases", "sortSwitchCases"]) + options: options, exclude: [.wrapSwitchCases, .sortSwitchCases]) } func testMultilineGuardLet() { diff --git a/Tests/RulesTests+Indentation.swift b/Tests/RulesTests+Indentation.swift index 45c645626..b33aa0b0e 100644 --- a/Tests/RulesTests+Indentation.swift +++ b/Tests/RulesTests+Indentation.swift @@ -35,7 +35,7 @@ class IndentTests: RulesTests { func testNestedScope() { let input = "foo(\nbar {\n}\n)" let output = "foo(\n bar {\n }\n)" - testFormatting(for: input, output, rule: .indent, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.emptyBraces]) } func testNestedScopeOnSameLine() { @@ -88,7 +88,7 @@ class IndentTests: RulesTests { paymentFormURL: .paymentForm) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentPreservedForNestedWrappedParameters2() { @@ -99,7 +99,7 @@ class IndentTests: RulesTests { paymentFormURL: .paymentForm)) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentPreservedForNestedWrappedParameters3() { @@ -112,7 +112,7 @@ class IndentTests: RulesTests { ) """ let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentTrailingClosureInParensContainingUnwrappedArguments() { @@ -206,7 +206,7 @@ class IndentTests: RulesTests { func testNoIndentBlankLines() { let input = "{\n\n// foo\n}" let output = "{\n\n // foo\n}" - testFormatting(for: input, output, rule: .indent, exclude: ["blankLinesAtStartOfScope"]) + testFormatting(for: input, output, rule: .indent, exclude: [.blankLinesAtStartOfScope]) } func testNestedBraces() { @@ -224,13 +224,13 @@ class IndentTests: RulesTests { func testBraceIndentAfterClosingScope() { let input = "foo(bar(baz), {\nquux\nbleem\n})" let output = "foo(bar(baz), {\n quux\n bleem\n})" - testFormatting(for: input, output, rule: .indent, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .indent, exclude: [.trailingClosures]) } func testBraceIndentAfterLineWithParens() { let input = "({\nfoo()\nbar\n})" let output = "({\n foo()\n bar\n})" - testFormatting(for: input, output, rule: .indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantParens]) } func testUnindentClosingParenAroundBraces() { @@ -298,7 +298,7 @@ class IndentTests: RulesTests { } ) """ - testFormatting(for: input, rule: .indent, exclude: ["wrapArguments"]) + testFormatting(for: input, rule: .indent, exclude: [.wrapArguments]) } func testIndentWrappedClosureParameters() { @@ -346,7 +346,7 @@ class IndentTests: RulesTests { return x + y } """ - testFormatting(for: input, rule: .indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: [.propertyType]) } func testIndentWrappedClosureCaptureListWithUnwrappedParameters() { @@ -373,7 +373,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(closingParenPosition: .sameLine) - testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentAllmanTrailingClosureArguments() { @@ -389,7 +389,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentAllmanTrailingClosureArguments2() { @@ -423,7 +423,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(allmanBraces: true) testFormatting(for: input, rule: .indent, options: options, - exclude: ["redundantReturn"]) + exclude: [.redundantReturn]) } func testNoDoubleIndentClosureArguments() { @@ -448,7 +448,7 @@ class IndentTests: RulesTests { } """ testFormatting(for: input, rule: .indent, - exclude: ["braces", "wrapMultilineStatementBraces", "redundantProperty"]) + exclude: [.braces, .wrapMultilineStatementBraces, .redundantProperty]) } func testIndentLineAfterIndentedInlineClosure() { @@ -460,7 +460,7 @@ class IndentTests: RulesTests { return viewController } """ - testFormatting(for: input, rule: .indent, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .indent, exclude: [.redundantProperty]) } func testIndentLineAfterNonIndentedClosure() { @@ -473,7 +473,7 @@ class IndentTests: RulesTests { return viewController } """ - testFormatting(for: input, rule: .indent, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .indent, exclude: [.redundantProperty]) } func testIndentMultilineStatementDoesntFailToTerminate() { @@ -498,25 +498,25 @@ class IndentTests: RulesTests { func testSwitchWrappedCaseIndenting() { let input = "switch x {\ncase foo,\nbar,\n baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase foo,\n bar,\n baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: .indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.sortSwitchCases]) } func testSwitchWrappedEnumCaseIndenting() { let input = "switch x {\ncase .foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase .foo,\n .bar,\n .baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: .indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.sortSwitchCases]) } func testSwitchWrappedEnumCaseIndentingVariant2() { let input = "switch x {\ncase\n.foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\ncase\n .foo,\n .bar,\n .baz:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: .indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.sortSwitchCases]) } func testSwitchWrappedEnumCaseIsIndenting() { let input = "switch x {\ncase is Foo.Type,\n is Bar.Type:\n break\ndefault:\n break\n}" let output = "switch x {\ncase is Foo.Type,\n is Bar.Type:\n break\ndefault:\n break\n}" - testFormatting(for: input, output, rule: .indent, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.sortSwitchCases]) } func testSwitchCaseIsDictionaryIndenting() { @@ -539,7 +539,7 @@ class IndentTests: RulesTests { Baz } """ - testFormatting(for: input, output, rule: .indent, exclude: ["wrapEnumCases"]) + testFormatting(for: input, output, rule: .indent, exclude: [.wrapEnumCases]) } func testGenericEnumCaseIndenting() { @@ -550,18 +550,18 @@ class IndentTests: RulesTests { func testIndentSwitchAfterRangeCase() { let input = "switch x {\ncase 0 ..< 2:\n switch y {\n default:\n break\n }\ndefault:\n break\n}" - testFormatting(for: input, rule: .indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, rule: .indent, exclude: [.blankLineAfterSwitchCase]) } func testIndentEnumDeclarationInsideSwitchCase() { let input = "switch x {\ncase y:\nenum Foo {\ncase z\n}\nbar()\ndefault: break\n}" let output = "switch x {\ncase y:\n enum Foo {\n case z\n }\n bar()\ndefault: break\n}" - testFormatting(for: input, output, rule: .indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, exclude: [.blankLineAfterSwitchCase]) } func testIndentEnumCaseBodyAfterWhereClause() { let input = "switch foo {\ncase _ where baz < quux:\n print(1)\n print(2)\ndefault:\n break\n}" - testFormatting(for: input, rule: .indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, rule: .indent, exclude: [.blankLineAfterSwitchCase]) } func testIndentSwitchCaseCommentsCorrectly() { @@ -587,7 +587,7 @@ class IndentTests: RulesTests { break } """ - testFormatting(for: input, output, rule: .indent, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, exclude: [.blankLineAfterSwitchCase]) } func testIndentMultilineSwitchCaseCommentsCorrectly() { @@ -654,7 +654,7 @@ class IndentTests: RulesTests { let input = "{\nguard case .Foo = error else {}\n}" let output = "{\n guard case .Foo = error else {}\n}" testFormatting(for: input, output, rule: .indent, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testIndentIfElse() { @@ -815,7 +815,7 @@ class IndentTests: RulesTests { } """ testFormatting(for: input, rule: .indent, - exclude: ["wrapMultilineStatementBraces"]) + exclude: [.wrapMultilineStatementBraces]) } func testWrappedClassDeclarationLikeXcode() { @@ -897,7 +897,7 @@ class IndentTests: RulesTests { let input = "switch x {\ncase .foo,\n.bar,\n .baz:\n break\ndefault:\n break\n}" let output = "switch x {\n case .foo,\n .bar,\n .baz:\n break\n default:\n break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: .indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.sortSwitchCases]) } func testIndentMultilineSwitchCaseCommentsWithIndentCaseTrue() { @@ -997,7 +997,7 @@ class IndentTests: RulesTests { func testWrappedBeforeComma() { let input = "let a = b\n, b = c" let output = "let a = b\n , b = c" - testFormatting(for: input, output, rule: .indent, exclude: ["leadingDelimiters"]) + testFormatting(for: input, output, rule: .indent, exclude: [.leadingDelimiters]) } func testWrappedLineAfterCommaInsideArray() { @@ -1011,7 +1011,7 @@ class IndentTests: RulesTests { let output = "[\n foo\n , bar,\n]" let options = FormatOptions(wrapCollections: .disabled) testFormatting(for: input, output, rule: .indent, options: options, - exclude: ["leadingDelimiters"]) + exclude: [.leadingDelimiters]) } func testWrappedLineAfterCommaInsideInlineArray() { @@ -1026,7 +1026,7 @@ class IndentTests: RulesTests { let output = "[foo\n , bar]" let options = FormatOptions(wrapCollections: .disabled) testFormatting(for: input, output, rule: .indent, options: options, - exclude: ["leadingDelimiters"]) + exclude: [.leadingDelimiters]) } func testWrappedLineAfterColonInFunction() { @@ -1038,13 +1038,13 @@ class IndentTests: RulesTests { func testNoDoubleIndentOfWrapAfterAsAfterOpenScope() { let input = "(foo as\nBar)" let output = "(foo as\n Bar)" - testFormatting(for: input, output, rule: .indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantParens]) } func testNoDoubleIndentOfWrapBeforeAsAfterOpenScope() { let input = "(foo\nas Bar)" let output = "(foo\n as Bar)" - testFormatting(for: input, output, rule: .indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantParens]) } func testDoubleIndentWhenScopesSeparatedByWrap() { @@ -1060,14 +1060,14 @@ class IndentTests: RulesTests { baz }) """ - testFormatting(for: input, output, rule: .indent, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantParens]) } func testNoDoubleIndentWhenScopesSeparatedByWrap() { let input = "(foo\nas Bar {\nbaz\n}\n)" let output = "(foo\n as Bar {\n baz\n }\n)" testFormatting(for: input, output, rule: .indent, - exclude: ["wrapArguments", "redundantParens"]) + exclude: [.wrapArguments, .redundantParens]) } func testNoPermanentReductionInScopeAfterWrap() { @@ -1127,14 +1127,14 @@ class IndentTests: RulesTests { func testWrappedLineBeforeGuardElse() { let input = "guard let foo = bar\nelse { return }" testFormatting(for: input, rule: .indent, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testWrappedLineAfterGuardElse() { // Don't indent because this case is handled by braces rule let input = "guard let foo = bar else\n{ return }" testFormatting(for: input, rule: .indent, - exclude: ["elseOnSameLine", "wrapConditionalBodies"]) + exclude: [.elseOnSameLine, .wrapConditionalBodies]) } func testWrappedLineAfterComment() { @@ -1157,7 +1157,7 @@ class IndentTests: RulesTests { } """ testFormatting(for: input, rule: .indent, - exclude: ["wrapMultilineStatementBraces", "wrapConditionalBodies"]) + exclude: [.wrapMultilineStatementBraces, .wrapConditionalBodies]) } func testConsecutiveWraps() { @@ -1190,7 +1190,7 @@ class IndentTests: RulesTests { func testNoIndentAfterDefaultAsIdentifier() { let input = "let foo = FileManager.default\n/// Comment\nlet bar = 0" - testFormatting(for: input, rule: .indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: [.propertyType]) } func testIndentClosureStartingOnIndentedLine() { @@ -1234,7 +1234,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, output, rule: .indent, exclude: ["andOperator", "wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.andOperator, .wrapMultilineStatementBraces]) } func testWrappedEnumThatLooksLikeIf() { @@ -1313,7 +1313,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(xcodeIndentation: true) testFormatting(for: input, rule: .indent, options: options, - exclude: ["blankLinesBetweenScopes"]) + exclude: [.blankLinesBetweenScopes]) } func testChainedFunctionIndents() { @@ -1392,34 +1392,34 @@ class IndentTests: RulesTests { func testChainedFunctionsAfterAnIfStatement() { let input = "if foo {}\nbar\n.baz {\n}\n.quux()" let output = "if foo {}\nbar\n .baz {\n }\n .quux()" - testFormatting(for: input, output, rule: .indent, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.emptyBraces]) } func testIndentInsideWrappedIfStatementWithClosureCondition() { let input = "if foo({ 1 }) ||\nbar {\nbaz()\n}" let output = "if foo({ 1 }) ||\n bar {\n baz()\n}" - testFormatting(for: input, output, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.wrapMultilineStatementBraces]) } func testIndentInsideWrappedClassDefinition() { let input = "class Foo\n: Bar {\nbaz()\n}" let output = "class Foo\n : Bar {\n baz()\n}" testFormatting(for: input, output, rule: .indent, - exclude: ["leadingDelimiters", "wrapMultilineStatementBraces"]) + exclude: [.leadingDelimiters, .wrapMultilineStatementBraces]) } func testIndentInsideWrappedProtocolDefinition() { let input = "protocol Foo\n: Bar, Baz {\nbaz()\n}" let output = "protocol Foo\n : Bar, Baz {\n baz()\n}" testFormatting(for: input, output, rule: .indent, - exclude: ["leadingDelimiters", "wrapMultilineStatementBraces"]) + exclude: [.leadingDelimiters, .wrapMultilineStatementBraces]) } func testIndentInsideWrappedVarStatement() { let input = "var Foo:\nBar {\nreturn 5\n}" let output = "var Foo:\n Bar {\n return 5\n}" testFormatting(for: input, output, rule: .indent, - exclude: ["wrapMultilineStatementBraces"]) + exclude: [.wrapMultilineStatementBraces]) } func testNoIndentAfterOperatorDeclaration() { @@ -1456,7 +1456,7 @@ class IndentTests: RulesTests { let input = "foobar(baz: { a &&\nb })" let output = "foobar(baz: { a &&\n b })" testFormatting(for: input, output, rule: .indent, - exclude: ["trailingClosures", "braces"]) + exclude: [.trailingClosures, .braces]) } func testIndentWrappedFunctionWithClosureArgument() { @@ -1527,7 +1527,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .balanced) testFormatting(for: input, rule: .indent, options: options, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testSingleIndentTrailingClosureBody2() { @@ -1542,7 +1542,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: ["wrapConditionalBodies", "wrapMultilineStatementBraces"]) + exclude: [.wrapConditionalBodies, .wrapMultilineStatementBraces]) } func testDoubleIndentTrailingClosureBody() { @@ -1558,7 +1558,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: ["wrapConditionalBodies", "wrapMultilineStatementBraces"]) + exclude: [.wrapConditionalBodies, .wrapMultilineStatementBraces]) } func testDoubleIndentTrailingClosureBody2() { @@ -1574,7 +1574,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: ["wrapMultilineStatementBraces"]) + exclude: [.wrapMultilineStatementBraces]) } func testNoDoubleIndentTrailingClosureBodyIfLineStartsWithClosingBrace() { @@ -1586,7 +1586,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) - testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testSingleIndentTrailingClosureBodyThatStartsOnFollowingLine() { @@ -1603,7 +1603,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: ["braces", "wrapConditionalBodies"]) + exclude: [.braces, .wrapConditionalBodies]) } func testSingleIndentTrailingClosureBodyOfShortMethod() { @@ -1615,7 +1615,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoDoubleIndentInInsideClosure() { @@ -1626,7 +1626,7 @@ class IndentTests: RulesTests { }) """ testFormatting(for: input, rule: .indent, - exclude: ["trailingClosures"]) + exclude: [.trailingClosures]) } func testNoDoubleIndentInInsideClosure2() { @@ -1718,7 +1718,7 @@ class IndentTests: RulesTests { } """ testFormatting(for: input, rule: .indent, - exclude: ["wrapArguments", "wrapMultilineStatementBraces"]) + exclude: [.wrapArguments, .wrapMultilineStatementBraces]) } func testIndentChainedPropertiesAfterFunctionCall() { @@ -1729,7 +1729,7 @@ class IndentTests: RulesTests { .bar .baz """ - testFormatting(for: input, rule: .indent, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, exclude: [.propertyType]) } func testIndentChainedPropertiesAfterFunctionCallWithXcodeIndentation() { @@ -1741,7 +1741,7 @@ class IndentTests: RulesTests { .baz """ let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: .indent, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.propertyType]) } func testIndentChainedPropertiesAfterFunctionCall2() { @@ -1753,7 +1753,7 @@ class IndentTests: RulesTests { .baz """ testFormatting(for: input, rule: .indent, - exclude: ["trailingClosures", "propertyType"]) + exclude: [.trailingClosures, .propertyType]) } func testIndentChainedPropertiesAfterFunctionCallWithXcodeIndentation2() { @@ -1766,7 +1766,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(xcodeIndentation: true) testFormatting(for: input, rule: .indent, options: options, - exclude: ["trailingClosures", "propertyType"]) + exclude: [.trailingClosures, .propertyType]) } func testIndentChainedMethodsAfterTrailingClosure() { @@ -1915,7 +1915,7 @@ class IndentTests: RulesTests { else { return } """ testFormatting(for: input, rule: .indent, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testChainedFunctionInGuardWithXcodeIndentation() { @@ -1935,7 +1935,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(xcodeIndentation: true) testFormatting(for: input, output, rule: .indent, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } func testChainedFunctionInGuardIndentation2() { @@ -1950,7 +1950,7 @@ class IndentTests: RulesTests { else { return } """ testFormatting(for: input, rule: .indent, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testChainedFunctionInGuardWithXcodeIndentation2() { @@ -1977,7 +1977,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(xcodeIndentation: true) testFormatting(for: input, output, rule: .indent, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } func testWrappedChainedFunctionsWithNestedScopeIndent() { @@ -2207,7 +2207,7 @@ class IndentTests: RulesTests { { print("foo") } } """ - testFormatting(for: input, rule: .indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: [.braces]) } func testWrappedMultilineClosureOnNewLine() { @@ -2219,7 +2219,7 @@ class IndentTests: RulesTests { } } """ - testFormatting(for: input, rule: .indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: [.braces]) } func testWrappedMultilineClosureOnNewLineWithAllmanBraces() { @@ -2233,7 +2233,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(allmanBraces: true) testFormatting(for: input, rule: .indent, options: options, - exclude: ["braces"]) + exclude: [.braces]) } func testIndentChainedPropertiesAfterMultilineStringXcode() { @@ -2268,7 +2268,7 @@ class IndentTests: RulesTests { viewModel.snake, ] """ - testFormatting(for: input, rule: .indent, exclude: ["hoistTry"]) + testFormatting(for: input, rule: .indent, exclude: [.hoistTry]) } func testIndentChainedFunctionAfterTryInParens() { @@ -2965,14 +2965,14 @@ class IndentTests: RulesTests { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\ncase .bar:\n #if x\n bar()\n #endif\n baz()\ncase .baz: break\n}" let options = FormatOptions(indentCase: false) - testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.blankLineAfterSwitchCase]) } func testSwitchIfEndifInsideCaseIndenting2() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\n case .bar:\n #if x\n bar()\n #endif\n baz()\n case .baz: break\n}" let options = FormatOptions(indentCase: true) - testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.blankLineAfterSwitchCase]) } func testIfUnknownCaseEndifIndenting() { @@ -3207,14 +3207,14 @@ class IndentTests: RulesTests { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\ncase .bar:\n #if x\n bar()\n #endif\n baz()\ncase .baz: break\n}" let options = FormatOptions(indentCase: false, ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.blankLineAfterSwitchCase]) } func testIfEndifInsideCaseNoIndenting2() { let input = "switch foo {\ncase .bar:\n#if x\nbar()\n#endif\nbaz()\ncase .baz: break\n}" let output = "switch foo {\n case .bar:\n #if x\n bar()\n #endif\n baz()\n case .baz: break\n}" let options = FormatOptions(indentCase: true, ifdefIndent: .noIndent) - testFormatting(for: input, output, rule: .indent, options: options, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.blankLineAfterSwitchCase]) } func testSwitchCaseInIfEndif() { @@ -3578,7 +3578,7 @@ class IndentTests: RulesTests { let output = "class Foo {\n\n func foo() {\n bar()\n }" let options = FormatOptions(fragment: true) testFormatting(for: input, output, rule: .indent, options: options, - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testOverTerminatedFragment() { @@ -3650,7 +3650,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: true) - testFormatting(for: input, output, rule: .indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.sortSwitchCases]) } func testTabIndentCaseWithoutSmartTabs() { @@ -3669,7 +3669,7 @@ class IndentTests: RulesTests { } """ let options = FormatOptions(indent: "\t", tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: .indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.sortSwitchCases]) } func testTabIndentCaseWithoutSmartTabs2() { @@ -3689,7 +3689,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(indent: "\t", indentCase: true, tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: .indent, options: options, exclude: ["sortSwitchCases"]) + testFormatting(for: input, output, rule: .indent, options: options, exclude: [.sortSwitchCases]) } // indent blank lines @@ -3720,7 +3720,7 @@ class IndentTests: RulesTests { """ let options = FormatOptions(indent: "\t", truncateBlankLines: false, tabWidth: 2) testFormatting(for: input, rule: .indent, options: options, - exclude: ["consecutiveBlankLines", "wrapConditionalBodies"]) + exclude: [.consecutiveBlankLines, .wrapConditionalBodies]) } // async @@ -3836,7 +3836,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, output, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .indent, exclude: [.wrapMultilineStatementBraces]) } func testIndentIfExpressionAssignmentOnSameLine() { @@ -3854,7 +3854,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, rule: .indent, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, rule: .indent, exclude: [.wrapMultilineConditionalAssignment]) } func testIndentSwitchExpressionAssignment() { @@ -3914,7 +3914,7 @@ class IndentTests: RulesTests { } """ - testFormatting(for: input, output, rule: .indent, exclude: ["redundantProperty"]) + testFormatting(for: input, output, rule: .indent, exclude: [.redundantProperty]) } func testIndentNestedSwitchExpressionAssignment() { @@ -4016,7 +4016,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, exclude: [.wrapMultilineStatementBraces]) } func testIndentMultilineIfExpression() { @@ -4037,7 +4037,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: .indent, exclude: ["braces"]) + testFormatting(for: input, rule: .indent, exclude: [.braces]) } func testIndentNestedIfExpressionWithComments() { @@ -4062,7 +4062,7 @@ class IndentTests: RulesTests { print(foo) """ - testFormatting(for: input, rule: .indent, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, rule: .indent, exclude: [.wrapMultilineStatementBraces]) } func testIndentIfExpressionWithMultilineComments() { @@ -4094,6 +4094,6 @@ class IndentTests: RulesTests { print(bullet) """ let options = FormatOptions() - testFormatting(for: input, rule: .indent, options: options, exclude: ["wrapConditionalBodies", "andOperator", "redundantParens"]) + testFormatting(for: input, rule: .indent, options: options, exclude: [.wrapConditionalBodies, .andOperator, .redundantParens]) } } diff --git a/Tests/RulesTests+Linebreaks.swift b/Tests/RulesTests+Linebreaks.swift index e8a2df69e..8afdd8234 100644 --- a/Tests/RulesTests+Linebreaks.swift +++ b/Tests/RulesTests+Linebreaks.swift @@ -134,7 +134,7 @@ class LinebreakTests: RulesTests { func testBlankLinesNotRemovedBetweenElementsInsideBrackets() { let input = "[foo,\n\n bar]" - testFormatting(for: input, rule: .blankLinesAtStartOfScope, exclude: ["wrapArguments"]) + testFormatting(for: input, rule: .blankLinesAtStartOfScope, exclude: [.wrapArguments]) } func testBlankLineRemovedFromStartOfTypeByDefault() { @@ -232,7 +232,7 @@ class LinebreakTests: RulesTests { let input = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n\n}" let output = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n}" testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testBlankLineRemovedFromEndOfTypeByDefault() { @@ -530,38 +530,38 @@ class LinebreakTests: RulesTests { let input = "func foo() {\n}\nfunc bar() {\n}" let output = "func foo() {\n}\n\nfunc bar() {\n}" testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testNoBlankLineBetweenPropertyAndFunction() { let input = "var foo: Int\nfunc bar() {\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) } func testBlankLineBetweenFunctionsIsBeforeComment() { let input = "func foo() {\n}\n/// headerdoc\nfunc bar() {\n}" let output = "func foo() {\n}\n\n/// headerdoc\nfunc bar() {\n}" testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testBlankLineBeforeAtObjcOnLineBeforeProtocol() { let input = "@objc\nprotocol Foo {\n}\n@objc\nprotocol Bar {\n}" let output = "@objc\nprotocol Foo {\n}\n\n@objc\nprotocol Bar {\n}" testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testBlankLineBeforeAtAvailabilityOnLineBeforeClass() { let input = "protocol Foo {\n}\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" let output = "protocol Foo {\n}\n\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testNoExtraBlankLineBetweenFunctions() { let input = "func foo() {\n}\n\nfunc bar() {\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) } func testNoBlankLineBetweenFunctionsInProtocol() { @@ -578,7 +578,7 @@ class LinebreakTests: RulesTests { let input = "protocol Foo {\n}\nvar bar: String" let output = "protocol Foo {\n}\n\nvar bar: String" testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testNoExtraBlankLineAfterSingleLineComment() { @@ -603,33 +603,33 @@ class LinebreakTests: RulesTests { func testNoBlankLineBetweenIfStatements() { let input = "func foo() {\n if x {\n }\n if y {\n }\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) } func testNoBlanksInsideClassFunc() { let input = "class func foo {\n if x {\n }\n if y {\n }\n}" let options = FormatOptions(fragment: true) testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testNoBlanksInsideClassVar() { let input = "class var foo: Int {\n if x {\n }\n if y {\n }\n}" let options = FormatOptions(fragment: true) testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testBlankLineBetweenCalledClosures() { let input = "class Foo {\n var foo = {\n }()\n func bar {\n }\n}" let output = "class Foo {\n var foo = {\n }()\n\n func bar {\n }\n}" testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testNoBlankLineAfterCalledClosureAtEndOfScope() { let input = "class Foo {\n var foo = {\n }()\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: ["emptyBraces"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) } func testNoBlankLineBeforeWhileInRepeatWhile() { @@ -640,7 +640,7 @@ class LinebreakTests: RulesTests { { print("bar") }() """ let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, exclude: ["redundantClosure", "wrapLoopBodies"]) + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, exclude: [.redundantClosure, .wrapLoopBodies]) } func testBlankLineBeforeWhileIfNotRepeatWhile() { @@ -648,7 +648,7 @@ class LinebreakTests: RulesTests { let output = "func foo(x)\n{\n}\n\nwhile true\n{\n}" let options = FormatOptions(allmanBraces: true) testFormatting(for: input, output, rule: .blankLinesBetweenScopes, options: options, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testNoInsertBlankLinesInConditionalCompilation() { @@ -664,7 +664,7 @@ class LinebreakTests: RulesTests { } """ testFormatting(for: input, rule: .blankLinesBetweenScopes, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testNoInsertBlankLineAfterBraceBeforeSourceryComment() { diff --git a/Tests/RulesTests+Organization.swift b/Tests/RulesTests+Organization.swift index 8c4f3aa89..0c57dc23f 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/RulesTests+Organization.swift @@ -105,7 +105,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -211,7 +211,7 @@ class OrganizationTests: RulesTests { visibilityOrder: airbnbVisibilityOrder.components(separatedBy: ","), typeOrder: airbnbTypeOrder.components(separatedBy: ",") ), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -282,7 +282,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -311,7 +311,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports] ) } @@ -372,7 +372,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(organizationMode: .type), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports] ) } @@ -397,7 +397,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "sortImports"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .sortImports] ) } @@ -465,7 +465,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -539,7 +539,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -576,7 +576,7 @@ class OrganizationTests: RulesTests { visibilityOrder: ["private", "internal", "public"], typeOrder: DeclarationType.allCases.map(\.rawValue) ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -626,7 +626,7 @@ class OrganizationTests: RulesTests { visibilityOrder: ["private", "internal", "public"], typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -674,7 +674,7 @@ class OrganizationTests: RulesTests { organizationMode: .type, typeOrder: ["beforeMarks", "instanceLifecycle", "instanceMethod", "nestedType", "instanceProperty", "overriddenMethod"] ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -719,7 +719,7 @@ class OrganizationTests: RulesTests { organizationMode: .type, typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty"] ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -771,7 +771,7 @@ class OrganizationTests: RulesTests { visibilityOrder: ["private", "internal", "public"], typeOrder: ["beforeMarks", "nestedType", "instanceLifecycle", "instanceMethod", "instanceProperty", "overriddenMethod"] ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -804,7 +804,7 @@ class OrganizationTests: RulesTests { visibilityOrder: ["instanceMethod"] + Visibility.allCases.map(\.rawValue), typeOrder: DeclarationType.allCases.map(\.rawValue).filter { $0 != "instanceMethod" } ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -837,7 +837,7 @@ class OrganizationTests: RulesTests { visibilityOrder: Visibility.allCases.map(\.rawValue), typeOrder: DeclarationType.allCases.map(\.rawValue) ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -878,7 +878,7 @@ class OrganizationTests: RulesTests { organizationMode: .visibility, customVisibilityMarks: ["instanceLifecycle:Init", "public:Public_Group"] ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -919,7 +919,7 @@ class OrganizationTests: RulesTests { organizationMode: .type, customTypeMarks: ["instanceLifecycle:Init", "instanceProperty:Bar_Bar", "instanceMethod:Buuuz Lightyeeeaaar"] ), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -958,7 +958,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "enumNamespaces"] + exclude: [.blankLinesAtStartOfScope, .enumNamespaces] ) } @@ -1010,7 +1010,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -1040,7 +1040,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "redundantInternal"] + exclude: [.blankLinesAtStartOfScope, .redundantInternal] ) } @@ -1130,7 +1130,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtEndOfScope", "redundantType", "redundantClosure"] + exclude: [.blankLinesAtEndOfScope, .redundantType, .redundantClosure] ) } @@ -1259,7 +1259,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "MARK: %c", organizationMode: .type), - exclude: ["blankLinesAtEndOfScope", "blankLinesAtStartOfScope", "redundantType", "redundantClosure"] + exclude: [.blankLinesAtEndOfScope, .blankLinesAtStartOfScope, .redundantType, .redundantClosure] ) } @@ -1294,7 +1294,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtEndOfScope", "unusedArguments"] + exclude: [.blankLinesAtEndOfScope, .unusedArguments] ) } @@ -1329,7 +1329,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(beforeMarks: ["typealias", "struct"]), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -1382,7 +1382,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(lifecycleMethods: ["viewDidLoad", "viewWillAppear", "viewDidAppear"]), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -1411,7 +1411,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(categoryMarkComment: "- %c"), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -1454,7 +1454,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(organizeStructThreshold: 2), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -1535,7 +1535,7 @@ class OrganizationTests: RulesTests { options: FormatOptions( organizeTypes: ["class", "struct", "enum", "extension"], organizeExtensionThreshold: 2 - ), exclude: ["blankLinesAtStartOfScope"] + ), exclude: [.blankLinesAtStartOfScope] ) } @@ -1557,7 +1557,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testUpdatesMalformedMarks() { @@ -1612,7 +1612,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testDoesntAttemptToUpdateMarksNotAtTopLevel() { @@ -1643,7 +1643,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "docCommentsBeforeAttributes"]) + exclude: [.blankLinesAtStartOfScope, .docCommentsBeforeAttributes]) } func testHandlesTrailingCommentCorrectly() { @@ -1672,7 +1672,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testDoesntInsertMarkWhenOnlyOneCategory() { @@ -1739,7 +1739,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, output, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testOrganizesTypesBelowConditionalCompilationBlock() { @@ -1773,7 +1773,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, output, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testOrganizesNestedTypesWithinConditionalCompilationBlock() { @@ -1851,7 +1851,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, output, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "propertyType"]) + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .propertyType]) } func testOrganizesTypeBelowSymbolImport() { @@ -1895,7 +1895,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "sortImports"] + exclude: [.blankLinesAtStartOfScope, .sortImports] ) } @@ -1967,7 +1967,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -1993,7 +1993,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -2015,7 +2015,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -2063,7 +2063,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -2095,7 +2095,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, rule: .organizeDeclarations, options: FormatOptions(organizeStructThreshold: 20), - exclude: ["blankLinesAtStartOfScope"] + exclude: [.blankLinesAtStartOfScope] ) } @@ -2142,7 +2142,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: .organizeDeclarations, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .organizeDeclarations, exclude: [.redundantClosure]) } func testFuncWithNestedInitNotTreatedAsLifecycle() { @@ -2166,7 +2166,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testOrganizeRuleNotConfusedByClassProtocol() { @@ -2199,7 +2199,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope"]) + exclude: [.blankLinesAtStartOfScope]) } func testOrganizeClassDeclarationsIntoCategoriesWithNoBlankLineAfterMark() { @@ -2249,7 +2249,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: options, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -2331,7 +2331,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) } func testOrganizeConditionalPublicFunction() { @@ -2354,7 +2354,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) } // MARK: extensionAccessControl .onDeclarations @@ -2385,7 +2385,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations), - exclude: ["redundantInternal"] + exclude: [.redundantInternal] ) } @@ -2532,7 +2532,7 @@ class OrganizationTests: RulesTests { testFormatting( for: input, output, rule: .extensionAccessControl, options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4"), - exclude: ["propertyType"] + exclude: [.propertyType] ) } @@ -2671,7 +2671,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, output, rule: .extensionAccessControl, - exclude: ["redundantFileprivate"]) + exclude: [.redundantFileprivate]) } func testDoesntHoistPrivateVisibilityFromExtensionBodyDeclarations() { @@ -2749,7 +2749,7 @@ class OrganizationTests: RulesTests { func bar() {} } """ - testFormatting(for: input, rule: .extensionAccessControl, exclude: ["redundantInternal"]) + testFormatting(for: input, rule: .extensionAccessControl, exclude: [.redundantInternal]) } func testNoHoistAccessModifierForExtensionThatAddsProtocolConformance() { @@ -3268,7 +3268,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(markTypes: .never) testFormatting( for: input, rule: .markTypes, options: options, - exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] ) } @@ -3301,7 +3301,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(markTypes: .ifNotEmpty) testFormatting( for: input, output, rule: .markTypes, options: options, - exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] ) } @@ -3320,7 +3320,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(markExtensions: .never) testFormatting( for: input, rule: .markTypes, options: options, - exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] ) } @@ -3353,7 +3353,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(markExtensions: .ifNotEmpty) testFormatting( for: input, output, rule: .markTypes, options: options, - exclude: ["emptyBraces", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope", "blankLinesBetweenScopes"] + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] ) } @@ -3650,14 +3650,14 @@ class OrganizationTests: RulesTests { let input = "import Foo\n// important comment\n// (very important)\nimport Bar" let output = "// important comment\n// (very important)\nimport Bar\nimport Foo" testFormatting(for: input, output, rule: .sortImports, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testSortImportsKeepsPreviousCommentWithImport2() { let input = "// important comment\n// (very important)\nimport Foo\nimport Bar" let output = "import Bar\n// important comment\n// (very important)\nimport Foo" testFormatting(for: input, output, rule: .sortImports, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testSortImportsDoesntMoveHeaderComment() { @@ -3670,7 +3670,7 @@ class OrganizationTests: RulesTests { let input = "// header comment\n\n// important comment\nimport Foo\nimport Bar" let output = "// header comment\n\nimport Bar\n// important comment\nimport Foo" testFormatting(for: input, output, rule: .sortImports, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testSortImportsOnSameLine() { @@ -3682,7 +3682,7 @@ class OrganizationTests: RulesTests { func testSortImportsWithSemicolonAndCommentOnSameLine() { let input = "import Foo; // foobar\nimport Bar\nimport Baz" let output = "import Bar\nimport Baz\nimport Foo; // foobar" - testFormatting(for: input, output, rule: .sortImports, exclude: ["semicolons"]) + testFormatting(for: input, output, rule: .sortImports, exclude: [.semicolons]) } func testSortImportEnum() { @@ -3756,14 +3756,14 @@ class OrganizationTests: RulesTests { func testNoDeleteCodeBetweenImports() { let input = "import Foo\nfunc bar() {}\nimport Bar" testFormatting(for: input, rule: .sortImports, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testNoDeleteCodeBetweenImports2() { let input = "import Foo\nimport Bar\nfoo = bar\nimport Bar" let output = "import Bar\nimport Foo\nfoo = bar\nimport Bar" testFormatting(for: input, output, rule: .sortImports, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testNoDeleteCodeBetweenImports3() { @@ -3785,7 +3785,7 @@ class OrganizationTests: RulesTests { let input = "import Foo\nimport Bar\nfunc bar() {}\nimport Quux\nimport Baz" let output = "import Bar\nimport Foo\nfunc bar() {}\nimport Baz\nimport Quux" testFormatting(for: input, output, rule: .sortImports, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testNoMangleImportsPrecededByComment() { @@ -3864,7 +3864,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, rule: .sortSwitchCases, exclude: ["redundantSelf"]) + testFormatting(for: input, rule: .sortSwitchCases, exclude: [.redundantSelf]) } func testSortedSwitchCaseMultilineWithOneComment() { @@ -3900,7 +3900,7 @@ class OrganizationTests: RulesTests { break } """ - testFormatting(for: input, output, rule: .sortSwitchCases, exclude: ["indent"]) + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: [.indent]) } func testSortedSwitchCaseMultilineWithCommentsAndMoreThanOneCasePerLine() { @@ -3956,7 +3956,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: ["wrapSwitchCases"]) + exclude: [.wrapSwitchCases]) } func testSortedSwitchCaseOneLineWithoutSpaces() { @@ -3973,7 +3973,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: ["wrapSwitchCases", "spaceAroundOperators"]) + exclude: [.wrapSwitchCases, .spaceAroundOperators]) } func testSortedSwitchCaseLet() { @@ -3990,7 +3990,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: ["wrapSwitchCases"]) + exclude: [.wrapSwitchCases]) } func testSortedSwitchCaseOneCaseDoesNothing() { @@ -4017,7 +4017,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: ["wrapSwitchCases"]) + exclude: [.wrapSwitchCases]) } func testSortedSwitchWhereConditionNotLastCase() { @@ -4029,7 +4029,7 @@ class OrganizationTests: RulesTests { """ testFormatting(for: input, rule: .sortSwitchCases, - exclude: ["wrapSwitchCases"]) + exclude: [.wrapSwitchCases]) } func testSortedSwitchWhereConditionLastCase() { @@ -4046,7 +4046,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: ["wrapSwitchCases"]) + exclude: [.wrapSwitchCases]) } func testSortNumericSwitchCases() { @@ -4063,7 +4063,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: ["wrapSwitchCases"]) + exclude: [.wrapSwitchCases]) } func testSortedSwitchTuples() { @@ -4169,7 +4169,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: ["wrapSwitchCases"]) + exclude: [.wrapSwitchCases]) } // MARK: - modifierOrder @@ -4236,7 +4236,7 @@ class OrganizationTests: RulesTests { let input = "consuming public func close()" let output = "public consuming func close()" let options = FormatOptions(modifierOrder: ["public", "consuming"]) - testFormatting(for: input, output, rule: .modifierOrder, options: options, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, output, rule: .modifierOrder, options: options, exclude: [.noExplicitOwnership]) } func testNoConfusePostfixIdentifierWithKeyword() { @@ -4378,7 +4378,7 @@ class OrganizationTests: RulesTests { } """ - testFormatting(for: input, output, rule: .sortDeclarations, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, output, rule: .sortDeclarations, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) } func testSortClassWithMixedDeclarationTypes() { @@ -4416,7 +4416,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, [output], rules: [.sortDeclarations, .consecutiveBlankLines], - exclude: ["blankLinesBetweenScopes", "propertyType"]) + exclude: [.blankLinesBetweenScopes, .propertyType]) } func testSortBetweenDirectiveCommentsInType() { @@ -4552,7 +4552,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, [output], rules: [.organizeDeclarations, .blankLinesBetweenScopes], - exclude: ["blankLinesAtEndOfScope"]) + exclude: [.blankLinesAtEndOfScope]) } func testSortsWithinOrganizeDeclarationsByClassName() { @@ -4599,7 +4599,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, [output], rules: [.organizeDeclarations, .blankLinesBetweenScopes], options: .init(alphabeticallySortedDeclarationPatterns: ["FeatureFlags"]), - exclude: ["blankLinesAtEndOfScope"]) + exclude: [.blankLinesAtEndOfScope]) } func testSortsWithinOrganizeDeclarationsByPartialClassName() { @@ -4646,7 +4646,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, [output], rules: [.organizeDeclarations, .blankLinesBetweenScopes], options: .init(alphabeticallySortedDeclarationPatterns: ["ureFla"]), - exclude: ["blankLinesAtEndOfScope"]) + exclude: [.blankLinesAtEndOfScope]) } func testDontSortsWithinOrganizeDeclarationsByClassNameInComment() { @@ -4673,7 +4673,7 @@ class OrganizationTests: RulesTests { testFormatting(for: input, rules: [.organizeDeclarations, .blankLinesBetweenScopes], options: .init(alphabeticallySortedDeclarationPatterns: ["Comment"]), - exclude: ["blankLinesAtEndOfScope"]) + exclude: [.blankLinesAtEndOfScope]) } func testSortDeclarationsSortsByNamePattern() { @@ -4807,7 +4807,7 @@ class OrganizationTests: RulesTests { let options = FormatOptions(organizeTypes: ["extension"]) testFormatting(for: input, output, rule: .organizeDeclarations, options: options, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) } func testOrganizeDeclarationsContainingNonisolated() { @@ -4844,7 +4844,7 @@ class OrganizationTests: RulesTests { } """ testFormatting(for: input, output, rule: .organizeDeclarations, - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) } func testSortStructPropertiesWithAttributes() { @@ -4884,7 +4884,7 @@ class OrganizationTests: RulesTests { """ let options = FormatOptions(indent: " ", organizeTypes: ["struct"]) testFormatting(for: input, output, rule: .organizeDeclarations, - options: options, exclude: ["blankLinesAtStartOfScope"]) + options: options, exclude: [.blankLinesAtStartOfScope]) } // MARK: - sortTypealiases @@ -5109,7 +5109,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -5164,7 +5164,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -5221,7 +5221,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } @@ -5276,7 +5276,7 @@ class OrganizationTests: RulesTests { for: input, output, rule: .organizeDeclarations, options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), - exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"] + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } } diff --git a/Tests/RulesTests+Parens.swift b/Tests/RulesTests+Parens.swift index 1d141403f..b5b8d0978 100644 --- a/Tests/RulesTests+Parens.swift +++ b/Tests/RulesTests+Parens.swift @@ -63,7 +63,7 @@ class ParensTests: RulesTests { func testRequiredParensNotRemoved3() { let input = "x+(-5)" testFormatting(for: input, rule: .redundantParens, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testRedundantParensAroundIsNotRemoved() { @@ -116,7 +116,7 @@ class ParensTests: RulesTests { func testMeaningfulParensNotRemovedAroundFileLiteral() { let input = "func foo(_ file: String = (#file)) {}" - testFormatting(for: input, rule: .redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.unusedArguments]) } func testMeaningfulParensNotRemovedAroundOperator() { @@ -127,13 +127,13 @@ class ParensTests: RulesTests { func testMeaningfulParensNotRemovedAroundOperatorWithSpaces() { let input = "let foo: (Int, Int) -> Bool = ( < )" testFormatting(for: input, rule: .redundantParens, - exclude: ["spaceAroundOperators", "spaceInsideParens"]) + exclude: [.spaceAroundOperators, .spaceInsideParens]) } func testMeaningfulParensNotRemovedAroundPrefixOperator() { let input = "let foo: (Int) -> Int = ( -)" testFormatting(for: input, rule: .redundantParens, - exclude: ["spaceAroundOperators", "spaceInsideParens"]) + exclude: [.spaceAroundOperators, .spaceInsideParens]) } func testMeaningfulParensAroundPrefixExpressionFollowedByDotNotRemoved() { @@ -144,7 +144,7 @@ class ParensTests: RulesTests { func testMeaningfulParensAroundPrefixExpressionWithSpacesFollowedByDotNotRemoved() { let input = "let foo = ( !bar ).description" testFormatting(for: input, rule: .redundantParens, - exclude: ["spaceAroundOperators", "spaceInsideParens"]) + exclude: [.spaceAroundOperators, .spaceInsideParens]) } func testMeaningfulParensAroundPrefixExpressionFollowedByPostfixExpressionNotRemoved() { @@ -332,7 +332,7 @@ class ParensTests: RulesTests { func testOuterParensRemovedInWhile() { let input = "while ((x || y) && z) {}" let output = "while (x || y) && z {}" - testFormatting(for: input, output, rule: .redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.andOperator]) } func testOuterParensRemovedInIf() { @@ -344,7 +344,7 @@ class ParensTests: RulesTests { func testCaseOuterParensRemoved() { let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" let output = "switch foo {\ncase Foo.bar(let baz):\n}" - testFormatting(for: input, output, rule: .redundantParens, exclude: ["hoistPatternLet"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.hoistPatternLet]) } func testCaseLetOuterParensRemoved() { @@ -363,7 +363,7 @@ class ParensTests: RulesTests { let input = "guard (x == y) else { return }" let output = "guard x == y else { return }" testFormatting(for: input, output, rule: .redundantParens, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testForValueParensRemoved() { @@ -374,7 +374,7 @@ class ParensTests: RulesTests { func testParensForLoopWhereClauseMethodNotRemoved() { let input = "for foo in foos where foo.method() { print(foo) }" - testFormatting(for: input, rule: .redundantParens, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.wrapLoopBodies]) } func testSpaceInsertedWhenRemovingParens() { @@ -467,7 +467,7 @@ class ParensTests: RulesTests { func testRequiredParensNotRemovedAroundOptionalClosureType() { let input = "let foo = (() -> ())?" - testFormatting(for: input, rule: .redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.void]) } func testRequiredParensNotRemovedAroundOptionalRange() { @@ -478,7 +478,7 @@ class ParensTests: RulesTests { func testRedundantParensRemovedAroundOptionalUnwrap() { let input = "let foo = (bar!)+5" testFormatting(for: input, rule: .redundantParens, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testRedundantParensRemovedAroundOptionalOptional() { @@ -539,7 +539,7 @@ class ParensTests: RulesTests { func testRedundantParensRemovedAroundOptionalClosureType() { let input = "let foo = ((() -> ()))?" let output = "let foo = (() -> ())?" - testFormatting(for: input, output, rule: .redundantParens, exclude: ["void"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.void]) } func testRequiredParensNotRemovedAfterClosureArgument() { @@ -564,12 +564,12 @@ class ParensTests: RulesTests { func testRequiredParensNotRemovedAfterClosureInsideArrayWithTrailingComma() { let input = "[{ /* code */ }(),]" - testFormatting(for: input, rule: .redundantParens, exclude: ["trailingCommas"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.trailingCommas]) } func testRequiredParensNotRemovedAfterClosureInWhereClause() { let input = "case foo where { x == y }():" - testFormatting(for: input, rule: .redundantParens, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.redundantClosure]) } // around closure arguments @@ -577,19 +577,19 @@ class ParensTests: RulesTests { func testSingleClosureArgumentUnwrapped() { let input = "{ (foo) in }" let output = "{ foo in }" - testFormatting(for: input, output, rule: .redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.unusedArguments]) } func testSingleMainActorClosureArgumentUnwrapped() { let input = "{ @MainActor (foo) in }" let output = "{ @MainActor foo in }" - testFormatting(for: input, output, rule: .redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.unusedArguments]) } func testSingleClosureArgumentWithReturnValueUnwrapped() { let input = "{ (foo) -> Int in 5 }" let output = "{ foo -> Int in 5 }" - testFormatting(for: input, output, rule: .redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantParens, exclude: [.unusedArguments]) } func testSingleAnonymousClosureArgumentUnwrapped() { @@ -600,7 +600,7 @@ class ParensTests: RulesTests { func testSingleAnonymousClosureArgumentNotUnwrapped() { let input = "{ (_ foo) in }" - testFormatting(for: input, rule: .redundantParens, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.unusedArguments]) } func testTypedClosureArgumentNotUnwrapped() { @@ -707,12 +707,12 @@ class ParensTests: RulesTests { func testParensNotRemovedBeforeIfBody2() { let input = "if try foo as Bar && baz() { /* some code */ }" - testFormatting(for: input, rule: .redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.andOperator]) } func testParensNotRemovedBeforeIfBody3() { let input = "if #selector(foo(_:)) && bar() { /* some code */ }" - testFormatting(for: input, rule: .redundantParens, exclude: ["andOperator"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.andOperator]) } func testParensNotRemovedBeforeIfBody4() { @@ -761,7 +761,7 @@ class ParensTests: RulesTests { func testParensNotRemovedAfterAnonymousClosureInsideIfStatementBody() { let input = "if let foo = bar(), { x == y }() {}" - testFormatting(for: input, rule: .redundantParens, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.redundantClosure]) } func testParensNotRemovedInGenericInit() { @@ -786,12 +786,12 @@ class ParensTests: RulesTests { func testParensNotRemovedInGenericInstantiation() { let input = "let foo = Foo()" - testFormatting(for: input, rule: .redundantParens, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.propertyType]) } func testParensNotRemovedInGenericInstantiation2() { let input = "let foo = Foo(bar)" - testFormatting(for: input, rule: .redundantParens, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.propertyType]) } func testRedundantParensRemovedAfterGenerics() { @@ -931,17 +931,17 @@ class ParensTests: RulesTests { func testParensNotRemovedAroundVoidGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: .redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.void]) } func testParensNotRemovedAroundTupleGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: .redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.void]) } func testParensNotRemovedAroundLabeledTupleGenerics() { let input = "let foo = Foo" - testFormatting(for: input, rule: .redundantParens, exclude: ["void"]) + testFormatting(for: input, rule: .redundantParens, exclude: [.void]) } // after indexed tuple @@ -985,13 +985,13 @@ class ParensTests: RulesTests { let input = "(a)...(b)" let output = "a...b" testFormatting(for: input, output, rule: .redundantParens, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testParensNotRemovedAroundRangeArgumentBeginningWithDot() { let input = "a...(.b)" testFormatting(for: input, rule: .redundantParens, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testParensNotRemovedAroundTrailingRangeFollowedByDot() { @@ -1002,7 +1002,7 @@ class ParensTests: RulesTests { func testParensNotRemovedAroundRangeArgumentBeginningWithPrefixOperator() { let input = "a...(-b)" testFormatting(for: input, rule: .redundantParens, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testParensRemovedAroundRangeArgumentBeginningWithDot() { diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift index 133560aa5..d0827f319 100644 --- a/Tests/RulesTests+Redundancy.swift +++ b/Tests/RulesTests+Redundancy.swift @@ -76,7 +76,7 @@ class RedundancyTests: RulesTests { case 1: print(1); } """ - testFormatting(for: input, output, rule: .redundantBreak, exclude: ["semicolons"]) + testFormatting(for: input, output, rule: .redundantBreak, exclude: [.semicolons]) } // MARK: - redundantExtensionACL @@ -226,7 +226,7 @@ class RedundancyTests: RulesTests { let kFoo = Foo().foo """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAVar() { @@ -262,7 +262,7 @@ class RedundancyTests: RulesTests { print({ Foo().foo }()) """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.redundantClosure]) } func testFileprivateVarNotChangedToPrivateIfAccessedFromAnExtensionOnAnotherType() { @@ -333,7 +333,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "4") testFormatting(for: input, output, rule: .redundantFileprivate, options: options, - exclude: ["redundantSelf"]) + exclude: [.redundantSelf]) } func testFileprivateMultiLetNotChangedToPrivateIfAccessedOutsideType() { @@ -382,7 +382,7 @@ class RedundancyTests: RulesTests { let foo = Foo() """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) } func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType2() { @@ -396,7 +396,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) } func testFileprivateStructMemberNotChangedToPrivateIfConstructorCalledOutsideType() { @@ -408,7 +408,7 @@ class RedundancyTests: RulesTests { let foo = Foo(bar: "test") """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) } func testFileprivateClassMemberChangedToPrivateEvenIfConstructorCalledOutsideType() { @@ -427,7 +427,7 @@ class RedundancyTests: RulesTests { let foo = Foo() """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) } func testFileprivateExtensionFuncNotChangedToPrivateIfPartOfProtocolConformance() { @@ -460,7 +460,7 @@ class RedundancyTests: RulesTests { testFormatting(for: input, rule: .redundantFileprivate, options: options, - exclude: ["wrapEnumCases"]) + exclude: [.wrapEnumCases]) } func testFileprivateClassTypeMemberNotChangedToPrivate() { @@ -531,7 +531,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) } func testFileprivateInitNotChangedToPrivateWhenUsingTrailingClosureInit() { @@ -679,7 +679,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "4") testFormatting(for: input, rule: .redundantFileprivate, - options: options, exclude: ["typeSugar"]) + options: options, exclude: [.typeSugar]) } // MARK: - redundantGet @@ -829,7 +829,7 @@ class RedundancyTests: RulesTests { let Foo = Foo.self let foo = Foo.init() """ - testFormatting(for: input, rule: .redundantInit, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantInit, exclude: [.propertyType]) } func testNoRemoveInitForLocalLetType2() { @@ -854,7 +854,7 @@ class RedundancyTests: RulesTests { #endif } """ - testFormatting(for: input, rule: .redundantInit, exclude: ["indent"]) + testFormatting(for: input, rule: .redundantInit, exclude: [.indent]) } func testNoRemoveInitInsideIfdef2() { @@ -867,7 +867,7 @@ class RedundancyTests: RulesTests { #endif } """ - testFormatting(for: input, rule: .redundantInit, exclude: ["indent"]) + testFormatting(for: input, rule: .redundantInit, exclude: [.indent]) } func testRemoveInitAfterCollectionLiterals() { @@ -885,7 +885,7 @@ class RedundancyTests: RulesTests { let tupleArray = [(key: String, value: Int)]() let dictionary = [String: Int]() """ - testFormatting(for: input, output, rule: .redundantInit, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantInit, exclude: [.propertyType]) } func testPreservesInitAfterTypeOfCall() { @@ -906,7 +906,7 @@ class RedundancyTests: RulesTests { // (String!.init("Foo") isn't valid Swift code, so we don't test for it) """ - testFormatting(for: input, output, rule: .redundantInit, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantInit, exclude: [.propertyType]) } func testPreservesTryBeforeInit() { @@ -931,7 +931,7 @@ class RedundancyTests: RulesTests { let atomicDictionary = Atomic<[String: Int]>() """ - testFormatting(for: input, output, rule: .redundantInit, exclude: ["typeSugar", "propertyType"]) + testFormatting(for: input, output, rule: .redundantInit, exclude: [.typeSugar, .propertyType]) } func testPreserveNonRedundantInitInTernaryOperator() { @@ -1253,14 +1253,14 @@ class RedundancyTests: RulesTests { let input = "let foo: Void = Void()" let options = FormatOptions(redundantType: .inferred) testFormatting(for: input, rule: .redundantType, - options: options, exclude: ["void"]) + options: options, exclude: [.void]) } func testNoRemoveRedundantTypeIfVoid2() { let input = "let foo: () = ()" let options = FormatOptions(redundantType: .inferred) testFormatting(for: input, rule: .redundantType, - options: options, exclude: ["void"]) + options: options, exclude: [.void]) } func testNoRemoveRedundantTypeIfVoid3() { @@ -1273,7 +1273,7 @@ class RedundancyTests: RulesTests { let input = "let foo: Array = Array()" let options = FormatOptions(redundantType: .inferred) testFormatting(for: input, rule: .redundantType, - options: options, exclude: ["typeSugar"]) + options: options, exclude: [.typeSugar]) } func testNoRemoveRedundantTypeIfVoid5() { @@ -1286,7 +1286,7 @@ class RedundancyTests: RulesTests { let input = "let foo: Optional = Optional.none" let options = FormatOptions(redundantType: .inferred) testFormatting(for: input, rule: .redundantType, - options: options, exclude: ["typeSugar"]) + options: options, exclude: [.typeSugar]) } func testRedundantTypeWithLiterals() { @@ -1368,7 +1368,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) } func testRedundantTypeWithIfExpression_inferred() { @@ -1387,7 +1387,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) } func testRedundantTypeWithIfExpression_explicit() { @@ -1406,7 +1406,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment, .propertyType]) } func testRedundantTypeWithNestedIfExpression_inferred() { @@ -1445,7 +1445,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) } func testRedundantTypeWithNestedIfExpression_explicit() { @@ -1484,7 +1484,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment", "propertyType"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment, .propertyType]) } func testRedundantTypeWithLiteralsInIfExpression() { @@ -1503,7 +1503,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) } // --redundanttype explicit @@ -1513,7 +1513,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testVarRedundantTypeRemovalExplicitType2() { @@ -1521,7 +1521,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = .init /* foo */()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["spaceAroundComments", "propertyType"]) + options: options, exclude: [.spaceAroundComments, .propertyType]) } func testLetRedundantGenericTypeRemovalExplicitType() { @@ -1529,7 +1529,7 @@ class RedundancyTests: RulesTests { let output = "let relay: BehaviourRelay = .init(value: nil)" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testLetRedundantGenericTypeRemovalExplicitTypeIfValueOnNextLine() { @@ -1537,7 +1537,7 @@ class RedundancyTests: RulesTests { let output = "let relay: Foo = \n .default" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["trailingSpace", "propertyType"]) + options: options, exclude: [.trailingSpace, .propertyType]) } func testVarNonRedundantTypeDoesNothingExplicitType() { @@ -1551,7 +1551,7 @@ class RedundancyTests: RulesTests { let output = "let view: UIView = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeRemovedIfValueOnNextLineExplicitType() { @@ -1565,7 +1565,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeRemovedIfValueOnNextLine2ExplicitType() { @@ -1579,7 +1579,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeRemovalWithCommentExplicitType() { @@ -1587,7 +1587,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView /* view */ = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeRemovalWithComment2ExplicitType() { @@ -1595,7 +1595,7 @@ class RedundancyTests: RulesTests { let output = "var view: UIView = /* view */ .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeRemovalWithStaticMember() { @@ -1617,7 +1617,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeRemovalWithStaticFunc() { @@ -1639,13 +1639,13 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeDoesNothingWithChainedMember() { let input = "let session: URLSession = URLSession.default.makeCopy()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) } func testRedundantRedundantChainedMemberTypeRemovedOnSwift5_4() { @@ -1653,44 +1653,44 @@ class RedundancyTests: RulesTests { let output = "let session: URLSession = .default.makeCopy()" let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeDoesNothingWithChainedMember2() { let input = "let color: UIColor = UIColor.red.withAlphaComponent(0.5)" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) } func testRedundantTypeDoesNothingWithChainedMember3() { let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) } func testRedundantTypeRemovedWithChainedMemberOnSwift5_4() { let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" let output = "let url: URL = .init(fileURLWithPath: #file).deletingLastPathComponent()" let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.propertyType]) } func testRedundantTypeDoesNothingIfLet() { let input = "if let foo: Foo = Foo() {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) } func testRedundantTypeDoesNothingGuardLet() { let input = "guard let foo: Foo = Foo() else {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) } func testRedundantTypeDoesNothingIfLetAfterComma() { let input = "if check == true, let foo: Foo = Foo() {}" let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) } func testRedundantTypeWorksAfterIf() { @@ -1704,7 +1704,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeIfVoid() { @@ -1712,7 +1712,7 @@ class RedundancyTests: RulesTests { let output = "let foo: [Void] = .init()" let options = FormatOptions(redundantType: .explicit) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantTypeWithIntegerLiteralNotMangled() { @@ -1794,7 +1794,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(redundantType: .inferLocalsOnly) testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } // MARK: - redundantNilInit @@ -1848,7 +1848,7 @@ class RedundancyTests: RulesTests { let input = "lazy private(set) public var foo: Int? = nil" let options = FormatOptions(nilInit: .remove) testFormatting(for: input, rule: .redundantNilInit, options: options, - exclude: ["modifierOrder"]) + exclude: [.modifierOrder]) } func testNoRemoveCodableNilInit() { @@ -2055,7 +2055,7 @@ class RedundancyTests: RulesTests { let input = "lazy private(set) public var foo: Int?" let options = FormatOptions(nilInit: .insert) testFormatting(for: input, rule: .redundantNilInit, options: options, - exclude: ["modifierOrder"]) + exclude: [.modifierOrder]) } func testNoInsertCodableNilInit() { @@ -2292,7 +2292,7 @@ class RedundancyTests: RulesTests { func testRemoveRedundantLetInCase() { let input = "if case .foo(let _) = bar {}" let output = "if case .foo(_) = bar {}" - testFormatting(for: input, output, rule: .redundantLet, exclude: ["redundantPattern"]) + testFormatting(for: input, output, rule: .redundantLet, exclude: [.redundantPattern]) } func testRemoveRedundantVarsInCase() { @@ -2314,7 +2314,7 @@ class RedundancyTests: RulesTests { func testNoRemoveLetInGuard() { let input = "guard let _ = foo else {}" testFormatting(for: input, rule: .redundantLet, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoRemoveLetInWhile() { @@ -2434,7 +2434,7 @@ class RedundancyTests: RulesTests { func testSimplifyLetPattern() { let input = "let(_, _) = bar" let output = "let _ = bar" - testFormatting(for: input, output, rule: .redundantPattern, exclude: ["redundantLet"]) + testFormatting(for: input, output, rule: .redundantPattern, exclude: [.redundantLet]) } func testNoRemoveVoidFunctionCall() { @@ -2459,14 +2459,14 @@ class RedundancyTests: RulesTests { let input = "enum Foo: String { case bar = \"bar\", baz = \"baz\" }" let output = "enum Foo: String { case bar, baz }" testFormatting(for: input, output, rule: .redundantRawValues, - exclude: ["wrapEnumCases"]) + exclude: [.wrapEnumCases]) } func testRemoveBacktickCaseRawStringCases() { let input = "enum Foo: String { case `as` = \"as\", `let` = \"let\" }" let output = "enum Foo: String { case `as`, `let` }" testFormatting(for: input, output, rule: .redundantRawValues, - exclude: ["wrapEnumCases"]) + exclude: [.wrapEnumCases]) } func testNoRemoveRawStringIfNameDoesntMatch() { @@ -2563,13 +2563,13 @@ class RedundancyTests: RulesTests { func testRemoveRedundantReturnInClosure() { let input = "foo(with: { return 5 })" let output = "foo(with: { 5 })" - testFormatting(for: input, output, rule: .redundantReturn, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.trailingClosures]) } func testRemoveRedundantReturnInClosureWithArgs() { let input = "foo(with: { foo in return foo })" let output = "foo(with: { foo in foo })" - testFormatting(for: input, output, rule: .redundantReturn, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.trailingClosures]) } func testRemoveRedundantReturnInMap() { @@ -2611,13 +2611,13 @@ class RedundancyTests: RulesTests { func testRemoveReturnInVarClosure() { let input = "var foo = { return 5 }()" let output = "var foo = { 5 }()" - testFormatting(for: input, output, rule: .redundantReturn, exclude: ["redundantClosure"]) + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.redundantClosure]) } func testRemoveReturnInParenthesizedClosure() { let input = "var foo = ({ return 5 }())" let output = "var foo = ({ 5 }())" - testFormatting(for: input, output, rule: .redundantReturn, exclude: ["redundantParens", "redundantClosure"]) + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.redundantParens, .redundantClosure]) } func testNoRemoveReturnInFunction() { @@ -2634,7 +2634,7 @@ class RedundancyTests: RulesTests { func testNoRemoveReturnInOperatorFunction() { let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" - testFormatting(for: input, rule: .redundantReturn, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantReturn, exclude: [.unusedArguments]) } func testRemoveReturnInOperatorFunction() { @@ -2642,7 +2642,7 @@ class RedundancyTests: RulesTests { let output = "func + (lhs: Int, rhs: Int) -> Int { 5 }" let options = FormatOptions(swiftVersion: "5.1") testFormatting(for: input, output, rule: .redundantReturn, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testNoRemoveReturnInFailableInit() { @@ -2692,7 +2692,7 @@ class RedundancyTests: RulesTests { func testNoRemoveReturnInSubscript() { let input = "subscript(index: Int) -> String { return nil }" - testFormatting(for: input, rule: .redundantReturn, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .redundantReturn, exclude: [.unusedArguments]) } func testRemoveReturnInSubscript() { @@ -2700,7 +2700,7 @@ class RedundancyTests: RulesTests { let output = "subscript(index: Int) -> String { nil }" let options = FormatOptions(swiftVersion: "5.1") testFormatting(for: input, output, rule: .redundantReturn, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testNoRemoveReturnInDoCatch() { @@ -2761,30 +2761,30 @@ class RedundancyTests: RulesTests { func testNoRemoveReturnInForIn() { let input = "for foo in bar { return 5 }" - testFormatting(for: input, rule: .redundantReturn, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantReturn, exclude: [.wrapLoopBodies]) } func testNoRemoveReturnInForWhere() { let input = "for foo in bar where baz { return 5 }" - testFormatting(for: input, rule: .redundantReturn, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantReturn, exclude: [.wrapLoopBodies]) } func testNoRemoveReturnInIfLetTry() { let input = "if let foo = try? bar() { return 5 }" testFormatting(for: input, rule: .redundantReturn, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoRemoveReturnInMultiIfLetTry() { let input = "if let foo = bar, let bar = baz { return 5 }" testFormatting(for: input, rule: .redundantReturn, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoRemoveReturnAfterMultipleAs() { let input = "if foo as? bar as? baz { return 5 }" testFormatting(for: input, rule: .redundantReturn, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testRemoveVoidReturn() { @@ -2796,13 +2796,13 @@ class RedundancyTests: RulesTests { func testNoRemoveReturnAfterKeyPath() { let input = "func foo() { if bar == #keyPath(baz) { return 5 } }" testFormatting(for: input, rule: .redundantReturn, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoRemoveReturnAfterParentheses() { let input = "if let foo = (bar as? String) { return foo }" testFormatting(for: input, rule: .redundantReturn, - exclude: ["redundantParens", "wrapConditionalBodies"]) + exclude: [.redundantParens, .wrapConditionalBodies]) } func testRemoveReturnInTupleVarGetter() { @@ -2824,7 +2824,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.1") testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: ["spaceAroundBraces", "spaceAroundParens"]) + exclude: [.spaceAroundBraces, .spaceAroundParens]) } func testNoRemoveReturnInIfWithUnParenthesizedClosure() { @@ -2849,7 +2849,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, output, rule: .redundantReturn, - exclude: ["indent"]) + exclude: [.indent]) } func testRemoveRedundantReturnInFunctionWithWhereClause() { @@ -2892,7 +2892,7 @@ class RedundancyTests: RulesTests { return bar }() """ - testFormatting(for: input, rule: .redundantReturn, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantReturn, exclude: [.redundantProperty]) } func testNoRemoveReturnInForWhereLoop() { @@ -2919,7 +2919,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, output, rule: .redundantReturn, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testRedundantReturnInVoidFunction2() { @@ -2990,7 +2990,7 @@ class RedundancyTests: RulesTests { """ testFormatting(for: input, rule: .redundantReturn, options: FormatOptions(swiftVersion: "5.1"), - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoRemoveReturnInForCasewhere() { @@ -3015,7 +3015,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, rule: .redundantReturn, - options: FormatOptions(swiftVersion: "5.1"), exclude: ["redundantProperty"]) + options: FormatOptions(swiftVersion: "5.1"), exclude: [.redundantProperty]) } func testNoRemoveRequiredReturnInIfClosure() { @@ -3165,7 +3165,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: ["conditionalAssignment"]) + exclude: [.conditionalAssignment]) } func testRedundantIfStatementReturnInClosure() { @@ -3205,7 +3205,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: ["conditionalAssignment"]) + exclude: [.conditionalAssignment]) } func testNoRemoveReturnInConsecutiveIfStatements() { @@ -3246,7 +3246,7 @@ class RedundancyTests: RulesTests { testFormatting(for: input, [output], rules: [.redundantReturn, .conditionalAssignment, .redundantClosure, .indent], - options: options, exclude: ["wrapMultilineConditionalAssignment"]) + options: options, exclude: [.wrapMultilineConditionalAssignment]) } func testRedundantSwitchStatementReturnInFunction() { @@ -3289,7 +3289,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: ["conditionalAssignment"]) + exclude: [.conditionalAssignment]) } func testClosureAroundConditionalAssignmentNotRedundantForExplicitReturn() { @@ -3306,7 +3306,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, rule: .redundantClosure, options: options, - exclude: ["redundantReturn", "propertyType"]) + exclude: [.redundantReturn, .propertyType]) } func testNonRedundantSwitchStatementReturnInFunction() { @@ -3615,7 +3615,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: ["wrapSwitchCases", "sortSwitchCases"]) + exclude: [.wrapSwitchCases, .sortSwitchCases]) } func testDoesntRemoveReturnFromIfExpressionConditionalCastInSwift5_9() { @@ -3824,7 +3824,7 @@ class RedundancyTests: RulesTests { func testNoRemoveBackticksAroundTypeInsideType() { let input = "struct Foo {\n enum `Type` {}\n}" - testFormatting(for: input, rule: .redundantBackticks, exclude: ["enumNamespaces"]) + testFormatting(for: input, rule: .redundantBackticks, exclude: [.enumNamespaces]) } func testNoRemoveBackticksAroundLetArgument() { @@ -3851,7 +3851,7 @@ class RedundancyTests: RulesTests { func testNoRemoveBackticksAroundTypePropertyInsideType() { let input = "struct Foo {\n enum `Type` {}\n}" - testFormatting(for: input, rule: .redundantBackticks, exclude: ["enumNamespaces"]) + testFormatting(for: input, rule: .redundantBackticks, exclude: [.enumNamespaces]) } func testNoRemoveBackticksAroundTrueProperty() { @@ -3863,19 +3863,19 @@ class RedundancyTests: RulesTests { let input = "var type = Foo.`true`" let output = "var type = Foo.true" let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantBackticks, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantBackticks, options: options, exclude: [.propertyType]) } func testRemoveBackticksAroundProperty() { let input = "var type = Foo.`bar`" let output = "var type = Foo.bar" - testFormatting(for: input, output, rule: .redundantBackticks, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantBackticks, exclude: [.propertyType]) } func testRemoveBackticksAroundKeywordProperty() { let input = "var type = Foo.`default`" let output = "var type = Foo.default" - testFormatting(for: input, output, rule: .redundantBackticks, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantBackticks, exclude: [.propertyType]) } func testRemoveBackticksAroundKeypathProperty() { @@ -3899,7 +3899,7 @@ class RedundancyTests: RulesTests { func testNoRemoveBackticksAroundInitPropertyInSwift5() { let input = "let foo: Foo = .`init`" let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, rule: .redundantBackticks, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantBackticks, options: options, exclude: [.propertyType]) } func testNoRemoveBackticksAroundAnyProperty() { @@ -4101,7 +4101,7 @@ class RedundancyTests: RulesTests { func testNoRemoveSelfInClosureInsideIf() { let input = "if foo { bar { self.baz() } }" testFormatting(for: input, rule: .redundantSelf, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoRemoveSelfForErrorInCatch() { @@ -4146,12 +4146,12 @@ class RedundancyTests: RulesTests { func testNoRemoveSelfForIndexVarInFor() { let input = "for foo in bar { self.foo = foo }" - testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) } func testNoRemoveSelfForKeyValueTupleInFor() { let input = "for (foo, bar) in baz { self.foo = foo; self.bar = bar }" - testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) } func testRemoveSelfFromComputedVar() { @@ -4230,7 +4230,7 @@ class RedundancyTests: RulesTests { func testNoRemoveSelfFromLazyVarClosure() { let input = "lazy var foo = { self.bar }()" - testFormatting(for: input, rule: .redundantSelf, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantSelf, exclude: [.redundantClosure]) } func testNoRemoveSelfFromLazyVarClosure2() { @@ -4289,13 +4289,13 @@ class RedundancyTests: RulesTests { func testNoRemoveSelfInLocalVarPrecededByIfLetContainingClosure() { let input = "func foo() {\n if let bar = 5 { baz { _ in } }\n let quux = self.quux\n}" testFormatting(for: input, rule: .redundantSelf, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoRemoveSelfForVarCreatedInGuardScope() { let input = "func foo() {\n guard let bar = 5 else {}\n let baz = self.bar\n}" testFormatting(for: input, rule: .redundantSelf, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testRemoveSelfForVarCreatedInIfScope() { @@ -4306,13 +4306,13 @@ class RedundancyTests: RulesTests { func testNoRemoveSelfForVarDeclaredInWhileCondition() { let input = "while let foo = bar { self.foo = foo }" - testFormatting(for: input, rule: .redundantSelf, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) } func testRemoveSelfForVarNotDeclaredInWhileCondition() { let input = "while let foo == bar { self.baz = 5 }" let output = "while let foo == bar { baz = 5 }" - testFormatting(for: input, output, rule: .redundantSelf, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, output, rule: .redundantSelf, exclude: [.wrapLoopBodies]) } func testNoRemoveSelfForVarDeclaredInSwitchCase() { @@ -4334,14 +4334,14 @@ class RedundancyTests: RulesTests { func testRemoveSelfInStaticFunction() { let input = "struct Foo {\n static func foo() {\n func bar() { self.foo() }\n }\n}" let output = "struct Foo {\n static func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: .redundantSelf, exclude: ["enumNamespaces"]) + testFormatting(for: input, output, rule: .redundantSelf, exclude: [.enumNamespaces]) } func testRemoveSelfInClassFunctionWithModifiers() { let input = "class Foo {\n class private func foo() {\n func bar() { self.foo() }\n }\n}" let output = "class Foo {\n class private func foo() {\n func bar() { foo() }\n }\n}" testFormatting(for: input, output, rule: .redundantSelf, - exclude: ["modifierOrder"]) + exclude: [.modifierOrder]) } func testNoRemoveSelfInClassFunction() { @@ -4481,7 +4481,7 @@ class RedundancyTests: RulesTests { let vc = UIHostingController(rootView: InspectionView(inspection: self.inspection)) """ let options = FormatOptions(selfRequired: ["InspectionView"]) - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.propertyType]) } func testNoMistakeProtocolClassModifierForClassFunction() { @@ -4799,7 +4799,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: .redundantSelf, options: options, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .redundantSelf, options: options, exclude: [.unusedArguments]) } func testRedundantSelfRemovesSelfInClosureWithNestedExplicitStrongCapture() { @@ -4996,7 +4996,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.8") testFormatting(for: input, output, rule: .redundantSelf, - options: options, exclude: ["redundantOptionalBinding"]) + options: options, exclude: [.redundantOptionalBinding]) } func testWeakSelfNotRemovedIfNotUnwrapped() { @@ -5108,7 +5108,7 @@ class RedundancyTests: RulesTests { func testRedundantSelfDoesntGetStuckIfNoParensFound() { let input = "init_ foo: T {}" testFormatting(for: input, rule: .redundantSelf, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testNoRemoveSelfInIfLetSelf() { @@ -5304,7 +5304,7 @@ class RedundancyTests: RulesTests { }) {} """ testFormatting(for: input, rule: .redundantSelf, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testStructSelfRemovedInTrailingClosureInIfCase() { @@ -5449,7 +5449,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, rule: .redundantSelf, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoRemoveSelfInAssignmentInsideIfAsStatement() { @@ -5497,7 +5497,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, output, rule: .redundantSelf, - exclude: ["hoistPatternLet"]) + exclude: [.hoistPatternLet]) } func testRedundantSelfParsingBug2() { @@ -5683,7 +5683,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) } func testDisableRedundantSelfDirective() { @@ -5697,7 +5697,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) } func testDisableRedundantSelfDirective2() { @@ -5712,7 +5712,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) } func testSelfInsertDirective() { @@ -5726,7 +5726,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) } func testNoRemoveVariableShadowedLaterInScopeInOlderSwiftVersions() { @@ -5938,7 +5938,7 @@ class RedundancyTests: RulesTests { func testNoInsertSelfForNestedVarReference() { let input = "class Foo {\n func bar() {\n var bar = 5\n repeat { bar = 6 } while true\n }\n}" let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.wrapLoopBodies]) } func testNoInsertSelfInSwitchCaseLet() { @@ -5951,7 +5951,7 @@ class RedundancyTests: RulesTests { let input = "import class Foo.Bar\nfunc foo() {\n var bar = 5\n if true {\n bar = 6\n }\n}" let options = FormatOptions(explicitSelf: .insert) testFormatting(for: input, rule: .redundantSelf, options: options, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testNoInsertSelfForSubscriptGetSet() { @@ -5965,7 +5965,7 @@ class RedundancyTests: RulesTests { let input = "enum Foo {\n case bar(Int)\n var value: Int? {\n if case let .bar(value) = self { return value }\n }\n}" let options = FormatOptions(explicitSelf: .insert) testFormatting(for: input, rule: .redundantSelf, options: options, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoInsertSelfForPatternLet() { @@ -6358,7 +6358,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(explicitSelf: .insert) testFormatting(for: input, rule: .redundantSelf, options: options, - exclude: ["hoistPatternLet"]) + exclude: [.hoistPatternLet]) } func testNoInsertSelfForVarDefinedInFor() { @@ -6717,7 +6717,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["redundantClosure"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantClosure]) } func testRedundantSelfRuleFailsInInitOnlyMode2() { @@ -6888,7 +6888,7 @@ class RedundancyTests: RulesTests { }) """ testFormatting(for: input, rule: .redundantSelf, - exclude: ["hoistPatternLet"]) + exclude: [.hoistPatternLet]) } func testSelfRemovalParsingBug7() { @@ -7325,7 +7325,7 @@ class RedundancyTests: RulesTests { } """ let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: ["enumNamespaces"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.enumNamespaces]) } func testRedundantSelfNotConfusedByMainActor() { @@ -7456,7 +7456,7 @@ class RedundancyTests: RulesTests { func testStaticSelfNotRemovedWhenUsedAsExplicitInitializer() { let input = "enum E { static func foo() { Self.init().bar() } }" - testFormatting(for: input, rule: .redundantStaticSelf, exclude: ["redundantInit"]) + testFormatting(for: input, rule: .redundantStaticSelf, exclude: [.redundantInit]) } func testPreservesStaticSelfInFunctionAfterStaticVar() { @@ -7477,7 +7477,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: .redundantStaticSelf, exclude: ["propertyType"]) + testFormatting(for: input, rule: .redundantStaticSelf, exclude: [.propertyType]) } func testPreserveStaticSelfInInstanceFunction() { @@ -7854,7 +7854,7 @@ class RedundancyTests: RulesTests { return parser } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty", "propertyType"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty, .propertyType]) } func testShadowedClosureArgument2() { @@ -7864,7 +7864,7 @@ class RedundancyTests: RulesTests { return input } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) } func testUnusedPropertyWrapperArgument() { @@ -8052,7 +8052,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, rule: .unusedArguments, - exclude: ["hoistPatternLet"]) + exclude: [.hoistPatternLet]) } // functions @@ -8118,7 +8118,7 @@ class RedundancyTests: RulesTests { func testLabelsAreNotArguments() { let input = "func foo(bar: Int, baz: String) {\n bar: while true { print(baz) }\n}" let output = "func foo(bar _: Int, baz: String) {\n bar: while true { print(baz) }\n}" - testFormatting(for: input, output, rule: .unusedArguments, exclude: ["wrapLoopBodies"]) + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.wrapLoopBodies]) } func testDictionaryLiteralsRuinEverything() { @@ -8254,7 +8254,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, rule: .unusedArguments, - exclude: ["sortSwitchCases"]) + exclude: [.sortSwitchCases]) } func testTryArgumentNotMarkedUnused() { @@ -8265,7 +8265,7 @@ class RedundancyTests: RulesTests { return bar } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) } func testTryAwaitArgumentNotMarkedUnused() { @@ -8276,7 +8276,7 @@ class RedundancyTests: RulesTests { return bar } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) } func testTypedTryAwaitArgumentNotMarkedUnused() { @@ -8287,7 +8287,7 @@ class RedundancyTests: RulesTests { return bar } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) } func testConditionalIfLetMarkedAsUnused() { @@ -8509,7 +8509,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, output, rule: .unusedArguments, - exclude: ["braces", "wrapArguments"]) + exclude: [.braces, .wrapArguments]) } func testArgumentUsedInDictionaryLiteral() { @@ -8524,7 +8524,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, rule: .unusedArguments, - exclude: ["trailingCommas"]) + exclude: [.trailingCommas]) } func testArgumentUsedAfterIfDefInsideSwitchBlock() { @@ -8551,7 +8551,7 @@ class RedundancyTests: RulesTests { file.close() } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.noExplicitOwnership]) } func testUsedConsumingBorrowingArguments() { @@ -8561,7 +8561,7 @@ class RedundancyTests: RulesTests { borrow(b) } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.noExplicitOwnership]) } func testUnusedConsumingArgument() { @@ -8575,7 +8575,7 @@ class RedundancyTests: RulesTests { print("no-op") } """ - testFormatting(for: input, output, rule: .unusedArguments, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.noExplicitOwnership]) } func testUnusedConsumingBorrowingArguments() { @@ -8589,7 +8589,7 @@ class RedundancyTests: RulesTests { print("no-op") } """ - testFormatting(for: input, output, rule: .unusedArguments, exclude: ["noExplicitOwnership"]) + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.noExplicitOwnership]) } func testFunctionArgumentUsedInGuardNotRemoved() { @@ -8643,7 +8643,7 @@ class RedundancyTests: RulesTests { } """ testFormatting(for: input, rule: .unusedArguments, - exclude: ["wrapArguments", "wrapConditionalBodies", "indent"]) + exclude: [.wrapArguments, .wrapConditionalBodies, .indent]) } // functions (closure-only) @@ -8774,7 +8774,7 @@ class RedundancyTests: RulesTests { self?.configure(update) } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantParens"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantParens]) } func testIssue1696() { @@ -8787,7 +8787,7 @@ class RedundancyTests: RulesTests { return parameter } """ - testFormatting(for: input, rule: .unusedArguments, exclude: ["redundantProperty"]) + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) } // MARK: redundantClosure @@ -8839,7 +8839,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure], - options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) } func testRedundantClosureWithExplicitReturn2() { @@ -8906,7 +8906,7 @@ class RedundancyTests: RulesTests { lazy var bar = Bar() """ - testFormatting(for: input, output, rule: .redundantClosure, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantClosure, exclude: [.propertyType]) } func testRemoveRedundantClosureInMultiLinePropertyDeclarationWithString() { @@ -8943,7 +8943,7 @@ class RedundancyTests: RulesTests { """ testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure, - .semicolons], exclude: ["propertyType"]) + .semicolons], exclude: [.propertyType]) } func testRemoveRedundantClosureInWrappedPropertyDeclaration_beforeFirst() { @@ -8964,7 +8964,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) testFormatting(for: input, [output], rules: [.redundantClosure, .wrapArguments], - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRemoveRedundantClosureInWrappedPropertyDeclaration_afterFirst() { @@ -8983,7 +8983,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) testFormatting(for: input, [output], rules: [.redundantClosure, .wrapArguments], - options: options, exclude: ["propertyType"]) + options: options, exclude: [.propertyType]) } func testRedundantClosureKeepsMultiStatementClosureThatSetsProperty() { @@ -9071,7 +9071,7 @@ class RedundancyTests: RulesTests { """ testFormatting(for: input, rule: .redundantClosure, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testRemovesClosureWithIfStatementInsideOtherClosure() { @@ -9137,7 +9137,7 @@ class RedundancyTests: RulesTests { """ testFormatting(for: input, rule: .redundantClosure, - exclude: ["redundantReturn"]) + exclude: [.redundantReturn]) } func testRemovesClosureThatHasNestedFatalError() { @@ -9151,7 +9151,7 @@ class RedundancyTests: RulesTests { lazy var foo = Foo(handle: { fatalError() }) """ - testFormatting(for: input, output, rule: .redundantClosure, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .redundantClosure, exclude: [.propertyType]) } func testPreservesClosureWithMultipleVoidMethodCalls() { @@ -9185,7 +9185,7 @@ class RedundancyTests: RulesTests { }) """ - testFormatting(for: input, [output], rules: [.redundantClosure, .indent], exclude: ["redundantType"]) + testFormatting(for: input, [output], rules: [.redundantClosure, .indent], exclude: [.redundantType]) } func testKeepsClosureThatThrowsError() { @@ -9238,7 +9238,7 @@ class RedundancyTests: RulesTests { testFormatting(for: input, [output], rules: [.redundantReturn, .conditionalAssignment, .redundantClosure], - options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) } func testRedundantClosureDoesntLeaveStrayTryAwait() { @@ -9262,7 +9262,7 @@ class RedundancyTests: RulesTests { testFormatting(for: input, [output], rules: [.redundantReturn, .conditionalAssignment, .redundantClosure], - options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) } func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInOperatorChain() { @@ -9380,7 +9380,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) } func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInArray() { @@ -9428,7 +9428,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) } func testRedundantClosureRemovesClosureAsReturnTryStatement2() { @@ -9455,7 +9455,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) } func testRedundantClosureRemovesClosureAsReturnTryStatement3() { @@ -9482,7 +9482,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) } func testRedundantClosureRemovesClosureAsReturnTryStatement4() { @@ -9509,7 +9509,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["redundantReturn", "indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) } func testRedundantClosureRemovesClosureAsReturnStatement() { @@ -9537,7 +9537,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, [output], rules: [.redundantClosure], - options: options, exclude: ["redundantReturn", "indent"]) + options: options, exclude: [.redundantReturn, .indent]) } func testRedundantClosureRemovesClosureAsImplicitReturnStatement() { @@ -9564,7 +9564,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["indent"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent]) } func testClosureNotRemovedAroundIfExpressionInGuard() { @@ -9715,7 +9715,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: ["indent", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) } func testRedundantClosureDoesntBreakBuildWithRedundantReturnRuleDisabled() { @@ -9741,7 +9741,7 @@ class RedundancyTests: RulesTests { let options = FormatOptions(swiftVersion: "5.9") testFormatting(for: input, output, rule: .redundantClosure, options: options, - exclude: ["redundantReturn", "blankLinesBetweenScopes", "propertyType"]) + exclude: [.redundantReturn, .blankLinesBetweenScopes, .propertyType]) } func testRedundantClosureWithSwitchExpressionDoesntBreakBuildWithRedundantReturnRuleDisabled() { @@ -9781,8 +9781,8 @@ class RedundancyTests: RulesTests { rules: [.redundantReturn, .conditionalAssignment, .redundantClosure], options: options, - exclude: ["indent", "blankLinesBetweenScopes", "wrapMultilineConditionalAssignment", - "propertyType"]) + exclude: [.indent, .blankLinesBetweenScopes, .wrapMultilineConditionalAssignment, + .propertyType]) } func testRemovesRedundantClosureWithGenericExistentialTypes() { @@ -9911,7 +9911,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options, exclude: ["elseOnSameLine"]) + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options, exclude: [.elseOnSameLine]) } func testRemovesMultipleOptionalBindings() { @@ -10010,7 +10010,7 @@ class RedundancyTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .redundantOptionalBinding, options: options, exclude: ["redundantSelf"]) + testFormatting(for: input, rule: .redundantOptionalBinding, options: options, exclude: [.redundantSelf]) } func testRedundantSelfAndRedundantOptionalTogether() { @@ -10119,7 +10119,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: .redundantInternal, exclude: ["redundantExtensionACL"]) + testFormatting(for: input, output, rule: .redundantInternal, exclude: [.redundantExtensionACL]) } func testPreserveInternalImport() { @@ -10155,7 +10155,7 @@ class RedundancyTests: RulesTests { func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} """ - testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: [.unusedArguments]) } func testRemovesOwnershipKeywordsFromClosure() { @@ -10179,7 +10179,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: ["unusedArguments"]) + testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: [.unusedArguments]) } func testRemovesOwnershipKeywordsFromType() { @@ -10212,7 +10212,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: .redundantProperty, exclude: ["redundantReturn"]) + testFormatting(for: input, output, rule: .redundantProperty, exclude: [.redundantReturn]) } func testRemovesRedundantPropertyWithIfExpression() { @@ -10287,7 +10287,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, [output], rules: [.propertyType, .redundantProperty, .redundantInit], exclude: ["redundantReturn"]) + testFormatting(for: input, [output], rules: [.propertyType, .redundantProperty, .redundantInit], exclude: [.redundantReturn]) } func testRemovesRedundantPropertyWithComments() { @@ -10308,7 +10308,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: .redundantProperty, exclude: ["redundantReturn"]) + testFormatting(for: input, output, rule: .redundantProperty, exclude: [.redundantReturn]) } func testRemovesRedundantPropertyFollowingOtherProperty() { @@ -10538,7 +10538,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: [.emptyBraces]) } func testDoNotRemoveFilePrivateUsedInNestedStruct() { @@ -10599,7 +10599,7 @@ class RedundancyTests: RulesTests { } """ - testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: ["emptyBraces"]) + testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: [.emptyBraces]) } func testRemovePrivateDeclarationButDoNotRemovePrivateExtension() { @@ -10640,7 +10640,7 @@ class RedundancyTests: RulesTests { static let foo = Foo() } """ - testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: ["propertyType"]) + testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: [.propertyType]) } func testCanDisableUnusedPrivateDeclarationRule() { @@ -10698,7 +10698,7 @@ class RedundancyTests: RulesTests { } } """ - testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: ["redundantBackticks"]) + testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: [.redundantBackticks]) } func testDoNotRemovePreservedPrivateDeclarations() { diff --git a/Tests/RulesTests+Spacing.swift b/Tests/RulesTests+Spacing.swift index 0cd384574..8fafc9ca2 100644 --- a/Tests/RulesTests+Spacing.swift +++ b/Tests/RulesTests+Spacing.swift @@ -73,12 +73,12 @@ class SpacingTests: RulesTests { func testSpaceBetweenParenAndAs() { let input = "(foo.bar) as? String" - testFormatting(for: input, rule: .spaceAroundParens, exclude: ["redundantParens"]) + testFormatting(for: input, rule: .spaceAroundParens, exclude: [.redundantParens]) } func testNoSpaceAfterParenAtEndOfFile() { let input = "(foo.bar)" - testFormatting(for: input, rule: .spaceAroundParens, exclude: ["redundantParens"]) + testFormatting(for: input, rule: .spaceAroundParens, exclude: [.redundantParens]) } func testSpaceBetweenParenAndFoo() { @@ -160,19 +160,19 @@ class SpacingTests: RulesTests { func testAddSpaceBetweenCaptureListAndArguments() { let input = "{ [weak self](foo) in print(foo) }" let output = "{ [weak self] (foo) in print(foo) }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantParens]) } func testAddSpaceBetweenCaptureListAndArguments2() { let input = "{ [weak self]() -> Void in }" let output = "{ [weak self] () -> Void in }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantVoidReturnType]) } func testAddSpaceBetweenCaptureListAndArguments3() { let input = "{ [weak self]() throws -> Void in }" let output = "{ [weak self] () throws -> Void in }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantVoidReturnType]) } func testAddSpaceBetweenCaptureListAndArguments4() { @@ -196,13 +196,13 @@ class SpacingTests: RulesTests { func testAddSpaceBetweenCaptureListAndArguments7() { let input = "Foo(0) { [weak self]() -> Void in }" let output = "Foo(0) { [weak self] () -> Void in }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantVoidReturnType]) } func testAddSpaceBetweenCaptureListAndArguments8() { let input = "{ [weak self]() throws(Foo) -> Void in }" let output = "{ [weak self] () throws(Foo) -> Void in }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantVoidReturnType]) } func testAddSpaceBetweenEscapingAndParenthesizedClosure() { @@ -226,13 +226,13 @@ class SpacingTests: RulesTests { func testNoSpaceBetweenClosingBraceAndParens() { let input = "{ block } ()" let output = "{ block }()" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: ["redundantClosure"]) + testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantClosure]) } func testDontRemoveSpaceBetweenOpeningBraceAndParens() { let input = "a = (b + c)" testFormatting(for: input, rule: .spaceAroundParens, - exclude: ["redundantParens"]) + exclude: [.redundantParens]) } func testKeywordAsIdentifierParensSpacing() { @@ -332,7 +332,7 @@ class SpacingTests: RulesTests { let input = "func foo(_: borrowing(any Foo)) {}" let output = "func foo(_: borrowing (any Foo)) {}" testFormatting(for: input, output, rule: .spaceAroundParens, - exclude: ["noExplicitOwnership"]) + exclude: [.noExplicitOwnership]) } func testAddSpaceBetweenParenAndIsolated() { @@ -435,14 +435,14 @@ class SpacingTests: RulesTests { let input = "func foo(arg _: consuming[String]) {}" let output = "func foo(arg _: consuming [String]) {}" testFormatting(for: input, output, rule: .spaceAroundBrackets, - exclude: ["noExplicitOwnership"]) + exclude: [.noExplicitOwnership]) } func testAddSpaceBetweenBorrowingAndStringArray() { let input = "func foo(arg _: borrowing[String]) {}" let output = "func foo(arg _: borrowing [String]) {}" testFormatting(for: input, output, rule: .spaceAroundBrackets, - exclude: ["noExplicitOwnership"]) + exclude: [.noExplicitOwnership]) } func testAddSpaceBetweenSendingAndStringArray() { @@ -478,13 +478,13 @@ class SpacingTests: RulesTests { let input = "if x{ y }else{ z }" let output = "if x { y } else { z }" testFormatting(for: input, output, rule: .spaceAroundBraces, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testNoSpaceAroundClosureInsiderParens() { let input = "foo({ $0 == 5 })" testFormatting(for: input, rule: .spaceAroundBraces, - exclude: ["trailingClosures"]) + exclude: [.trailingClosures]) } func testNoExtraSpaceAroundBracesAtStartOrEndOfFile() { @@ -531,17 +531,17 @@ class SpacingTests: RulesTests { func testSpaceInsideBraces() { let input = "foo({bar})" let output = "foo({ bar })" - testFormatting(for: input, output, rule: .spaceInsideBraces, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .spaceInsideBraces, exclude: [.trailingClosures]) } func testNoExtraSpaceInsidebraces() { let input = "{ foo }" - testFormatting(for: input, rule: .spaceInsideBraces, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .spaceInsideBraces, exclude: [.trailingClosures]) } func testNoSpaceAddedInsideEmptybraces() { let input = "foo({})" - testFormatting(for: input, rule: .spaceInsideBraces, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .spaceInsideBraces, exclude: [.trailingClosures]) } func testNoSpaceAddedBetweenDoublebraces() { @@ -559,7 +559,7 @@ class SpacingTests: RulesTests { func testSpaceAroundGenericsFollowedByAndOperator() { let input = "if foo is Foo && baz {}" - testFormatting(for: input, rule: .spaceAroundGenerics, exclude: ["andOperator"]) + testFormatting(for: input, rule: .spaceAroundGenerics, exclude: [.andOperator]) } func testSpaceAroundGenericResultBuilder() { @@ -649,7 +649,7 @@ class SpacingTests: RulesTests { func testNoRemoveSpaceAroundEnumInBrackets() { let input = "[ .red ]" testFormatting(for: input, rule: .spaceAroundOperators, - exclude: ["spaceInsideBrackets"]) + exclude: [.spaceInsideBrackets]) } func testSpaceBetweenSemicolonAndEnumValue() { @@ -695,7 +695,7 @@ class SpacingTests: RulesTests { let input = "print(foo\n ,bar)" let output = "print(foo\n , bar)" testFormatting(for: input, output, rule: .spaceAroundOperators, - exclude: ["leadingDelimiters"]) + exclude: [.leadingDelimiters]) } func testSpaceAroundInfixMinus() { @@ -882,14 +882,14 @@ class SpacingTests: RulesTests { let input = "foo/* hello */-bar" let output = "foo/* hello */ -bar" testFormatting(for: input, output, rule: .spaceAroundOperators, - exclude: ["spaceAroundComments"]) + exclude: [.spaceAroundComments]) } func testSpaceAroundCommentsInInfixExpression() { let input = "a/* */+/* */b" let output = "a/* */ + /* */b" testFormatting(for: input, output, rule: .spaceAroundOperators, - exclude: ["spaceAroundComments"]) + exclude: [.spaceAroundComments]) } func testSpaceAroundCommentInPrefixExpression() { @@ -962,7 +962,7 @@ class SpacingTests: RulesTests { func testNoAddSpaceAroundOperatorInsideParens() { let input = "(!=)" - testFormatting(for: input, rule: .spaceAroundOperators, exclude: ["redundantParens"]) + testFormatting(for: input, rule: .spaceAroundOperators, exclude: [.redundantParens]) } func testSpaceAroundPlusBeforeHash() { @@ -1077,28 +1077,28 @@ class SpacingTests: RulesTests { let input = "let range = 0 +\n4" let options = FormatOptions(noSpaceOperators: ["+"]) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["indent"]) + exclude: [.indent]) } func testSpaceOnOneSideOfPlusMatchedByLinebreakNotRemoved2() { let input = "let range = 0\n+ 4" let options = FormatOptions(noSpaceOperators: ["+"]) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["indent"]) + exclude: [.indent]) } func testSpaceAroundPlusWithLinebreakOnOneSideNotRemoved() { let input = "let range = 0 + \n4" let options = FormatOptions(noSpaceOperators: ["+"]) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["indent", "trailingSpace"]) + exclude: [.indent, .trailingSpace]) } func testSpaceAroundPlusWithLinebreakOnOneSideNotRemoved2() { let input = "let range = 0\n + 4" let options = FormatOptions(noSpaceOperators: ["+"]) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["indent"]) + exclude: [.indent]) } func testAddSpaceEvenAfterLHSClosure() { @@ -1198,56 +1198,56 @@ class SpacingTests: RulesTests { let input = "let range = 0 .../* foo */4" let options = FormatOptions(spaceAroundRangeOperators: false) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["spaceAroundComments"]) + exclude: [.spaceAroundComments]) } func testSpaceOnOneSideOfRangeMatchedByCommentNotRemoved2() { let input = "let range = 0/* foo */... 4" let options = FormatOptions(spaceAroundRangeOperators: false) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["spaceAroundComments"]) + exclude: [.spaceAroundComments]) } func testSpaceAroundRangeWithCommentOnOneSideNotRemoved() { let input = "let range = 0 ... /* foo */4" let options = FormatOptions(spaceAroundRangeOperators: false) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["spaceAroundComments"]) + exclude: [.spaceAroundComments]) } func testSpaceAroundRangeWithCommentOnOneSideNotRemoved2() { let input = "let range = 0/* foo */ ... 4" let options = FormatOptions(spaceAroundRangeOperators: false) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["spaceAroundComments"]) + exclude: [.spaceAroundComments]) } func testSpaceOnOneSideOfRangeMatchedByLinebreakNotRemoved() { let input = "let range = 0 ...\n4" let options = FormatOptions(spaceAroundRangeOperators: false) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["indent"]) + exclude: [.indent]) } func testSpaceOnOneSideOfRangeMatchedByLinebreakNotRemoved2() { let input = "let range = 0\n... 4" let options = FormatOptions(spaceAroundRangeOperators: false) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["indent"]) + exclude: [.indent]) } func testSpaceAroundRangeWithLinebreakOnOneSideNotRemoved() { let input = "let range = 0 ... \n4" let options = FormatOptions(spaceAroundRangeOperators: false) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["indent", "trailingSpace"]) + exclude: [.indent, .trailingSpace]) } func testSpaceAroundRangeWithLinebreakOnOneSideNotRemoved2() { let input = "let range = 0\n ... 4" let options = FormatOptions(spaceAroundRangeOperators: false) testFormatting(for: input, rule: .spaceAroundOperators, options: options, - exclude: ["indent"]) + exclude: [.indent]) } func testSpaceNotRemovedAroundRangeFollowedByPrefixOperator() { @@ -1325,7 +1325,7 @@ class SpacingTests: RulesTests { let input = "(/* foo */)" let output = "( /* foo */ )" testFormatting(for: input, output, rule: .spaceAroundComments, - exclude: ["redundantParens"]) + exclude: [.redundantParens]) } func testNoSpaceAroundCommentAtStartAndEndOfFile() { @@ -1373,7 +1373,7 @@ class SpacingTests: RulesTests { func testSpaceInsideMultilineHeaderdocComment() { let input = "/**foo\n bar*/" let output = "/** foo\n bar */" - testFormatting(for: input, output, rule: .spaceInsideComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .spaceInsideComments, exclude: [.docComments]) } func testSpaceInsideMultilineHeaderdocCommentType2() { @@ -1390,7 +1390,7 @@ class SpacingTests: RulesTests { func testNoExtraSpaceInsideMultilineHeaderdocComment() { let input = "/** foo\n bar */" - testFormatting(for: input, rule: .spaceInsideComments, exclude: ["docComments"]) + testFormatting(for: input, rule: .spaceInsideComments, exclude: [.docComments]) } func testNoExtraSpaceInsideMultilineHeaderdocCommentType2() { @@ -1433,7 +1433,7 @@ class SpacingTests: RulesTests { func testNoSpaceAddedToFirstLineOfDocComment() { let input = "/**\n Comment\n */" - testFormatting(for: input, rule: .spaceInsideComments, exclude: ["docComments"]) + testFormatting(for: input, rule: .spaceInsideComments, exclude: [.docComments]) } func testNoSpaceAddedToEmptyDocComment() { @@ -1451,7 +1451,7 @@ class SpacingTests: RulesTests { func bar() {} } """ - testFormatting(for: input, rule: .spaceInsideComments, exclude: ["indent"]) + testFormatting(for: input, rule: .spaceInsideComments, exclude: [.indent]) } // MARK: - consecutiveSpaces @@ -1537,7 +1537,7 @@ class SpacingTests: RulesTests { func testTrailingSpaceInArray() { let input = "let foo = [\n 1,\n \n 2,\n]" let output = "let foo = [\n 1,\n\n 2,\n]" - testFormatting(for: input, output, rule: .trailingSpace, exclude: ["redundantSelf"]) + testFormatting(for: input, output, rule: .trailingSpace, exclude: [.redundantSelf]) } // truncateBlankLines = false @@ -1788,7 +1788,7 @@ class SpacingTests: RulesTests { } """ - testFormatting(for: input, rule: .blankLineAfterSwitchCase, exclude: ["sortSwitchCases", "wrapSwitchCases", "blockComments"]) + testFormatting(for: input, rule: .blankLineAfterSwitchCase, exclude: [.sortSwitchCases, .wrapSwitchCases, .blockComments]) } func testMixedSingleLineAndMultiLineCases() { @@ -1829,7 +1829,7 @@ class SpacingTests: RulesTests { energyShields.engage() } """ - testFormatting(for: input, output, rule: .blankLineAfterSwitchCase, exclude: ["consistentSwitchCaseSpacing"]) + testFormatting(for: input, output, rule: .blankLineAfterSwitchCase, exclude: [.consistentSwitchCaseSpacing]) } func testAllowsBlankLinesAfterSingleLineCases() { @@ -2166,6 +2166,6 @@ class SpacingTests: RulesTests { } """ - testFormatting(for: input, rule: .consistentSwitchCaseSpacing, exclude: ["blankLineAfterSwitchCase"]) + testFormatting(for: input, rule: .consistentSwitchCaseSpacing, exclude: [.blankLineAfterSwitchCase]) } } diff --git a/Tests/RulesTests+Syntax.swift b/Tests/RulesTests+Syntax.swift index 837031d99..2670a4ae9 100644 --- a/Tests/RulesTests+Syntax.swift +++ b/Tests/RulesTests+Syntax.swift @@ -62,7 +62,7 @@ class SyntaxTests: RulesTests { /// TODO: foo /// Some more docs """ - testFormatting(for: input, rule: .todos, exclude: ["docComments"]) + testFormatting(for: input, rule: .todos, exclude: [.docComments]) } func testTodoNotReplacedAtStartOfDocBlock() { @@ -70,7 +70,7 @@ class SyntaxTests: RulesTests { /// TODO: foo /// Some docs """ - testFormatting(for: input, rule: .todos, exclude: ["docComments"]) + testFormatting(for: input, rule: .todos, exclude: [.docComments]) } func testTodoNotReplacedAtEndOfDocBlock() { @@ -78,7 +78,7 @@ class SyntaxTests: RulesTests { /// Some docs /// TODO: foo """ - testFormatting(for: input, rule: .todos, exclude: ["docComments"]) + testFormatting(for: input, rule: .todos, exclude: [.docComments]) } func testMarkWithNoSpaceAfterColon() { @@ -197,7 +197,7 @@ class SyntaxTests: RulesTests { func testAnonymousVoidArgumentNotConvertedToEmptyParens() { let input = "{ (_: Void) -> Void in }" - testFormatting(for: input, rule: .void, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, rule: .void, exclude: [.redundantVoidReturnType]) } func testFuncWithAnonymousVoidArgumentNotStripped() { @@ -256,12 +256,12 @@ class SyntaxTests: RulesTests { func testEmptyClosureReturnValueConvertedToVoid() { let input = "{ () -> () in }" let output = "{ () -> Void in }" - testFormatting(for: input, output, rule: .void, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .void, exclude: [.redundantVoidReturnType]) } func testAnonymousVoidClosureNotChanged() { let input = "{ (_: Void) in }" - testFormatting(for: input, rule: .void, exclude: ["unusedArguments"]) + testFormatting(for: input, rule: .void, exclude: [.unusedArguments]) } func testVoidLiteralConvertedToParens() { @@ -379,7 +379,7 @@ class SyntaxTests: RulesTests { let input = "{ () -> Void in }" let output = "{ () -> () in }" let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: .void, options: options, exclude: ["redundantVoidReturnType"]) + testFormatting(for: input, output, rule: .void, options: options, exclude: [.redundantVoidReturnType]) } func testNoConvertVoidSelfToTuple() { @@ -437,7 +437,7 @@ class SyntaxTests: RulesTests { func testClosureArgumentAfterLinebreakInGuardNotMadeTrailing() { let input = "guard let foo =\n bar({ /* some code */ })\nelse { return }" testFormatting(for: input, rule: .trailingClosures, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testClosureMadeTrailingForNumericTupleMember() { @@ -488,7 +488,7 @@ class SyntaxTests: RulesTests { func testParensAroundTrailingClosureInGuardCaseLetNotRemoved() { let input = "guard case let .foo(bar) = baz.filter({ $0 == quux }).isEmpty else {}" testFormatting(for: input, rule: .trailingClosures, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testParensAroundTrailingClosureInWhereClauseLetNotRemoved() { @@ -934,7 +934,7 @@ class SyntaxTests: RulesTests { } """ testFormatting(for: input, rule: .enumNamespaces, - exclude: ["modifierOrder"]) + exclude: [.modifierOrder]) } func testOpenClassNotReplacedByEnum() { @@ -1270,7 +1270,7 @@ class SyntaxTests: RulesTests { let input = "guard true && true\nelse { return }" let output = "guard true, true\nelse { return }" testFormatting(for: input, output, rule: .andOperator, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testWhileAndReplaced() { @@ -1289,7 +1289,7 @@ class SyntaxTests: RulesTests { let input = "if true && (true && true) {}" let output = "if true, (true && true) {}" testFormatting(for: input, output, rule: .andOperator, - exclude: ["redundantParens"]) + exclude: [.redundantParens]) } func testIfFunctionAndReplaced() { @@ -1345,13 +1345,13 @@ class SyntaxTests: RulesTests { func testHandleAndAtStartOfLine() { let input = "if a == b\n && b == c {}" let output = "if a == b,\n b == c {}" - testFormatting(for: input, output, rule: .andOperator, exclude: ["indent"]) + testFormatting(for: input, output, rule: .andOperator, exclude: [.indent]) } func testHandleAndAtStartOfLineAfterComment() { let input = "if a == b // foo\n && b == c {}" let output = "if a == b, // foo\n b == c {}" - testFormatting(for: input, output, rule: .andOperator, exclude: ["indent"]) + testFormatting(for: input, output, rule: .andOperator, exclude: [.indent]) } func testNoReplaceAndOperatorWhereGenericsAmbiguous() { @@ -1602,7 +1602,7 @@ class SyntaxTests: RulesTests { let output = "import Foundation\nprotocol Foo: AnyObject {}" let options = FormatOptions(swiftVersion: "4.1") testFormatting(for: input, output, rule: .anyObjectProtocol, options: options, - exclude: ["blankLineAfterImports"]) + exclude: [.blankLineAfterImports]) } func testClassDeclarationNotReplacedByAnyObject() { @@ -1806,7 +1806,7 @@ class SyntaxTests: RulesTests { func testOptionalTypeInsideCaseConvertedToSugar() { let input = "if case .some(Optional.some(let foo)) = bar else {}" let output = "if case .some(Any?.some(let foo)) = bar else {}" - testFormatting(for: input, output, rule: .typeSugar, exclude: ["hoistPatternLet"]) + testFormatting(for: input, output, rule: .typeSugar, exclude: [.hoistPatternLet]) } func testSwitchCaseOptionalNotReplaced() { @@ -1833,17 +1833,17 @@ class SyntaxTests: RulesTests { func testAvoidSwiftParserBugWithClosuresInsideArrays() { let input = "var foo = Array<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) } func testAvoidSwiftParserBugWithClosuresInsideDictionaries() { let input = "var foo = Dictionary Void>()" - testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) } func testAvoidSwiftParserBugWithClosuresInsideOptionals() { let input = "var foo = Optional<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: ["propertyType"]) + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) } func testDontOverApplyBugWorkaround() { @@ -1871,21 +1871,21 @@ class SyntaxTests: RulesTests { let input = "var foo = Array<(image: Data?) -> Void>()" let output = "var foo = [(image: Data?) -> Void]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) } func testDontOverApplyBugWorkaround5() { let input = "var foo = Array<(Data?) -> Void>()" let output = "var foo = [(Data?) -> Void]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) } func testDontOverApplyBugWorkaround6() { let input = "var foo = Dictionary Void>>()" let output = "var foo = [Int: Array<(_ image: Data?) -> Void>]()" let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) } // MARK: - preferKeyPath @@ -1919,7 +1919,7 @@ class SyntaxTests: RulesTests { let output = "let foo = bar.map(\\ . foo . bar)" let options = FormatOptions(swiftVersion: "5.2") testFormatting(for: input, output, rule: .preferKeyPath, - options: options, exclude: ["spaceAroundOperators"]) + options: options, exclude: [.spaceAroundOperators]) } func testMultilineMapPropertyToKeyPath() { @@ -2080,7 +2080,7 @@ class SyntaxTests: RulesTests { struct ScreenID {} """ - testFormatting(for: input, output, rule: .acronyms, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .acronyms, exclude: [.propertyType]) } func testUppercaseCustomAcronym() { @@ -2184,7 +2184,7 @@ class SyntaxTests: RulesTests { /// This is a documentation comment, /// not a standard comment. """ - testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) } func testBlockDocCommentsWithoutAsterisksOnEachLine() { @@ -2198,7 +2198,7 @@ class SyntaxTests: RulesTests { /// This is a documentation comment, /// not a standard comment. """ - testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) } func testBlockCommentWithBulletPoints() { @@ -2298,7 +2298,7 @@ class SyntaxTests: RulesTests { /// bar. } """ - testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) } func testLongBlockCommentsWithoutPerLineMarkersFullyConverted() { @@ -2362,7 +2362,7 @@ class SyntaxTests: RulesTests { /// Line 3. foo(bar) """ - testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) } func testBlockCommentImmediatelyFollowedByCode3() { @@ -2376,7 +2376,7 @@ class SyntaxTests: RulesTests { // bar func foo() {} """ - testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) } func testBlockCommentFollowedByBlankLine() { @@ -2396,7 +2396,7 @@ class SyntaxTests: RulesTests { func foo() {} """ - testFormatting(for: input, output, rule: .blockComments, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) } // MARK: - opaqueGenericParameters @@ -2912,7 +2912,7 @@ class SyntaxTests: RulesTests { let output = "extension Array {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) } func testUpdatesOptionalGenericExtensionToAngleBracketSyntax() { @@ -2920,7 +2920,7 @@ class SyntaxTests: RulesTests { let output = "extension Optional {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) } func testUpdatesArrayGenericExtensionToAngleBracketSyntaxWithSelf() { @@ -2928,7 +2928,7 @@ class SyntaxTests: RulesTests { let output = "extension Array {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) } func testUpdatesArrayWithGenericElement() { @@ -2936,7 +2936,7 @@ class SyntaxTests: RulesTests { let output = "extension Array> {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) } func testUpdatesDictionaryGenericExtensionToAngleBracketSyntax() { @@ -2944,7 +2944,7 @@ class SyntaxTests: RulesTests { let output = "extension Dictionary {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) } func testRequiresAllGenericTypesToBeProvided() { @@ -2960,7 +2960,7 @@ class SyntaxTests: RulesTests { let output = "extension Array<[[Foo: Bar]]> {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: ["typeSugar"]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) } func testDoesntUpdateIneligibleConstraints() { @@ -3178,7 +3178,7 @@ class SyntaxTests: RulesTests { let output = "func f(x: some B) -> T where T: A {}" let options = FormatOptions(swiftVersion: "5.7") testFormatting(for: input, output, rule: .opaqueGenericParameters, - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } // MARK: docComments @@ -3267,7 +3267,7 @@ class SyntaxTests: RulesTests { """ testFormatting(for: input, output, rule: .docComments, - exclude: ["spaceInsideComments", "propertyType"]) + exclude: [.spaceInsideComments, .propertyType]) } func testConvertDocCommentsToComments() { @@ -3342,7 +3342,7 @@ class SyntaxTests: RulesTests { """ testFormatting(for: input, output, rule: .docComments, - exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) + exclude: [.spaceInsideComments, .redundantProperty, .propertyType]) } func testPreservesDocComments() { @@ -3419,7 +3419,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(preserveDocComments: true) - testFormatting(for: input, output, rule: .docComments, options: options, exclude: ["spaceInsideComments", "redundantProperty", "propertyType"]) + testFormatting(for: input, output, rule: .docComments, options: options, exclude: [.spaceInsideComments, .redundantProperty, .propertyType]) } func testDoesntConvertCommentBeforeConsecutivePropertiesToDocComment() { @@ -3782,7 +3782,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) } func testConvertsSimpleSwitchStatementAssignment() { @@ -3804,7 +3804,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) } func testConvertsTrivialSwitchStatementAssignment() { @@ -3822,7 +3822,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) } func testConvertsNestedIfAndStatementAssignments() { @@ -3874,7 +3874,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["redundantType", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) } func testConvertsIfStatementAssignmentPreservingComment() { @@ -3897,7 +3897,7 @@ class SyntaxTests: RulesTests { } """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["indent", "redundantType", "wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.indent, .redundantType, .wrapMultilineConditionalAssignment]) } func testDoesntConvertsIfStatementAssigningMultipleProperties() { @@ -4153,7 +4153,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) } // TODO: update branches parser to handle this case properly @@ -4232,7 +4232,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) } func testConvertsSwitchWithDefaultCase() { @@ -4260,7 +4260,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment", "redundantType"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType]) } func testConvertsSwitchWithUnknownDefaultCase() { @@ -4288,7 +4288,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: ["wrapMultilineConditionalAssignment", "redundantType"]) + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType]) } func testPreservesSwitchWithReturnInDefaultCase() { @@ -4617,7 +4617,7 @@ class SyntaxTests: RulesTests { potatoes.forEach({ $0.bake() }) """ - testFormatting(for: input, output, rule: .preferForLoop, exclude: ["trailingClosures"]) + testFormatting(for: input, output, rule: .preferForLoop, exclude: [.trailingClosures]) } func testNoConvertAnonymousForEachToForLoop() { @@ -4631,7 +4631,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(preserveAnonymousForEach: true, preserveSingleLineForEach: false) - testFormatting(for: input, rule: .preferForLoop, options: options, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .preferForLoop, options: options, exclude: [.trailingClosures]) } func testConvertSingleLineForEachToForLoop() { @@ -4640,7 +4640,7 @@ class SyntaxTests: RulesTests { let options = FormatOptions(preserveSingleLineForEach: false) testFormatting(for: input, output, rule: .preferForLoop, options: options, - exclude: ["wrapLoopBodies"]) + exclude: [.wrapLoopBodies]) } func testConvertSingleLineAnonymousForEachToForLoop() { @@ -4649,7 +4649,7 @@ class SyntaxTests: RulesTests { let options = FormatOptions(preserveSingleLineForEach: false) testFormatting(for: input, output, rule: .preferForLoop, options: options, - exclude: ["wrapLoopBodies"]) + exclude: [.wrapLoopBodies]) } func testConvertNestedForEach() { @@ -4856,7 +4856,7 @@ class SyntaxTests: RulesTests { print(item) } """ - testFormatting(for: input, output, rule: .preferForLoop, exclude: ["redundantParens"]) + testFormatting(for: input, output, rule: .preferForLoop, exclude: [.redundantParens]) } func testPreservesForEachAfterMultilineChain() { @@ -4871,7 +4871,7 @@ class SyntaxTests: RulesTests { .map({ $0.uppercased() }) .forEach({ print($0) }) """ - testFormatting(for: input, rule: .preferForLoop, exclude: ["trailingClosures"]) + testFormatting(for: input, rule: .preferForLoop, exclude: [.trailingClosures]) } func testPreservesChainWithClosure() { @@ -5111,7 +5111,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .propertyType, options: options, exclude: ["void"]) + testFormatting(for: input, rule: .propertyType, options: options, exclude: [.void]) } func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { @@ -5126,7 +5126,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .propertyType, options: options, exclude: ["redundantType"]) + testFormatting(for: input, rule: .propertyType, options: options, exclude: [.redundantType]) } func testCompatibleWithRedundantTypeInferred() { @@ -5394,7 +5394,7 @@ class SyntaxTests: RulesTests { """ let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["init"]) - testFormatting(for: input, output, rule: .propertyType, options: options, exclude: ["redundantInit"]) + testFormatting(for: input, output, rule: .propertyType, options: options, exclude: [.redundantInit]) } func testClosureBodyIsConsideredLocal() { @@ -5618,7 +5618,7 @@ class SyntaxTests: RulesTests { } """ - testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: ["blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) } func testPreservesCommentsBetweenAttributes() { @@ -5648,7 +5648,7 @@ class SyntaxTests: RulesTests { func bar() {} """ - testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) } func testPreservesCommentOnSameLineAsAttribute() { @@ -5657,7 +5657,7 @@ class SyntaxTests: RulesTests { func foo() {} """ - testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) } func testPreservesRegularComments() { @@ -5667,7 +5667,7 @@ class SyntaxTests: RulesTests { func foo() {} """ - testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: ["docComments"]) + testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) } func testCombinesWithDocCommentsRule() { diff --git a/Tests/RulesTests+Wrapping.swift b/Tests/RulesTests+Wrapping.swift index 270be9da7..ee4a36d1d 100644 --- a/Tests/RulesTests+Wrapping.swift +++ b/Tests/RulesTests+Wrapping.swift @@ -16,25 +16,25 @@ class WrappingTests: RulesTests { let input = "if true {\n 1\n}\nelse { 2 }" let output = "if true {\n 1\n} else { 2 }" testFormatting(for: input, output, rule: .elseOnSameLine, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testElseOnSameLineOnlyAppliedToDanglingBrace() { let input = "if true { 1 }\nelse { 2 }" testFormatting(for: input, rule: .elseOnSameLine, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testGuardNotAffectedByElseOnSameLine() { let input = "guard true\nelse { return }" testFormatting(for: input, rule: .elseOnSameLine, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testElseOnSameLineDoesntEatPreviousStatement() { let input = "if true {}\nguard true else { return }" testFormatting(for: input, rule: .elseOnSameLine, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testElseNotOnSameLineForAllman() { @@ -42,7 +42,7 @@ class WrappingTests: RulesTests { let output = "if true\n{\n 1\n}\nelse { 2 }" let options = FormatOptions(allmanBraces: true) testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } func testElseOnNextLineOption() { @@ -50,14 +50,14 @@ class WrappingTests: RulesTests { let output = "if true {\n 1\n}\nelse { 2 }" let options = FormatOptions(elseOnNextLine: true) testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } func testGuardNotAffectedByElseOnSameLineForAllman() { let input = "guard true else { return }" let options = FormatOptions(allmanBraces: true) testFormatting(for: input, rule: .elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } func testRepeatWhileNotOnSameLineForAllman() { @@ -113,7 +113,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(elseOnNextLine: false) testFormatting(for: input, rule: .elseOnSameLine, options: options, - exclude: ["braces"]) + exclude: [.braces]) } // guardelse = auto @@ -121,20 +121,20 @@ class WrappingTests: RulesTests { func testSingleLineGuardElseNotWrappedByDefault() { let input = "guard foo = bar else {}" testFormatting(for: input, rule: .elseOnSameLine, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testSingleLineGuardElseNotUnwrappedByDefault() { let input = "guard foo = bar\nelse {}" testFormatting(for: input, rule: .elseOnSameLine, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testSingleLineGuardElseWrappedByDefaultIfBracesOnNextLine() { let input = "guard foo = bar else\n{}" let output = "guard foo = bar\nelse {}" testFormatting(for: input, output, rule: .elseOnSameLine, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testMultilineGuardElseNotWrappedByDefault() { @@ -145,7 +145,7 @@ class WrappingTests: RulesTests { } """ testFormatting(for: input, rule: .elseOnSameLine, - exclude: ["wrapMultilineStatementBraces"]) + exclude: [.wrapMultilineStatementBraces]) } func testMultilineGuardElseWrappedByDefaultIfBracesOnNextLine() { @@ -194,14 +194,14 @@ class WrappingTests: RulesTests { let input = "guard foo = bar else {}" let options = FormatOptions(guardElsePosition: .nextLine) testFormatting(for: input, rule: .elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } func testSingleLineGuardElseNotUnwrapped() { let input = "guard foo = bar\nelse {}" let options = FormatOptions(guardElsePosition: .nextLine) testFormatting(for: input, rule: .elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } func testSingleLineGuardElseWrappedIfBracesOnNextLine() { @@ -209,7 +209,7 @@ class WrappingTests: RulesTests { let output = "guard foo = bar\nelse {}" let options = FormatOptions(guardElsePosition: .nextLine) testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } func testMultilineGuardElseWrapped() { @@ -228,7 +228,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(guardElsePosition: .nextLine) testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: ["wrapMultilineStatementBraces"]) + options: options, exclude: [.wrapMultilineStatementBraces]) } func testMultilineGuardElseEndingInParen() { @@ -269,7 +269,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(guardElsePosition: .sameLine) testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: ["wrapMultilineStatementBraces"]) + options: options, exclude: [.wrapMultilineStatementBraces]) } func testGuardElseUnwrappedIfBracesOnNextLine() { @@ -397,13 +397,13 @@ class WrappingTests: RulesTests { func testEmptyGuardReturnWithSpaceDoesNothing() { let input = "guard let foo = bar else { }" testFormatting(for: input, rule: .wrapConditionalBodies, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testEmptyGuardReturnWithoutSpaceDoesNothing() { let input = "guard let foo = bar else {}" testFormatting(for: input, rule: .wrapConditionalBodies, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testGuardReturnWithValueWraps() { @@ -457,7 +457,7 @@ class WrappingTests: RulesTests { } """ testFormatting(for: input, output, rule: .wrapConditionalBodies, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testGuardReturnOnNewlineUnchanged() { @@ -579,13 +579,13 @@ class WrappingTests: RulesTests { func testEmptyIfElseBodiesWithSpaceDoNothing() { let input = "if foo { } else if baz { } else { }" testFormatting(for: input, rule: .wrapConditionalBodies, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testEmptyIfElseBodiesWithoutSpaceDoNothing() { let input = "if foo {} else if baz {} else {}" testFormatting(for: input, rule: .wrapConditionalBodies, - exclude: ["emptyBraces"]) + exclude: [.emptyBraces]) } func testGuardElseBraceStartingOnDifferentLine() { @@ -601,7 +601,7 @@ class WrappingTests: RulesTests { """ testFormatting(for: input, output, rule: .wrapConditionalBodies, - exclude: ["braces", "indent", "elseOnSameLine"]) + exclude: [.braces, .indent, .elseOnSameLine]) } func testIfElseBracesStartingOnDifferentLines() { @@ -628,7 +628,7 @@ class WrappingTests: RulesTests { } """ testFormatting(for: input, output, rule: .wrapConditionalBodies, - exclude: ["braces", "indent", "elseOnSameLine"]) + exclude: [.braces, .indent, .elseOnSameLine]) } // MARK: - wrapLoopBodies @@ -717,7 +717,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapClosure() { @@ -807,7 +807,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 25) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapFunctionIfReturnTypeExceedsMaxWidthWithXcodeIndentation() { @@ -832,7 +832,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 25) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth2() { @@ -848,7 +848,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation() { @@ -870,7 +870,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation2() { @@ -892,7 +892,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth3() { @@ -908,7 +908,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth3WithXcodeIndentation() { @@ -930,7 +930,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth4() { @@ -946,7 +946,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapFunctionIfReturnTypeExceedsMaxWidth4WithXcodeIndentation() { @@ -968,7 +968,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapChainedFunctionAfterSubscriptCollection() { @@ -1008,7 +1008,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 42) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) } func testWrapTypedThrowingFunctionIfReturnTypeExceedsMaxWidth() { @@ -1024,7 +1024,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(maxWidth: 42) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: ["wrapMultilineStatementBraces"]) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) } func testNoWrapInterpolatedStringLiteral() { @@ -1040,14 +1040,14 @@ class WrappingTests: RulesTests { let output = "let foo =\n bar+baz+quux" let options = FormatOptions(maxWidth: 15) testFormatting(for: input, output, rule: .wrap, options: options, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testNoWrapAtUnspacedEquals() { let input = "let foo=bar+baz+quux" let options = FormatOptions(maxWidth: 15) testFormatting(for: input, rule: .wrap, options: options, - exclude: ["spaceAroundOperators"]) + exclude: [.spaceAroundOperators]) } func testNoWrapSingleParameter() { @@ -1109,7 +1109,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(maxWidth: 10) testFormatting(for: input, output, rule: .wrap, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testNoCrashWrap2() { @@ -1137,7 +1137,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .preserve, maxWidth: 80) testFormatting(for: input, output, rule: .wrap, options: options, - exclude: ["indent", "wrapArguments"]) + exclude: [.indent, .wrapArguments]) } func testNoCrashWrap3() throws { @@ -1410,7 +1410,7 @@ class WrappingTests: RulesTests { Thing(), ]) """ - testFormatting(for: input, output, rule: .wrapArguments, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapArguments, exclude: [.propertyType]) } func testWrapArgumentsDoesntIndentTrailingComment() { @@ -1593,7 +1593,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) } // MARK: wrapParameters @@ -1682,7 +1682,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: ["unusedArguments", "wrap"]) + exclude: [.unusedArguments, .wrap]) } func testWrapAfterFirstIfMaxLengthExceeded2() { @@ -1696,7 +1696,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: ["unusedArguments", "wrap"]) + exclude: [.unusedArguments, .wrap]) } func testWrapAfterFirstIfMaxLengthExceeded3() { @@ -1709,7 +1709,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: ["unusedArguments", "wrap"]) + exclude: [.unusedArguments, .wrap]) } func testWrapAfterFirstIfMaxLengthExceeded3WithWrap() { @@ -1729,7 +1729,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) testFormatting(for: input, [output, output2], rules: [.wrapArguments, .wrap], - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } func testWrapAfterFirstIfMaxLengthExceeded4WithWrap() { @@ -1744,7 +1744,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) testFormatting(for: input, [output], rules: [.wrapArguments, .wrap], - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } func testWrapAfterFirstIfMaxLengthExceededInClassScopeWithWrap() { @@ -1772,7 +1772,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) testFormatting(for: input, [output, output2], rules: [.wrapArguments, .wrap], - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } func testWrapParametersListInClosureType() { @@ -1813,7 +1813,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 50) testFormatting(for: input, [input, output2], rules: [.wrapArguments], - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } func testWrapParametersAfterFirstWithSeparatedArgumentLabels() { @@ -1831,7 +1831,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .afterFirst) testFormatting(for: input, output, rule: .wrapArguments, - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } // MARK: beforeFirst @@ -1961,7 +1961,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [output], rules: [.wrapArguments], options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParams() { @@ -1980,7 +1980,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [output], rules: [.wrapArguments], options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParamsAfterWrappedClosure() { @@ -1999,7 +1999,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [output], rules: [.wrapArguments], options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testWrapParametersListBeforeFirstInEscapingClosureTypeAsFunctionParameter() { @@ -2018,7 +2018,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [output], rules: [.wrapArguments], options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testWrapParametersListBeforeFirstInNoEscapeClosureTypeAsFunctionParameter() { @@ -2037,7 +2037,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [output], rules: [.wrapArguments], options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testWrapParametersListBeforeFirstInEscapingAutoclosureTypeAsFunctionParameter() { @@ -2056,7 +2056,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [output], rules: [.wrapArguments], options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } // MARK: beforeFirst, maxWidth @@ -2073,7 +2073,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testNoWrapBeforeFirstIfMaxLengthNotExceeded() { @@ -2082,7 +2082,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 42) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testNoWrapGenericsIfClosingBracketWithinMaxWidth() { @@ -2097,7 +2097,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testWrapAlreadyWrappedArgumentsIfMaxLengthExceeded() { @@ -2114,7 +2114,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 26) testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testWrapParametersBeforeFirstIfMaxLengthExceededInReturnType() { @@ -2130,7 +2130,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 50) testFormatting(for: input, [input, output2], rules: [.wrapArguments], - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } func testWrapParametersBeforeFirstWithSeparatedArgumentLabels() { @@ -2148,7 +2148,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst) testFormatting(for: input, output, rule: .wrapArguments, - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } func testWrapParametersListBeforeFirstInClosureTypeWithMaxWidth() { @@ -2185,35 +2185,35 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 37) testFormatting(for: input, rule: .wrapArguments, - options: options, exclude: ["unusedArguments"]) + options: options, exclude: [.unusedArguments]) } func testNoWrapSubscriptWithSingleElement() { let input = "guard let foo = bar[0] {}" let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 20) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["wrap"]) + exclude: [.wrap]) } func testNoWrapArrayWithSingleElement() { let input = "let foo = [0]" let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 11) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["wrap"]) + exclude: [.wrap]) } func testNoWrapDictionaryWithSingleElement() { let input = "let foo = [bar: baz]" let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 15) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["wrap"]) + exclude: [.wrap]) } func testNoWrapImageLiteral() { let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["wrap"]) + exclude: [.wrap]) } func testNoWrapColorLiteral() { @@ -2222,7 +2222,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["wrap"]) + exclude: [.wrap]) } func testWrapArgumentsNoIndentBlankLines() { @@ -2235,7 +2235,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapCollections: .beforeFirst) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["wrap", "blankLinesAtStartOfScope", "blankLinesAtEndOfScope"]) + exclude: [.wrap, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) } // MARK: closingParenPosition = true @@ -2270,7 +2270,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, tabWidth: 2) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } func testTabIndentWrappedFunctionWithoutSmartTabs() { @@ -2285,7 +2285,7 @@ class WrappingTests: RulesTests { let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, tabWidth: 2, smartTabs: false) testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: ["unusedArguments"]) + exclude: [.unusedArguments]) } // MARK: - wrapArguments --wrapArguments @@ -2346,7 +2346,7 @@ class WrappingTests: RulesTests { ) {} """ let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["indent"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.indent]) } func testConsecutiveCodeCommentsNotIndented() { @@ -2373,7 +2373,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 20) testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: ["unusedArguments", "wrap"]) + exclude: [.unusedArguments, .wrap]) } // MARK: beforeFirst @@ -2382,7 +2382,7 @@ class WrappingTests: RulesTests { let input = "foo({\n bar()\n})" let options = FormatOptions(wrapArguments: .beforeFirst) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["trailingClosures"]) + exclude: [.trailingClosures]) } func testNoMangleCommentedLinesWhenWrappingArguments() { @@ -2429,7 +2429,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapArguments: .preserve) testFormatting(for: input, rule: .wrapArguments, - options: options, exclude: ["wrapConditionalBodies"]) + options: options, exclude: [.wrapConditionalBodies]) } // MARK: - --wrapArguments, --wrapParameter @@ -2444,7 +2444,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst) testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: ["redundantParens"]) + exclude: [.redundantParens]) } // MARK: beforeFirst, maxWidth : string interpolation @@ -2813,7 +2813,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 40) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_multipleTypealiases_beforeFirst() { @@ -2836,7 +2836,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 45) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_afterFirst() { @@ -2852,7 +2852,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 40) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_multipleTypealiases_afterFirst() { @@ -2873,7 +2873,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 45) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_shorterThanMaxWidth() { @@ -2882,7 +2882,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 100) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently() { @@ -2899,7 +2899,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently2() { @@ -2921,7 +2921,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently3() { @@ -2939,7 +2939,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently4() { @@ -2959,7 +2959,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistentlyWithComment() { @@ -2979,7 +2979,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_singleTypePreserved() { @@ -2988,7 +2988,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 10) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["wrap"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.wrap]) } func testWrapArguments_typealias_preservesCommentsBetweenTypes() { @@ -3003,7 +3003,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_preservesCommentsAfterTypes() { @@ -3015,7 +3015,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } func testWrapArguments_typealias_withAssociatedType() { @@ -3032,7 +3032,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 50) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: ["sortTypealiases"]) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) } // MARK: - -return wrap-if-multiline @@ -3181,7 +3181,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(wrapEffects: .never) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) } func testWrapEffectsNeverPreservesComments() { @@ -3217,7 +3217,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, rule: .wrapArguments, options: options, - exclude: ["indent"] + exclude: [.indent] ) } @@ -3241,7 +3241,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, rule: .wrapArguments, options: options, - exclude: ["indent"] + exclude: [.indent] ) } @@ -3266,7 +3266,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, rule: .wrapArguments, options: options, - exclude: ["indent"] + exclude: [.indent] ) } @@ -3285,7 +3285,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, rule: .wrapArguments, options: options, - exclude: ["indent"] + exclude: [.indent] ) } @@ -3342,7 +3342,7 @@ class WrappingTests: RulesTests { wrapReturnType: .ifMultiline ) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: ["propertyType"]) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) } func testPreserveReturnOnMultilineFunctionDeclarationByDefault() { @@ -3397,7 +3397,7 @@ class WrappingTests: RulesTests { } """ testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - exclude: ["wrapArguments", "unusedArguments"]) + exclude: [.wrapArguments, .unusedArguments]) } func testMultilineInitBraceOnNextLine() { @@ -3415,7 +3415,7 @@ class WrappingTests: RulesTests { } """ testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - exclude: ["wrapArguments", "unusedArguments"]) + exclude: [.wrapArguments, .unusedArguments]) } func testMultilineForLoopBraceOnNextLine() { @@ -3479,7 +3479,7 @@ class WrappingTests: RulesTests { } """ testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - exclude: ["braces", "elseOnSameLine"]) + exclude: [.braces, .elseOnSameLine]) } func testInnerMultilineIfBraceOnNextLine() { @@ -3512,7 +3512,7 @@ class WrappingTests: RulesTests { print("statement body") } """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces, exclude: ["propertyType"]) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, exclude: [.propertyType]) } func testSingleLineIfBraceOnSameLine() { @@ -3550,7 +3550,7 @@ class WrappingTests: RulesTests { let baz = quux else { return } """ testFormatting(for: input, rule: .wrapMultilineStatementBraces, - exclude: ["wrapConditionalBodies"]) + exclude: [.wrapConditionalBodies]) } func testMultilineGuardBraceOnSameLineAsElse() { @@ -3609,7 +3609,7 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - options: options, exclude: ["indent"]) + options: options, exclude: [.indent]) } func testMultilineBraceAppliedToTrailingClosure2_wrapBeforeFirst() { @@ -3667,7 +3667,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [output], rules: [ .wrapMultilineStatementBraces, .indent, - ], options: options, exclude: ["propertyType"]) + ], options: options, exclude: [.propertyType]) } func testMultilineBraceAppliedToTrailingClosure_wrapAfterFirst() { @@ -3691,7 +3691,7 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - options: options, exclude: ["indent"]) + options: options, exclude: [.indent]) } func testMultilineBraceAppliedToGetterBody_wrapAfterFirst() { @@ -3710,7 +3710,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, [], rules: [ .wrapMultilineStatementBraces, .wrapArguments, - ], options: options, exclude: ["propertyType"]) + ], options: options, exclude: [.propertyType]) } func testMultilineBraceAppliedToSubscriptBody() { @@ -3728,7 +3728,7 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, rule: .wrapMultilineStatementBraces, - options: options, exclude: ["trailingClosures"]) + options: options, exclude: [.trailingClosures]) } func testWrapsMultilineStatementConsistently() { @@ -4036,7 +4036,7 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, rule: .wrapMultilineStatementBraces, - options: options, exclude: ["trailingClosures"]) + options: options, exclude: [.trailingClosures]) } func testOpenBraceAfterEqualsInGuardNotWrapped() { @@ -4054,7 +4054,7 @@ class WrappingTests: RulesTests { closingParenPosition: .sameLine ) testFormatting(for: input, rules: [.wrapMultilineStatementBraces, .wrap], - options: options, exclude: ["indent", "redundantClosure", "wrapConditionalBodies"]) + options: options, exclude: [.indent, .redundantClosure, .wrapConditionalBodies]) } // MARK: wrapConditions before-first @@ -4086,7 +4086,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, rules: [.wrapArguments, .indent], options: FormatOptions(closingParenPosition: .sameLine, wrapConditions: .beforeFirst), - exclude: ["propertyType"] + exclude: [.propertyType] ) } @@ -4130,7 +4130,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, rule: .wrapArguments, options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), - exclude: ["wrapConditionalBodies"] + exclude: [.wrapConditionalBodies] ) } @@ -4153,7 +4153,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, rule: .wrapArguments, options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), - exclude: ["elseOnSameLine", "wrapConditionalBodies"] + exclude: [.elseOnSameLine, .wrapConditionalBodies] ) } @@ -4201,7 +4201,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, output, rule: .wrapArguments, options: FormatOptions(indent: " ", wrapConditions: .afterFirst), - exclude: ["wrapConditionalBodies"] + exclude: [.wrapConditionalBodies] ) } @@ -4244,7 +4244,7 @@ class WrappingTests: RulesTests { testFormatting( for: input, [output], rules: [.wrapArguments, .indent], options: FormatOptions(wrapConditions: .afterFirst), - exclude: ["wrapConditionalBodies"] + exclude: [.wrapConditionalBodies] ) } @@ -4463,7 +4463,7 @@ class WrappingTests: RulesTests { [output], rules: [.wrapArguments], options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120), - exclude: ["wrapMultilineStatementBraces"] + exclude: [.wrapMultilineStatementBraces] ) } @@ -4579,7 +4579,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(funcAttributes: .prevLine) testFormatting(for: input, output, rule: .wrapAttributes, - options: options, exclude: ["redundantObjc"]) + options: options, exclude: [.redundantObjc]) } func testFuncAttributeStaysWrapped() { @@ -4759,7 +4759,7 @@ class WrappingTests: RulesTests { let myClass = MyClass() """ let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) } func testClassImportAttributeNotTreatedAsType() { @@ -4779,7 +4779,7 @@ class WrappingTests: RulesTests { private(set) dynamic var foo = Foo() """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) } func testWrapPrivateSetVarAttributes() { @@ -4791,7 +4791,7 @@ class WrappingTests: RulesTests { private(set) dynamic var foo = Foo() """ let options = FormatOptions(varAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) } func testDontWrapPrivateSetVarAttributes() { @@ -4803,7 +4803,7 @@ class WrappingTests: RulesTests { @objc private(set) dynamic var foo = Foo() """ let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) } func testWrapConvenienceInitAttribute() { @@ -4968,7 +4968,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) } func testComplexAttributesException() { @@ -5039,7 +5039,7 @@ class WrappingTests: RulesTests { """ let options = FormatOptions(storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) } func testEscapingClosureNotMistakenForComplexAttribute() { @@ -5111,7 +5111,7 @@ class WrappingTests: RulesTests { } """ let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: ["propertyType"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) } func testWrapOrDontAttributesInSwiftUIView() { @@ -5343,7 +5343,7 @@ class WrappingTests: RulesTests { } """ testFormatting(for: input, rule: .wrapEnumCases, - exclude: ["hoistPatternLet"]) + exclude: [.hoistPatternLet]) } func testNoMangleUnindentedEnumCases() { @@ -5358,7 +5358,7 @@ class WrappingTests: RulesTests { case bar } """ - testFormatting(for: input, output, rule: .wrapEnumCases, exclude: ["indent"]) + testFormatting(for: input, output, rule: .wrapEnumCases, exclude: [.indent]) } func testNoMangleEnumCaseOnOpeningLine() { @@ -5374,7 +5374,7 @@ class WrappingTests: RulesTests { case desc(String) } """ - testFormatting(for: input, output, rule: .wrapEnumCases, exclude: ["indent"]) + testFormatting(for: input, output, rule: .wrapEnumCases, exclude: [.indent]) } func testNoWrapSingleLineEnumCases() { @@ -5475,7 +5475,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 6), - exclude: ["spaceInsideComments"]) + exclude: [.spaceInsideComments]) } func testWrapDocComment() { @@ -5489,7 +5489,7 @@ class WrappingTests: RulesTests { """ testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 7), exclude: ["docComments"]) + options: FormatOptions(maxWidth: 7), exclude: [.docComments]) } func testWrapDocLineCommentWithNoLeadingSpace() { @@ -5504,7 +5504,7 @@ class WrappingTests: RulesTests { testFormatting(for: input, output, rule: .wrapSingleLineComments, options: FormatOptions(maxWidth: 6), - exclude: ["spaceInsideComments", "docComments"]) + exclude: [.spaceInsideComments, .docComments]) } func testWrapSingleLineCommentWithIndent() { @@ -5523,7 +5523,7 @@ class WrappingTests: RulesTests { """ testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 14), exclude: ["docComments"]) + options: FormatOptions(maxWidth: 14), exclude: [.docComments]) } func testWrapSingleLineCommentAfterCode() { @@ -5541,7 +5541,7 @@ class WrappingTests: RulesTests { """ testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 29), exclude: ["wrap"]) + options: FormatOptions(maxWidth: 29), exclude: [.wrap]) } func testWrapDocCommentWithLongURL() { @@ -5550,7 +5550,7 @@ class WrappingTests: RulesTests { """ testFormatting(for: input, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 100), exclude: ["docComments"]) + options: FormatOptions(maxWidth: 100), exclude: [.docComments]) } func testWrapDocCommentWithLongURL2() { @@ -5572,7 +5572,7 @@ class WrappingTests: RulesTests { """ testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 40), exclude: ["docComments"]) + options: FormatOptions(maxWidth: 40), exclude: [.docComments]) } // MARK: - wrapMultilineConditionalAssignment diff --git a/Tests/RulesTests.swift b/Tests/RulesTests.swift index 7498c70d6..279382f37 100644 --- a/Tests/RulesTests.swift +++ b/Tests/RulesTests.swift @@ -36,7 +36,7 @@ class RulesTests: XCTestCase { // MARK: - shared test infra func testFormatting(for input: String, _ output: String? = nil, rule: FormatRule, - options: FormatOptions = .default, exclude: [String] = [], + options: FormatOptions = .default, exclude: [FormatRule] = [], file: StaticString = #file, line: UInt = #line) { testFormatting(for: input, output.map { [$0] } ?? [], rules: [rule], @@ -44,7 +44,7 @@ class RulesTests: XCTestCase { } func testFormatting(for input: String, _ outputs: [String] = [], rules: [FormatRule], - options: FormatOptions = .default, exclude: [String] = [], + options: FormatOptions = .default, exclude: [FormatRule] = [], file: StaticString = #file, line: UInt = #line) { // Always make sure the rule registry is up-to-date before running the tests @@ -75,9 +75,9 @@ class RulesTests: XCTestCase { precondition(input != outputs.first || input != outputs.last, "Redundant output parameter") precondition((0 ... 2).contains(outputs.count), "Only 0, 1 or 2 output parameters permitted") - precondition(Set(exclude).intersection(rules.map { $0.name }).isEmpty, "Cannot exclude rule under test") + precondition(Set(exclude.map(\.name)).intersection(rules.map(\.name)).isEmpty, "Cannot exclude rule under test") let output = outputs.first ?? input, output2 = outputs.last ?? input - let exclude = exclude + FormatRules.deprecated + let exclude = exclude.map(\.name) + FormatRules.deprecated + (rules.first?.name == "linebreakAtEndOfFile" ? [] : ["linebreakAtEndOfFile"]) + (rules.first?.name == "organizeDeclarations" ? [] : ["organizeDeclarations"]) + (rules.first?.name == "extensionAccessControl" ? [] : ["extensionAccessControl"]) From e4947dfeddb5ac92573a470ee4154ebfe137b046 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 29 Jul 2024 09:58:10 -0700 Subject: [PATCH 28/52] Implement tests for each rule in a separate file (#1789) --- Sources/DeclarationHelpers.swift | 2 +- Sources/Tokenizer.swift | 2 +- SwiftFormat.xcodeproj/project.pbxproj | 488 +- Tests/GlobsTests.swift | 4 +- Tests/Rules/AcronymsTests.swift | 78 + Tests/Rules/AndOperatorTests.swift | 184 + Tests/Rules/AnyObjectProtocolTests.swift | 52 + Tests/Rules/ApplicationMainTests.swift | 47 + Tests/Rules/AssertionFailuresTests.swift | 62 + Tests/Rules/BlankLineAfterImportsTests.swift | 110 + .../Rules/BlankLineAfterSwitchCaseTests.swift | 213 + Tests/Rules/BlankLinesAroundMarkTests.swift | 123 + Tests/Rules/BlankLinesAtEndOfScopeTests.swift | 107 + .../Rules/BlankLinesAtStartOfScopeTests.swift | 106 + ...ankLinesBetweenChainedFunctionsTests.swift | 64 + .../Rules/BlankLinesBetweenImportsTests.swift | 75 + .../Rules/BlankLinesBetweenScopesTests.swift | 218 + Tests/Rules/BlockCommentsTests.swift | 298 + .../BracesTests.swift} | 10 +- Tests/Rules/ConditionalAssignmentTests.swift | 828 ++ Tests/Rules/ConsecutiveBlankLinesTests.swift | 86 + Tests/Rules/ConsecutiveSpacesTests.swift | 56 + .../ConsistentSwitchCaseSpacingTests.swift | 327 + .../DocCommentsBeforeAttributesTests.swift | 197 + Tests/Rules/DocCommentsTests.swift | 579 + Tests/Rules/DuplicateImportsTests.swift | 64 + Tests/Rules/ElseOnSameLineTests.swift | 382 + Tests/Rules/EmptyBracesTests.swift | 111 + Tests/Rules/EnumNamespacesTests.swift | 455 + Tests/Rules/ExtensionAccessControlTests.swift | 463 + Tests/Rules/FileHeaderTests.swift | 514 + Tests/Rules/GenericExtensionsTests.swift | 121 + Tests/Rules/HeaderFileNameTests.swift | 27 + Tests/Rules/HoistAwaitTests.swift | 234 + Tests/Rules/HoistPatternLetTests.swift | 423 + Tests/Rules/HoistTryTests.swift | 440 + .../IndentTests.swift} | 30 +- Tests/Rules/InitCoderUnavailableTests.swift | 143 + Tests/Rules/IsEmptyTests.swift | 159 + Tests/Rules/LeadingDelimitersTests.swift | 48 + Tests/Rules/LinebreakAtEndOfFileTests.swift | 24 + Tests/Rules/LinebreaksTests.swift | 36 + Tests/Rules/MarkTypesTests.swift | 838 ++ Tests/Rules/ModifierOrderTests.swift | 98 + Tests/Rules/NoExplicitOwnershipTests.swift | 64 + Tests/Rules/NumberFormattingTests.swift | 203 + .../Rules/OpaqueGenericParametersTests.swift | 683 + .../OrganizeDeclarationsTests.swift} | 2688 +--- Tests/Rules/PreferForLoopTests.swift | 391 + Tests/Rules/PreferKeyPathTests.swift | 113 + Tests/Rules/PropertyTypeTests.swift | 560 + Tests/Rules/RedundantBackticksTests.swift | 214 + Tests/Rules/RedundantBreakTests.swift | 79 + Tests/Rules/RedundantClosureTests.swift | 1040 ++ Tests/Rules/RedundantExtensionACLTests.swift | 48 + Tests/Rules/RedundantFileprivateTests.swift | 575 + Tests/Rules/RedundantGetTests.swift | 56 + Tests/Rules/RedundantInitTests.swift | 223 + Tests/Rules/RedundantInternalTests.swift | 108 + Tests/Rules/RedundantLetErrorTests.swift | 24 + Tests/Rules/RedundantLetTests.swift | 136 + Tests/Rules/RedundantNilInitTests.swift | 489 + Tests/Rules/RedundantObjcTests.swift | 161 + .../Rules/RedundantOptionalBindingTests.swift | 183 + .../RedundantParensTests.swift} | 10 +- Tests/Rules/RedundantPatternTests.swift | 55 + Tests/Rules/RedundantPropertyTests.swift | 182 + Tests/Rules/RedundantRawValuesTests.swift | 37 + Tests/Rules/RedundantReturnTests.swift | 1264 ++ Tests/Rules/RedundantSelfTests.swift | 3393 +++++ Tests/Rules/RedundantStaticSelfTests.swift | 226 + Tests/Rules/RedundantTypeTests.swift | 701 + Tests/Rules/RedundantTypedThrowsTests.swift | 61 + .../Rules/RedundantVoidReturnTypeTests.swift | 94 + Tests/Rules/SemicolonsTests.swift | 77 + Tests/Rules/SortDeclarationsTests.swift | 289 + Tests/Rules/SortImportsTests.swift | 214 + Tests/Rules/SortSwitchCasesTests.swift | 340 + Tests/Rules/SortTypealiasesTests.swift | 178 + Tests/Rules/SpaceAroundBracesTests.swift | 64 + Tests/Rules/SpaceAroundBracketsTests.swift | 100 + Tests/Rules/SpaceAroundCommentsTests.swift | 36 + Tests/Rules/SpaceAroundGenericsTests.swift | 28 + Tests/Rules/SpaceAroundOperatorsTests.swift | 754 ++ Tests/Rules/SpaceAroundParensTests.swift | 347 + Tests/Rules/SpaceInsideBracesTests.swift | 33 + Tests/Rules/SpaceInsideBracketsTests.swift | 31 + Tests/Rules/SpaceInsideCommentsTests.swift | 119 + Tests/Rules/SpaceInsideGenericsTests.swift | 18 + Tests/Rules/SpaceInsideParensTests.swift | 24 + Tests/Rules/StrongOutletsTests.swift | 63 + Tests/Rules/StrongifiedSelfTests.swift | 71 + Tests/Rules/TodosTests.swift | 150 + Tests/Rules/TrailingClosuresTests.swift | 229 + Tests/Rules/TrailingCommasTests.swift | 264 + Tests/Rules/TrailingSpaceTests.swift | 58 + Tests/Rules/TypeSugarTests.swift | 238 + Tests/Rules/UnusedArgumentsTests.swift | 1117 ++ .../Rules/UnusedPrivateDeclarationTests.swift | 355 + Tests/Rules/VoidTests.swift | 261 + Tests/Rules/WrapArgumentsTests.swift | 2447 ++++ Tests/Rules/WrapAttributesTests.swift | 645 + Tests/Rules/WrapConditionalBodiesTests.swift | 259 + Tests/Rules/WrapEnumCasesTests.swift | 229 + Tests/Rules/WrapLoopBodiesTests.swift | 42 + ...pMultilineConditionalAssignmentTests.swift | 148 + .../WrapMultilineStatementBracesTests.swift | 706 + Tests/Rules/WrapSingleLineCommentsTests.swift | 160 + Tests/Rules/WrapSwitchCasesTests.swift | 53 + Tests/Rules/WrapTests.swift | 725 ++ Tests/Rules/YodaConditionsTests.swift | 293 + Tests/RulesTests+General.swift | 1355 -- Tests/RulesTests+Hoisting.swift | 1079 -- Tests/RulesTests+Linebreaks.swift | 861 -- Tests/RulesTests+Redundancy.swift | 10766 ---------------- Tests/RulesTests+Spacing.swift | 2171 ---- Tests/RulesTests+Syntax.swift | 5688 -------- Tests/RulesTests+Wrapping.swift | 5715 -------- ....swift => XCTestCase+testFormatting.swift} | 6 +- 119 files changed, 31562 insertions(+), 30231 deletions(-) create mode 100644 Tests/Rules/AcronymsTests.swift create mode 100644 Tests/Rules/AndOperatorTests.swift create mode 100644 Tests/Rules/AnyObjectProtocolTests.swift create mode 100644 Tests/Rules/ApplicationMainTests.swift create mode 100644 Tests/Rules/AssertionFailuresTests.swift create mode 100644 Tests/Rules/BlankLineAfterImportsTests.swift create mode 100644 Tests/Rules/BlankLineAfterSwitchCaseTests.swift create mode 100644 Tests/Rules/BlankLinesAroundMarkTests.swift create mode 100644 Tests/Rules/BlankLinesAtEndOfScopeTests.swift create mode 100644 Tests/Rules/BlankLinesAtStartOfScopeTests.swift create mode 100644 Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift create mode 100644 Tests/Rules/BlankLinesBetweenImportsTests.swift create mode 100644 Tests/Rules/BlankLinesBetweenScopesTests.swift create mode 100644 Tests/Rules/BlockCommentsTests.swift rename Tests/{RulesTests+Braces.swift => Rules/BracesTests.swift} (98%) create mode 100644 Tests/Rules/ConditionalAssignmentTests.swift create mode 100644 Tests/Rules/ConsecutiveBlankLinesTests.swift create mode 100644 Tests/Rules/ConsecutiveSpacesTests.swift create mode 100644 Tests/Rules/ConsistentSwitchCaseSpacingTests.swift create mode 100644 Tests/Rules/DocCommentsBeforeAttributesTests.swift create mode 100644 Tests/Rules/DocCommentsTests.swift create mode 100644 Tests/Rules/DuplicateImportsTests.swift create mode 100644 Tests/Rules/ElseOnSameLineTests.swift create mode 100644 Tests/Rules/EmptyBracesTests.swift create mode 100644 Tests/Rules/EnumNamespacesTests.swift create mode 100644 Tests/Rules/ExtensionAccessControlTests.swift create mode 100644 Tests/Rules/FileHeaderTests.swift create mode 100644 Tests/Rules/GenericExtensionsTests.swift create mode 100644 Tests/Rules/HeaderFileNameTests.swift create mode 100644 Tests/Rules/HoistAwaitTests.swift create mode 100644 Tests/Rules/HoistPatternLetTests.swift create mode 100644 Tests/Rules/HoistTryTests.swift rename Tests/{RulesTests+Indentation.swift => Rules/IndentTests.swift} (99%) create mode 100644 Tests/Rules/InitCoderUnavailableTests.swift create mode 100644 Tests/Rules/IsEmptyTests.swift create mode 100644 Tests/Rules/LeadingDelimitersTests.swift create mode 100644 Tests/Rules/LinebreakAtEndOfFileTests.swift create mode 100644 Tests/Rules/LinebreaksTests.swift create mode 100644 Tests/Rules/MarkTypesTests.swift create mode 100644 Tests/Rules/ModifierOrderTests.swift create mode 100644 Tests/Rules/NoExplicitOwnershipTests.swift create mode 100644 Tests/Rules/NumberFormattingTests.swift create mode 100644 Tests/Rules/OpaqueGenericParametersTests.swift rename Tests/{RulesTests+Organization.swift => Rules/OrganizeDeclarationsTests.swift} (51%) create mode 100644 Tests/Rules/PreferForLoopTests.swift create mode 100644 Tests/Rules/PreferKeyPathTests.swift create mode 100644 Tests/Rules/PropertyTypeTests.swift create mode 100644 Tests/Rules/RedundantBackticksTests.swift create mode 100644 Tests/Rules/RedundantBreakTests.swift create mode 100644 Tests/Rules/RedundantClosureTests.swift create mode 100644 Tests/Rules/RedundantExtensionACLTests.swift create mode 100644 Tests/Rules/RedundantFileprivateTests.swift create mode 100644 Tests/Rules/RedundantGetTests.swift create mode 100644 Tests/Rules/RedundantInitTests.swift create mode 100644 Tests/Rules/RedundantInternalTests.swift create mode 100644 Tests/Rules/RedundantLetErrorTests.swift create mode 100644 Tests/Rules/RedundantLetTests.swift create mode 100644 Tests/Rules/RedundantNilInitTests.swift create mode 100644 Tests/Rules/RedundantObjcTests.swift create mode 100644 Tests/Rules/RedundantOptionalBindingTests.swift rename Tests/{RulesTests+Parens.swift => Rules/RedundantParensTests.swift} (99%) create mode 100644 Tests/Rules/RedundantPatternTests.swift create mode 100644 Tests/Rules/RedundantPropertyTests.swift create mode 100644 Tests/Rules/RedundantRawValuesTests.swift create mode 100644 Tests/Rules/RedundantReturnTests.swift create mode 100644 Tests/Rules/RedundantSelfTests.swift create mode 100644 Tests/Rules/RedundantStaticSelfTests.swift create mode 100644 Tests/Rules/RedundantTypeTests.swift create mode 100644 Tests/Rules/RedundantTypedThrowsTests.swift create mode 100644 Tests/Rules/RedundantVoidReturnTypeTests.swift create mode 100644 Tests/Rules/SemicolonsTests.swift create mode 100644 Tests/Rules/SortDeclarationsTests.swift create mode 100644 Tests/Rules/SortImportsTests.swift create mode 100644 Tests/Rules/SortSwitchCasesTests.swift create mode 100644 Tests/Rules/SortTypealiasesTests.swift create mode 100644 Tests/Rules/SpaceAroundBracesTests.swift create mode 100644 Tests/Rules/SpaceAroundBracketsTests.swift create mode 100644 Tests/Rules/SpaceAroundCommentsTests.swift create mode 100644 Tests/Rules/SpaceAroundGenericsTests.swift create mode 100644 Tests/Rules/SpaceAroundOperatorsTests.swift create mode 100644 Tests/Rules/SpaceAroundParensTests.swift create mode 100644 Tests/Rules/SpaceInsideBracesTests.swift create mode 100644 Tests/Rules/SpaceInsideBracketsTests.swift create mode 100644 Tests/Rules/SpaceInsideCommentsTests.swift create mode 100644 Tests/Rules/SpaceInsideGenericsTests.swift create mode 100644 Tests/Rules/SpaceInsideParensTests.swift create mode 100644 Tests/Rules/StrongOutletsTests.swift create mode 100644 Tests/Rules/StrongifiedSelfTests.swift create mode 100644 Tests/Rules/TodosTests.swift create mode 100644 Tests/Rules/TrailingClosuresTests.swift create mode 100644 Tests/Rules/TrailingCommasTests.swift create mode 100644 Tests/Rules/TrailingSpaceTests.swift create mode 100644 Tests/Rules/TypeSugarTests.swift create mode 100644 Tests/Rules/UnusedArgumentsTests.swift create mode 100644 Tests/Rules/UnusedPrivateDeclarationTests.swift create mode 100644 Tests/Rules/VoidTests.swift create mode 100644 Tests/Rules/WrapArgumentsTests.swift create mode 100644 Tests/Rules/WrapAttributesTests.swift create mode 100644 Tests/Rules/WrapConditionalBodiesTests.swift create mode 100644 Tests/Rules/WrapEnumCasesTests.swift create mode 100644 Tests/Rules/WrapLoopBodiesTests.swift create mode 100644 Tests/Rules/WrapMultilineConditionalAssignmentTests.swift create mode 100644 Tests/Rules/WrapMultilineStatementBracesTests.swift create mode 100644 Tests/Rules/WrapSingleLineCommentsTests.swift create mode 100644 Tests/Rules/WrapSwitchCasesTests.swift create mode 100644 Tests/Rules/WrapTests.swift create mode 100644 Tests/Rules/YodaConditionsTests.swift delete mode 100644 Tests/RulesTests+General.swift delete mode 100644 Tests/RulesTests+Hoisting.swift delete mode 100644 Tests/RulesTests+Linebreaks.swift delete mode 100644 Tests/RulesTests+Redundancy.swift delete mode 100644 Tests/RulesTests+Spacing.swift delete mode 100644 Tests/RulesTests+Syntax.swift delete mode 100644 Tests/RulesTests+Wrapping.swift rename Tests/{RulesTests.swift => XCTestCase+testFormatting.swift} (98%) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index e0554011f..180c278fe 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -15,7 +15,7 @@ import Foundation /// /// Forms a tree of declaratons, since `type` declarations have a body /// that contains child declarations. -enum Declaration: Equatable { +enum Declaration: Hashable { /// A type-like declaration with body of additional declarations (`class`, `struct`, etc) indirect case type( kind: String, diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index 549804561..b3af04832 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -134,7 +134,7 @@ public enum OperatorType { public typealias OriginalLine = Int /// All token types -public enum Token: Equatable { +public enum Token: Hashable { case number(String, NumberType) case linebreak(String, OriginalLine) case startOfScope(String) diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index 3ab24f64d..ae0a1c48e 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -16,7 +16,6 @@ 01045A9E2119A37F00D2BE3D /* ArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01045A9C2119A21000D2BE3D /* ArgumentsTests.swift */; }; 01045A9F2119D30D00D2BE3D /* Inference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01045A90211988F100D2BE3D /* Inference.swift */; }; 01045AA0211A1EE300D2BE3D /* Arguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01045A982119979400D2BE3D /* Arguments.swift */; }; - 011676A22707D312001CCDCE /* RulesTests+General.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011676A12707D312001CCDCE /* RulesTests+General.swift */; }; 011A53EB21FFAA4200DD9268 /* VersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 011A53E921FFAA3A00DD9268 /* VersionTests.swift */; }; 01426E4E23AA29B100E7D871 /* ParsingHelpersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01426E4D23AA29B100E7D871 /* ParsingHelpersTests.swift */; }; 0142C77023C3FB6D005D5832 /* LintFileCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0142C76F23C3FB6D005D5832 /* LintFileCommand.swift */; }; @@ -35,7 +34,7 @@ 018E82751D62E730008CA0F8 /* TokenizerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 018E82741D62E730008CA0F8 /* TokenizerTests.swift */; }; 01A0EAA81D5DB4CF00A0A8E3 /* SwiftFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 01A0EAA71D5DB4CF00A0A8E3 /* SwiftFormat.h */; settings = {ATTRIBUTES = (Public, ); }; }; 01A0EAAF1D5DB4D000A0A8E3 /* SwiftFormat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01A0EAA41D5DB4CF00A0A8E3 /* SwiftFormat.framework */; }; - 01A0EAB41D5DB4D000A0A8E3 /* RulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAB31D5DB4D000A0A8E3 /* RulesTests.swift */; }; + 01A0EAB41D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAB31D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift */; }; 01A0EAC11D5DB4F700A0A8E3 /* FormatRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */; }; 01A0EAC21D5DB4F700A0A8E3 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; 01A0EAC51D5DB54A00A0A8E3 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; @@ -61,18 +60,8 @@ 01BBD85C21DAA2A700457380 /* Globs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BBD85821DAA2A000457380 /* Globs.swift */; }; 01BBD85E21DAA30700457380 /* GlobsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BBD85D21DAA30700457380 /* GlobsTests.swift */; }; 01BEC5772236E1A700D0DD83 /* MetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01BEC5762236E1A700D0DD83 /* MetadataTests.swift */; }; - 01C209AF2502CD3C00E728A2 /* RulesTests+Spacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209AE2502CD3C00E728A2 /* RulesTests+Spacing.swift */; }; - 01C209B12502CEF700E728A2 /* RulesTests+Linebreaks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B02502CEF700E728A2 /* RulesTests+Linebreaks.swift */; }; - 01C209B32502CF8300E728A2 /* RulesTests+Indentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B22502CF8300E728A2 /* RulesTests+Indentation.swift */; }; - 01C209B52502D27800E728A2 /* RulesTests+Braces.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B42502D27800E728A2 /* RulesTests+Braces.swift */; }; - 01C209B72502D2FD00E728A2 /* RulesTests+Parens.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B62502D2FD00E728A2 /* RulesTests+Parens.swift */; }; - 01C209B92502D3C500E728A2 /* RulesTests+Wrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209B82502D3C500E728A2 /* RulesTests+Wrapping.swift */; }; - 01C209BB2502D62000E728A2 /* RulesTests+Organization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209BA2502D62000E728A2 /* RulesTests+Organization.swift */; }; - 01C209BD2502D71F00E728A2 /* RulesTests+Redundancy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C209BC2502D71F00E728A2 /* RulesTests+Redundancy.swift */; }; 01C4D3292BB518D400BDF1AF /* ZRegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C4D3282BB518D400BDF1AF /* ZRegressionTests.swift */; }; 01D3B28624E9C9C700888DE0 /* FormattingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */; }; - 01EF830F25616089003F6F2D /* RulesTests+Syntax.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EF830E25616089003F6F2D /* RulesTests+Syntax.swift */; }; - 01EFC8B729CF2B5100222029 /* RulesTests+Hoisting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EFC8B629CF2B5100222029 /* RulesTests+Hoisting.swift */; }; 01F17E821E25870700DCD359 /* CommandLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F17E811E25870700DCD359 /* CommandLine.swift */; }; 01F17E831E25870700DCD359 /* CommandLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F17E811E25870700DCD359 /* CommandLine.swift */; }; 01F17E851E258A4900DCD359 /* CommandLineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01F17E841E258A4900DCD359 /* CommandLineTests.swift */; }; @@ -529,6 +518,113 @@ 2E2BADB52C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; 2E2BADB62C57F6DD00590239 /* SortDeclarations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */; }; 2E7D30A42A7940C500C32174 /* Singularize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E7D30A32A7940C500C32174 /* Singularize.swift */; }; + 2E8DE6F82C57FEB30032BF25 /* RedundantClosureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE68D2C57FEB30032BF25 /* RedundantClosureTests.swift */; }; + 2E8DE6F92C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE68E2C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift */; }; + 2E8DE6FA2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE68F2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift */; }; + 2E8DE6FB2C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6902C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift */; }; + 2E8DE6FC2C57FEB30032BF25 /* WrapArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6912C57FEB30032BF25 /* WrapArgumentsTests.swift */; }; + 2E8DE6FD2C57FEB30032BF25 /* RedundantSelfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6922C57FEB30032BF25 /* RedundantSelfTests.swift */; }; + 2E8DE6FE2C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6932C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift */; }; + 2E8DE6FF2C57FEB30032BF25 /* DuplicateImportsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6942C57FEB30032BF25 /* DuplicateImportsTests.swift */; }; + 2E8DE7002C57FEB30032BF25 /* TodosTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6952C57FEB30032BF25 /* TodosTests.swift */; }; + 2E8DE7012C57FEB30032BF25 /* FileHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6962C57FEB30032BF25 /* FileHeaderTests.swift */; }; + 2E8DE7022C57FEB30032BF25 /* EnumNamespacesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6972C57FEB30032BF25 /* EnumNamespacesTests.swift */; }; + 2E8DE7032C57FEB30032BF25 /* PreferForLoopTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6982C57FEB30032BF25 /* PreferForLoopTests.swift */; }; + 2E8DE7042C57FEB30032BF25 /* ExtensionAccessControlTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6992C57FEB30032BF25 /* ExtensionAccessControlTests.swift */; }; + 2E8DE7052C57FEB30032BF25 /* TrailingCommasTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69A2C57FEB30032BF25 /* TrailingCommasTests.swift */; }; + 2E8DE7062C57FEB30032BF25 /* WrapLoopBodiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69B2C57FEB30032BF25 /* WrapLoopBodiesTests.swift */; }; + 2E8DE7072C57FEB30032BF25 /* StrongifiedSelfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69C2C57FEB30032BF25 /* StrongifiedSelfTests.swift */; }; + 2E8DE7082C57FEB30032BF25 /* RedundantPropertyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69D2C57FEB30032BF25 /* RedundantPropertyTests.swift */; }; + 2E8DE7092C57FEB30032BF25 /* OpaqueGenericParametersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69E2C57FEB30032BF25 /* OpaqueGenericParametersTests.swift */; }; + 2E8DE70A2C57FEB30032BF25 /* SpaceInsideBracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE69F2C57FEB30032BF25 /* SpaceInsideBracesTests.swift */; }; + 2E8DE70B2C57FEB30032BF25 /* ModifierOrderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A02C57FEB30032BF25 /* ModifierOrderTests.swift */; }; + 2E8DE70C2C57FEB30032BF25 /* WrapAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A12C57FEB30032BF25 /* WrapAttributesTests.swift */; }; + 2E8DE70D2C57FEB30032BF25 /* RedundantObjcTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A22C57FEB30032BF25 /* RedundantObjcTests.swift */; }; + 2E8DE70E2C57FEB30032BF25 /* HoistPatternLetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A32C57FEB30032BF25 /* HoistPatternLetTests.swift */; }; + 2E8DE70F2C57FEB30032BF25 /* WrapTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A42C57FEB30032BF25 /* WrapTests.swift */; }; + 2E8DE7102C57FEB30032BF25 /* SpaceInsideGenericsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A52C57FEB30032BF25 /* SpaceInsideGenericsTests.swift */; }; + 2E8DE7112C57FEB30032BF25 /* RedundantStaticSelfTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A62C57FEB30032BF25 /* RedundantStaticSelfTests.swift */; }; + 2E8DE7122C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A72C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift */; }; + 2E8DE7132C57FEB30032BF25 /* RedundantInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A82C57FEB30032BF25 /* RedundantInitTests.swift */; }; + 2E8DE7142C57FEB30032BF25 /* RedundantRawValuesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6A92C57FEB30032BF25 /* RedundantRawValuesTests.swift */; }; + 2E8DE7152C57FEB30032BF25 /* SortImportsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AA2C57FEB30032BF25 /* SortImportsTests.swift */; }; + 2E8DE7162C57FEB30032BF25 /* TrailingSpaceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AB2C57FEB30032BF25 /* TrailingSpaceTests.swift */; }; + 2E8DE7172C57FEB30032BF25 /* YodaConditionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AC2C57FEB30032BF25 /* YodaConditionsTests.swift */; }; + 2E8DE7182C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AD2C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift */; }; + 2E8DE7192C57FEB30032BF25 /* UnusedArgumentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AE2C57FEB30032BF25 /* UnusedArgumentsTests.swift */; }; + 2E8DE71A2C57FEB30032BF25 /* GenericExtensionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6AF2C57FEB30032BF25 /* GenericExtensionsTests.swift */; }; + 2E8DE71B2C57FEB30032BF25 /* NumberFormattingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B02C57FEB30032BF25 /* NumberFormattingTests.swift */; }; + 2E8DE71C2C57FEB30032BF25 /* RedundantTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B12C57FEB30032BF25 /* RedundantTypeTests.swift */; }; + 2E8DE71D2C57FEB30032BF25 /* TypeSugarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B22C57FEB30032BF25 /* TypeSugarTests.swift */; }; + 2E8DE71E2C57FEB30032BF25 /* SpaceAroundBracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B32C57FEB30032BF25 /* SpaceAroundBracesTests.swift */; }; + 2E8DE71F2C57FEB30032BF25 /* SortTypealiasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B42C57FEB30032BF25 /* SortTypealiasesTests.swift */; }; + 2E8DE7202C57FEB30032BF25 /* SortSwitchCasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B52C57FEB30032BF25 /* SortSwitchCasesTests.swift */; }; + 2E8DE7212C57FEB30032BF25 /* EmptyBracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B62C57FEB30032BF25 /* EmptyBracesTests.swift */; }; + 2E8DE7222C57FEB30032BF25 /* SortDeclarationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B72C57FEB30032BF25 /* SortDeclarationsTests.swift */; }; + 2E8DE7232C57FEB30032BF25 /* BlockCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B82C57FEB30032BF25 /* BlockCommentsTests.swift */; }; + 2E8DE7242C57FEB30032BF25 /* StrongOutletsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6B92C57FEB30032BF25 /* StrongOutletsTests.swift */; }; + 2E8DE7252C57FEB30032BF25 /* BracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BA2C57FEB30032BF25 /* BracesTests.swift */; }; + 2E8DE7262C57FEB30032BF25 /* AnyObjectProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BB2C57FEB30032BF25 /* AnyObjectProtocolTests.swift */; }; + 2E8DE7272C57FEB30032BF25 /* RedundantBreakTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BC2C57FEB30032BF25 /* RedundantBreakTests.swift */; }; + 2E8DE7282C57FEB30032BF25 /* RedundantNilInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BD2C57FEB30032BF25 /* RedundantNilInitTests.swift */; }; + 2E8DE7292C57FEB30032BF25 /* RedundantParensTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BE2C57FEB30032BF25 /* RedundantParensTests.swift */; }; + 2E8DE72A2C57FEB30032BF25 /* PreferKeyPathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6BF2C57FEB30032BF25 /* PreferKeyPathTests.swift */; }; + 2E8DE72B2C57FEB30032BF25 /* SpaceAroundParensTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C02C57FEB30032BF25 /* SpaceAroundParensTests.swift */; }; + 2E8DE72C2C57FEB30032BF25 /* RedundantLetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C12C57FEB30032BF25 /* RedundantLetTests.swift */; }; + 2E8DE72D2C57FEB30032BF25 /* VoidTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C22C57FEB30032BF25 /* VoidTests.swift */; }; + 2E8DE72E2C57FEB30032BF25 /* IndentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C32C57FEB30032BF25 /* IndentTests.swift */; }; + 2E8DE72F2C57FEB30032BF25 /* RedundantBackticksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C42C57FEB30032BF25 /* RedundantBackticksTests.swift */; }; + 2E8DE7302C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C52C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift */; }; + 2E8DE7312C57FEB30032BF25 /* HoistAwaitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C62C57FEB30032BF25 /* HoistAwaitTests.swift */; }; + 2E8DE7322C57FEB30032BF25 /* RedundantLetErrorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C72C57FEB30032BF25 /* RedundantLetErrorTests.swift */; }; + 2E8DE7332C57FEB30032BF25 /* SpaceAroundCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C82C57FEB30032BF25 /* SpaceAroundCommentsTests.swift */; }; + 2E8DE7342C57FEB30032BF25 /* PropertyTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6C92C57FEB30032BF25 /* PropertyTypeTests.swift */; }; + 2E8DE7352C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CA2C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift */; }; + 2E8DE7362C57FEB30032BF25 /* SemicolonsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CB2C57FEB30032BF25 /* SemicolonsTests.swift */; }; + 2E8DE7372C57FEB30032BF25 /* RedundantPatternTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CC2C57FEB30032BF25 /* RedundantPatternTests.swift */; }; + 2E8DE7382C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CD2C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift */; }; + 2E8DE7392C57FEB30032BF25 /* RedundantInternalTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CE2C57FEB30032BF25 /* RedundantInternalTests.swift */; }; + 2E8DE73A2C57FEB30032BF25 /* IsEmptyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6CF2C57FEB30032BF25 /* IsEmptyTests.swift */; }; + 2E8DE73B2C57FEB30032BF25 /* AcronymsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D02C57FEB30032BF25 /* AcronymsTests.swift */; }; + 2E8DE73C2C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D12C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift */; }; + 2E8DE73D2C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D22C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift */; }; + 2E8DE73E2C57FEB30032BF25 /* WrapEnumCasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D32C57FEB30032BF25 /* WrapEnumCasesTests.swift */; }; + 2E8DE73F2C57FEB30032BF25 /* NoExplicitOwnershipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D42C57FEB30032BF25 /* NoExplicitOwnershipTests.swift */; }; + 2E8DE7402C57FEB30032BF25 /* HoistTryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D52C57FEB30032BF25 /* HoistTryTests.swift */; }; + 2E8DE7412C57FEB30032BF25 /* RedundantOptionalBindingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D62C57FEB30032BF25 /* RedundantOptionalBindingTests.swift */; }; + 2E8DE7422C57FEB30032BF25 /* ConsecutiveSpacesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D72C57FEB30032BF25 /* ConsecutiveSpacesTests.swift */; }; + 2E8DE7432C57FEB30032BF25 /* SpaceAroundBracketsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D82C57FEB30032BF25 /* SpaceAroundBracketsTests.swift */; }; + 2E8DE7442C57FEB30032BF25 /* TrailingClosuresTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6D92C57FEB30032BF25 /* TrailingClosuresTests.swift */; }; + 2E8DE7452C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DA2C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift */; }; + 2E8DE7462C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DB2C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift */; }; + 2E8DE7472C57FEB30032BF25 /* ConditionalAssignmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DC2C57FEB30032BF25 /* ConditionalAssignmentTests.swift */; }; + 2E8DE7482C57FEB30032BF25 /* RedundantGetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DD2C57FEB30032BF25 /* RedundantGetTests.swift */; }; + 2E8DE7492C57FEB30032BF25 /* HeaderFileNameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DE2C57FEB30032BF25 /* HeaderFileNameTests.swift */; }; + 2E8DE74A2C57FEB30032BF25 /* RedundantExtensionACLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6DF2C57FEB30032BF25 /* RedundantExtensionACLTests.swift */; }; + 2E8DE74B2C57FEB30032BF25 /* LeadingDelimitersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E02C57FEB30032BF25 /* LeadingDelimitersTests.swift */; }; + 2E8DE74C2C57FEB30032BF25 /* WrapConditionalBodiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E12C57FEB30032BF25 /* WrapConditionalBodiesTests.swift */; }; + 2E8DE74D2C57FEB30032BF25 /* OrganizeDeclarationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E22C57FEB30032BF25 /* OrganizeDeclarationsTests.swift */; }; + 2E8DE74E2C57FEB30032BF25 /* DocCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */; }; + 2E8DE74F2C57FEB30032BF25 /* ElseOnSameLineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */; }; + 2E8DE7502C57FEB30032BF25 /* RedundantReturnTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E52C57FEB30032BF25 /* RedundantReturnTests.swift */; }; + 2E8DE7512C57FEB30032BF25 /* LinebreaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E62C57FEB30032BF25 /* LinebreaksTests.swift */; }; + 2E8DE7522C57FEB30032BF25 /* MarkTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E72C57FEB30032BF25 /* MarkTypesTests.swift */; }; + 2E8DE7532C57FEB30032BF25 /* SpaceInsideParensTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E82C57FEB30032BF25 /* SpaceInsideParensTests.swift */; }; + 2E8DE7542C57FEB30032BF25 /* AssertionFailuresTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E92C57FEB30032BF25 /* AssertionFailuresTests.swift */; }; + 2E8DE7552C57FEB30032BF25 /* RedundantTypedThrowsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EA2C57FEB30032BF25 /* RedundantTypedThrowsTests.swift */; }; + 2E8DE7562C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EB2C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift */; }; + 2E8DE7572C57FEB30032BF25 /* ApplicationMainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EC2C57FEB30032BF25 /* ApplicationMainTests.swift */; }; + 2E8DE7582C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6ED2C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift */; }; + 2E8DE7592C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EE2C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift */; }; + 2E8DE75A2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6EF2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift */; }; + 2E8DE75B2C57FEB30032BF25 /* AndOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F02C57FEB30032BF25 /* AndOperatorTests.swift */; }; + 2E8DE75C2C57FEB30032BF25 /* WrapSwitchCasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F12C57FEB30032BF25 /* WrapSwitchCasesTests.swift */; }; + 2E8DE75D2C57FEB30032BF25 /* SpaceInsideBracketsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F22C57FEB30032BF25 /* SpaceInsideBracketsTests.swift */; }; + 2E8DE75E2C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F32C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift */; }; + 2E8DE75F2C57FEB30032BF25 /* BlankLineAfterImportsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F42C57FEB30032BF25 /* BlankLineAfterImportsTests.swift */; }; + 2E8DE7602C57FEB30032BF25 /* InitCoderUnavailableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F52C57FEB30032BF25 /* InitCoderUnavailableTests.swift */; }; + 2E8DE7612C57FEB30032BF25 /* RedundantFileprivateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */; }; + 2E8DE7622C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */; }; 37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; }; 37D828AC24BF77DA0012FC0A /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9028F7831DA4B435009FE5B4 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; @@ -636,7 +732,6 @@ 01045A90211988F100D2BE3D /* Inference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Inference.swift; sourceTree = ""; }; 01045A982119979400D2BE3D /* Arguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arguments.swift; sourceTree = ""; }; 01045A9C2119A21000D2BE3D /* ArgumentsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArgumentsTests.swift; sourceTree = ""; }; - 011676A12707D312001CCDCE /* RulesTests+General.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+General.swift"; sourceTree = ""; }; 011A53E921FFAA3A00DD9268 /* VersionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionTests.swift; sourceTree = ""; }; 01426E4D23AA29B100E7D871 /* ParsingHelpersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParsingHelpersTests.swift; sourceTree = ""; }; 0142C76F23C3FB6D005D5832 /* LintFileCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LintFileCommand.swift; sourceTree = ""; }; @@ -653,7 +748,7 @@ 01A0EAA71D5DB4CF00A0A8E3 /* SwiftFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftFormat.h; sourceTree = ""; }; 01A0EAA91D5DB4CF00A0A8E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 01A0EAAE1D5DB4D000A0A8E3 /* SwiftFormatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftFormatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 01A0EAB31D5DB4D000A0A8E3 /* RulesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulesTests.swift; sourceTree = ""; }; + 01A0EAB31D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+testFormatting.swift"; sourceTree = ""; }; 01A0EAB51D5DB4D000A0A8E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormatRule.swift; sourceTree = ""; }; 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tokenizer.swift; sourceTree = ""; }; @@ -666,18 +761,8 @@ 01BBD85821DAA2A000457380 /* Globs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globs.swift; sourceTree = ""; }; 01BBD85D21DAA30700457380 /* GlobsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobsTests.swift; sourceTree = ""; }; 01BEC5762236E1A700D0DD83 /* MetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetadataTests.swift; sourceTree = ""; }; - 01C209AE2502CD3C00E728A2 /* RulesTests+Spacing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Spacing.swift"; sourceTree = ""; }; - 01C209B02502CEF700E728A2 /* RulesTests+Linebreaks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Linebreaks.swift"; sourceTree = ""; }; - 01C209B22502CF8300E728A2 /* RulesTests+Indentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Indentation.swift"; sourceTree = ""; }; - 01C209B42502D27800E728A2 /* RulesTests+Braces.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Braces.swift"; sourceTree = ""; }; - 01C209B62502D2FD00E728A2 /* RulesTests+Parens.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Parens.swift"; sourceTree = ""; }; - 01C209B82502D3C500E728A2 /* RulesTests+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Wrapping.swift"; sourceTree = ""; }; - 01C209BA2502D62000E728A2 /* RulesTests+Organization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Organization.swift"; sourceTree = ""; }; - 01C209BC2502D71F00E728A2 /* RulesTests+Redundancy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Redundancy.swift"; sourceTree = ""; }; 01C4D3282BB518D400BDF1AF /* ZRegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZRegressionTests.swift; sourceTree = ""; }; 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattingHelpers.swift; sourceTree = ""; }; - 01EF830E25616089003F6F2D /* RulesTests+Syntax.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Syntax.swift"; sourceTree = ""; }; - 01EFC8B629CF2B5100222029 /* RulesTests+Hoisting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RulesTests+Hoisting.swift"; sourceTree = ""; }; 01F17E811E25870700DCD359 /* CommandLine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLine.swift; sourceTree = ""; }; 01F17E841E258A4900DCD359 /* CommandLineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CommandLineTests.swift; sourceTree = ""; }; 01F3DF8B1DB9FD3F00454944 /* Options.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Options.swift; sourceTree = ""; }; @@ -795,6 +880,113 @@ 2E2BABFD2C57F6DD00590239 /* RedundantType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantType.swift; sourceTree = ""; }; 2E2BABFE2C57F6DD00590239 /* SortDeclarations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDeclarations.swift; sourceTree = ""; }; 2E7D30A32A7940C500C32174 /* Singularize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Singularize.swift; sourceTree = ""; }; + 2E8DE68D2C57FEB30032BF25 /* RedundantClosureTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantClosureTests.swift; sourceTree = ""; }; + 2E8DE68E2C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenChainedFunctionsTests.swift; sourceTree = ""; }; + 2E8DE68F2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundGenericsTests.swift; sourceTree = ""; }; + 2E8DE6902C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAroundMarkTests.swift; sourceTree = ""; }; + 2E8DE6912C57FEB30032BF25 /* WrapArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapArgumentsTests.swift; sourceTree = ""; }; + 2E8DE6922C57FEB30032BF25 /* RedundantSelfTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantSelfTests.swift; sourceTree = ""; }; + 2E8DE6932C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenScopesTests.swift; sourceTree = ""; }; + 2E8DE6942C57FEB30032BF25 /* DuplicateImportsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DuplicateImportsTests.swift; sourceTree = ""; }; + 2E8DE6952C57FEB30032BF25 /* TodosTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TodosTests.swift; sourceTree = ""; }; + 2E8DE6962C57FEB30032BF25 /* FileHeaderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileHeaderTests.swift; sourceTree = ""; }; + 2E8DE6972C57FEB30032BF25 /* EnumNamespacesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnumNamespacesTests.swift; sourceTree = ""; }; + 2E8DE6982C57FEB30032BF25 /* PreferForLoopTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferForLoopTests.swift; sourceTree = ""; }; + 2E8DE6992C57FEB30032BF25 /* ExtensionAccessControlTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExtensionAccessControlTests.swift; sourceTree = ""; }; + 2E8DE69A2C57FEB30032BF25 /* TrailingCommasTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingCommasTests.swift; sourceTree = ""; }; + 2E8DE69B2C57FEB30032BF25 /* WrapLoopBodiesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapLoopBodiesTests.swift; sourceTree = ""; }; + 2E8DE69C2C57FEB30032BF25 /* StrongifiedSelfTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrongifiedSelfTests.swift; sourceTree = ""; }; + 2E8DE69D2C57FEB30032BF25 /* RedundantPropertyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantPropertyTests.swift; sourceTree = ""; }; + 2E8DE69E2C57FEB30032BF25 /* OpaqueGenericParametersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpaqueGenericParametersTests.swift; sourceTree = ""; }; + 2E8DE69F2C57FEB30032BF25 /* SpaceInsideBracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideBracesTests.swift; sourceTree = ""; }; + 2E8DE6A02C57FEB30032BF25 /* ModifierOrderTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModifierOrderTests.swift; sourceTree = ""; }; + 2E8DE6A12C57FEB30032BF25 /* WrapAttributesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapAttributesTests.swift; sourceTree = ""; }; + 2E8DE6A22C57FEB30032BF25 /* RedundantObjcTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantObjcTests.swift; sourceTree = ""; }; + 2E8DE6A32C57FEB30032BF25 /* HoistPatternLetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistPatternLetTests.swift; sourceTree = ""; }; + 2E8DE6A42C57FEB30032BF25 /* WrapTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapTests.swift; sourceTree = ""; }; + 2E8DE6A52C57FEB30032BF25 /* SpaceInsideGenericsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideGenericsTests.swift; sourceTree = ""; }; + 2E8DE6A62C57FEB30032BF25 /* RedundantStaticSelfTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantStaticSelfTests.swift; sourceTree = ""; }; + 2E8DE6A72C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAtEndOfScopeTests.swift; sourceTree = ""; }; + 2E8DE6A82C57FEB30032BF25 /* RedundantInitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantInitTests.swift; sourceTree = ""; }; + 2E8DE6A92C57FEB30032BF25 /* RedundantRawValuesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantRawValuesTests.swift; sourceTree = ""; }; + 2E8DE6AA2C57FEB30032BF25 /* SortImportsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortImportsTests.swift; sourceTree = ""; }; + 2E8DE6AB2C57FEB30032BF25 /* TrailingSpaceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingSpaceTests.swift; sourceTree = ""; }; + 2E8DE6AC2C57FEB30032BF25 /* YodaConditionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YodaConditionsTests.swift; sourceTree = ""; }; + 2E8DE6AD2C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsecutiveBlankLinesTests.swift; sourceTree = ""; }; + 2E8DE6AE2C57FEB30032BF25 /* UnusedArgumentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedArgumentsTests.swift; sourceTree = ""; }; + 2E8DE6AF2C57FEB30032BF25 /* GenericExtensionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenericExtensionsTests.swift; sourceTree = ""; }; + 2E8DE6B02C57FEB30032BF25 /* NumberFormattingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumberFormattingTests.swift; sourceTree = ""; }; + 2E8DE6B12C57FEB30032BF25 /* RedundantTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantTypeTests.swift; sourceTree = ""; }; + 2E8DE6B22C57FEB30032BF25 /* TypeSugarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeSugarTests.swift; sourceTree = ""; }; + 2E8DE6B32C57FEB30032BF25 /* SpaceAroundBracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundBracesTests.swift; sourceTree = ""; }; + 2E8DE6B42C57FEB30032BF25 /* SortTypealiasesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortTypealiasesTests.swift; sourceTree = ""; }; + 2E8DE6B52C57FEB30032BF25 /* SortSwitchCasesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortSwitchCasesTests.swift; sourceTree = ""; }; + 2E8DE6B62C57FEB30032BF25 /* EmptyBracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyBracesTests.swift; sourceTree = ""; }; + 2E8DE6B72C57FEB30032BF25 /* SortDeclarationsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SortDeclarationsTests.swift; sourceTree = ""; }; + 2E8DE6B82C57FEB30032BF25 /* BlockCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockCommentsTests.swift; sourceTree = ""; }; + 2E8DE6B92C57FEB30032BF25 /* StrongOutletsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StrongOutletsTests.swift; sourceTree = ""; }; + 2E8DE6BA2C57FEB30032BF25 /* BracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BracesTests.swift; sourceTree = ""; }; + 2E8DE6BB2C57FEB30032BF25 /* AnyObjectProtocolTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyObjectProtocolTests.swift; sourceTree = ""; }; + 2E8DE6BC2C57FEB30032BF25 /* RedundantBreakTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantBreakTests.swift; sourceTree = ""; }; + 2E8DE6BD2C57FEB30032BF25 /* RedundantNilInitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantNilInitTests.swift; sourceTree = ""; }; + 2E8DE6BE2C57FEB30032BF25 /* RedundantParensTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantParensTests.swift; sourceTree = ""; }; + 2E8DE6BF2C57FEB30032BF25 /* PreferKeyPathTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferKeyPathTests.swift; sourceTree = ""; }; + 2E8DE6C02C57FEB30032BF25 /* SpaceAroundParensTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundParensTests.swift; sourceTree = ""; }; + 2E8DE6C12C57FEB30032BF25 /* RedundantLetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantLetTests.swift; sourceTree = ""; }; + 2E8DE6C22C57FEB30032BF25 /* VoidTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VoidTests.swift; sourceTree = ""; }; + 2E8DE6C32C57FEB30032BF25 /* IndentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IndentTests.swift; sourceTree = ""; }; + 2E8DE6C42C57FEB30032BF25 /* RedundantBackticksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantBackticksTests.swift; sourceTree = ""; }; + 2E8DE6C52C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLineAfterSwitchCaseTests.swift; sourceTree = ""; }; + 2E8DE6C62C57FEB30032BF25 /* HoistAwaitTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistAwaitTests.swift; sourceTree = ""; }; + 2E8DE6C72C57FEB30032BF25 /* RedundantLetErrorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantLetErrorTests.swift; sourceTree = ""; }; + 2E8DE6C82C57FEB30032BF25 /* SpaceAroundCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundCommentsTests.swift; sourceTree = ""; }; + 2E8DE6C92C57FEB30032BF25 /* PropertyTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyTypeTests.swift; sourceTree = ""; }; + 2E8DE6CA2C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundOperatorsTests.swift; sourceTree = ""; }; + 2E8DE6CB2C57FEB30032BF25 /* SemicolonsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SemicolonsTests.swift; sourceTree = ""; }; + 2E8DE6CC2C57FEB30032BF25 /* RedundantPatternTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantPatternTests.swift; sourceTree = ""; }; + 2E8DE6CD2C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapMultilineConditionalAssignmentTests.swift; sourceTree = ""; }; + 2E8DE6CE2C57FEB30032BF25 /* RedundantInternalTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantInternalTests.swift; sourceTree = ""; }; + 2E8DE6CF2C57FEB30032BF25 /* IsEmptyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IsEmptyTests.swift; sourceTree = ""; }; + 2E8DE6D02C57FEB30032BF25 /* AcronymsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AcronymsTests.swift; sourceTree = ""; }; + 2E8DE6D12C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsistentSwitchCaseSpacingTests.swift; sourceTree = ""; }; + 2E8DE6D22C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnusedPrivateDeclarationTests.swift; sourceTree = ""; }; + 2E8DE6D32C57FEB30032BF25 /* WrapEnumCasesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapEnumCasesTests.swift; sourceTree = ""; }; + 2E8DE6D42C57FEB30032BF25 /* NoExplicitOwnershipTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoExplicitOwnershipTests.swift; sourceTree = ""; }; + 2E8DE6D52C57FEB30032BF25 /* HoistTryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HoistTryTests.swift; sourceTree = ""; }; + 2E8DE6D62C57FEB30032BF25 /* RedundantOptionalBindingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantOptionalBindingTests.swift; sourceTree = ""; }; + 2E8DE6D72C57FEB30032BF25 /* ConsecutiveSpacesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConsecutiveSpacesTests.swift; sourceTree = ""; }; + 2E8DE6D82C57FEB30032BF25 /* SpaceAroundBracketsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceAroundBracketsTests.swift; sourceTree = ""; }; + 2E8DE6D92C57FEB30032BF25 /* TrailingClosuresTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingClosuresTests.swift; sourceTree = ""; }; + 2E8DE6DA2C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapMultilineStatementBracesTests.swift; sourceTree = ""; }; + 2E8DE6DB2C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinebreakAtEndOfFileTests.swift; sourceTree = ""; }; + 2E8DE6DC2C57FEB30032BF25 /* ConditionalAssignmentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionalAssignmentTests.swift; sourceTree = ""; }; + 2E8DE6DD2C57FEB30032BF25 /* RedundantGetTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantGetTests.swift; sourceTree = ""; }; + 2E8DE6DE2C57FEB30032BF25 /* HeaderFileNameTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeaderFileNameTests.swift; sourceTree = ""; }; + 2E8DE6DF2C57FEB30032BF25 /* RedundantExtensionACLTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantExtensionACLTests.swift; sourceTree = ""; }; + 2E8DE6E02C57FEB30032BF25 /* LeadingDelimitersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeadingDelimitersTests.swift; sourceTree = ""; }; + 2E8DE6E12C57FEB30032BF25 /* WrapConditionalBodiesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapConditionalBodiesTests.swift; sourceTree = ""; }; + 2E8DE6E22C57FEB30032BF25 /* OrganizeDeclarationsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganizeDeclarationsTests.swift; sourceTree = ""; }; + 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocCommentsTests.swift; sourceTree = ""; }; + 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElseOnSameLineTests.swift; sourceTree = ""; }; + 2E8DE6E52C57FEB30032BF25 /* RedundantReturnTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantReturnTests.swift; sourceTree = ""; }; + 2E8DE6E62C57FEB30032BF25 /* LinebreaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinebreaksTests.swift; sourceTree = ""; }; + 2E8DE6E72C57FEB30032BF25 /* MarkTypesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkTypesTests.swift; sourceTree = ""; }; + 2E8DE6E82C57FEB30032BF25 /* SpaceInsideParensTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideParensTests.swift; sourceTree = ""; }; + 2E8DE6E92C57FEB30032BF25 /* AssertionFailuresTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssertionFailuresTests.swift; sourceTree = ""; }; + 2E8DE6EA2C57FEB30032BF25 /* RedundantTypedThrowsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantTypedThrowsTests.swift; sourceTree = ""; }; + 2E8DE6EB2C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesAtStartOfScopeTests.swift; sourceTree = ""; }; + 2E8DE6EC2C57FEB30032BF25 /* ApplicationMainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationMainTests.swift; sourceTree = ""; }; + 2E8DE6ED2C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantVoidReturnTypeTests.swift; sourceTree = ""; }; + 2E8DE6EE2C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLinesBetweenImportsTests.swift; sourceTree = ""; }; + 2E8DE6EF2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideCommentsTests.swift; sourceTree = ""; }; + 2E8DE6F02C57FEB30032BF25 /* AndOperatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AndOperatorTests.swift; sourceTree = ""; }; + 2E8DE6F12C57FEB30032BF25 /* WrapSwitchCasesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSwitchCasesTests.swift; sourceTree = ""; }; + 2E8DE6F22C57FEB30032BF25 /* SpaceInsideBracketsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpaceInsideBracketsTests.swift; sourceTree = ""; }; + 2E8DE6F32C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocCommentsBeforeAttributesTests.swift; sourceTree = ""; }; + 2E8DE6F42C57FEB30032BF25 /* BlankLineAfterImportsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlankLineAfterImportsTests.swift; sourceTree = ""; }; + 2E8DE6F52C57FEB30032BF25 /* InitCoderUnavailableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitCoderUnavailableTests.swift; sourceTree = ""; }; + 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantFileprivateTests.swift; sourceTree = ""; }; + 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSingleLineCommentsTests.swift; sourceTree = ""; }; 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; 90C4B6CA1DA4B04A009EB000 /* SwiftFormat for Xcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftFormat for Xcode.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 90C4B6CC1DA4B04A009EB000 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -958,6 +1150,7 @@ 01A0EAB21D5DB4D000A0A8E3 /* Tests */ = { isa = PBXGroup; children = ( + 2E8DE68C2C57FEB30032BF25 /* Rules */, 01BEC5762236E1A700D0DD83 /* MetadataTests.swift */, 01045A9C2119A21000D2BE3D /* ArgumentsTests.swift */, 01F17E841E258A4900DCD359 /* CommandLineTests.swift */, @@ -968,24 +1161,13 @@ E4E4D3CD2033F1EF000D7CB1 /* EnumAssociableTests.swift */, E43EF47B202FF47C00E523BD /* OptionDescriptorTests.swift */, 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */, - 01A0EAB31D5DB4D000A0A8E3 /* RulesTests.swift */, - 01C209B42502D27800E728A2 /* RulesTests+Braces.swift */, - 011676A12707D312001CCDCE /* RulesTests+General.swift */, - 01C209B22502CF8300E728A2 /* RulesTests+Indentation.swift */, - 01EFC8B629CF2B5100222029 /* RulesTests+Hoisting.swift */, - 01C209B02502CEF700E728A2 /* RulesTests+Linebreaks.swift */, - 01C209BA2502D62000E728A2 /* RulesTests+Organization.swift */, - 01C209B62502D2FD00E728A2 /* RulesTests+Parens.swift */, - 01C209BC2502D71F00E728A2 /* RulesTests+Redundancy.swift */, - 01C209AE2502CD3C00E728A2 /* RulesTests+Spacing.swift */, - 01EF830E25616089003F6F2D /* RulesTests+Syntax.swift */, - 01C209B82502D3C500E728A2 /* RulesTests+Wrapping.swift */, 015CE8B02B448CCE00924504 /* SingularizeTests.swift */, 0142F06E1D72FE10007D66CC /* SwiftFormatTests.swift */, 01BBD85D21DAA30700457380 /* GlobsTests.swift */, 018E82741D62E730008CA0F8 /* TokenizerTests.swift */, 011A53E921FFAA3A00DD9268 /* VersionTests.swift */, 01C4D3282BB518D400BDF1AF /* ZRegressionTests.swift */, + 01A0EAB31D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift */, ); path = Tests; sourceTree = ""; @@ -1124,6 +1306,120 @@ path = Rules; sourceTree = ""; }; + 2E8DE68C2C57FEB30032BF25 /* Rules */ = { + isa = PBXGroup; + children = ( + 2E8DE6D02C57FEB30032BF25 /* AcronymsTests.swift */, + 2E8DE6F02C57FEB30032BF25 /* AndOperatorTests.swift */, + 2E8DE6BB2C57FEB30032BF25 /* AnyObjectProtocolTests.swift */, + 2E8DE6EC2C57FEB30032BF25 /* ApplicationMainTests.swift */, + 2E8DE6E92C57FEB30032BF25 /* AssertionFailuresTests.swift */, + 2E8DE6F42C57FEB30032BF25 /* BlankLineAfterImportsTests.swift */, + 2E8DE6C52C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift */, + 2E8DE6902C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift */, + 2E8DE6A72C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift */, + 2E8DE6EB2C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift */, + 2E8DE68E2C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift */, + 2E8DE6EE2C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift */, + 2E8DE6932C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift */, + 2E8DE6B82C57FEB30032BF25 /* BlockCommentsTests.swift */, + 2E8DE6BA2C57FEB30032BF25 /* BracesTests.swift */, + 2E8DE6DC2C57FEB30032BF25 /* ConditionalAssignmentTests.swift */, + 2E8DE6AD2C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift */, + 2E8DE6D72C57FEB30032BF25 /* ConsecutiveSpacesTests.swift */, + 2E8DE6D12C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift */, + 2E8DE6F32C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift */, + 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */, + 2E8DE6942C57FEB30032BF25 /* DuplicateImportsTests.swift */, + 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */, + 2E8DE6B62C57FEB30032BF25 /* EmptyBracesTests.swift */, + 2E8DE6972C57FEB30032BF25 /* EnumNamespacesTests.swift */, + 2E8DE6992C57FEB30032BF25 /* ExtensionAccessControlTests.swift */, + 2E8DE6962C57FEB30032BF25 /* FileHeaderTests.swift */, + 2E8DE6AF2C57FEB30032BF25 /* GenericExtensionsTests.swift */, + 2E8DE6DE2C57FEB30032BF25 /* HeaderFileNameTests.swift */, + 2E8DE6C62C57FEB30032BF25 /* HoistAwaitTests.swift */, + 2E8DE6A32C57FEB30032BF25 /* HoistPatternLetTests.swift */, + 2E8DE6D52C57FEB30032BF25 /* HoistTryTests.swift */, + 2E8DE6C32C57FEB30032BF25 /* IndentTests.swift */, + 2E8DE6F52C57FEB30032BF25 /* InitCoderUnavailableTests.swift */, + 2E8DE6CF2C57FEB30032BF25 /* IsEmptyTests.swift */, + 2E8DE6E02C57FEB30032BF25 /* LeadingDelimitersTests.swift */, + 2E8DE6DB2C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift */, + 2E8DE6E62C57FEB30032BF25 /* LinebreaksTests.swift */, + 2E8DE6E72C57FEB30032BF25 /* MarkTypesTests.swift */, + 2E8DE6A02C57FEB30032BF25 /* ModifierOrderTests.swift */, + 2E8DE6D42C57FEB30032BF25 /* NoExplicitOwnershipTests.swift */, + 2E8DE6B02C57FEB30032BF25 /* NumberFormattingTests.swift */, + 2E8DE69E2C57FEB30032BF25 /* OpaqueGenericParametersTests.swift */, + 2E8DE6E22C57FEB30032BF25 /* OrganizeDeclarationsTests.swift */, + 2E8DE6982C57FEB30032BF25 /* PreferForLoopTests.swift */, + 2E8DE6BF2C57FEB30032BF25 /* PreferKeyPathTests.swift */, + 2E8DE6C92C57FEB30032BF25 /* PropertyTypeTests.swift */, + 2E8DE6C42C57FEB30032BF25 /* RedundantBackticksTests.swift */, + 2E8DE6BC2C57FEB30032BF25 /* RedundantBreakTests.swift */, + 2E8DE68D2C57FEB30032BF25 /* RedundantClosureTests.swift */, + 2E8DE6DF2C57FEB30032BF25 /* RedundantExtensionACLTests.swift */, + 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */, + 2E8DE6DD2C57FEB30032BF25 /* RedundantGetTests.swift */, + 2E8DE6A82C57FEB30032BF25 /* RedundantInitTests.swift */, + 2E8DE6CE2C57FEB30032BF25 /* RedundantInternalTests.swift */, + 2E8DE6C72C57FEB30032BF25 /* RedundantLetErrorTests.swift */, + 2E8DE6C12C57FEB30032BF25 /* RedundantLetTests.swift */, + 2E8DE6BD2C57FEB30032BF25 /* RedundantNilInitTests.swift */, + 2E8DE6A22C57FEB30032BF25 /* RedundantObjcTests.swift */, + 2E8DE6D62C57FEB30032BF25 /* RedundantOptionalBindingTests.swift */, + 2E8DE6BE2C57FEB30032BF25 /* RedundantParensTests.swift */, + 2E8DE6CC2C57FEB30032BF25 /* RedundantPatternTests.swift */, + 2E8DE69D2C57FEB30032BF25 /* RedundantPropertyTests.swift */, + 2E8DE6A92C57FEB30032BF25 /* RedundantRawValuesTests.swift */, + 2E8DE6E52C57FEB30032BF25 /* RedundantReturnTests.swift */, + 2E8DE6922C57FEB30032BF25 /* RedundantSelfTests.swift */, + 2E8DE6A62C57FEB30032BF25 /* RedundantStaticSelfTests.swift */, + 2E8DE6EA2C57FEB30032BF25 /* RedundantTypedThrowsTests.swift */, + 2E8DE6B12C57FEB30032BF25 /* RedundantTypeTests.swift */, + 2E8DE6ED2C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift */, + 2E8DE6CB2C57FEB30032BF25 /* SemicolonsTests.swift */, + 2E8DE6B72C57FEB30032BF25 /* SortDeclarationsTests.swift */, + 2E8DE6AA2C57FEB30032BF25 /* SortImportsTests.swift */, + 2E8DE6B52C57FEB30032BF25 /* SortSwitchCasesTests.swift */, + 2E8DE6B42C57FEB30032BF25 /* SortTypealiasesTests.swift */, + 2E8DE6B32C57FEB30032BF25 /* SpaceAroundBracesTests.swift */, + 2E8DE6D82C57FEB30032BF25 /* SpaceAroundBracketsTests.swift */, + 2E8DE6C82C57FEB30032BF25 /* SpaceAroundCommentsTests.swift */, + 2E8DE68F2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift */, + 2E8DE6CA2C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift */, + 2E8DE6C02C57FEB30032BF25 /* SpaceAroundParensTests.swift */, + 2E8DE69F2C57FEB30032BF25 /* SpaceInsideBracesTests.swift */, + 2E8DE6F22C57FEB30032BF25 /* SpaceInsideBracketsTests.swift */, + 2E8DE6EF2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift */, + 2E8DE6A52C57FEB30032BF25 /* SpaceInsideGenericsTests.swift */, + 2E8DE6E82C57FEB30032BF25 /* SpaceInsideParensTests.swift */, + 2E8DE69C2C57FEB30032BF25 /* StrongifiedSelfTests.swift */, + 2E8DE6B92C57FEB30032BF25 /* StrongOutletsTests.swift */, + 2E8DE6952C57FEB30032BF25 /* TodosTests.swift */, + 2E8DE6D92C57FEB30032BF25 /* TrailingClosuresTests.swift */, + 2E8DE69A2C57FEB30032BF25 /* TrailingCommasTests.swift */, + 2E8DE6AB2C57FEB30032BF25 /* TrailingSpaceTests.swift */, + 2E8DE6B22C57FEB30032BF25 /* TypeSugarTests.swift */, + 2E8DE6AE2C57FEB30032BF25 /* UnusedArgumentsTests.swift */, + 2E8DE6D22C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift */, + 2E8DE6C22C57FEB30032BF25 /* VoidTests.swift */, + 2E8DE6912C57FEB30032BF25 /* WrapArgumentsTests.swift */, + 2E8DE6A12C57FEB30032BF25 /* WrapAttributesTests.swift */, + 2E8DE6E12C57FEB30032BF25 /* WrapConditionalBodiesTests.swift */, + 2E8DE6D32C57FEB30032BF25 /* WrapEnumCasesTests.swift */, + 2E8DE69B2C57FEB30032BF25 /* WrapLoopBodiesTests.swift */, + 2E8DE6CD2C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift */, + 2E8DE6DA2C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift */, + 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */, + 2E8DE6F12C57FEB30032BF25 /* WrapSwitchCasesTests.swift */, + 2E8DE6A42C57FEB30032BF25 /* WrapTests.swift */, + 2E8DE6AC2C57FEB30032BF25 /* YodaConditionsTests.swift */, + ); + path = Rules; + sourceTree = ""; + }; 9016DEFD1DA5042E008A4E36 /* Resources */ = { isa = PBXGroup; children = ( @@ -1616,33 +1912,129 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 01A0EAB41D5DB4D000A0A8E3 /* RulesTests.swift in Sources */, - 011676A22707D312001CCDCE /* RulesTests+General.swift in Sources */, - 01C209B92502D3C500E728A2 /* RulesTests+Wrapping.swift in Sources */, + 2E8DE74A2C57FEB30032BF25 /* RedundantExtensionACLTests.swift in Sources */, + 01A0EAB41D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift in Sources */, + 2E8DE7602C57FEB30032BF25 /* InitCoderUnavailableTests.swift in Sources */, + 2E8DE75C2C57FEB30032BF25 /* WrapSwitchCasesTests.swift in Sources */, + 2E8DE71B2C57FEB30032BF25 /* NumberFormattingTests.swift in Sources */, + 2E8DE74C2C57FEB30032BF25 /* WrapConditionalBodiesTests.swift in Sources */, + 2E8DE7332C57FEB30032BF25 /* SpaceAroundCommentsTests.swift in Sources */, + 2E8DE7342C57FEB30032BF25 /* PropertyTypeTests.swift in Sources */, + 2E8DE7162C57FEB30032BF25 /* TrailingSpaceTests.swift in Sources */, + 2E8DE7472C57FEB30032BF25 /* ConditionalAssignmentTests.swift in Sources */, 01F17E851E258A4900DCD359 /* CommandLineTests.swift in Sources */, + 2E8DE7082C57FEB30032BF25 /* RedundantPropertyTests.swift in Sources */, 01426E4E23AA29B100E7D871 /* ParsingHelpersTests.swift in Sources */, + 2E8DE70A2C57FEB30032BF25 /* SpaceInsideBracesTests.swift in Sources */, + 2E8DE7442C57FEB30032BF25 /* TrailingClosuresTests.swift in Sources */, + 2E8DE7402C57FEB30032BF25 /* HoistTryTests.swift in Sources */, + 2E8DE7062C57FEB30032BF25 /* WrapLoopBodiesTests.swift in Sources */, + 2E8DE6F92C57FEB30032BF25 /* BlankLinesBetweenChainedFunctionsTests.swift in Sources */, + 2E8DE7382C57FEB30032BF25 /* WrapMultilineConditionalAssignmentTests.swift in Sources */, + 2E8DE74F2C57FEB30032BF25 /* ElseOnSameLineTests.swift in Sources */, + 2E8DE6FD2C57FEB30032BF25 /* RedundantSelfTests.swift in Sources */, 018E82751D62E730008CA0F8 /* TokenizerTests.swift in Sources */, - 01C209BB2502D62000E728A2 /* RulesTests+Organization.swift in Sources */, + 2E8DE74D2C57FEB30032BF25 /* OrganizeDeclarationsTests.swift in Sources */, + 2E8DE71E2C57FEB30032BF25 /* SpaceAroundBracesTests.swift in Sources */, + 2E8DE73C2C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift in Sources */, + 2E8DE75B2C57FEB30032BF25 /* AndOperatorTests.swift in Sources */, + 2E8DE71C2C57FEB30032BF25 /* RedundantTypeTests.swift in Sources */, + 2E8DE7492C57FEB30032BF25 /* HeaderFileNameTests.swift in Sources */, + 2E8DE7172C57FEB30032BF25 /* YodaConditionsTests.swift in Sources */, + 2E8DE7152C57FEB30032BF25 /* SortImportsTests.swift in Sources */, + 2E8DE7302C57FEB30032BF25 /* BlankLineAfterSwitchCaseTests.swift in Sources */, 011A53EB21FFAA4200DD9268 /* VersionTests.swift in Sources */, - 01EF830F25616089003F6F2D /* RulesTests+Syntax.swift in Sources */, + 2E8DE72E2C57FEB30032BF25 /* IndentTests.swift in Sources */, + 2E8DE7532C57FEB30032BF25 /* SpaceInsideParensTests.swift in Sources */, + 2E8DE7462C57FEB30032BF25 /* LinebreakAtEndOfFileTests.swift in Sources */, 01BBD85E21DAA30700457380 /* GlobsTests.swift in Sources */, + 2E8DE7542C57FEB30032BF25 /* AssertionFailuresTests.swift in Sources */, 01B3987B1D763424009ADE61 /* FormatterTests.swift in Sources */, + 2E8DE7502C57FEB30032BF25 /* RedundantReturnTests.swift in Sources */, + 2E8DE7052C57FEB30032BF25 /* TrailingCommasTests.swift in Sources */, + 2E8DE7362C57FEB30032BF25 /* SemicolonsTests.swift in Sources */, + 2E8DE7272C57FEB30032BF25 /* RedundantBreakTests.swift in Sources */, + 2E8DE7232C57FEB30032BF25 /* BlockCommentsTests.swift in Sources */, + 2E8DE75A2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift in Sources */, + 2E8DE75D2C57FEB30032BF25 /* SpaceInsideBracketsTests.swift in Sources */, + 2E8DE72D2C57FEB30032BF25 /* VoidTests.swift in Sources */, + 2E8DE72B2C57FEB30032BF25 /* SpaceAroundParensTests.swift in Sources */, + 2E8DE73F2C57FEB30032BF25 /* NoExplicitOwnershipTests.swift in Sources */, 01C4D3292BB518D400BDF1AF /* ZRegressionTests.swift in Sources */, - 01C209BD2502D71F00E728A2 /* RulesTests+Redundancy.swift in Sources */, + 2E8DE7092C57FEB30032BF25 /* OpaqueGenericParametersTests.swift in Sources */, + 2E8DE7202C57FEB30032BF25 /* SortSwitchCasesTests.swift in Sources */, + 2E8DE7452C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift in Sources */, + 2E8DE7242C57FEB30032BF25 /* StrongOutletsTests.swift in Sources */, + 2E8DE7292C57FEB30032BF25 /* RedundantParensTests.swift in Sources */, 0142F06F1D72FE10007D66CC /* SwiftFormatTests.swift in Sources */, - 01C209AF2502CD3C00E728A2 /* RulesTests+Spacing.swift in Sources */, + 2E8DE7432C57FEB30032BF25 /* SpaceAroundBracketsTests.swift in Sources */, + 2E8DE7612C57FEB30032BF25 /* RedundantFileprivateTests.swift in Sources */, + 2E8DE6F82C57FEB30032BF25 /* RedundantClosureTests.swift in Sources */, + 2E8DE7562C57FEB30032BF25 /* BlankLinesAtStartOfScopeTests.swift in Sources */, + 2E8DE75F2C57FEB30032BF25 /* BlankLineAfterImportsTests.swift in Sources */, + 2E8DE7182C57FEB30032BF25 /* ConsecutiveBlankLinesTests.swift in Sources */, + 2E8DE7192C57FEB30032BF25 /* UnusedArgumentsTests.swift in Sources */, + 2E8DE7022C57FEB30032BF25 /* EnumNamespacesTests.swift in Sources */, + 2E8DE7572C57FEB30032BF25 /* ApplicationMainTests.swift in Sources */, + 2E8DE7102C57FEB30032BF25 /* SpaceInsideGenericsTests.swift in Sources */, + 2E8DE70C2C57FEB30032BF25 /* WrapAttributesTests.swift in Sources */, + 2E8DE73D2C57FEB30032BF25 /* UnusedPrivateDeclarationTests.swift in Sources */, + 2E8DE71D2C57FEB30032BF25 /* TypeSugarTests.swift in Sources */, + 2E8DE73E2C57FEB30032BF25 /* WrapEnumCasesTests.swift in Sources */, E4E4D3CE2033F1EF000D7CB1 /* EnumAssociableTests.swift in Sources */, + 2E8DE73B2C57FEB30032BF25 /* AcronymsTests.swift in Sources */, E43EF47C202FF47C00E523BD /* OptionDescriptorTests.swift in Sources */, + 2E8DE71F2C57FEB30032BF25 /* SortTypealiasesTests.swift in Sources */, + 2E8DE7012C57FEB30032BF25 /* FileHeaderTests.swift in Sources */, + 2E8DE7322C57FEB30032BF25 /* RedundantLetErrorTests.swift in Sources */, 01045A9E2119A37F00D2BE3D /* ArgumentsTests.swift in Sources */, - 01C209B52502D27800E728A2 /* RulesTests+Braces.swift in Sources */, - 01EFC8B729CF2B5100222029 /* RulesTests+Hoisting.swift in Sources */, - 01C209B72502D2FD00E728A2 /* RulesTests+Parens.swift in Sources */, + 2E8DE71A2C57FEB30032BF25 /* GenericExtensionsTests.swift in Sources */, + 2E8DE7372C57FEB30032BF25 /* RedundantPatternTests.swift in Sources */, + 2E8DE7622C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift in Sources */, + 2E8DE72A2C57FEB30032BF25 /* PreferKeyPathTests.swift in Sources */, + 2E8DE7592C57FEB30032BF25 /* BlankLinesBetweenImportsTests.swift in Sources */, + 2E8DE7392C57FEB30032BF25 /* RedundantInternalTests.swift in Sources */, + 2E8DE70B2C57FEB30032BF25 /* ModifierOrderTests.swift in Sources */, + 2E8DE7422C57FEB30032BF25 /* ConsecutiveSpacesTests.swift in Sources */, + 2E8DE7312C57FEB30032BF25 /* HoistAwaitTests.swift in Sources */, + 2E8DE7132C57FEB30032BF25 /* RedundantInitTests.swift in Sources */, + 2E8DE7142C57FEB30032BF25 /* RedundantRawValuesTests.swift in Sources */, + 2E8DE7262C57FEB30032BF25 /* AnyObjectProtocolTests.swift in Sources */, + 2E8DE70F2C57FEB30032BF25 /* WrapTests.swift in Sources */, + 2E8DE7032C57FEB30032BF25 /* PreferForLoopTests.swift in Sources */, + 2E8DE6FC2C57FEB30032BF25 /* WrapArgumentsTests.swift in Sources */, + 2E8DE70D2C57FEB30032BF25 /* RedundantObjcTests.swift in Sources */, + 2E8DE7212C57FEB30032BF25 /* EmptyBracesTests.swift in Sources */, + 2E8DE7072C57FEB30032BF25 /* StrongifiedSelfTests.swift in Sources */, + 2E8DE73A2C57FEB30032BF25 /* IsEmptyTests.swift in Sources */, 01BEC5772236E1A700D0DD83 /* MetadataTests.swift in Sources */, + 2E8DE6FB2C57FEB30032BF25 /* BlankLinesAroundMarkTests.swift in Sources */, + 2E8DE7582C57FEB30032BF25 /* RedundantVoidReturnTypeTests.swift in Sources */, + 2E8DE7352C57FEB30032BF25 /* SpaceAroundOperatorsTests.swift in Sources */, + 2E8DE7412C57FEB30032BF25 /* RedundantOptionalBindingTests.swift in Sources */, + 2E8DE7512C57FEB30032BF25 /* LinebreaksTests.swift in Sources */, 01F3DF901DBA003E00454944 /* InferenceTests.swift in Sources */, - 01C209B32502CF8300E728A2 /* RulesTests+Indentation.swift in Sources */, - 01C209B12502CEF700E728A2 /* RulesTests+Linebreaks.swift in Sources */, + 2E8DE7482C57FEB30032BF25 /* RedundantGetTests.swift in Sources */, + 2E8DE72F2C57FEB30032BF25 /* RedundantBackticksTests.swift in Sources */, + 2E8DE6FE2C57FEB30032BF25 /* BlankLinesBetweenScopesTests.swift in Sources */, + 2E8DE7002C57FEB30032BF25 /* TodosTests.swift in Sources */, + 2E8DE7282C57FEB30032BF25 /* RedundantNilInitTests.swift in Sources */, + 2E8DE6FA2C57FEB30032BF25 /* SpaceAroundGenericsTests.swift in Sources */, + 2E8DE7222C57FEB30032BF25 /* SortDeclarationsTests.swift in Sources */, + 2E8DE7122C57FEB30032BF25 /* BlankLinesAtEndOfScopeTests.swift in Sources */, + 2E8DE7252C57FEB30032BF25 /* BracesTests.swift in Sources */, + 2E8DE74B2C57FEB30032BF25 /* LeadingDelimitersTests.swift in Sources */, + 2E8DE7552C57FEB30032BF25 /* RedundantTypedThrowsTests.swift in Sources */, 015CE8B12B448CCE00924504 /* SingularizeTests.swift in Sources */, + 2E8DE7042C57FEB30032BF25 /* ExtensionAccessControlTests.swift in Sources */, 015F83FB2BF1448D0060A07E /* ReporterTests.swift in Sources */, + 2E8DE70E2C57FEB30032BF25 /* HoistPatternLetTests.swift in Sources */, + 2E8DE74E2C57FEB30032BF25 /* DocCommentsTests.swift in Sources */, + 2E8DE7522C57FEB30032BF25 /* MarkTypesTests.swift in Sources */, + 2E8DE75E2C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift in Sources */, + 2E8DE6FF2C57FEB30032BF25 /* DuplicateImportsTests.swift in Sources */, + 2E8DE7112C57FEB30032BF25 /* RedundantStaticSelfTests.swift in Sources */, + 2E8DE72C2C57FEB30032BF25 /* RedundantLetTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Tests/GlobsTests.swift b/Tests/GlobsTests.swift index 4c4c9bef6..66622323e 100644 --- a/Tests/GlobsTests.swift +++ b/Tests/GlobsTests.swift @@ -85,14 +85,14 @@ class GlobsTests: XCTestCase { func testExpandPathWithWildcardAtStart() { let path = "*Tests.swift" let directory = URL(fileURLWithPath: #file).deletingLastPathComponent() - XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 16) + XCTAssertGreaterThanOrEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 15) } func testExpandPathWithSubdirectoryAndWildcard() { let path = "Tests/*Tests.swift" let directory = URL(fileURLWithPath: #file) .deletingLastPathComponent().deletingLastPathComponent() - XCTAssertEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 16) + XCTAssertGreaterThanOrEqual(try matchGlobs(expandGlobs(path, in: directory.path), in: directory.path).count, 15) } func testSingleWildcardDoesNotMatchDirectorySlash() { diff --git a/Tests/Rules/AcronymsTests.swift b/Tests/Rules/AcronymsTests.swift new file mode 100644 index 000000000..481848579 --- /dev/null +++ b/Tests/Rules/AcronymsTests.swift @@ -0,0 +1,78 @@ +// +// AcronymsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class AcronymsTests: XCTestCase { + func testUppercaseAcronyms() { + let input = """ + let url: URL + let destinationUrl: URL + let id: ID + let screenId = "screenId" // We intentionally don't change the content of strings + let validUrls: Set + let validUrlschemes: Set + + let uniqueIdentifier = UUID() + + /// Opens Urls based on their scheme + struct UrlRouter {} + + /// The Id of a screen that can be displayed in the app + struct ScreenId {} + """ + + let output = """ + let url: URL + let destinationURL: URL + let id: ID + let screenID = "screenId" // We intentionally don't change the content of strings + let validURLs: Set + let validUrlschemes: Set + + let uniqueIdentifier = UUID() + + /// Opens URLs based on their scheme + struct URLRouter {} + + /// The ID of a screen that can be displayed in the app + struct ScreenID {} + """ + + testFormatting(for: input, output, rule: .acronyms, exclude: [.propertyType]) + } + + func testUppercaseCustomAcronym() { + let input = """ + let url: URL + let destinationUrl: URL + let pngData: Data + let imageInPngFormat: UIImage + """ + + let output = """ + let url: URL + let destinationUrl: URL + let pngData: Data + let imageInPNGFormat: UIImage + """ + + testFormatting(for: input, output, rule: .acronyms, options: FormatOptions(acronyms: ["png"])) + } + + func testDisableUppercaseAcronym() { + let input = """ + // swiftformat:disable:next acronyms + typeNotOwnedByAuthor.destinationUrl = URL() + typeOwnedByAuthor.destinationURL = URL() + """ + + testFormatting(for: input, rule: .acronyms) + } +} diff --git a/Tests/Rules/AndOperatorTests.swift b/Tests/Rules/AndOperatorTests.swift new file mode 100644 index 000000000..499609a95 --- /dev/null +++ b/Tests/Rules/AndOperatorTests.swift @@ -0,0 +1,184 @@ +// +// AndOperatorTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class AndOperatorTests: XCTestCase { + func testIfAndReplaced() { + let input = "if true && true {}" + let output = "if true, true {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testGuardAndReplaced() { + let input = "guard true && true\nelse { return }" + let output = "guard true, true\nelse { return }" + testFormatting(for: input, output, rule: .andOperator, + exclude: [.wrapConditionalBodies]) + } + + func testWhileAndReplaced() { + let input = "while true && true {}" + let output = "while true, true {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testIfDoubleAndReplaced() { + let input = "if true && true && true {}" + let output = "if true, true, true {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testIfAndParensReplaced() { + let input = "if true && (true && true) {}" + let output = "if true, (true && true) {}" + testFormatting(for: input, output, rule: .andOperator, + exclude: [.redundantParens]) + } + + func testIfFunctionAndReplaced() { + let input = "if functionReturnsBool() && true {}" + let output = "if functionReturnsBool(), true {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testNoReplaceIfOrAnd() { + let input = "if foo || bar && baz {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceIfAndOr() { + let input = "if foo && bar || baz {}" + testFormatting(for: input, rule: .andOperator) + } + + func testIfAndReplacedInFunction() { + let input = "func someFunc() { if bar && baz {} }" + let output = "func someFunc() { if bar, baz {} }" + testFormatting(for: input, output, rule: .andOperator) + } + + func testNoReplaceIfCaseLetAnd() { + let input = "if case let a = foo && bar {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceWhileCaseLetAnd() { + let input = "while case let a = foo && bar {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceRepeatWhileAnd() { + let input = """ + repeat {} while true && !false + foo {} + """ + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceIfLetAndLetAnd() { + let input = "if let a = b && c, let d = e && f {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceIfTryAnd() { + let input = "if try true && explode() {}" + testFormatting(for: input, rule: .andOperator) + } + + func testHandleAndAtStartOfLine() { + let input = "if a == b\n && b == c {}" + let output = "if a == b,\n b == c {}" + testFormatting(for: input, output, rule: .andOperator, exclude: [.indent]) + } + + func testHandleAndAtStartOfLineAfterComment() { + let input = "if a == b // foo\n && b == c {}" + let output = "if a == b, // foo\n b == c {}" + testFormatting(for: input, output, rule: .andOperator, exclude: [.indent]) + } + + func testNoReplaceAndOperatorWhereGenericsAmbiguous() { + let input = "if x < y && z > (a * b) {}" + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceAndOperatorWhereGenericsAmbiguous2() { + let input = "if x < y && z && w > (a * b) {}" + let output = "if x < y, z && w > (a * b) {}" + testFormatting(for: input, output, rule: .andOperator) + } + + func testAndOperatorCrash() { + let input = """ + DragGesture().onChanged { gesture in + if gesture.translation.width < 50 && gesture.translation.height > 50 { + offset = gesture.translation + } + } + """ + let output = """ + DragGesture().onChanged { gesture in + if gesture.translation.width < 50, gesture.translation.height > 50 { + offset = gesture.translation + } + } + """ + testFormatting(for: input, output, rule: .andOperator) + } + + func testNoReplaceAndInViewBuilder() { + let input = """ + SomeView { + if foo == 5 && bar { + Text("5") + } else { + Text("Not 5") + } + } + """ + testFormatting(for: input, rule: .andOperator) + } + + func testNoReplaceAndInViewBuilder2() { + let input = """ + var body: some View { + ZStack { + if self.foo && self.bar { + self.closedPath + } + } + } + """ + testFormatting(for: input, rule: .andOperator) + } + + func testReplaceAndInViewBuilderInSwift5_3() { + let input = """ + SomeView { + if foo == 5 && bar { + Text("5") + } else { + Text("Not 5") + } + } + """ + let output = """ + SomeView { + if foo == 5, bar { + Text("5") + } else { + Text("Not 5") + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .andOperator, options: options) + } +} diff --git a/Tests/Rules/AnyObjectProtocolTests.swift b/Tests/Rules/AnyObjectProtocolTests.swift new file mode 100644 index 000000000..fcdfc2905 --- /dev/null +++ b/Tests/Rules/AnyObjectProtocolTests.swift @@ -0,0 +1,52 @@ +// +// AnyObjectProtocolTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class AnyObjectProtocolTests: XCTestCase { + func testClassReplacedByAnyObject() { + let input = "protocol Foo: class {}" + let output = "protocol Foo: AnyObject {}" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options) + } + + func testClassReplacedByAnyObjectWithOtherProtocols() { + let input = "protocol Foo: class, Codable {}" + let output = "protocol Foo: AnyObject, Codable {}" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options) + } + + func testClassReplacedByAnyObjectImmediatelyAfterImport() { + let input = "import Foundation\nprotocol Foo: class {}" + let output = "import Foundation\nprotocol Foo: AnyObject {}" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, output, rule: .anyObjectProtocol, options: options, + exclude: [.blankLineAfterImports]) + } + + func testClassDeclarationNotReplacedByAnyObject() { + let input = "class Foo: Codable {}" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, rule: .anyObjectProtocol, options: options) + } + + func testClassImportNotReplacedByAnyObject() { + let input = "import class Foo.Bar" + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, rule: .anyObjectProtocol, options: options) + } + + func testClassNotReplacedByAnyObjectIfSwiftVersionLessThan4_1() { + let input = "protocol Foo: class {}" + let options = FormatOptions(swiftVersion: "4.0") + testFormatting(for: input, rule: .anyObjectProtocol, options: options) + } +} diff --git a/Tests/Rules/ApplicationMainTests.swift b/Tests/Rules/ApplicationMainTests.swift new file mode 100644 index 000000000..80ee20f5c --- /dev/null +++ b/Tests/Rules/ApplicationMainTests.swift @@ -0,0 +1,47 @@ +// +// ApplicationMainTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ApplicationMainTests: XCTestCase { + func testUIApplicationMainReplacedByMain() { + let input = """ + @UIApplicationMain + class AppDelegate: UIResponder, UIApplicationDelegate {} + """ + let output = """ + @main + class AppDelegate: UIResponder, UIApplicationDelegate {} + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .applicationMain, options: options) + } + + func testNSApplicationMainReplacedByMain() { + let input = """ + @NSApplicationMain + class AppDelegate: NSObject, NSApplicationDelegate {} + """ + let output = """ + @main + class AppDelegate: NSObject, NSApplicationDelegate {} + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .applicationMain, options: options) + } + + func testNSApplicationMainNotReplacedInSwift5_2() { + let input = """ + @NSApplicationMain + class AppDelegate: NSObject, NSApplicationDelegate {} + """ + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .applicationMain, options: options) + } +} diff --git a/Tests/Rules/AssertionFailuresTests.swift b/Tests/Rules/AssertionFailuresTests.swift new file mode 100644 index 000000000..9979f7db6 --- /dev/null +++ b/Tests/Rules/AssertionFailuresTests.swift @@ -0,0 +1,62 @@ +// +// AssertionFailuresTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class AssertionFailuresTests: XCTestCase { + func testAssertionFailuresForAssertFalse() { + let input = "assert(false)" + let output = "assertionFailure()" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForAssertFalseWithSpaces() { + let input = "assert ( false )" + let output = "assertionFailure()" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForAssertFalseWithLinebreaks() { + let input = """ + assert( + false + ) + """ + let output = "assertionFailure()" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForAssertTrue() { + let input = "assert(true)" + testFormatting(for: input, rule: .assertionFailures) + } + + func testAssertionFailuresForAssertFalseWithArgs() { + let input = "assert(false, msg, 20, 21)" + let output = "assertionFailure(msg, 20, 21)" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForPreconditionFalse() { + let input = "precondition(false)" + let output = "preconditionFailure()" + testFormatting(for: input, output, rule: .assertionFailures) + } + + func testAssertionFailuresForPreconditionTrue() { + let input = "precondition(true)" + testFormatting(for: input, rule: .assertionFailures) + } + + func testAssertionFailuresForPreconditionFalseWithArgs() { + let input = "precondition(false, msg, 0, 1)" + let output = "preconditionFailure(msg, 0, 1)" + testFormatting(for: input, output, rule: .assertionFailures) + } +} diff --git a/Tests/Rules/BlankLineAfterImportsTests.swift b/Tests/Rules/BlankLineAfterImportsTests.swift new file mode 100644 index 000000000..63a62f1b5 --- /dev/null +++ b/Tests/Rules/BlankLineAfterImportsTests.swift @@ -0,0 +1,110 @@ +// +// BlankLineAfterImportsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLineAfterImportsTests: XCTestCase { + func testBlankLineAfterImport() { + let input = """ + import ModuleA + @testable import ModuleB + import ModuleC + @testable import ModuleD + @_exported import ModuleE + @_implementationOnly import ModuleF + @_spi(SPI) import ModuleG + @_spiOnly import ModuleH + @preconcurrency import ModuleI + class foo {} + """ + let output = """ + import ModuleA + @testable import ModuleB + import ModuleC + @testable import ModuleD + @_exported import ModuleE + @_implementationOnly import ModuleF + @_spi(SPI) import ModuleG + @_spiOnly import ModuleH + @preconcurrency import ModuleI + + class foo {} + """ + testFormatting(for: input, output, rule: .blankLineAfterImports) + } + + func testBlankLinesBetweenConditionalImports() { + let input = """ + #if foo + import ModuleA + #else + import ModuleB + #endif + import ModuleC + func foo() {} + """ + let output = """ + #if foo + import ModuleA + #else + import ModuleB + #endif + import ModuleC + + func foo() {} + """ + testFormatting(for: input, output, rule: .blankLineAfterImports) + } + + func testBlankLinesBetweenNestedConditionalImports() { + let input = """ + #if foo + import ModuleA + #if bar + import ModuleB + #endif + #else + import ModuleC + #endif + import ModuleD + func foo() {} + """ + let output = """ + #if foo + import ModuleA + #if bar + import ModuleB + #endif + #else + import ModuleC + #endif + import ModuleD + + func foo() {} + """ + testFormatting(for: input, output, rule: .blankLineAfterImports) + } + + func testBlankLineAfterScopedImports() { + let input = """ + internal import UIKit + internal import Foundation + private import Time + public class Foo {} + """ + let output = """ + internal import UIKit + internal import Foundation + private import Time + + public class Foo {} + """ + testFormatting(for: input, output, rule: .blankLineAfterImports) + } +} diff --git a/Tests/Rules/BlankLineAfterSwitchCaseTests.swift b/Tests/Rules/BlankLineAfterSwitchCaseTests.swift new file mode 100644 index 000000000..4e4385e57 --- /dev/null +++ b/Tests/Rules/BlankLineAfterSwitchCaseTests.swift @@ -0,0 +1,213 @@ +// +// BlankLineAfterSwitchCaseTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLineAfterSwitchCaseTests: XCTestCase { + func testAddsBlankLineAfterMultilineSwitchCases() { + let input = """ + func handle(_ action: SpaceshipAction) { + switch action { + // The warp drive can be engaged by pressing a button on the control panel + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + // Triggered automatically whenever we detect an energy blast was fired in our direction + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + } + } + """ + + let output = """ + func handle(_ action: SpaceshipAction) { + switch action { + // The warp drive can be engaged by pressing a button on the control panel + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + // Triggered automatically whenever we detect an energy blast was fired in our direction + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + } + } + """ + testFormatting(for: input, output, rule: .blankLineAfterSwitchCase) + } + + func testRemovesBlankLineAfterLastSwitchCase() { + let input = """ + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArticialLife() + + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + + } + } + """ + + let output = """ + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArticialLife() + + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + } + } + """ + testFormatting(for: input, output, rule: .blankLineAfterSwitchCase) + } + + func testDoesntAddBlankLineAfterSingleLineSwitchCase() { + let input = """ + var planetType: PlanetType { + switch self { + case .mercury, .venus, .earth, .mars: + // The terrestrial planets are smaller and have a solid, rocky surface + .terrestrial + case .jupiter, .saturn, .uranus, .neptune: + // The gas giants are huge and lack a solid surface + .gasGiant + } + } + + var planetType: PlanetType { + switch self { + // The terrestrial planets are smaller and have a solid, rocky surface + case .mercury, .venus, .earth, .mars: + .terrestrial + // The gas giants are huge and lack a solid surface + case .jupiter, .saturn, .uranus, .neptune: + .gasGiant + } + } + + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + case .venus: + "Venus" + // The best planet, where everything cool happens + case .earth: + "Earth" + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + case .jupiter: + "Jupiter" + case .saturn: + // Other planets have rings, but satun's are the best. + // It's rings are the only once that are usually visible in photos. + "Saturn" + case .uranus: + /* + * The pronunciation of this planet's name is subject of scholarly debate + */ + "Uranus" + case .neptune: + "Neptune" + } + } + """ + + testFormatting(for: input, rule: .blankLineAfterSwitchCase, exclude: [.sortSwitchCases, .wrapSwitchCases, .blockComments]) + } + + func testMixedSingleLineAndMultiLineCases() { + let input = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + let output = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + testFormatting(for: input, output, rule: .blankLineAfterSwitchCase, exclude: [.consistentSwitchCaseSpacing]) + } + + func testAllowsBlankLinesAfterSingleLineCases() { + let input = """ + switch action { + case .engageWarpDrive: + warpDrive.engage() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + case let .scanPlanet(planet): + scanner.scan(planet) + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + testFormatting(for: input, rule: .blankLineAfterSwitchCase) + } +} diff --git a/Tests/Rules/BlankLinesAroundMarkTests.swift b/Tests/Rules/BlankLinesAroundMarkTests.swift new file mode 100644 index 000000000..93ed0f81e --- /dev/null +++ b/Tests/Rules/BlankLinesAroundMarkTests.swift @@ -0,0 +1,123 @@ +// +// BlankLinesAroundMarkTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesAroundMarkTests: XCTestCase { + func testInsertBlankLinesAroundMark() { + let input = """ + let foo = "foo" + // MARK: bar + let bar = "bar" + """ + let output = """ + let foo = "foo" + + // MARK: bar + + let bar = "bar" + """ + testFormatting(for: input, output, rule: .blankLinesAroundMark) + } + + func testNoInsertExtraBlankLinesAroundMark() { + let input = """ + let foo = "foo" + + // MARK: bar + + let bar = "bar" + """ + testFormatting(for: input, rule: .blankLinesAroundMark) + } + + func testInsertBlankLineAfterMarkAtStartOfFile() { + let input = """ + // MARK: bar + let bar = "bar" + """ + let output = """ + // MARK: bar + + let bar = "bar" + """ + testFormatting(for: input, output, rule: .blankLinesAroundMark) + } + + func testInsertBlankLineBeforeMarkAtEndOfFile() { + let input = """ + let foo = "foo" + // MARK: bar + """ + let output = """ + let foo = "foo" + + // MARK: bar + """ + testFormatting(for: input, output, rule: .blankLinesAroundMark) + } + + func testNoInsertBlankLineBeforeMarkAtStartOfScope() { + let input = """ + do { + // MARK: foo + + let foo = "foo" + } + """ + testFormatting(for: input, rule: .blankLinesAroundMark) + } + + func testNoInsertBlankLineAfterMarkAtEndOfScope() { + let input = """ + do { + let foo = "foo" + + // MARK: foo + } + """ + testFormatting(for: input, rule: .blankLinesAroundMark) + } + + func testInsertBlankLinesJustBeforeMarkNotAfter() { + let input = """ + let foo = "foo" + // MARK: bar + let bar = "bar" + """ + let output = """ + let foo = "foo" + + // MARK: bar + let bar = "bar" + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting(for: input, output, rule: .blankLinesAroundMark, options: options) + } + + func testNoInsertExtraBlankLinesAroundMarkWithNoBlankLineAfterMark() { + let input = """ + let foo = "foo" + + // MARK: bar + let bar = "bar" + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting(for: input, rule: .blankLinesAroundMark, options: options) + } + + func testNoInsertBlankLineAfterMarkAtStartOfFile() { + let input = """ + // MARK: bar + let bar = "bar" + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting(for: input, rule: .blankLinesAroundMark, options: options) + } +} diff --git a/Tests/Rules/BlankLinesAtEndOfScopeTests.swift b/Tests/Rules/BlankLinesAtEndOfScopeTests.swift new file mode 100644 index 000000000..d65c9d894 --- /dev/null +++ b/Tests/Rules/BlankLinesAtEndOfScopeTests.swift @@ -0,0 +1,107 @@ +// +// BlankLinesAtEndOfScopeTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesAtEndOfScopeTests: XCTestCase { + func testBlankLinesRemovedAtEndOfFunction() { + let input = "func foo() {\n // code\n\n}" + let output = "func foo() {\n // code\n}" + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) + } + + func testBlankLinesRemovedAtEndOfParens() { + let input = "(\n foo: Int\n\n)" + let output = "(\n foo: Int\n)" + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) + } + + func testBlankLinesRemovedAtEndOfBrackets() { + let input = "[\n foo,\n bar,\n\n]" + let output = "[\n foo,\n bar,\n]" + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) + } + + func testBlankLineNotRemovedBeforeElse() { + let input = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n\n}" + let output = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n}" + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, + exclude: [.blankLinesAtStartOfScope]) + } + + func testBlankLineRemovedFromEndOfTypeByDefault() { + let input = """ + class FooTests { + func testFoo() {} + + } + """ + + let output = """ + class FooTests { + func testFoo() {} + } + """ + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) + } + + func testBlankLinesNotRemovedFromEndOfTypeWithOptionEnabled() { + let input = """ + class FooClass { + func fooMethod() {} + + } + + struct FooStruct { + func fooMethod() {} + + } + + enum FooEnum { + func fooMethod() {} + + } + + actor FooActor { + func fooMethod() {} + + } + + protocol FooProtocol { + func fooMethod() + } + + extension Array where Element == Foo { + func fooMethod() {} + + } + """ + testFormatting(for: input, rule: .blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + } + + func testBlankLineAtEndOfScopeRemovedFromMethodInType() { + let input = """ + class Foo { + func bar() { + print("hello world") + + } + } + """ + + let output = """ + class Foo { + func bar() { + print("hello world") + } + } + """ + testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + } +} diff --git a/Tests/Rules/BlankLinesAtStartOfScopeTests.swift b/Tests/Rules/BlankLinesAtStartOfScopeTests.swift new file mode 100644 index 000000000..3ef8c61e8 --- /dev/null +++ b/Tests/Rules/BlankLinesAtStartOfScopeTests.swift @@ -0,0 +1,106 @@ +// +// BlankLinesAtStartOfScopeTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesAtStartOfScopeTests: XCTestCase { + func testBlankLinesRemovedAtStartOfFunction() { + let input = "func foo() {\n\n // code\n}" + let output = "func foo() {\n // code\n}" + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) + } + + func testBlankLinesRemovedAtStartOfParens() { + let input = "(\n\n foo: Int\n)" + let output = "(\n foo: Int\n)" + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) + } + + func testBlankLinesRemovedAtStartOfBrackets() { + let input = "[\n\n foo,\n bar,\n]" + let output = "[\n foo,\n bar,\n]" + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) + } + + func testBlankLinesNotRemovedBetweenElementsInsideBrackets() { + let input = "[foo,\n\n bar]" + testFormatting(for: input, rule: .blankLinesAtStartOfScope, exclude: [.wrapArguments]) + } + + func testBlankLineRemovedFromStartOfTypeByDefault() { + let input = """ + class FooTests { + + func testFoo() {} + } + """ + + let output = """ + class FooTests { + func testFoo() {} + } + """ + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) + } + + func testBlankLinesNotRemovedFromStartOfTypeWithOptionEnabled() { + let input = """ + class FooClass { + + func fooMethod() {} + } + + struct FooStruct { + + func fooMethod() {} + } + + enum FooEnum { + + func fooMethod() {} + } + + actor FooActor { + + func fooMethod() {} + } + + protocol FooProtocol { + + func fooMethod() + } + + extension Array where Element == Foo { + + func fooMethod() {} + } + """ + testFormatting(for: input, rule: .blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + } + + func testBlankLineAtStartOfScopeRemovedFromMethodInType() { + let input = """ + class Foo { + func bar() { + + print("hello world") + } + } + """ + + let output = """ + class Foo { + func bar() { + print("hello world") + } + } + """ + testFormatting(for: input, output, rule: .blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) + } +} diff --git a/Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift b/Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift new file mode 100644 index 000000000..fda0bf460 --- /dev/null +++ b/Tests/Rules/BlankLinesBetweenChainedFunctionsTests.swift @@ -0,0 +1,64 @@ +// +// BlankLinesBetweenChainedFunctionsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesBetweenChainedFunctionsTests: XCTestCase { + func testBlankLinesBetweenChainedFunctions() { + let input = """ + [0, 1, 2] + .map { $0 * 2 } + + + + .map { $0 * 3 } + """ + let output1 = """ + [0, 1, 2] + .map { $0 * 2 } + .map { $0 * 3 } + """ + let output2 = """ + [0, 1, 2] + .map { $0 * 2 } + .map { $0 * 3 } + """ + testFormatting(for: input, [output1, output2], rules: [.blankLinesBetweenChainedFunctions]) + } + + func testBlankLinesWithCommentsBetweenChainedFunctions() { + let input = """ + [0, 1, 2] + .map { $0 * 2 } + + // Multiplies by 3 + + .map { $0 * 3 } + """ + let output = """ + [0, 1, 2] + .map { $0 * 2 } + // Multiplies by 3 + .map { $0 * 3 } + """ + testFormatting(for: input, output, rule: .blankLinesBetweenChainedFunctions) + } + + func testBlankLinesWithMarkCommentBetweenChainedFunctions() { + let input = """ + [0, 1, 2] + .map { $0 * 2 } + + // MARK: hello + + .map { $0 * 3 } + """ + testFormatting(for: input, rules: [.blankLinesBetweenChainedFunctions, .blankLinesAroundMark]) + } +} diff --git a/Tests/Rules/BlankLinesBetweenImportsTests.swift b/Tests/Rules/BlankLinesBetweenImportsTests.swift new file mode 100644 index 000000000..510c3c94d --- /dev/null +++ b/Tests/Rules/BlankLinesBetweenImportsTests.swift @@ -0,0 +1,75 @@ +// +// BlankLinesBetweenImportsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesBetweenImportsTests: XCTestCase { + func testBlankLinesBetweenImportsShort() { + let input = """ + import ModuleA + + import ModuleB + """ + let output = """ + import ModuleA + import ModuleB + """ + testFormatting(for: input, output, rule: .blankLinesBetweenImports) + } + + func testBlankLinesBetweenImportsLong() { + let input = """ + import ModuleA + import ModuleB + + import ModuleC + import ModuleD + import ModuleE + + import ModuleF + + import ModuleG + import ModuleH + """ + let output = """ + import ModuleA + import ModuleB + import ModuleC + import ModuleD + import ModuleE + import ModuleF + import ModuleG + import ModuleH + """ + testFormatting(for: input, output, rule: .blankLinesBetweenImports) + } + + func testBlankLinesBetweenImportsWithTestable() { + let input = """ + import ModuleA + + @testable import ModuleB + import ModuleC + + @testable import ModuleD + @testable import ModuleE + + @testable import ModuleF + """ + let output = """ + import ModuleA + @testable import ModuleB + import ModuleC + @testable import ModuleD + @testable import ModuleE + @testable import ModuleF + """ + testFormatting(for: input, output, rule: .blankLinesBetweenImports) + } +} diff --git a/Tests/Rules/BlankLinesBetweenScopesTests.swift b/Tests/Rules/BlankLinesBetweenScopesTests.swift new file mode 100644 index 000000000..c25c1dbfb --- /dev/null +++ b/Tests/Rules/BlankLinesBetweenScopesTests.swift @@ -0,0 +1,218 @@ +// +// BlankLinesBetweenScopesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlankLinesBetweenScopesTests: XCTestCase { + func testBlankLineBetweenFunctions() { + let input = "func foo() {\n}\nfunc bar() {\n}" + let output = "func foo() {\n}\n\nfunc bar() {\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoBlankLineBetweenPropertyAndFunction() { + let input = "var foo: Int\nfunc bar() {\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) + } + + func testBlankLineBetweenFunctionsIsBeforeComment() { + let input = "func foo() {\n}\n/// headerdoc\nfunc bar() {\n}" + let output = "func foo() {\n}\n\n/// headerdoc\nfunc bar() {\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testBlankLineBeforeAtObjcOnLineBeforeProtocol() { + let input = "@objc\nprotocol Foo {\n}\n@objc\nprotocol Bar {\n}" + let output = "@objc\nprotocol Foo {\n}\n\n@objc\nprotocol Bar {\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testBlankLineBeforeAtAvailabilityOnLineBeforeClass() { + let input = "protocol Foo {\n}\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" + let output = "protocol Foo {\n}\n\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoExtraBlankLineBetweenFunctions() { + let input = "func foo() {\n}\n\nfunc bar() {\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) + } + + func testNoBlankLineBetweenFunctionsInProtocol() { + let input = "protocol Foo {\n func bar() -> Void\n func baz() -> Int\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineInsideInitFunction() { + let input = "init() {\n super.init()\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testBlankLineAfterProtocolBeforeProperty() { + let input = "protocol Foo {\n}\nvar bar: String" + let output = "protocol Foo {\n}\n\nvar bar: String" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoExtraBlankLineAfterSingleLineComment() { + let input = "var foo: Bar? // comment\n\nfunc bar() {}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoExtraBlankLineAfterMultilineComment() { + let input = "var foo: Bar? /* comment */\n\nfunc bar() {}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBeforeFuncAsIdentifier() { + let input = "var foo: Bar?\nfoo.func(x) {}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBetweenFunctionsWithInlineBody() { + let input = "class Foo {\n func foo() { print(\"foo\") }\n func bar() { print(\"bar\") }\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBetweenIfStatements() { + let input = "func foo() {\n if x {\n }\n if y {\n }\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) + } + + func testNoBlanksInsideClassFunc() { + let input = "class func foo {\n if x {\n }\n if y {\n }\n}" + let options = FormatOptions(fragment: true) + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, + exclude: [.emptyBraces]) + } + + func testNoBlanksInsideClassVar() { + let input = "class var foo: Int {\n if x {\n }\n if y {\n }\n}" + let options = FormatOptions(fragment: true) + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, + exclude: [.emptyBraces]) + } + + func testBlankLineBetweenCalledClosures() { + let input = "class Foo {\n var foo = {\n }()\n func bar {\n }\n}" + let output = "class Foo {\n var foo = {\n }()\n\n func bar {\n }\n}" + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoBlankLineAfterCalledClosureAtEndOfScope() { + let input = "class Foo {\n var foo = {\n }()\n}" + testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) + } + + func testNoBlankLineBeforeWhileInRepeatWhile() { + let input = """ + repeat + { print("foo") } + while false + { print("bar") }() + """ + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, exclude: [.redundantClosure, .wrapLoopBodies]) + } + + func testBlankLineBeforeWhileIfNotRepeatWhile() { + let input = "func foo(x)\n{\n}\nwhile true\n{\n}" + let output = "func foo(x)\n{\n}\n\nwhile true\n{\n}" + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, output, rule: .blankLinesBetweenScopes, options: options, + exclude: [.emptyBraces]) + } + + func testNoInsertBlankLinesInConditionalCompilation() { + let input = """ + struct Foo { + #if BAR + func something() { + } + #else + func something() { + } + #endif + } + """ + testFormatting(for: input, rule: .blankLinesBetweenScopes, + exclude: [.emptyBraces]) + } + + func testNoInsertBlankLineAfterBraceBeforeSourceryComment() { + let input = """ + struct Foo { + var bar: String + + // sourcery:inline:Foo.init + public init(bar: String) { + self.bar = bar + } + // sourcery:end + } + """ + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBetweenChainedClosures() { + let input = """ + foo { + doFoo() + } + // bar + .bar { + doBar() + } + // baz + .baz { + doBaz($0) + } + """ + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testNoBlankLineBetweenTrailingClosures() { + let input = """ + UIView.animate(withDuration: 0) { + fromView.transform = .identity + } + completion: { finished in + context.completeTransition(finished) + } + """ + testFormatting(for: input, rule: .blankLinesBetweenScopes) + } + + func testBlankLineBetweenTrailingClosureAndLabelledLoop() { + let input = """ + UIView.animate(withDuration: 0) { + fromView.transform = .identity + } + completion: for foo in bar { + print(foo) + } + """ + let output = """ + UIView.animate(withDuration: 0) { + fromView.transform = .identity + } + + completion: for foo in bar { + print(foo) + } + """ + testFormatting(for: input, output, rule: .blankLinesBetweenScopes) + } +} diff --git a/Tests/Rules/BlockCommentsTests.swift b/Tests/Rules/BlockCommentsTests.swift new file mode 100644 index 000000000..d904b4d50 --- /dev/null +++ b/Tests/Rules/BlockCommentsTests.swift @@ -0,0 +1,298 @@ +// +// BlockCommentsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class BlockCommentsTests: XCTestCase { + func testBlockCommentsOneLine() { + let input = "foo = bar /* comment */" + let output = "foo = bar // comment" + testFormatting(for: input, output, rule: .blockComments) + } + + func testDocBlockCommentsOneLine() { + let input = "foo = bar /** doc comment */" + let output = "foo = bar /// doc comment" + testFormatting(for: input, output, rule: .blockComments) + } + + func testPreservesBlockCommentInSingleLineScope() { + let input = "if foo { /* code */ }" + testFormatting(for: input, rule: .blockComments) + } + + func testBlockCommentsMultiLine() { + let input = """ + /* + * foo + * bar + */ + """ + let output = """ + // foo + // bar + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsWithoutBlankFirstLine() { + let input = """ + /* foo + * bar + */ + """ + let output = """ + // foo + // bar + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsWithBlankLine() { + let input = """ + /* + * foo + * + * bar + */ + """ + let output = """ + // foo + // + // bar + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockDocCommentsWithAsterisksOnEachLine() { + let input = """ + /** + * This is a documentation comment, + * not a standard comment. + */ + """ + let output = """ + /// This is a documentation comment, + /// not a standard comment. + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testBlockDocCommentsWithoutAsterisksOnEachLine() { + let input = """ + /** + This is a documentation comment, + not a standard comment. + */ + """ + let output = """ + /// This is a documentation comment, + /// not a standard comment. + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testBlockCommentWithBulletPoints() { + let input = """ + /* + This is a list of nice colors: + + * green + * blue + * red + + Yellow is also great. + */ + + /* + * Another comment. + */ + """ + let output = """ + // This is a list of nice colors: + // + // * green + // * blue + // * red + // + // Yellow is also great. + + // Another comment. + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsNested() { + let input = """ + /* + * comment + * /* inside */ + * a comment + */ + """ + let output = """ + // comment + // inside + // a comment + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsIndentPreserved() { + let input = """ + func foo() { + /* + foo + bar. + */ + } + """ + let output = """ + func foo() { + // foo + // bar. + } + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentsIndentPreserved2() { + let input = """ + func foo() { + /* + * foo + * bar. + */ + } + """ + let output = """ + func foo() { + // foo + // bar. + } + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockDocCommentsIndentPreserved() { + let input = """ + func foo() { + /** + * foo + * bar. + */ + } + """ + let output = """ + func foo() { + /// foo + /// bar. + } + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testLongBlockCommentsWithoutPerLineMarkersFullyConverted() { + let input = """ + /* + The beginnings of the lines in this multiline comment body + have only spaces in them. There are no asterisks, only spaces. + + This should not cause the blockComments rule to convert only + part of the comment body and leave the rest hanging. + + The comment must have at least this many lines to trigger the bug. + */ + """ + let output = """ + // The beginnings of the lines in this multiline comment body + // have only spaces in them. There are no asterisks, only spaces. + // + // This should not cause the blockComments rule to convert only + // part of the comment body and leave the rest hanging. + // + // The comment must have at least this many lines to trigger the bug. + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentImmediatelyFollowedByCode() { + let input = """ + /** + foo + + bar + */ + func foo() {} + """ + let output = """ + /// foo + /// + /// bar + func foo() {} + """ + testFormatting(for: input, output, rule: .blockComments) + } + + func testBlockCommentImmediatelyFollowedByCode2() { + let input = """ + /** + Line 1. + + Line 2. + + Line 3. + */ + foo(bar) + """ + let output = """ + /// Line 1. + /// + /// Line 2. + /// + /// Line 3. + foo(bar) + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testBlockCommentImmediatelyFollowedByCode3() { + let input = """ + /* foo + bar */ + func foo() {} + """ + let output = """ + // foo + // bar + func foo() {} + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } + + func testBlockCommentFollowedByBlankLine() { + let input = """ + /** + foo + + bar + */ + + func foo() {} + """ + let output = """ + /// foo + /// + /// bar + + func foo() {} + """ + testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) + } +} diff --git a/Tests/RulesTests+Braces.swift b/Tests/Rules/BracesTests.swift similarity index 98% rename from Tests/RulesTests+Braces.swift rename to Tests/Rules/BracesTests.swift index 21f13171f..9bdfad10f 100644 --- a/Tests/RulesTests+Braces.swift +++ b/Tests/Rules/BracesTests.swift @@ -1,17 +1,15 @@ // -// RulesTests+Braces.swift +// BracesTests.swift // SwiftFormatTests // -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. // import XCTest @testable import SwiftFormat -class BracesTests: RulesTests { - // MARK: - braces - +class BracesTests: XCTestCase { func testAllmanBracesAreConverted() { let input = "func foo()\n{\n statement\n}" let output = "func foo() {\n statement\n}" diff --git a/Tests/Rules/ConditionalAssignmentTests.swift b/Tests/Rules/ConditionalAssignmentTests.swift new file mode 100644 index 000000000..fed0806d5 --- /dev/null +++ b/Tests/Rules/ConditionalAssignmentTests.swift @@ -0,0 +1,828 @@ +// +// ConditionalAssignmentTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ConditionalAssignmentTests: XCTestCase { + func testDoesntConvertIfStatementAssignmentSwift5_8() { + let input = """ + let foo: Foo + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testConvertsIfStatementAssignment() { + let input = """ + let foo: Foo + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + """ + let output = """ + let foo: Foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) + } + + func testConvertsSimpleSwitchStatementAssignment() { + let input = """ + let foo: Foo + switch condition { + case true: + foo = Foo("foo") + case false: + foo = Foo("bar") + } + """ + let output = """ + let foo: Foo = switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) + } + + func testConvertsTrivialSwitchStatementAssignment() { + let input = """ + let foo: Foo + switch enumWithOnceCase(let value) { + case singleCase: + foo = value + } + """ + let output = """ + let foo: Foo = switch enumWithOnceCase(let value) { + case singleCase: + value + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testConvertsNestedIfAndStatementAssignments() { + let input = """ + let foo: Foo + switch condition { + case true: + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + + case false: + switch condition { + case true: + foo = Foo("baaz") + + case false: + if condition { + foo = Foo("quux") + } else { + foo = Foo("quack") + } + } + } + """ + let output = """ + let foo: Foo = switch condition { + case true: + if condition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + switch condition { + case true: + Foo("baaz") + + case false: + if condition { + Foo("quux") + } else { + Foo("quack") + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) + } + + func testConvertsIfStatementAssignmentPreservingComment() { + let input = """ + let foo: Foo + // This is a comment between the property and condition + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + """ + let output = """ + let foo: Foo + // This is a comment between the property and condition + = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.indent, .redundantType, .wrapMultilineConditionalAssignment]) + } + + func testDoesntConvertsIfStatementAssigningMultipleProperties() { + let input = """ + let foo: Foo + let bar: Bar + if condition { + foo = Foo("foo") + bar = Bar("foo") + } else { + foo = Foo("bar") + bar = Bar("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertsIfStatementAssigningDifferentProperties() { + let input = """ + var foo: Foo? + var bar: Bar? + if condition { + foo = Foo("foo") + } else { + bar = Bar("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertNonExhaustiveIfStatementAssignment1() { + let input = """ + var foo: Foo? + if condition { + foo = Foo("foo") + } else if someOtherCondition { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertNonExhaustiveIfStatementAssignment2() { + let input = """ + var foo: Foo? + if condition { + if condition { + foo = Foo("foo") + } + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementAssignment1() { + let input = """ + let foo: Foo + if condition { + foo = Foo("foo") + print("Multi-statement") + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementAssignment2() { + let input = """ + let foo: Foo + switch condition { + case true: + foo = Foo("foo") + print("Multi-statement") + + case false: + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementAssignment3() { + let input = """ + let foo: Foo + if condition { + if condition { + foo = Foo("bar") + } else { + foo = Foo("baaz") + } + print("Multi-statement") + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementAssignment4() { + let input = """ + let foo: Foo + switch condition { + case true: + if condition { + foo = Foo("bar") + } else { + foo = Foo("baaz") + } + print("Multi-statement") + + case false: + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithStringLiteral() { + let input = """ + let text: String + if conditionOne { + text = "Hello World!" + doSomeStuffHere() + } else { + text = "Goodbye!" + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithCollectionLiteral() { + let input = """ + let text: [String] + if conditionOne { + text = [] + doSomeStuffHere() + } else { + text = ["Goodbye!"] + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithIntLiteral() { + let input = """ + let number: Int? + if conditionOne { + number = 5 + doSomeStuffHere() + } else { + number = 10 + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithNilLiteral() { + let input = """ + let number: Int? + if conditionOne { + number = nil + doSomeStuffHere() + } else { + number = 10 + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertMultiStatementIfStatementWithOtherProperty() { + let input = """ + let number: Int? + if conditionOne { + number = someOtherProperty + doSomeStuffHere() + } else { + number = 10 + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertConditionalCastInSwift5_9() { + // The following code doesn't compile in Swift 5.9 due to this issue: + // https://github.com/apple/swift/issues/68764 + // + // let result = if condition { + // foo as? String + // } else { + // "bar" + // } + // + let input = """ + let result1: String? + if condition { + result1 = foo as? String + } else { + result1 = "bar" + } + + let result2: String? + switch condition { + case true: + result2 = foo as? String + case false: + result2 = "bar" + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testAllowsAsWithinInnerScope() { + let input = """ + let result: String? + switch condition { + case true: + result = method(string: foo as? String) + case false: + result = "bar" + } + """ + + let output = """ + let result: String? = switch condition { + case true: + method(string: foo as? String) + case false: + "bar" + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + // TODO: update branches parser to handle this case properly + func testIgnoreSwitchWithConditionalCompilation() { + let input = """ + func foo() -> String? { + let result: String? + switch condition { + #if os(macOS) + case .foo: + result = method(string: foo as? String) + #endif + case .bar: + return nil + } + return result + } + """ + + let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + // TODO: update branches parser to handle this scenario properly + func testIgnoreSwitchWithConditionalCompilation2() { + let input = """ + func foo() -> String? { + let result: String? + switch condition { + case .foo: + result = method(string: foo as? String) + #if os(macOS) + case .bar: + return nil + #endif + } + return result + } + """ + + let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testConvertsConditionalCastInSwift5_10() { + let input = """ + let result1: String? + if condition { + result1 = foo as? String + } else { + result1 = "bar" + } + + let result2: String? + switch condition { + case true: + result2 = foo as? String + case false: + result2 = "bar" + } + """ + + let output = """ + let result1: String? = if condition { + foo as? String + } else { + "bar" + } + + let result2: String? = switch condition { + case true: + foo as? String + case false: + "bar" + } + """ + + let options = FormatOptions(swiftVersion: "5.10") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testConvertsSwitchWithDefaultCase() { + let input = """ + let foo: Foo + switch condition { + case .foo: + foo = Foo("foo") + case .bar: + foo = Foo("bar") + default: + foo = Foo("default") + } + """ + + let output = """ + let foo: Foo = switch condition { + case .foo: + Foo("foo") + case .bar: + Foo("bar") + default: + Foo("default") + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType]) + } + + func testConvertsSwitchWithUnknownDefaultCase() { + let input = """ + let foo: Foo + switch condition { + case .foo: + foo = Foo("foo") + case .bar: + foo = Foo("bar") + @unknown default: + foo = Foo("default") + } + """ + + let output = """ + let foo: Foo = switch condition { + case .foo: + Foo("foo") + case .bar: + Foo("bar") + @unknown default: + Foo("default") + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType]) + } + + func testPreservesSwitchWithReturnInDefaultCase() { + let input = """ + let foo: Foo + switch condition { + case .foo: + foo = Foo("foo") + case .bar: + foo = Foo("bar") + default: + return + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testPreservesSwitchWithReturnInUnknownDefaultCase() { + let input = """ + let foo: Foo + switch condition { + case .foo: + foo = Foo("foo") + case .bar: + foo = Foo("bar") + @unknown default: + return + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testDoesntConvertIfStatementWithForLoopInBranch() { + let input = """ + var foo: Foo? + if condition { + foo = Foo("foo") + for foo in foos { + print(foo) + } + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .conditionalAssignment, options: options) + } + + func testConvertsIfStatementNotFollowingPropertyDefinition() { + let input = """ + if condition { + property = Foo("foo") + } else { + property = Foo("bar") + } + """ + + let output = """ + property = + if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testPreservesIfStatementNotFollowingPropertyDefinitionWithInvalidBranch() { + let input = """ + if condition { + property = Foo("foo") + } else { + property = Foo("bar") + print("A second expression on this branch") + } + + if condition { + property = Foo("foo") + } else { + if otherCondition { + property = Foo("foo") + } + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testPreservesNonExhaustiveIfStatementNotFollowingPropertyDefinition() { + let input = """ + if condition { + property = Foo("foo") + } + + if condition { + property = Foo("foo") + } else if otherCondition { + property = Foo("foo") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testConvertsSwitchStatementNotFollowingPropertyDefinition() { + let input = """ + switch condition { + case true: + property = Foo("foo") + case false: + property = Foo("bar") + } + """ + + let output = """ + property = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testConvertsSwitchStatementWithComplexLValueNotFollowingPropertyDefinition() { + let input = """ + switch condition { + case true: + property?.foo!.bar["baaz"] = Foo("foo") + case false: + property?.foo!.bar["baaz"] = Foo("bar") + } + """ + + let output = """ + property?.foo!.bar["baaz"] = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testDoesntMergePropertyWithUnrelatedCondition() { + let input = """ + let differentProperty: Foo + switch condition { + case true: + property = Foo("foo") + case false: + property = Foo("bar") + } + """ + + let output = """ + let differentProperty: Foo + property = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testConvertsNestedIfSwitchStatementNotFollowingPropertyDefinition() { + let input = """ + switch firstCondition { + case true: + if secondCondition { + property = Foo("foo") + } else { + property = Foo("bar") + } + + case false: + if thirdCondition { + property = Foo("baaz") + } else { + property = Foo("quux") + } + } + """ + + let output = """ + property = + switch firstCondition { + case true: + if secondCondition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + if thirdCondition { + Foo("baaz") + } else { + Foo("quux") + } + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testPreservesSwitchConditionWithIneligibleBranch() { + let input = """ + switch firstCondition { + case true: + // Even though this condition is eligible to be converted, + // we leave it as-is because it's nested in an ineligible condition. + if secondCondition { + property = Foo("foo") + } else { + property = Foo("bar") + } + + case false: + if thirdCondition { + property = Foo("baaz") + } else { + property = Foo("quux") + print("A second expression on this branch") + } + } + """ + + let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } + + func testPreservesIfConditionWithIneligibleBranch() { + let input = """ + if firstCondition { + // Even though this condition is eligible to be converted, + // we leave it as-is because it's nested in an ineligible condition. + if secondCondition { + property = Foo("foo") + } else { + property = Foo("bar") + } + } else { + if thirdCondition { + property = Foo("baaz") + } else { + property = Foo("quux") + print("A second expression on this branch") + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) + } +} diff --git a/Tests/Rules/ConsecutiveBlankLinesTests.swift b/Tests/Rules/ConsecutiveBlankLinesTests.swift new file mode 100644 index 000000000..2395bb5e7 --- /dev/null +++ b/Tests/Rules/ConsecutiveBlankLinesTests.swift @@ -0,0 +1,86 @@ +// +// ConsecutiveBlankLinesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ConsecutiveBlankLinesTests: XCTestCase { + func testConsecutiveBlankLines() { + let input = "foo\n\n \nbar" + let output = "foo\n\nbar" + testFormatting(for: input, output, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAtEndOfFile() { + let input = "foo\n\n" + let output = "foo\n" + testFormatting(for: input, output, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAtStartOfFile() { + let input = "\n\n\nfoo" + let output = "\n\nfoo" + testFormatting(for: input, output, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesInsideStringLiteral() { + let input = "\"\"\"\nhello\n\n\nworld\n\"\"\"" + testFormatting(for: input, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAtStartOfStringLiteral() { + let input = "\"\"\"\n\n\nhello world\n\"\"\"" + testFormatting(for: input, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAfterStringLiteral() { + let input = "\"\"\"\nhello world\n\"\"\"\n\n\nfoo()" + let output = "\"\"\"\nhello world\n\"\"\"\n\nfoo()" + testFormatting(for: input, output, rule: .consecutiveBlankLines) + } + + func testFragmentWithTrailingLinebreaks() { + let input = "func foo() {}\n\n\n" + let output = "func foo() {}\n\n" + let options = FormatOptions(fragment: true) + testFormatting(for: input, output, rule: .consecutiveBlankLines, options: options) + } + + func testConsecutiveBlankLinesNoInterpolation() { + let input = """ + \"\"\" + AAA + ZZZ + + + + \"\"\" + """ + testFormatting(for: input, rule: .consecutiveBlankLines) + } + + func testConsecutiveBlankLinesAfterInterpolation() { + let input = """ + \"\"\" + AAA + \\(interpolated) + + + + \"\"\" + """ + testFormatting(for: input, rule: .consecutiveBlankLines) + } + + func testLintingConsecutiveBlankLinesReportsCorrectLine() { + let input = "foo\n \n\nbar" + XCTAssertEqual(try lint(input, rules: [.consecutiveBlankLines]), [ + .init(line: 3, rule: .consecutiveBlankLines, filePath: nil), + ]) + } +} diff --git a/Tests/Rules/ConsecutiveSpacesTests.swift b/Tests/Rules/ConsecutiveSpacesTests.swift new file mode 100644 index 000000000..d090e8729 --- /dev/null +++ b/Tests/Rules/ConsecutiveSpacesTests.swift @@ -0,0 +1,56 @@ +// +// ConsecutiveSpacesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ConsecutiveSpacesTests: XCTestCase { + func testConsecutiveSpaces() { + let input = "let foo = bar" + let output = "let foo = bar" + testFormatting(for: input, output, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesAfterComment() { + let input = "// comment\nfoo bar" + let output = "// comment\nfoo bar" + testFormatting(for: input, output, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntStripIndent() { + let input = "{\n let foo = bar\n}" + let output = "{\n let foo = bar\n}" + testFormatting(for: input, output, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntAffectMultilineComments() { + let input = "/* comment */" + testFormatting(for: input, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesRemovedBetweenComments() { + let input = "/* foo */ /* bar */" + let output = "/* foo */ /* bar */" + testFormatting(for: input, output, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntAffectNestedMultilineComments() { + let input = "/* foo /* bar */ baz */" + testFormatting(for: input, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntAffectNestedMultilineComments2() { + let input = "/* /* foo */ /* bar */ */" + testFormatting(for: input, rule: .consecutiveSpaces) + } + + func testConsecutiveSpacesDoesntAffectSingleLineComments() { + let input = "// foo bar" + testFormatting(for: input, rule: .consecutiveSpaces) + } +} diff --git a/Tests/Rules/ConsistentSwitchCaseSpacingTests.swift b/Tests/Rules/ConsistentSwitchCaseSpacingTests.swift new file mode 100644 index 000000000..a672da07c --- /dev/null +++ b/Tests/Rules/ConsistentSwitchCaseSpacingTests.swift @@ -0,0 +1,327 @@ +// +// ConsistentSwitchCaseSpacingTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ConsistentSwitchCaseSpacingTests: XCTestCase { + func testInsertsBlankLinesToMakeSwitchStatementSpacingConsistent1() { + let input = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + let output = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + testFormatting(for: input, output, rule: .consistentSwitchCaseSpacing) + } + + func testInsertsBlankLinesToMakeSwitchStatementSpacingConsistent2() { + let input = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + let output = """ + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + testFormatting(for: input, output, rule: .consistentSwitchCaseSpacing) + } + + func testInsertsBlankLinesToMakeSwitchStatementSpacingConsistent3() { + let input = """ + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + // Similar to Earth but way more deadly + case .venus: + "Venus" + + // The best planet, where everything cool happens + case .earth: + "Earth" + + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + + // The biggest planet with the most moons + case .jupiter: + "Jupiter" + + // Other planets have rings, but satun's are the best. + case .saturn: + "Saturn" + case .uranus: + "Uranus" + case .neptune: + "Neptune" + } + } + """ + + let output = """ + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + + // Similar to Earth but way more deadly + case .venus: + "Venus" + + // The best planet, where everything cool happens + case .earth: + "Earth" + + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + + // The biggest planet with the most moons + case .jupiter: + "Jupiter" + + // Other planets have rings, but satun's are the best. + case .saturn: + "Saturn" + + case .uranus: + "Uranus" + + case .neptune: + "Neptune" + } + } + """ + testFormatting(for: input, output, rule: .consistentSwitchCaseSpacing) + } + + func testRemovesBlankLinesToMakeSwitchStatementConsistent() { + let input = """ + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + + case .venus: + "Venus" + // The best planet, where everything cool happens + case .earth: + "Earth" + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + case .jupiter: + "Jupiter" + // Other planets have rings, but satun's are the best. + case .saturn: + "Saturn" + case .uranus: + "Uranus" + case .neptune: + "Neptune" + } + } + """ + + let output = """ + var name: PlanetType { + switch self { + // The planet closest to the sun + case .mercury: + "Mercury" + case .venus: + "Venus" + // The best planet, where everything cool happens + case .earth: + "Earth" + // This planet is entirely inhabited by robots. + // There are cool landers, rovers, and even a helicopter. + case .mars: + "Mars" + case .jupiter: + "Jupiter" + // Other planets have rings, but satun's are the best. + case .saturn: + "Saturn" + case .uranus: + "Uranus" + case .neptune: + "Neptune" + } + } + """ + + testFormatting(for: input, output, rule: .consistentSwitchCaseSpacing) + } + + func testSingleLineAndMultiLineSwitchCase1() { + let input = """ + switch planetType { + case .terrestrial: + if options.treatPlutoAsPlanet { + [.mercury, .venus, .earth, .mars, .pluto] + } else { + [.mercury, .venus, .earth, .mars] + } + case .gasGiant: + [.jupiter, .saturn, .uranus, .neptune] + } + """ + + let output = """ + switch planetType { + case .terrestrial: + if options.treatPlutoAsPlanet { + [.mercury, .venus, .earth, .mars, .pluto] + } else { + [.mercury, .venus, .earth, .mars] + } + + case .gasGiant: + [.jupiter, .saturn, .uranus, .neptune] + } + """ + + testFormatting(for: input, [output], rules: [.blankLineAfterSwitchCase, .consistentSwitchCaseSpacing]) + } + + func testSingleLineAndMultiLineSwitchCase2() { + let input = """ + switch planetType { + case .gasGiant: + [.jupiter, .saturn, .uranus, .neptune] + case .terrestrial: + if options.treatPlutoAsPlanet { + [.mercury, .venus, .earth, .mars, .pluto] + } else { + [.mercury, .venus, .earth, .mars] + } + } + """ + + testFormatting(for: input, rule: .consistentSwitchCaseSpacing) + } + + func testSwitchStatementWithSingleMultilineCase_blankLineAfterSwitchCaseEnabled() { + let input = """ + switch action { + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + case let .scanPlanet(planet): + scanner.scan(planet) + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + let output = """ + switch action { + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case let .scanPlanet(planet): + scanner.scan(planet) + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + testFormatting(for: input, [output], rules: [.consistentSwitchCaseSpacing, .blankLineAfterSwitchCase]) + } + + func testSwitchStatementWithSingleMultilineCase_blankLineAfterSwitchCaseDisabled() { + let input = """ + switch action { + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + case let .scanPlanet(planet): + scanner.scan(planet) + case .handleIncomingEnergyBlast: + energyShields.engage() + } + """ + + testFormatting(for: input, rule: .consistentSwitchCaseSpacing, exclude: [.blankLineAfterSwitchCase]) + } +} diff --git a/Tests/Rules/DocCommentsBeforeAttributesTests.swift b/Tests/Rules/DocCommentsBeforeAttributesTests.swift new file mode 100644 index 000000000..8a5f7f5ef --- /dev/null +++ b/Tests/Rules/DocCommentsBeforeAttributesTests.swift @@ -0,0 +1,197 @@ +// +// DocCommentsBeforeAttributesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class DocCommentsBeforeAttributesTests: XCTestCase { + func testDocCommentsBeforeAttributes() { + let input = """ + @MainActor + /// Doc comment on this type declaration + public struct Baaz { + @available(*, deprecated) + /// Doc comment on this property declaration. + /// This comment spans multiple lines. + private var bar: Bar + + @FooBarMacro(arg1: true, arg2: .baaz) + /** + * Doc comment on this function declaration + */ + func foo() {} + } + """ + + let output = """ + /// Doc comment on this type declaration + @MainActor + public struct Baaz { + /// Doc comment on this property declaration. + /// This comment spans multiple lines. + @available(*, deprecated) + private var bar: Bar + + /** + * Doc comment on this function declaration + */ + @FooBarMacro(arg1: true, arg2: .baaz) + func foo() {} + } + """ + + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes) + } + + func testDocCommentsBeforeMultipleAttributes() { + let input = """ + @MainActor @Macro(argument: true) @available(*, deprecated) + /// Doc comment on this function declaration after several attributes + public func foo() {} + + @MainActor + @Macro(argument: true) + @available(*, deprecated) + /// Doc comment on this function declaration after several attributes + public func bar() {} + """ + + let output = """ + /// Doc comment on this function declaration after several attributes + @MainActor @Macro(argument: true) @available(*, deprecated) + public func foo() {} + + /// Doc comment on this function declaration after several attributes + @MainActor + @Macro(argument: true) + @available(*, deprecated) + public func bar() {} + """ + + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes) + } + + func testUpdatesCommentsAfterMark() { + let input = """ + import FooBarKit + + // MARK: - Foo + + @MainActor + /// Doc comment on this type declaration. + enum Foo { + + // MARK: Public + + @MainActor + /// Doc comment on this function declaration. + public func foo() {} + + // MARK: Private + + // TODO: This function also has a TODO comment. + @MainActor + /// Doc comment on this function declaration. + private func bar() {} + + } + """ + + let output = """ + import FooBarKit + + // MARK: - Foo + + /// Doc comment on this type declaration. + @MainActor + enum Foo { + + // MARK: Public + + /// Doc comment on this function declaration. + @MainActor + public func foo() {} + + // MARK: Private + + // TODO: This function also has a TODO comment. + /// Doc comment on this function declaration. + @MainActor + private func bar() {} + + } + """ + + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + func testPreservesCommentsBetweenAttributes() { + let input = """ + @MainActor + /// Doc comment between attributes + @available(*, deprecated) + /// Doc comment before declaration + func bar() {} + + @MainActor /// Doc comment after main actor attribute + @available(*, deprecated) /// Doc comment after deprecation attribute + /// Doc comment before declaration + func bar() {} + """ + + let output = """ + /// Doc comment before declaration + @MainActor + /// Doc comment between attributes + @available(*, deprecated) + func bar() {} + + /// Doc comment before declaration + @MainActor /// Doc comment after main actor attribute + @available(*, deprecated) /// Doc comment after deprecation attribute + func bar() {} + """ + + testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) + } + + func testPreservesCommentOnSameLineAsAttribute() { + let input = """ + @MainActor /// Doc comment trailing attributes + func foo() {} + """ + + testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) + } + + func testPreservesRegularComments() { + let input = """ + @MainActor + // Comment after attribute + func foo() {} + """ + + testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) + } + + func testCombinesWithDocCommentsRule() { + let input = """ + @MainActor + // Comment after attribute + func foo() {} + """ + + let output = """ + /// Comment after attribute + @MainActor + func foo() {} + """ + + testFormatting(for: input, [output], rules: [.docComments, .docCommentsBeforeAttributes]) + } +} diff --git a/Tests/Rules/DocCommentsTests.swift b/Tests/Rules/DocCommentsTests.swift new file mode 100644 index 000000000..37992c038 --- /dev/null +++ b/Tests/Rules/DocCommentsTests.swift @@ -0,0 +1,579 @@ +// +// DocCommentsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class DocCommentsTests: XCTestCase { + func testConvertCommentsToDocComments() { + let input = """ + // Multi-line comment before class with + // attribute between comment and declaration + @objc + class Foo { + // Single line comment before property + let foo = Foo() + + // Single line comment before property with property wrapper + @State + let bar = Bar() + + // Single line comment + func foo() {} + + /* Single line block comment before method */ + func baaz() {} + + /* + Multi-line block comment before method with attribute. + + This comment has a blank line in it. + */ + @nonobjc + func baaz() {} + } + + // Enum with a case + enum Quux { + // Documentation on an enum case + case quux + } + + extension Collection where Element: Foo { + // Property in extension with where clause + var foo: Foo { + first! + } + } + """ + + let output = """ + /// Multi-line comment before class with + /// attribute between comment and declaration + @objc + class Foo { + /// Single line comment before property + let foo = Foo() + + /// Single line comment before property with property wrapper + @State + let bar = Bar() + + /// Single line comment + func foo() {} + + /** Single line block comment before method */ + func baaz() {} + + /** + Multi-line block comment before method with attribute. + + This comment has a blank line in it. + */ + @nonobjc + func baaz() {} + } + + /// Enum with a case + enum Quux { + /// Documentation on an enum case + case quux + } + + extension Collection where Element: Foo { + /// Property in extension with where clause + var foo: Foo { + first! + } + } + """ + + testFormatting(for: input, output, rule: .docComments, + exclude: [.spaceInsideComments, .propertyType]) + } + + func testConvertDocCommentsToComments() { + let input = """ + /// Comment not associated with class + + class Foo { + /** Comment not associated with function */ + + func bar() { + /// Comment inside function declaration. + /// This one is multi-line. + + /// This comment is inside a function and precedes a declaration, + /// but we don't want to use doc comments inside property or function + /// scopes since users typically don't think of these as doc comments, + /// and this also breaks a common pattern where comments introduce + /// an entire following block of code (not just the property) + let bar: Bar? = Bar() + print(bar) + } + + var baaz: Baaz { + /// Comment inside property getter + let baazImpl = Baaz() + return baazImpl + } + + var quux: Quux { + didSet { + /// Comment inside didSet + let newQuux = Quux() + print(newQuux) + } + } + } + """ + + let output = """ + // Comment not associated with class + + class Foo { + /* Comment not associated with function */ + + func bar() { + // Comment inside function declaration. + // This one is multi-line. + + // This comment is inside a function and precedes a declaration, + // but we don't want to use doc comments inside property or function + // scopes since users typically don't think of these as doc comments, + // and this also breaks a common pattern where comments introduce + // an entire following block of code (not just the property) + let bar: Bar? = Bar() + print(bar) + } + + var baaz: Baaz { + // Comment inside property getter + let baazImpl = Baaz() + return baazImpl + } + + var quux: Quux { + didSet { + // Comment inside didSet + let newQuux = Quux() + print(newQuux) + } + } + } + """ + + testFormatting(for: input, output, rule: .docComments, + exclude: [.spaceInsideComments, .redundantProperty, .propertyType]) + } + + func testPreservesDocComments() { + let input = """ + /// Comment not associated with class + + class Foo { + /** Comment not associated with function */ + + // Documentation for function + func bar() { + /// Comment inside function declaration. + /// This one is multi-line. + + /// This comment is inside a function and precedes a declaration. + /// Since the option to preserve doc comments is enabled, + /// it should be left as-is. + let bar: Bar? = Bar() + print(bar) + } + + // Documentation for property + var baaz: Baaz { + /// Comment inside property getter + let baazImpl = Baaz() + return baazImpl + } + + // Documentation for function + var quux: Quux { + didSet { + /// Comment inside didSet + let newQuux = Quux() + print(newQuux) + } + } + } + """ + + let output = """ + /// Comment not associated with class + + class Foo { + /** Comment not associated with function */ + + /// Documentation for function + func bar() { + /// Comment inside function declaration. + /// This one is multi-line. + + /// This comment is inside a function and precedes a declaration. + /// Since the option to preserve doc comments is enabled, + /// it should be left as-is. + let bar: Bar? = Bar() + print(bar) + } + + /// Documentation for property + var baaz: Baaz { + /// Comment inside property getter + let baazImpl = Baaz() + return baazImpl + } + + /// Documentation for function + var quux: Quux { + didSet { + /// Comment inside didSet + let newQuux = Quux() + print(newQuux) + } + } + } + """ + + let options = FormatOptions(preserveDocComments: true) + testFormatting(for: input, output, rule: .docComments, options: options, exclude: [.spaceInsideComments, .redundantProperty, .propertyType]) + } + + func testDoesntConvertCommentBeforeConsecutivePropertiesToDocComment() { + let input = """ + // Names of the planets + struct PlanetNames { + // Inner planets + let mercury = "Mercury" + let venus = "Venus" + let earth = "Earth" + let mars = "Mars" + + // Inner planets + let jupiter = "Jupiter" + let saturn = "Saturn" + let uranus = "Uranus" + let neptune = "Neptune" + + /// Dwarf planets + let pluto = "Pluto" + let ceres = "Ceres" + } + """ + + let output = """ + /// Names of the planets + struct PlanetNames { + // Inner planets + let mercury = "Mercury" + let venus = "Venus" + let earth = "Earth" + let mars = "Mars" + + // Inner planets + let jupiter = "Jupiter" + let saturn = "Saturn" + let uranus = "Uranus" + let neptune = "Neptune" + + /// Dwarf planets + let pluto = "Pluto" + let ceres = "Ceres" + } + """ + + testFormatting(for: input, output, rule: .docComments) + } + + func testConvertsCommentsToDocCommentsInConsecutiveDeclarations() { + let input = """ + // Names of the planets + enum PlanetNames { + // Mercuy + case mercury + // Venus + case venus + // Earth + case earth + // Mars + case mars + + // Jupiter + case jupiter + + // Saturn + case saturn + + // Uranus + case uranus + + // Neptune + case neptune + } + """ + + let output = """ + /// Names of the planets + enum PlanetNames { + /// Mercuy + case mercury + /// Venus + case venus + /// Earth + case earth + /// Mars + case mars + + /// Jupiter + case jupiter + + /// Saturn + case saturn + + /// Uranus + case uranus + + /// Neptune + case neptune + } + """ + + testFormatting(for: input, output, rule: .docComments) + } + + func testDoesntConvertCommentBeforeConsecutiveEnumCasesToDocComment() { + let input = """ + // Names of the planets + enum PlanetNames { + // Inner planets + case mercury + case venus + case earth + case mars + + // Inner planets + case jupiter + case saturn + case uranus + case neptune + + // Dwarf planets + case pluto + case ceres + } + """ + + let output = """ + /// Names of the planets + enum PlanetNames { + // Inner planets + case mercury + case venus + case earth + case mars + + // Inner planets + case jupiter + case saturn + case uranus + case neptune + + // Dwarf planets + case pluto + case ceres + } + """ + + testFormatting(for: input, output, rule: .docComments) + } + + func testDoesntConvertAnnotationCommentsToDocComments() { + let input = """ + // swiftformat:disable some_swift_format_rule + let testSwiftLint: Foo + + // swiftlint:disable some_swift_lint_rule + let testSwiftLint: Foo + + // sourcery:directive + let testSourcery: Foo + """ + + testFormatting(for: input, rule: .docComments) + } + + func testDoesntConvertTODOCommentsToDocComments() { + let input = """ + // TODO: Clean up this mess + func doSomething() {} + """ + + testFormatting(for: input, rule: .docComments) + } + + func testDoesntConvertCommentAfterTODOToDocComments() { + let input = """ + // TODO: Clean up this mess + // because it's bothering me + func doSomething() {} + """ + testFormatting(for: input, rule: .docComments) + } + + func testDoesntConvertCommentBeforeTODOToDocComments() { + let input = """ + // Something, something + // TODO: Clean up this mess + func doSomething() {} + """ + testFormatting(for: input, rule: .docComments) + } + + func testConvertNoteCommentsToDocComments() { + let input = """ + // Does something + // Note: not really + func doSomething() {} + """ + let output = """ + /// Does something + /// Note: not really + func doSomething() {} + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testConvertURLCommentsToDocComments() { + let input = """ + // Does something + // http://example.com + func doSomething() {} + """ + let output = """ + /// Does something + /// http://example.com + func doSomething() {} + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testMultilineDocCommentReplaced() { + let input = """ + // A class + // With some other details + class Foo {} + """ + let output = """ + /// A class + /// With some other details + class Foo {} + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testCommentWithBlankLineNotReplaced() { + let input = """ + // A class + // With some other details + + class Foo {} + """ + testFormatting(for: input, rule: .docComments) + } + + func testDocCommentsAssociatedTypeNotReplaced() { + let input = """ + /// An interesting comment about Foo. + associatedtype Foo + """ + testFormatting(for: input, rule: .docComments) + } + + func testNonDocCommentsAssociatedTypeReplaced() { + let input = """ + // An interesting comment about Foo. + associatedtype Foo + """ + let output = """ + /// An interesting comment about Foo. + associatedtype Foo + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testConditionalDeclarationCommentNotReplaced() { + let input = """ + if let foo = bar, + // baz + let baz = bar + {} + """ + testFormatting(for: input, rule: .docComments) + } + + func testCommentInsideSwitchCaseNotReplaced() { + let input = """ + switch foo { + case .bar: + // bar + let bar = baz() + + default: + // baz + let baz = quux() + } + """ + testFormatting(for: input, rule: .docComments) + } + + func testDocCommentInsideIfdef() { + let input = """ + #if DEBUG + // return 3 + func returnNumber() { 3 } + #endif + """ + let output = """ + #if DEBUG + /// return 3 + func returnNumber() { 3 } + #endif + """ + testFormatting(for: input, output, rule: .docComments) + } + + func testDocCommentInsideIfdefElse() { + let input = """ + #if DEBUG + #elseif PROD + /// return 2 + func returnNumber() { 2 } + #else + /// return 3 + func returnNumber() { 3 } + #endif + """ + testFormatting(for: input, rule: .docComments) + } + + func testDocCommentForMacro() { + let input = """ + /// Adds a static `logger` member to the type. + @attached(member, names: named(logger)) public macro StaticLogger( + subsystem: String? = nil, + category: String? = nil + ) = #externalMacro(module: "StaticLoggerMacros", type: "StaticLogger") + """ + testFormatting(for: input, rule: .docComments) + } +} diff --git a/Tests/Rules/DuplicateImportsTests.swift b/Tests/Rules/DuplicateImportsTests.swift new file mode 100644 index 000000000..cc2131fe8 --- /dev/null +++ b/Tests/Rules/DuplicateImportsTests.swift @@ -0,0 +1,64 @@ +// +// DuplicateImportsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class DuplicateImportsTests: XCTestCase { + func testRemoveDuplicateImport() { + let input = "import Foundation\nimport Foundation" + let output = "import Foundation" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testRemoveDuplicateConditionalImport() { + let input = "#if os(iOS)\n import Foo\n import Foo\n#else\n import Bar\n import Bar\n#endif" + let output = "#if os(iOS)\n import Foo\n#else\n import Bar\n#endif" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveOverlappingImports() { + let input = "import MyModule\nimport MyModule.Private" + testFormatting(for: input, rule: .duplicateImports) + } + + func testNoRemoveCaseDifferingImports() { + let input = "import Auth0.Authentication\nimport Auth0.authentication" + testFormatting(for: input, rule: .duplicateImports) + } + + func testRemoveDuplicateImportFunc() { + let input = "import func Foo.bar\nimport func Foo.bar" + let output = "import func Foo.bar" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveTestableDuplicateImport() { + let input = "import Foo\n@testable import Foo" + let output = "\n@testable import Foo" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveTestableDuplicateImport2() { + let input = "@testable import Foo\nimport Foo" + let output = "@testable import Foo" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveExportedDuplicateImport() { + let input = "import Foo\n@_exported import Foo" + let output = "\n@_exported import Foo" + testFormatting(for: input, output, rule: .duplicateImports) + } + + func testNoRemoveExportedDuplicateImport2() { + let input = "@_exported import Foo\nimport Foo" + let output = "@_exported import Foo" + testFormatting(for: input, output, rule: .duplicateImports) + } +} diff --git a/Tests/Rules/ElseOnSameLineTests.swift b/Tests/Rules/ElseOnSameLineTests.swift new file mode 100644 index 000000000..532c46735 --- /dev/null +++ b/Tests/Rules/ElseOnSameLineTests.swift @@ -0,0 +1,382 @@ +// +// ElseOnSameLineTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ElseOnSameLineTests: XCTestCase { + func testElseOnSameLine() { + let input = "if true {\n 1\n}\nelse { 2 }" + let output = "if true {\n 1\n} else { 2 }" + testFormatting(for: input, output, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testElseOnSameLineOnlyAppliedToDanglingBrace() { + let input = "if true { 1 }\nelse { 2 }" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testGuardNotAffectedByElseOnSameLine() { + let input = "guard true\nelse { return }" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testElseOnSameLineDoesntEatPreviousStatement() { + let input = "if true {}\nguard true else { return }" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testElseNotOnSameLineForAllman() { + let input = "if true\n{\n 1\n} else { 2 }" + let output = "if true\n{\n 1\n}\nelse { 2 }" + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testElseOnNextLineOption() { + let input = "if true {\n 1\n} else { 2 }" + let output = "if true {\n 1\n}\nelse { 2 }" + let options = FormatOptions(elseOnNextLine: true) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testGuardNotAffectedByElseOnSameLineForAllman() { + let input = "guard true else { return }" + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testRepeatWhileNotOnSameLineForAllman() { + let input = "repeat\n{\n foo\n} while x" + let output = "repeat\n{\n foo\n}\nwhile x" + let options = FormatOptions(allmanBraces: true) + testFormatting(for: input, output, rule: .elseOnSameLine, options: options) + } + + func testWhileNotAffectedByElseOnSameLineIfNotRepeatWhile() { + let input = "func foo(x) {}\n\nwhile true {}" + testFormatting(for: input, rule: .elseOnSameLine) + } + + func testCommentsNotDiscardedByElseOnSameLineRule() { + let input = "if true {\n 1\n}\n\n// comment\nelse {}" + testFormatting(for: input, rule: .elseOnSameLine) + } + + func testElseOnSameLineInferenceEdgeCase() { + let input = """ + func foo() { + if let foo == bar { + // ... + } else { + // ... + } + + if let foo == bar, + let baz = quux + { + print() + } + + if let foo == bar, + let baz = quux + { + print() + } + + if let foo == bar, + let baz = quux + { + print() + } + + if let foo == bar, + let baz = quux + { + print() + } + } + """ + let options = FormatOptions(elseOnNextLine: false) + testFormatting(for: input, rule: .elseOnSameLine, options: options, + exclude: [.braces]) + } + + // guardelse = auto + + func testSingleLineGuardElseNotWrappedByDefault() { + let input = "guard foo = bar else {}" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testSingleLineGuardElseNotUnwrappedByDefault() { + let input = "guard foo = bar\nelse {}" + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testSingleLineGuardElseWrappedByDefaultIfBracesOnNextLine() { + let input = "guard foo = bar else\n{}" + let output = "guard foo = bar\nelse {}" + testFormatting(for: input, output, rule: .elseOnSameLine, + exclude: [.wrapConditionalBodies]) + } + + func testMultilineGuardElseNotWrappedByDefault() { + let input = """ + guard let foo = bar, + bar > 5 else { + return + } + """ + testFormatting(for: input, rule: .elseOnSameLine, + exclude: [.wrapMultilineStatementBraces]) + } + + func testMultilineGuardElseWrappedByDefaultIfBracesOnNextLine() { + let input = """ + guard let foo = bar, + bar > 5 else + { + return + } + """ + let output = """ + guard let foo = bar, + bar > 5 + else { + return + } + """ + testFormatting(for: input, output, rule: .elseOnSameLine) + } + + func testWrappedMultilineGuardElseCorrectlyIndented() { + let input = """ + func foo() { + guard let foo = bar, + bar > 5 else + { + return + } + } + """ + let output = """ + func foo() { + guard let foo = bar, + bar > 5 + else { + return + } + } + """ + testFormatting(for: input, output, rule: .elseOnSameLine) + } + + // guardelse = nextLine + + func testSingleLineGuardElseNotWrapped() { + let input = "guard foo = bar else {}" + let options = FormatOptions(guardElsePosition: .nextLine) + testFormatting(for: input, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testSingleLineGuardElseNotUnwrapped() { + let input = "guard foo = bar\nelse {}" + let options = FormatOptions(guardElsePosition: .nextLine) + testFormatting(for: input, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testSingleLineGuardElseWrappedIfBracesOnNextLine() { + let input = "guard foo = bar else\n{}" + let output = "guard foo = bar\nelse {}" + let options = FormatOptions(guardElsePosition: .nextLine) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapConditionalBodies]) + } + + func testMultilineGuardElseWrapped() { + let input = """ + guard let foo = bar, + bar > 5 else { + return + } + """ + let output = """ + guard let foo = bar, + bar > 5 + else { + return + } + """ + let options = FormatOptions(guardElsePosition: .nextLine) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testMultilineGuardElseEndingInParen() { + let input = """ + guard let foo = bar, + let baz = quux() else + { + return + } + """ + let output = """ + guard let foo = bar, + let baz = quux() + else { + return + } + """ + let options = FormatOptions(guardElsePosition: .auto) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options) + } + + // guardelse = sameLine + + func testMultilineGuardElseUnwrapped() { + let input = """ + guard let foo = bar, + bar > 5 + else { + return + } + """ + let output = """ + guard let foo = bar, + bar > 5 else { + return + } + """ + let options = FormatOptions(guardElsePosition: .sameLine) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testGuardElseUnwrappedIfBracesOnNextLine() { + let input = "guard foo = bar\nelse {}" + let output = "guard foo = bar else {}" + let options = FormatOptions(guardElsePosition: .sameLine) + testFormatting(for: input, output, rule: .elseOnSameLine, + options: options) + } + + func testPreserveBlankLineBeforeElse() { + let input = """ + if foo { + print("foo") + } + + else if bar { + print("bar") + } + + else { + print("baaz") + } + """ + + testFormatting(for: input, rule: .elseOnSameLine) + } + + func testPreserveBlankLineBeforeElseOnSameLine() { + let input = """ + if foo { + print("foo") + } + + else if bar { + print("bar") + } + + else { + print("baaz") + } + """ + + let options = FormatOptions(elseOnNextLine: false) + testFormatting(for: input, rule: .elseOnSameLine, options: options) + } + + func testPreserveBlankLineBeforeElseWithComments() { + let input = """ + if foo { + print("foo") + } + // Comment before else if + else if bar { + print("bar") + } + + // Comment before else + else { + print("baaz") + } + """ + + testFormatting(for: input, rule: .elseOnSameLine) + } + + func testPreserveBlankLineBeforeElseDoesntAffectOtherCases() { + let input = """ + if foo { + print("foo") + } + else { + print("bar") + } + + guard foo else { + return + } + + guard + let foo, + let bar, + lat baaz else + { + return + } + """ + + let output = """ + if foo { + print("foo") + } else { + print("bar") + } + + guard foo else { + return + } + + guard + let foo, + let bar, + lat baaz + else { + return + } + """ + + let options = FormatOptions(elseOnNextLine: false, guardElsePosition: .nextLine) + testFormatting(for: input, output, rule: .elseOnSameLine, options: options) + } +} diff --git a/Tests/Rules/EmptyBracesTests.swift b/Tests/Rules/EmptyBracesTests.swift new file mode 100644 index 000000000..be56efa17 --- /dev/null +++ b/Tests/Rules/EmptyBracesTests.swift @@ -0,0 +1,111 @@ +// +// EmptyBracesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class EmptyBracesTests: XCTestCase { + func testLinebreaksRemovedInsideBraces() { + let input = "func foo() {\n \n }" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .emptyBraces) + } + + func testCommentNotRemovedInsideBraces() { + let input = "func foo() { // foo\n}" + testFormatting(for: input, rule: .emptyBraces) + } + + func testEmptyBracesNotRemovedInDoCatch() { + let input = """ + do { + } catch is FooError { + } catch {} + """ + testFormatting(for: input, rule: .emptyBraces) + } + + func testEmptyBracesNotRemovedInIfElse() { + let input = """ + if bar { + } else if foo { + } else {} + """ + testFormatting(for: input, rule: .emptyBraces) + } + + func testSpaceRemovedInsideEmptybraces() { + let input = "foo { }" + let output = "foo {}" + testFormatting(for: input, output, rule: .emptyBraces) + } + + func testSpaceAddedInsideEmptyBracesWithSpacedConfiguration() { + let input = "foo {}" + let output = "foo { }" + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, output, rule: .emptyBraces, options: options) + } + + func testLinebreaksRemovedInsideBracesWithSpacedConfiguration() { + let input = "func foo() {\n \n }" + let output = "func foo() { }" + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, output, rule: .emptyBraces, options: options) + } + + func testCommentNotRemovedInsideBracesWithSpacedConfiguration() { + let input = "func foo() { // foo\n}" + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, rule: .emptyBraces, options: options) + } + + func testEmptyBracesSpaceNotRemovedInDoCatchWithSpacedConfiguration() { + let input = """ + do { + } catch is FooError { + } catch { } + """ + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, rule: .emptyBraces, options: options) + } + + func testEmptyBracesSpaceNotRemovedInIfElseWithSpacedConfiguration() { + let input = """ + if bar { + } else if foo { + } else { } + """ + let options = FormatOptions(emptyBracesSpacing: .spaced) + testFormatting(for: input, rule: .emptyBraces, options: options) + } + + func testEmptyBracesLinebreakNotRemovedInIfElseWithLinebreakConfiguration() { + let input = """ + if bar { + } else if foo { + } else { + } + """ + let options = FormatOptions(emptyBracesSpacing: .linebreak) + testFormatting(for: input, rule: .emptyBraces, options: options) + } + + func testEmptyBracesLinebreakIndentedCorrectly() { + let input = """ + func foo() { + if bar { + } else if foo { + } else { + } + } + """ + let options = FormatOptions(emptyBracesSpacing: .linebreak) + testFormatting(for: input, rule: .emptyBraces, options: options) + } +} diff --git a/Tests/Rules/EnumNamespacesTests.swift b/Tests/Rules/EnumNamespacesTests.swift new file mode 100644 index 000000000..b05834517 --- /dev/null +++ b/Tests/Rules/EnumNamespacesTests.swift @@ -0,0 +1,455 @@ +// +// EnumNamespacesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class EnumNamespacesTests: XCTestCase { + func testEnumNamespacesClassAsProtocolRestriction() { + let input = """ + @objc protocol Foo: class { + @objc static var expressionTypes: [String: RuntimeType] { get } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesConformingOtherType() { + let input = "private final class CustomUITableViewCell: UITableViewCell {}" + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesImportClass() { + let input = """ + import class MyUIKit.AutoHeightTableView + + enum Foo { + static var bar: String + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesImportStruct() { + let input = """ + import struct Core.CurrencyFormatter + + enum Foo { + static var bar: String + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesClassFunction() { + let input = """ + class Container { + class func bar() {} + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesRemovingExtraKeywords() { + let input = """ + final class MyNamespace { + static let bar = "bar" + } + """ + let output = """ + enum MyNamespace { + static let bar = "bar" + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes() { + let input = """ + enum Namespace {} + extension Namespace { + struct Constants { + static let bar = "bar" + } + } + """ + let output = """ + enum Namespace {} + extension Namespace { + enum Constants { + static let bar = "bar" + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes2() { + let input = """ + struct Namespace { + struct NestedNamespace { + static let foo: Int + static let bar: Int + } + } + """ + let output = """ + enum Namespace { + enum NestedNamespace { + static let foo: Int + static let bar: Int + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes3() { + let input = """ + struct Namespace { + struct TypeNestedInNamespace { + let foo: Int + let bar: Int + } + } + """ + let output = """ + enum Namespace { + struct TypeNestedInNamespace { + let foo: Int + let bar: Int + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes4() { + let input = """ + struct Namespace { + static func staticFunction() { + struct NestedType { + init() {} + } + } + } + """ + let output = """ + enum Namespace { + static func staticFunction() { + struct NestedType { + init() {} + } + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNestedTypes5() { + let input = """ + struct Namespace { + static func staticFunction() { + func nestedFunction() { /* ... */ } + } + } + """ + let output = """ + enum Namespace { + static func staticFunction() { + func nestedFunction() { /* ... */ } + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesStaticVariable() { + let input = """ + struct Constants { + static let β = 0, 5 + } + """ + let output = """ + enum Constants { + static let β = 0, 5 + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesStaticAndInstanceVariable() { + let input = """ + struct Constants { + static let β = 0, 5 + let Ɣ = 0, 3 + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesStaticFunction() { + let input = """ + struct Constants { + static func remoteConfig() -> Int { + return 10 + } + } + """ + let output = """ + enum Constants { + static func remoteConfig() -> Int { + return 10 + } + } + """ + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesStaticAndInstanceFunction() { + let input = """ + struct Constants { + static func remoteConfig() -> Int { + return 10 + } + + func instanceConfig(offset: Int) -> Int { + return offset + 10 + } + } + """ + + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespaceDoesNothing() { + let input = """ + struct Foo { + #if BAR + func something() {} + #else + func something() {} + #endif + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespaceDoesNothingForEmptyDeclaration() { + let input = """ + struct Foo {} + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfTypeInitializedInternally() { + let input = """ + struct Foo { + static func bar() { + Foo().baz + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfInitializedInternally() { + let input = """ + struct Foo { + static func bar() { + Self().baz + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfInitializedInternally2() { + let input = """ + struct Foo { + static func bar() -> Foo { + self.init() + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfAssignedInternally() { + let input = """ + class Foo { + public static func bar() { + let bundle = Bundle(for: self) + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfAssignedInternally2() { + let input = """ + class Foo { + public static func bar() { + let `class` = self + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesDoesNothingIfSelfAssignedInternally3() { + let input = """ + class Foo { + public static func bar() { + let `class` = Foo.self + } + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testClassFuncNotReplacedByEnum() { + let input = """ + class Foo { + class override func foo() { + Bar.bar() + } + } + """ + testFormatting(for: input, rule: .enumNamespaces, + exclude: [.modifierOrder]) + } + + func testOpenClassNotReplacedByEnum() { + let input = """ + open class Foo { + public static let bar = "bar" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testClassNotReplacedByEnum() { + let input = """ + class Foo { + public static let bar = "bar" + } + """ + let options = FormatOptions(enumNamespaces: .structsOnly) + testFormatting(for: input, rule: .enumNamespaces, options: options) + } + + func testEnumNamespacesAfterImport() { + // https://github.com/nicklockwood/SwiftFormat/issues/1569 + let input = """ + import Foundation + + final class MyViewModel2 { + static let = "A" + } + """ + + let output = """ + import Foundation + + enum MyViewModel2 { + static let = "A" + } + """ + + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesAfterImport2() { + // https://github.com/nicklockwood/SwiftFormat/issues/1569 + let input = """ + final class MyViewModel { + static let = "A" + } + + import Foundation + + final class MyViewModel2 { + static let = "A" + } + """ + + let output = """ + enum MyViewModel { + static let = "A" + } + + import Foundation + + enum MyViewModel2 { + static let = "A" + } + """ + + testFormatting(for: input, output, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedToNonFinalClass() { + let input = """ + class Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfObjC() { + let input = """ + @objc(NSFoo) + final class Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfMacro() { + let input = """ + @FooBar + struct Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfParameterizedMacro() { + let input = """ + @FooMacro(arg: "Foo") + struct Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfGenericMacro() { + let input = """ + @FooMacro + struct Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } + + func testEnumNamespacesNotAppliedIfGenericParameterizedMacro() { + let input = """ + @FooMacro(arg: 5) + struct Foo { + static let = "A" + } + """ + testFormatting(for: input, rule: .enumNamespaces) + } +} diff --git a/Tests/Rules/ExtensionAccessControlTests.swift b/Tests/Rules/ExtensionAccessControlTests.swift new file mode 100644 index 000000000..2ed294edd --- /dev/null +++ b/Tests/Rules/ExtensionAccessControlTests.swift @@ -0,0 +1,463 @@ +// +// ExtensionAccessControlTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ExtensionAccessControlTests: XCTestCase { + func testUpdatesVisibilityOfExtensionMembers() { + let input = """ + private extension Foo { + var publicProperty: Int { 10 } + public func publicFunction1() {} + func publicFunction2() {} + internal func internalFunction() {} + private func privateFunction() {} + fileprivate var privateProperty: Int { 10 } + } + """ + + let output = """ + extension Foo { + fileprivate var publicProperty: Int { 10 } + public func publicFunction1() {} + fileprivate func publicFunction2() {} + internal func internalFunction() {} + private func privateFunction() {} + fileprivate var privateProperty: Int { 10 } + } + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations), + exclude: [.redundantInternal] + ) + } + + func testUpdatesVisibilityOfExtensionInConditionalCompilationBlock() { + let input = """ + #if DEBUG + public extension Foo { + var publicProperty: Int { 10 } + } + #endif + """ + + let output = """ + #if DEBUG + extension Foo { + public var publicProperty: Int { 10 } + } + #endif + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testUpdatesVisibilityOfExtensionMembersInConditionalCompilationBlock() { + let input = """ + public extension Foo { + #if DEBUG + var publicProperty: Int { 10 } + #endif + } + """ + + let output = """ + extension Foo { + #if DEBUG + public var publicProperty: Int { 10 } + #endif + } + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testDoesntUpdateDeclarationsInsideTypeInsideExtension() { + let input = """ + public extension Foo { + struct Bar { + var baz: Int + var quux: Int + } + } + """ + + let output = """ + extension Foo { + public struct Bar { + var baz: Int + var quux: Int + } + } + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testDoesNothingForInternalExtension() { + let input = """ + extension Foo { + func bar() {} + func baz() {} + public func quux() {} + } + """ + + testFormatting( + for: input, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testPlacesVisibilityKeywordAfterAnnotations() { + let input = """ + public extension Foo { + @discardableResult + func bar() -> Int { 10 } + + /// Doc comment + @discardableResult + @available(iOS 10.0, *) + func baz() -> Int { 10 } + + @objc func quux() {} + @available(iOS 10.0, *) func quixotic() {} + } + """ + + let output = """ + extension Foo { + @discardableResult + public func bar() -> Int { 10 } + + /// Doc comment + @discardableResult + @available(iOS 10.0, *) + public func baz() -> Int { 10 } + + @objc public func quux() {} + @available(iOS 10.0, *) public func quixotic() {} + } + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations) + ) + } + + func testConvertsExtensionPrivateToMemberFileprivate() { + let input = """ + private extension Foo { + var bar: Int + } + + let bar = Foo().bar + """ + + let output = """ + extension Foo { + fileprivate var bar: Int + } + + let bar = Foo().bar + """ + + testFormatting( + for: input, output, rule: .extensionAccessControl, + options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4"), + exclude: [.propertyType] + ) + } + + // MARK: extensionAccessControl .onExtension + + func testUpdatedVisibilityOfExtension() { + let input = """ + extension Foo { + public func bar() {} + public var baz: Int { 10 } + + public struct Foo2 { + var quux: Int + } + } + """ + + let output = """ + public extension Foo { + func bar() {} + var baz: Int { 10 } + + struct Foo2 { + var quux: Int + } + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testUpdatedVisibilityOfExtensionWithDeclarationsInConditionalCompilation() { + let input = """ + extension Foo { + #if DEBUG + public func bar() {} + public var baz: Int { 10 } + #endif + } + """ + + let output = """ + public extension Foo { + #if DEBUG + func bar() {} + var baz: Int { 10 } + #endif + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testDoesntUpdateExtensionVisibilityWithoutMajorityBodyVisibility() { + let input = """ + extension Foo { + public func foo() {} + public func bar() {} + var baz: Int { 10 } + var quux: Int { 5 } + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testUpdateExtensionVisibilityWithMajorityBodyVisibility() { + let input = """ + extension Foo { + public func foo() {} + public func bar() {} + public var baz: Int { 10 } + var quux: Int { 5 } + } + """ + + let output = """ + public extension Foo { + func foo() {} + func bar() {} + var baz: Int { 10 } + internal var quux: Int { 5 } + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testDoesntUpdateExtensionVisibilityWhenMajorityBodyVisibilityIsntMostVisible() { + let input = """ + extension Foo { + func foo() {} + func bar() {} + public var baz: Int { 10 } + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDoesntUpdateExtensionVisibilityWithInternalDeclarations() { + let input = """ + extension Foo { + func bar() {} + var baz: Int { 10 } + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDoesntUpdateExtensionThatAlreadyHasCorrectVisibilityKeyword() { + let input = """ + public extension Foo { + func bar() {} + func baz() {} + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testUpdatesExtensionThatHasHigherACLThanBodyDeclarations() { + let input = """ + public extension Foo { + fileprivate func bar() {} + fileprivate func baz() {} + } + """ + + let output = """ + fileprivate extension Foo { + func bar() {} + func baz() {} + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl, + exclude: [.redundantFileprivate]) + } + + func testDoesntHoistPrivateVisibilityFromExtensionBodyDeclarations() { + let input = """ + extension Foo { + private var bar() {} + private func baz() {} + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDoesntUpdatesExtensionThatHasLowerACLThanBodyDeclarations() { + let input = """ + private extension Foo { + public var bar() {} + public func baz() {} + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDoesntReduceVisibilityOfImplicitInternalDeclaration() { + let input = """ + extension Foo { + fileprivate var bar() {} + func baz() {} + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testUpdatesExtensionThatHasRedundantACLOnBodyDeclarations() { + let input = """ + public extension Foo { + func bar() {} + public func baz() {} + } + """ + + let output = """ + public extension Foo { + func bar() {} + func baz() {} + } + """ + + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testNoHoistAccessModifierForOpenMethod() { + let input = """ + extension Foo { + open func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDontChangePrivateExtensionToFileprivate() { + let input = """ + private extension Foo { + func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testDontRemoveInternalKeywordFromExtension() { + let input = """ + internal extension Foo { + func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl, exclude: [.redundantInternal]) + } + + func testNoHoistAccessModifierForExtensionThatAddsProtocolConformance() { + let input = """ + extension Foo: Bar { + public func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testProtocolConformanceCheckNotFooledByWhereClause() { + let input = """ + extension Foo where Self: Bar { + public func bar() {} + } + """ + let output = """ + public extension Foo where Self: Bar { + func bar() {} + } + """ + testFormatting(for: input, output, rule: .extensionAccessControl) + } + + func testAccessNotHoistedIfTypeVisibilityIsLower() { + let input = """ + class Foo {} + + extension Foo { + public func bar() {} + } + """ + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testExtensionAccessControlRuleTerminatesInFileWithConditionalCompilation() { + let input = """ + #if os(Linux) + #error("Linux is currently not supported") + #endif + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } + + func testExtensionAccessControlRuleTerminatesInFileWithEmptyType() { + let input = """ + struct Foo { + // This type is empty + } + + extension Foo { + // This extension is empty + } + """ + + testFormatting(for: input, rule: .extensionAccessControl) + } +} diff --git a/Tests/Rules/FileHeaderTests.swift b/Tests/Rules/FileHeaderTests.swift new file mode 100644 index 000000000..f63f59819 --- /dev/null +++ b/Tests/Rules/FileHeaderTests.swift @@ -0,0 +1,514 @@ +// +// FileHeaderTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class FileHeaderTests: XCTestCase { + func testStripHeader() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let output = "/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testStripHeaderWithWhenHeaderContainsUrl() { + let input = """ + // + // RulesTests+General.swift + // SwiftFormatTests + // + // Created by Nick Lockwood on 02/10/2021. + // Copyright © 2021 Nick Lockwood. All rights reserved. + // https://some.example.com + // + + /// func + func foo() {} + """ + let output = "/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testReplaceHeaderWhenFileContainsNoCode() { + let input = "// foobar" + let options = FormatOptions(fileHeader: "// foobar") + testFormatting(for: input, rule: .fileHeader, options: options, + exclude: [.linebreakAtEndOfFile]) + } + + func testReplaceHeaderWhenFileContainsNoCode2() { + let input = "// foobar\n" + let options = FormatOptions(fileHeader: "// foobar") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testMultilineCommentHeader() { + let input = "/****************************/\n/* Created by Nick Lockwood */\n/****************************/\n\n\n/// func\nfunc foo() {}" + let output = "/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testNoStripHeaderWhenDisabled() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: .ignore) + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripComment() { + let input = "\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripPackageHeader() { + let input = "// swift-tools-version:4.2\n\nimport PackageDescription" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripFormatDirective() { + let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripFormatDirectiveAfterHeader() { + let input = "// header\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoReplaceFormatDirective() { + let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" + let output = "// Hello World\n\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" + let options = FormatOptions(fileHeader: "// Hello World") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testSetSingleLineHeader() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let output = "// Hello World\n\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "// Hello World") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testSetMultilineHeader() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let output = "// Hello\n// World\n\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "// Hello\n// World") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testSetMultilineHeaderWithMarkup() { + let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" + let output = "/*--- Hello ---*/\n/*--- World ---*/\n\n/// func\nfunc foo() {}" + let options = FormatOptions(fileHeader: "/*--- Hello ---*/\n/*--- World ---*/") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testNoStripHeaderIfRuleDisabled() { + let input = "// swiftformat:disable fileHeader\n// test\n// swiftformat:enable fileHeader\n\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripHeaderIfNextRuleDisabled() { + let input = "// swiftformat:disable:next fileHeader\n// test\n\nfunc foo() {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testNoStripHeaderDocWithNewlineBeforeCode() { + let input = "/// Header doc\n\nclass Foo {}" + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options, exclude: [.docComments]) + } + + func testNoDuplicateHeaderIfMissingTrailingBlankLine() { + let input = "// Header comment\nclass Foo {}" + let output = "// Header comment\n\nclass Foo {}" + let options = FormatOptions(fileHeader: "Header comment") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testNoDuplicateHeaderContainingPossibleCommentDirective() { + let input = """ + // Copyright (c) 2010-2023 Foobar + // + // SPDX-License-Identifier: EPL-2.0 + + class Foo {} + """ + let output = """ + // Copyright (c) 2010-2024 Foobar + // + // SPDX-License-Identifier: EPL-2.0 + + class Foo {} + """ + let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// SPDX-License-Identifier: EPL-2.0") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testNoDuplicateHeaderContainingCommentDirective() { + let input = """ + // Copyright (c) 2010-2023 Foobar + // + // swiftformat:disable all + + class Foo {} + """ + let output = """ + // Copyright (c) 2010-2024 Foobar + // + // swiftformat:disable all + + class Foo {} + """ + let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// swiftformat:disable all") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderYearReplacement() { + let input = "let foo = bar" + let output: String = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy" + return "// Copyright © \(formatter.string(from: Date()))\n\nlet foo = bar" + }() + let options = FormatOptions(fileHeader: "// Copyright © {year}") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderCreationYearReplacement() { + let input = "let foo = bar" + let date = Date(timeIntervalSince1970: 0) + let output: String = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy" + return "// Copyright © \(formatter.string(from: date))\n\nlet foo = bar" + }() + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// Copyright © {created.year}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderAuthorReplacement() { + let name = "Test User" + let email = "test@email.com" + let input = "let foo = bar" + let output = "// Created by \(name) \(email)\n\nlet foo = bar" + let fileInfo = FileInfo(replacements: [.authorName: .constant(name), .authorEmail: .constant(email)]) + let options = FormatOptions(fileHeader: "// Created by {author.name} {author.email}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderAuthorReplacement2() { + let author = "Test User " + let input = "let foo = bar" + let output = "// Created by \(author)\n\nlet foo = bar" + let fileInfo = FileInfo(replacements: [.author: .constant(author)]) + let options = FormatOptions(fileHeader: "// Created by {author}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderMultipleReplacement() { + let name = "Test User" + let input = "let foo = bar" + let output = "// Copyright © \(name)\n// Created by \(name)\n\nlet foo = bar" + let fileInfo = FileInfo(replacements: [.authorName: .constant(name)]) + let options = FormatOptions(fileHeader: "// Copyright © {author.name}\n// Created by {author.name}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderCreationDateReplacement() { + let input = "let foo = bar" + let date = Date(timeIntervalSince1970: 0) + let output: String = { + let formatter = DateFormatter() + formatter.dateStyle = .short + formatter.timeStyle = .none + return "// Created by Nick Lockwood on \(formatter.string(from: date)).\n\nlet foo = bar" + }() + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderDateFormattingIso() { + let date = createTestDate("2023-08-09") + + let input = "let foo = bar" + let output = "// 2023-08-09\n\nlet foo = bar" + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// {created}", dateFormat: .iso, fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderDateFormattingDayMonthYear() { + let date = createTestDate("2023-08-09") + + let input = "let foo = bar" + let output = "// 09/08/2023\n\nlet foo = bar" + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// {created}", dateFormat: .dayMonthYear, fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderDateFormattingMonthDayYear() { + let date = createTestDate("2023-08-09") + + let input = "let foo = bar" + let output = "// 08/09/2023\n\nlet foo = bar" + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// {created}", + dateFormat: .monthDayYear, + fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderDateFormattingCustom() { + let date = createTestDate("2023-08-09T12:59:30.345Z", .timestamp) + + let input = "let foo = bar" + let output = "// 23.08.09-12.59.30.345\n\nlet foo = bar" + let fileInfo = FileInfo(creationDate: date) + let options = FormatOptions(fileHeader: "// {created}", + dateFormat: .custom("yy.MM.dd-HH.mm.ss.SSS"), + timeZone: .identifier("UTC"), + fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderFileReplacement() { + let input = "let foo = bar" + let output = "// MyFile.swift\n\nlet foo = bar" + let fileInfo = FileInfo(filePath: "~/MyFile.swift") + let options = FormatOptions(fileHeader: "// {file}", fileInfo: fileInfo) + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testEdgeCaseHeaderEndIndexPlusNewHeaderTokensCountEqualsFileTokensEndIndex() { + let input = "// Header comment\n\nclass Foo {}" + let output = "// Header line1\n// Header line2\n\nclass Foo {}" + let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderBlankLineNotRemovedBeforeFollowingComment() { + let input = """ + // + // Header + // + + // Something else... + """ + let options = FormatOptions(fileHeader: "//\n// Header\n//") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testFileHeaderBlankLineNotRemovedBeforeFollowingComment2() { + let input = """ + // + // Header + // + + // + // Something else... + // + """ + let options = FormatOptions(fileHeader: "//\n// Header\n//") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testFileHeaderRemovedAfterHashbang() { + let input = """ + #!/usr/bin/swift + + // Header line1 + // Header line2 + + let foo = 5 + """ + let output = """ + #!/usr/bin/swift + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testFileHeaderPlacedAfterHashbang() { + let input = """ + #!/usr/bin/swift + + let foo = 5 + """ + let output = """ + #!/usr/bin/swift + + // Header line1 + // Header line2 + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") + testFormatting(for: input, output, rule: .fileHeader, options: options) + } + + func testBlankLineAfterHashbangNotRemovedByFileHeader() { + let input = """ + #!/usr/bin/swift + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testLineAfterHashbangNotAffectedByFileHeaderRemoval() { + let input = """ + #!/usr/bin/swift + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testDisableFileHeaderCommentRespectedAfterHashbang() { + let input = """ + #!/usr/bin/swift + // swiftformat:disable fileHeader + + // Header line1 + // Header line2 + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + func testDisableFileHeaderCommentRespectedAfterHashbang2() { + let input = """ + #!/usr/bin/swift + + // swiftformat:disable fileHeader + // Header line1 + // Header line2 + + let foo = 5 + """ + let options = FormatOptions(fileHeader: "") + testFormatting(for: input, rule: .fileHeader, options: options) + } + + private func testTimeZone( + timeZone: FormatTimeZone, + tests: [String: String] + ) { + for (input, expected) in tests { + let date = createTestDate(input, .time) + let input = "let foo = bar" + let output = "// \(expected)\n\nlet foo = bar" + + let fileInfo = FileInfo(creationDate: date) + + let options = FormatOptions( + fileHeader: "// {created}", + dateFormat: .custom("HH:mm"), + timeZone: timeZone, + fileInfo: fileInfo + ) + + testFormatting(for: input, output, + rule: .fileHeader, + options: options) + } + } + + func testFileHeaderDateTimeZoneSystem() { + let baseDate = createTestDate("15:00Z", .time) + let offset = TimeZone.current.secondsFromGMT(for: baseDate) + + let date = baseDate.addingTimeInterval(Double(offset)) + + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm" + formatter.timeZone = TimeZone(secondsFromGMT: 0) + + let expected = formatter.string(from: date) + + testTimeZone(timeZone: .system, tests: [ + "15:00Z": expected, + "16:00+1": expected, + "01:00+10": expected, + "16:30+0130": expected, + ]) + } + + func testFileHeaderDateTimeZoneAbbreviations() { + // GMT+0530 + testTimeZone(timeZone: FormatTimeZone(rawValue: "IST")!, tests: [ + "15:00Z": "20:30", + "16:00+1": "20:30", + "01:00+10": "20:30", + "16:30+0130": "20:30", + ]) + } + + func testFileHeaderDateTimeZoneIdentifiers() { + // GMT+0845 + testTimeZone(timeZone: FormatTimeZone(rawValue: "Australia/Eucla")!, tests: [ + "15:00Z": "23:45", + "16:00+1": "23:45", + "01:00+10": "23:45", + "16:30+0130": "23:45", + ]) + } + + func testGitHelpersReturnsInfo() { + let info = GitFileInfo(url: URL(fileURLWithPath: #file)) + XCTAssertNotNil(info?.authorName) + XCTAssertNotNil(info?.authorEmail) + XCTAssertNotNil(info?.creationDate) + } + + func testFileHeaderRuleThrowsIfCreationDateUnavailable() { + let input = "let foo = bar" + let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: FileInfo()) + XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) + } + + func testFileHeaderRuleThrowsIfFileNameUnavailable() { + let input = "let foo = bar" + let options = FormatOptions(fileHeader: "// {file}.", fileInfo: FileInfo()) + XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) + } +} + +private enum TestDateFormat: String { + case basic = "yyyy-MM-dd" + case time = "HH:mmZZZZZ" + case timestamp = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" +} + +private func createTestDate( + _ input: String, + _ format: TestDateFormat = .basic +) -> Date { + let formatter = DateFormatter() + formatter.dateFormat = format.rawValue + formatter.timeZone = .current + + return formatter.date(from: input)! +} diff --git a/Tests/Rules/GenericExtensionsTests.swift b/Tests/Rules/GenericExtensionsTests.swift new file mode 100644 index 000000000..99bea6fe9 --- /dev/null +++ b/Tests/Rules/GenericExtensionsTests.swift @@ -0,0 +1,121 @@ +// +// GenericExtensionsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class GenericExtensionsTests: XCTestCase { + func testUpdatesArrayGenericExtensionToAngleBracketSyntax() { + let input = "extension Array where Element == Foo {}" + let output = "extension Array {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testUpdatesOptionalGenericExtensionToAngleBracketSyntax() { + let input = "extension Optional where Wrapped == Foo {}" + let output = "extension Optional {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testUpdatesArrayGenericExtensionToAngleBracketSyntaxWithSelf() { + let input = "extension Array where Self.Element == Foo {}" + let output = "extension Array {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testUpdatesArrayWithGenericElement() { + let input = "extension Array where Element == Foo {}" + let output = "extension Array> {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testUpdatesDictionaryGenericExtensionToAngleBracketSyntax() { + let input = "extension Dictionary where Key == Foo, Value == Bar {}" + let output = "extension Dictionary {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testRequiresAllGenericTypesToBeProvided() { + // No type provided for `Value`, so we can't use the angle bracket syntax + let input = "extension Dictionary where Key == Foo {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .genericExtensions, options: options) + } + + func testHandlesNestedCollectionTypes() { + let input = "extension Array where Element == [[Foo: Bar]] {}" + let output = "extension Array<[[Foo: Bar]]> {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + } + + func testDoesntUpdateIneligibleConstraints() { + // This could potentially by `extension Optional` in a future language version + // but that syntax isn't implemented as of Swift 5.7 + let input = "extension Optional where Wrapped: Fooable {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .genericExtensions, options: options) + } + + func testPreservesOtherConstraintsInWhereClause() { + let input = "extension Collection where Element == String, Index == Int {}" + let output = "extension Collection where Index == Int {}" + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .genericExtensions, options: options) + } + + func testSupportsUserProvidedGenericTypes() { + let input = """ + extension StateStore where State == FooState, Action == FooAction {} + extension LinkedList where Element == Foo {} + """ + let output = """ + extension StateStore {} + extension LinkedList {} + """ + + let options = FormatOptions( + genericTypes: "LinkedList;StateStore", + swiftVersion: "5.7" + ) + testFormatting(for: input, output, rule: .genericExtensions, options: options) + } + + func testSupportsMultilineUserProvidedGenericTypes() { + let input = """ + extension Reducer where + State == MyFeatureState, + Action == MyFeatureAction, + Environment == ApplicationEnvironment + {} + """ + let output = """ + extension Reducer {} + """ + + let options = FormatOptions( + genericTypes: "Reducer", + swiftVersion: "5.7" + ) + testFormatting(for: input, output, rule: .genericExtensions, options: options) + } +} diff --git a/Tests/Rules/HeaderFileNameTests.swift b/Tests/Rules/HeaderFileNameTests.swift new file mode 100644 index 000000000..fb469303a --- /dev/null +++ b/Tests/Rules/HeaderFileNameTests.swift @@ -0,0 +1,27 @@ +// +// HeaderFileNameTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class HeaderFileNameTests: XCTestCase { + func testHeaderFileNameReplaced() { + let input = """ + // MyFile.swift + + let foo = bar + """ + let output = """ + // YourFile.swift + + let foo = bar + """ + let options = FormatOptions(fileInfo: FileInfo(filePath: "~/YourFile.swift")) + testFormatting(for: input, output, rule: .headerFileName, options: options) + } +} diff --git a/Tests/Rules/HoistAwaitTests.swift b/Tests/Rules/HoistAwaitTests.swift new file mode 100644 index 000000000..e1c9a5626 --- /dev/null +++ b/Tests/Rules/HoistAwaitTests.swift @@ -0,0 +1,234 @@ +// +// HoistAwaitTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class HoistAwaitTests: XCTestCase { + func testHoistAwait() { + let input = "greet(await name, await surname)" + let output = "await greet(name, surname)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitInsideIf() { + let input = "if !(await isSomething()) {}" + let output = "if await !(isSomething()) {}" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), + exclude: [.redundantParens]) + } + + func testHoistAwaitInsideArgument() { + let input = """ + array.append(contentsOf: try await asyncFunction(param1: param1)) + """ + let output = """ + await array.append(contentsOf: try asyncFunction(param1: param1)) + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testHoistAwaitInsideStringInterpolation() { + let input = "\"\\(replace(regex: await something()))\"" + let output = "await \"\\(replace(regex: something()))\"" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitInsideStringInterpolation2() { + let input = """ + "Hello \\(try await someValue())" + """ + let output = """ + await "Hello \\(try someValue())" + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testNoHoistAwaitInsideDo() { + let input = """ + do { + rg.box.seal(.fulfilled(await body(error))) + } + """ + let output = """ + do { + await rg.box.seal(.fulfilled(body(error))) + } + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testNoHoistAwaitInsideDoThrows() { + let input = """ + do throws(Foo) { + rg.box.seal(.fulfilled(await body(error))) + } + """ + let output = """ + do throws(Foo) { + await rg.box.seal(.fulfilled(body(error))) + } + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitInExpressionWithNoSpaces() { + let input = "let foo=bar(contentsOf:await baz())" + let output = "let foo=await bar(contentsOf:baz())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.spaceAroundOperators]) + } + + func testHoistAwaitInExpressionWithExcessSpaces() { + let input = "let foo = bar ( contentsOf: await baz() )" + let output = "let foo = await bar ( contentsOf: baz() )" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), + exclude: [.spaceAroundParens, .spaceInsideParens]) + } + + func testHoistAwaitWithReturn() { + let input = "return .enumCase(try await service.greet())" + let output = "return await .enumCase(try service.greet())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testHoistDeeplyNestedAwaits() { + let input = "let foo = (bar: (5, (await quux(), 6)), baz: (7, quux: await quux()))" + let output = "let foo = await (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testAwaitNotHoistedOutOfClosure() { + let input = "let foo = { (await bar(), 5) }" + let output = "let foo = { await (bar(), 5) }" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testAwaitNotHoistedOutOfClosureWithArguments() { + let input = "let foo = { bar in (await baz(bar), 5) }" + let output = "let foo = { bar in await (baz(bar), 5) }" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testAwaitNotHoistedOutOfForCondition() { + let input = "for foo in bar(await baz()) {}" + let output = "for foo in await bar(baz()) {}" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testAwaitNotHoistedOutOfForIndex() { + let input = "for await foo in asyncSequence() {}" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitWithInitAssignment() { + let input = "let variable = String(try await asyncFunction())" + let output = "let variable = await String(try asyncFunction())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testHoistAwaitWithAssignment() { + let input = "let variable = (try await asyncFunction())" + let output = "let variable = await (try asyncFunction())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) + } + + func testHoistAwaitInRedundantScopePriorToNumber() { + let input = """ + let identifiersTypes = 1 + (try? await asyncFunction(param1: param1)) + """ + let output = """ + let identifiersTypes = 1 + await (try? asyncFunction(param1: param1)) + """ + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitOnlyOne() { + let input = "greet(name, await surname)" + let output = "await greet(name, surname)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitRedundantAwait() { + let input = "await greet(await name, await surname)" + let output = "await greet(name, surname)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testHoistAwaitDoesNothing() { + let input = "await greet(name, surname)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testNoHoistAwaitBeforeTry() { + let input = "try foo(await bar())" + let output = "try await foo(bar())" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5")) + } + + func testNoHoistAwaitInCapturingFunction() { + let input = "foo(await bar)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) + } + + func testNoHoistSecondArgumentAwaitInCapturingFunction() { + let input = "foo(bar, await baz)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) + } + + func testHoistAwaitAfterOrdinaryOperator() { + let input = "let foo = bar + (await baz)" + let output = "let foo = await bar + (baz)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.redundantParens]) + } + + func testHoistAwaitAfterUnknownOperator() { + let input = "let foo = bar ??? (await baz)" + let output = "let foo = await bar ??? (baz)" + testFormatting(for: input, output, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.redundantParens]) + } + + func testNoHoistAwaitAfterCapturingOperator() { + let input = "let foo = await bar ??? (await baz)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(asyncCapturing: ["???"], swiftVersion: "5.5")) + } + + func testNoHoistAwaitInMacroArgument() { + let input = "#expect (await monitor.isAvailable == false)" + testFormatting(for: input, rule: .hoistAwait, + options: FormatOptions(swiftVersion: "5.5"), exclude: [.spaceAroundParens]) + } +} diff --git a/Tests/Rules/HoistPatternLetTests.swift b/Tests/Rules/HoistPatternLetTests.swift new file mode 100644 index 000000000..9220f1a6b --- /dev/null +++ b/Tests/Rules/HoistPatternLetTests.swift @@ -0,0 +1,423 @@ +// +// HoistPatternLetTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class HoistPatternLetTests: XCTestCase { + // hoist = true + + func testHoistCaseLet() { + let input = "if case .foo(let bar, let baz) = quux {}" + let output = "if case let .foo(bar, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistLabelledCaseLet() { + let input = "if case .foo(bar: let bar, baz: let baz) = quux {}" + let output = "if case let .foo(bar: bar, baz: baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistCaseVar() { + let input = "if case .foo(var bar, var baz) = quux {}" + let output = "if case var .foo(bar, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testNoHoistMixedCaseLetVar() { + let input = "if case .foo(let bar, var baz) = quux {}" + testFormatting(for: input, rule: .hoistPatternLet) + } + + func testNoHoistIfFirstArgSpecified() { + let input = "if case .foo(bar, let baz) = quux {}" + testFormatting(for: input, rule: .hoistPatternLet) + } + + func testNoHoistIfLastArgSpecified() { + let input = "if case .foo(let bar, baz) = quux {}" + testFormatting(for: input, rule: .hoistPatternLet) + } + + func testHoistIfArgIsNumericLiteral() { + let input = "if case .foo(5, let baz) = quux {}" + let output = "if case let .foo(5, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistIfArgIsEnumCaseLiteral() { + let input = "if case .foo(.bar, let baz) = quux {}" + let output = "if case let .foo(.bar, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistIfArgIsNamespacedEnumCaseLiteralInParens() { + let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" + let output = "switch foo {\ncase let (Foo.bar(baz)):\n}" + testFormatting(for: input, output, rule: .hoistPatternLet, exclude: [.redundantParens]) + } + + func testHoistIfFirstArgIsUnderscore() { + let input = "if case .foo(_, let baz) = quux {}" + let output = "if case let .foo(_, baz) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistIfSecondArgIsUnderscore() { + let input = "if case .foo(let baz, _) = quux {}" + let output = "if case let .foo(baz, _) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testNestedHoistLet() { + let input = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" + let output = "if case let (.foo(a, b), .bar(c, d)) = quux {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistCommaSeparatedSwitchCaseLets() { + let input = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" + let output = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" + testFormatting(for: input, output, rule: .hoistPatternLet, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testHoistNewlineSeparatedSwitchCaseLets() { + let input = """ + switch foo { + case .foo(let bar), + .bar(let bar): + } + """ + + let output = """ + switch foo { + case let .foo(bar), + let .bar(bar): + } + """ + + testFormatting(for: input, output, rule: .hoistPatternLet, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testHoistCatchLet() { + let input = "do {} catch Foo.foo(bar: let bar) {}" + let output = "do {} catch let Foo.foo(bar: bar) {}" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testNoNestedHoistLetWithSpecifiedArgs() { + let input = "if case (.foo(let a, b), .bar(let c, d)) = quux {}" + testFormatting(for: input, rule: .hoistPatternLet) + } + + func testNoHoistClosureVariables() { + let input = "foo({ let bar = 5 })" + testFormatting(for: input, rule: .hoistPatternLet, exclude: [.trailingClosures]) + } + + // TODO: this should actually hoist the let, but that's tricky to implement without + // breaking the `testNoOverHoistSwitchCaseWithNestedParens` case + func testHoistSwitchCaseWithNestedParens() { + let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), Foo.bar): break\n}" + testFormatting(for: input, rule: .hoistPatternLet, + exclude: [.blankLineAfterImports]) + } + + // TODO: this could actually hoist the let by one level, but that's tricky to implement + func testNoOverHoistSwitchCaseWithNestedParens() { + let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), bar): break\n}" + testFormatting(for: input, rule: .hoistPatternLet, + exclude: [.blankLineAfterImports]) + } + + func testNoHoistLetWithEmptArg() { + let input = "if .foo(let _) = bar {}" + testFormatting(for: input, rule: .hoistPatternLet, + exclude: [.redundantLet, .redundantPattern]) + } + + func testHoistLetWithNoSpaceAfterCase() { + let input = "switch x { case.some(let y): return y }" + let output = "switch x { case let .some(y): return y }" + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testHoistWrappedGuardCaseLet() { + let input = """ + guard case Foo + .bar(let baz) + else { + return + } + """ + let output = """ + guard case let Foo + .bar(baz) + else { + return + } + """ + testFormatting(for: input, output, rule: .hoistPatternLet) + } + + func testNoHoistCaseLetContainingGenerics() { + // Hoisting in this case causes a compilation error as-of Swift 5.3 + // See: https://github.com/nicklockwood/SwiftFormat/issues/768 + let input = "if case .some(Optional.some(let foo)) = bar else {}" + testFormatting(for: input, rule: .hoistPatternLet, exclude: [.typeSugar]) + } + + // hoist = false + + func testUnhoistCaseLet() { + let input = "if case let .foo(bar, baz) = quux {}" + let output = "if case .foo(let bar, let baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCaseLetDictionaryTuple() { + let input = """ + switch (a, b) { + case let (c as [String: Any], d as [String: Any]): + break + } + """ + let output = """ + switch (a, b) { + case (let c as [String: Any], let d as [String: Any]): + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistLabelledCaseLet() { + let input = "if case let .foo(bar: bar, baz: baz) = quux {}" + let output = "if case .foo(bar: let bar, baz: let baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCaseVar() { + let input = "if case var .foo(bar, baz) = quux {}" + let output = "if case .foo(var bar, var baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistGuardCaseLetFollowedByFunction() { + let input = """ + guard case let foo as Foo = bar else { return } + foo.bar(foo: bar) + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options, + exclude: [.wrapConditionalBodies]) + } + + func testNoUnhoistSwitchCaseLetFollowedByWhere() { + let input = """ + switch foo { + case let bar? where bar >= baz(quux): + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistSwitchCaseLetFollowedByAs() { + let input = """ + switch foo { + case let bar as (String, String): + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testUnhoistSingleCaseLet() { + let input = "if case let .foo(bar) = quux {}" + let output = "if case .foo(let bar) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistIfArgIsEnumCaseLiteral() { + let input = "if case let .foo(.bar, baz) = quux {}" + let output = "if case .foo(.bar, let baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistIfArgIsEnumCaseLiteralInParens() { + let input = "switch foo {\ncase let (.bar(baz)):\n}" + let output = "switch foo {\ncase (.bar(let baz)):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, + exclude: [.redundantParens]) + } + + func testUnhoistIfArgIsNamespacedEnumCaseLiteral() { + let input = "switch foo {\ncase let Foo.bar(baz):\n}" + let output = "switch foo {\ncase Foo.bar(let baz):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { + let input = "switch foo {\ncase let (Foo.bar(baz)):\n}" + let output = "switch foo {\ncase (Foo.bar(let baz)):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, + exclude: [.redundantParens]) + } + + func testUnhoistIfArgIsUnderscore() { + let input = "if case let .foo(_, baz) = quux {}" + let output = "if case .foo(_, let baz) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testNestedUnhoistLet() { + let input = "if case let (.foo(a, b), .bar(c, d)) = quux {}" + let output = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCommaSeparatedSwitchCaseLets() { + let input = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" + let output = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testUnhoistCommaSeparatedSwitchCaseLets2() { + let input = "switch foo {\ncase let Foo.foo(bar), let Foo.bar(bar):\n}" + let output = "switch foo {\ncase Foo.foo(let bar), Foo.bar(let bar):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testUnhoistCatchLet() { + let input = "do {} catch let Foo.foo(bar: bar) {}" + let output = "do {} catch Foo.foo(bar: let bar) {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistTupleLet() { + let input = "let (bar, baz) = quux()" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistIfLetTuple() { + let input = "if let x = y, let (_, a) = z {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistIfCaseFollowedByLetTuple() { + let input = "if case .foo = bar, let (foo, bar) = baz {}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testNoUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { + let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options, + exclude: [.redundantParens]) + } + + func testNoDeleteCommentWhenUnhoistingWrappedLet() { + let input = """ + switch foo { + case /* next */ let .bar(bar): + } + """ + + let output = """ + switch foo { + case /* next */ .bar(let bar): + } + """ + + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, + options: options, exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testMultilineGuardLet() { + let input = """ + guard + let first = response?.first, + let last = response?.last, + case .foo(token: let foo, provider: let bar) = first, + case .foo(token: let baz, provider: let quux) = last + else { + return + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCaseWithNilValue() { + let input = """ + switch (foo, bar) { + case let (.some(unwrappedFoo), nil): + print(unwrappedFoo) + default: + break + } + """ + let output = """ + switch (foo, bar) { + case (.some(let unwrappedFoo), nil): + print(unwrappedFoo) + default: + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } + + func testUnhoistCaseWithBoolValue() { + let input = """ + switch (foo, bar) { + case let (.some(unwrappedFoo), false): + print(unwrappedFoo) + default: + break + } + """ + let output = """ + switch (foo, bar) { + case (.some(let unwrappedFoo), false): + print(unwrappedFoo) + default: + break + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, output, rule: .hoistPatternLet, options: options) + } +} diff --git a/Tests/Rules/HoistTryTests.swift b/Tests/Rules/HoistTryTests.swift new file mode 100644 index 000000000..6b13b844d --- /dev/null +++ b/Tests/Rules/HoistTryTests.swift @@ -0,0 +1,440 @@ +// +// HoistTryTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class HoistTryTests: XCTestCase { + func testHoistTry() { + let input = "greet(try name(), try surname())" + let output = "try greet(name(), surname())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryWithOptionalTry() { + let input = "greet(try name(), try? surname())" + let output = "try greet(name(), try? surname())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation() { + let input = "\"\\(replace(regex: try something()))\"" + let output = "try \"\\(replace(regex: something()))\"" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation2() { + let input = """ + "Hello \\(try await someValue())" + """ + let output = """ + try "Hello \\(await someValue())" + """ + testFormatting(for: input, output, rule: .hoistTry, + options: FormatOptions(swiftVersion: "5.5"), + exclude: [.hoistAwait]) + } + + func testHoistTryInsideStringInterpolation3() { + let input = """ + let text = "\"" + abc + \\(try bar()) + xyz + "\"" + """ + let output = """ + let text = try "\"" + abc + \\(bar()) + xyz + "\"" + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation4() { + let input = """ + let str = "&enrolments[\\(index)][userid]=\\(try Foo.tryMe())" + """ + let output = """ + let str = try "&enrolments[\\(index)][userid]=\\(Foo.tryMe())" + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation5() { + let input = """ + return str + + "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + + "&enrolments[\\(index)][userid]=\\(try user.requireMoodleID())" + """ + let output = """ + return try str + + "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + + "&enrolments[\\(index)][userid]=\\(user.requireMoodleID())" + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideStringInterpolation6() { + let input = #""" + """ + let \(object.varName) = + \(tripleQuote) + \(try encode(object.object)) + \(tripleQuote) + """ + """# + let output = #""" + try """ + let \(object.varName) = + \(tripleQuote) + \(encode(object.object)) + \(tripleQuote) + """ + """# + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideArgument() { + let input = """ + array.append(contentsOf: try await asyncFunction(param1: param1)) + """ + let output = """ + try array.append(contentsOf: await asyncFunction(param1: param1)) + """ + testFormatting(for: input, output, rule: .hoistTry, exclude: [.hoistAwait]) + } + + func testNoHoistTryInsideXCTAssert() { + let input = "XCTAssertFalse(try foo())" + testFormatting(for: input, rule: .hoistTry) + } + + func testNoMergeTrysInsideXCTAssert() { + let input = "XCTAssertEqual(try foo(), try bar())" + testFormatting(for: input, rule: .hoistTry) + } + + func testNoHoistTryInsideDo() { + let input = "do { rg.box.seal(.fulfilled(try body(error))) }" + let output = "do { try rg.box.seal(.fulfilled(body(error))) }" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryInsideDoThrows() { + let input = "do throws(Foo) { rg.box.seal(.fulfilled(try body(error))) }" + let output = "do throws(Foo) { try rg.box.seal(.fulfilled(body(error))) }" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryInsideMultilineDo() { + let input = """ + do { + rg.box.seal(.fulfilled(try body(error))) + } + """ + let output = """ + do { + try rg.box.seal(.fulfilled(body(error))) + } + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistedTryPlacedBeforeAwait() { + let input = "let foo = await bar(contentsOf: try baz())" + let output = "let foo = try await bar(contentsOf: baz())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInExpressionWithNoSpaces() { + let input = "let foo=bar(contentsOf:try baz())" + let output = "let foo=try bar(contentsOf:baz())" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.spaceAroundOperators]) + } + + func testHoistTryInExpressionWithExcessSpaces() { + let input = "let foo = bar ( contentsOf: try baz() )" + let output = "let foo = try bar ( contentsOf: baz() )" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.spaceAroundParens, .spaceInsideParens]) + } + + func testHoistTryWithReturn() { + let input = "return .enumCase(try await service.greet())" + let output = "return try .enumCase(await service.greet())" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.hoistAwait]) + } + + func testHoistDeeplyNestedTrys() { + let input = "let foo = (bar: (5, (try quux(), 6)), baz: (7, quux: try quux()))" + let output = "let foo = try (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testTryNotHoistedOutOfClosure() { + let input = "let foo = { (try bar(), 5) }" + let output = "let foo = { try (bar(), 5) }" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testTryNotHoistedOutOfClosureWithArguments() { + let input = "let foo = { bar in (try baz(bar), 5) }" + let output = "let foo = { bar in try (baz(bar), 5) }" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testTryNotHoistedOutOfForCondition() { + let input = "for foo in bar(try baz()) {}" + let output = "for foo in try bar(baz()) {}" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryWithInitAssignment() { + let input = "let variable = String(try await asyncFunction())" + let output = "let variable = try String(await asyncFunction())" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.hoistAwait]) + } + + func testHoistTryWithAssignment() { + let input = "let variable = (try await asyncFunction())" + let output = "let variable = try (await asyncFunction())" + testFormatting(for: input, output, rule: .hoistTry, + exclude: [.hoistAwait]) + } + + func testHoistTryOnlyOne() { + let input = "greet(name, try surname())" + let output = "try greet(name, surname())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryRedundantTry() { + let input = "try greet(try name(), try surname())" + let output = "try greet(name(), surname())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryWithAwaitOnDifferentStatement() { + let input = """ + let asyncVariable = try await performSomething() + return Foo(param1: try param1()) + """ + let output = """ + let asyncVariable = try await performSomething() + return try Foo(param1: param1()) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryDoubleParens() { + let input = """ + array.append((value: try compute())) + """ + let output = """ + try array.append((value: compute())) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryDoesNothing() { + let input = "try greet(name, surname)" + testFormatting(for: input, rule: .hoistTry) + } + + func testHoistOptionalTryDoesNothing() { + let input = "try? greet(name, surname)" + testFormatting(for: input, rule: .hoistTry) + } + + func testHoistedTryOnLineBeginningWithInfixDot() { + let input = """ + let foo = bar() + .baz(try quux()) + """ + let output = """ + let foo = try bar() + .baz(quux()) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistedTryOnLineBeginningWithInfixPlus() { + let input = """ + let foo = bar() + + baz(try quux()) + """ + let output = """ + let foo = try bar() + + baz(quux()) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistedTryOnLineBeginningWithPrefixOperator() { + let input = """ + foo() + !bar(try quux()) + """ + let output = """ + foo() + try !bar(quux()) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryIntoPreviousLineEndingWithPostfixOperator() { + let input = """ + let foo = bar! + (try baz(), quux()).foo() + """ + let output = """ + let foo = bar! + try (baz(), quux()).foo() + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryInCapturingFunction() { + let input = "foo(try bar)" + testFormatting(for: input, rule: .hoistTry, + options: FormatOptions(throwCapturing: ["foo"])) + } + + func testNoHoistSecondArgumentTryInCapturingFunction() { + let input = "foo(bar, try baz)" + testFormatting(for: input, rule: .hoistTry, + options: FormatOptions(throwCapturing: ["foo"])) + } + + func testNoHoistFailToTerminate() { + let input = """ + return ManyInitExample( + a: try Example(string: try throwingExample()), + b: try throwingExample(), + c: try throwingExample(), + d: try throwingExample(), + e: try throwingExample(), + f: try throwingExample(), + g: try throwingExample(), + h: try throwingExample(), + i: try throwingExample() + ) + """ + let output = """ + return try ManyInitExample( + a: Example(string: throwingExample()), + b: throwingExample(), + c: throwingExample(), + d: throwingExample(), + e: throwingExample(), + f: throwingExample(), + g: throwingExample(), + h: throwingExample(), + i: throwingExample() + ) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideOptionalFunction() { + let input = "foo?(try bar())" + let output = "try foo?(bar())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testNoHoistTryAfterOptionalTry() { + let input = "let foo = try? bar(try baz())" + testFormatting(for: input, rule: .hoistTry) + } + + func testHoistTryInsideOptionalSubscript() { + let input = "foo?[try bar()]" + let output = "try foo?[bar()]" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterGenericType() { + let input = "let foo = Tree.Foo(bar: try baz())" + let output = "let foo = try Tree.Foo(bar: baz())" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterArrayLiteral() { + let input = "if [.first, .second].contains(try foo()) {}" + let output = "if try [.first, .second].contains(foo()) {}" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterSubscript() { + let input = "if foo[5].bar(try baz()) {}" + let output = "if try foo[5].bar(baz()) {}" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideGenericInit() { + let input = """ + return Target( + file: try parseFile(path: $0) + ) + """ + let output = """ + return try Target( + file: parseFile(path: $0) + ) + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryInsideArrayClosure() { + let input = "foo[bar](try parseFile(path: $0))" + let output = "try foo[bar](parseFile(path: $0))" + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterString() { + let input = """ + let json = "{}" + + someFunction(try parse(json), "someKey") + """ + let output = """ + let json = "{}" + + try someFunction(parse(json), "someKey") + """ + testFormatting(for: input, output, rule: .hoistTry) + } + + func testHoistTryAfterMultilineString() { + let input = #""" + let json = """ + { + "foo": "bar" + } + """ + + someFunction(try parse(json), "someKey") + """# + let output = #""" + let json = """ + { + "foo": "bar" + } + """ + + try someFunction(parse(json), "someKey") + """# + testFormatting(for: input, output, rule: .hoistTry) + } +} diff --git a/Tests/RulesTests+Indentation.swift b/Tests/Rules/IndentTests.swift similarity index 99% rename from Tests/RulesTests+Indentation.swift rename to Tests/Rules/IndentTests.swift index b33aa0b0e..ec01d4d07 100644 --- a/Tests/RulesTests+Indentation.swift +++ b/Tests/Rules/IndentTests.swift @@ -1,17 +1,15 @@ // -// RulesTests+Indentation.swift +// IndentTests.swift // SwiftFormatTests // -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. // import XCTest @testable import SwiftFormat -class IndentTests: RulesTests { - // MARK: - indent - +class IndentTests: XCTestCase { func testReduceIndentAtStartOfFile() { let input = " foo()" let output = "foo()" @@ -3702,9 +3700,8 @@ class IndentTests: RulesTests { \tquux() } """ - let rules: [FormatRule] = [.indent, .trailingSpace] let options = FormatOptions(indent: "\t", truncateBlankLines: true, tabWidth: 2) - XCTAssertEqual(try lint(input, rules: rules, options: options), [ + XCTAssertEqual(try lint(input, rules: [.indent, .trailingSpace], options: options), [ Formatter.Change(line: 3, rule: .trailingSpace, filePath: nil), ]) } @@ -4096,4 +4093,21 @@ class IndentTests: RulesTests { let options = FormatOptions() testFormatting(for: input, rule: .indent, options: options, exclude: [.wrapConditionalBodies, .andOperator, .redundantParens]) } + + func testWrappedTernaryOperatorIndentsChainedCalls() { + let input = """ + let ternary = condition + ? values + .map { $0.bar } + .filter { $0.hasFoo } + .last + : other.values + .compactMap { $0 } + .first? + .with(property: updatedValue) + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, rule: .indent, options: options) + } } diff --git a/Tests/Rules/InitCoderUnavailableTests.swift b/Tests/Rules/InitCoderUnavailableTests.swift new file mode 100644 index 000000000..d3b953dae --- /dev/null +++ b/Tests/Rules/InitCoderUnavailableTests.swift @@ -0,0 +1,143 @@ +// +// InitCoderUnavailableTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class InitCoderUnavailableTests: XCTestCase { + func testInitCoderUnavailableEmptyFunction() { + let input = """ + struct A: UIView { + required init?(coder aDecoder: NSCoder) {} + } + """ + let output = """ + struct A: UIView { + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) {} + } + """ + testFormatting(for: input, output, rule: .initCoderUnavailable, + exclude: [.unusedArguments]) + } + + func testInitCoderUnavailableFatalErrorNilDisabled() { + let input = """ + extension Module { + final class A: UIView { + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + } + """ + let output = """ + extension Module { + final class A: UIView { + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + } + """ + let options = FormatOptions(initCoderNil: false) + testFormatting(for: input, output, rule: .initCoderUnavailable, options: options) + } + + func testInitCoderUnavailableFatalErrorNilEnabled() { + let input = """ + extension Module { + final class A: UIView { + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + } + """ + let output = """ + extension Module { + final class A: UIView { + @available(*, unavailable) + required init?(coder _: NSCoder) { + nil + } + } + } + """ + let options = FormatOptions(initCoderNil: true) + testFormatting(for: input, output, rule: .initCoderUnavailable, options: options) + } + + func testInitCoderUnavailableAlreadyPresent() { + let input = """ + extension Module { + final class A: UIView { + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError() + } + } + } + """ + testFormatting(for: input, rule: .initCoderUnavailable) + } + + func testInitCoderUnavailableImplemented() { + let input = """ + extension Module { + final class A: UIView { + required init?(coder aCoder: NSCoder) { + aCoder.doSomething() + } + } + } + """ + testFormatting(for: input, rule: .initCoderUnavailable) + } + + func testPublicInitCoderUnavailable() { + let input = """ + class Foo: UIView { + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + """ + let output = """ + class Foo: UIView { + @available(*, unavailable) + public required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + """ + testFormatting(for: input, output, rule: .initCoderUnavailable) + } + + func testPublicInitCoderUnavailable2() { + let input = """ + class Foo: UIView { + required public init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + } + """ + let output = """ + class Foo: UIView { + @available(*, unavailable) + required public init?(coder _: NSCoder) { + nil + } + } + """ + let options = FormatOptions(initCoderNil: true) + testFormatting(for: input, output, rule: .initCoderUnavailable, + options: options, exclude: [.modifierOrder]) + } +} diff --git a/Tests/Rules/IsEmptyTests.swift b/Tests/Rules/IsEmptyTests.swift new file mode 100644 index 000000000..4a4b5cb94 --- /dev/null +++ b/Tests/Rules/IsEmptyTests.swift @@ -0,0 +1,159 @@ +// +// IsEmptyTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class IsEmptyTests: XCTestCase { + // count == 0 + + func testCountEqualsZero() { + let input = "if foo.count == 0 {}" + let output = "if foo.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testFunctionCountEqualsZero() { + let input = "if foo().count == 0 {}" + let output = "if foo().isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testExpressionCountEqualsZero() { + let input = "if foo || bar.count == 0 {}" + let output = "if foo || bar.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCompoundIfCountEqualsZero() { + let input = "if foo, bar.count == 0 {}" + let output = "if foo, bar.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testOptionalCountEqualsZero() { + let input = "if foo?.count == 0 {}" + let output = "if foo?.isEmpty == true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testOptionalChainCountEqualsZero() { + let input = "if foo?.bar.count == 0 {}" + let output = "if foo?.bar.isEmpty == true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCompoundIfOptionalCountEqualsZero() { + let input = "if foo, bar?.count == 0 {}" + let output = "if foo, bar?.isEmpty == true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testTernaryCountEqualsZero() { + let input = "foo ? bar.count == 0 : baz.count == 0" + let output = "foo ? bar.isEmpty : baz.isEmpty" + testFormatting(for: input, output, rule: .isEmpty) + } + + // count != 0 + + func testCountNotEqualToZero() { + let input = "if foo.count != 0 {}" + let output = "if !foo.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testFunctionCountNotEqualToZero() { + let input = "if foo().count != 0 {}" + let output = "if !foo().isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testExpressionCountNotEqualToZero() { + let input = "if foo || bar.count != 0 {}" + let output = "if foo || !bar.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCompoundIfCountNotEqualToZero() { + let input = "if foo, bar.count != 0 {}" + let output = "if foo, !bar.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + // count > 0 + + func testCountGreaterThanZero() { + let input = "if foo.count > 0 {}" + let output = "if !foo.isEmpty {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountExpressionGreaterThanZero() { + let input = "if a.count - b.count > 0 {}" + testFormatting(for: input, rule: .isEmpty) + } + + // optional count + + func testOptionalCountNotEqualToZero() { + let input = "if foo?.count != 0 {}" // nil evaluates to true + let output = "if foo?.isEmpty != true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testOptionalChainCountNotEqualToZero() { + let input = "if foo?.bar.count != 0 {}" // nil evaluates to true + let output = "if foo?.bar.isEmpty != true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCompoundIfOptionalCountNotEqualToZero() { + let input = "if foo, bar?.count != 0 {}" + let output = "if foo, bar?.isEmpty != true {}" + testFormatting(for: input, output, rule: .isEmpty) + } + + // edge cases + + func testTernaryCountNotEqualToZero() { + let input = "foo ? bar.count != 0 : baz.count != 0" + let output = "foo ? !bar.isEmpty : !baz.isEmpty" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountEqualsZeroAfterOptionalOnPreviousLine() { + let input = "_ = foo?.bar\nbar.count == 0 ? baz() : quux()" + let output = "_ = foo?.bar\nbar.isEmpty ? baz() : quux()" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountEqualsZeroAfterOptionalCallOnPreviousLine() { + let input = "foo?.bar()\nbar.count == 0 ? baz() : quux()" + let output = "foo?.bar()\nbar.isEmpty ? baz() : quux()" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountEqualsZeroAfterTrailingCommentOnPreviousLine() { + let input = "foo?.bar() // foobar\nbar.count == 0 ? baz() : quux()" + let output = "foo?.bar() // foobar\nbar.isEmpty ? baz() : quux()" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountGreaterThanZeroAfterOpenParen() { + let input = "foo(bar.count > 0)" + let output = "foo(!bar.isEmpty)" + testFormatting(for: input, output, rule: .isEmpty) + } + + func testCountGreaterThanZeroAfterArgumentLabel() { + let input = "foo(bar: baz.count > 0)" + let output = "foo(bar: !baz.isEmpty)" + testFormatting(for: input, output, rule: .isEmpty) + } +} diff --git a/Tests/Rules/LeadingDelimitersTests.swift b/Tests/Rules/LeadingDelimitersTests.swift new file mode 100644 index 000000000..4b4b37ea5 --- /dev/null +++ b/Tests/Rules/LeadingDelimitersTests.swift @@ -0,0 +1,48 @@ +// +// LeadingDelimitersTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class LeadingDelimitersTests: XCTestCase { + func testLeadingCommaMovedToPreviousLine() { + let input = """ + let foo = 5 + , bar = 6 + """ + let output = """ + let foo = 5, + bar = 6 + """ + testFormatting(for: input, output, rule: .leadingDelimiters) + } + + func testLeadingColonFollowedByCommentMovedToPreviousLine() { + let input = """ + let foo + : /* string */ String + """ + let output = """ + let foo: + /* string */ String + """ + testFormatting(for: input, output, rule: .leadingDelimiters) + } + + func testCommaMovedBeforeCommentIfLineEndsInComment() { + let input = """ + let foo = 5 // first + , bar = 6 + """ + let output = """ + let foo = 5, // first + bar = 6 + """ + testFormatting(for: input, output, rule: .leadingDelimiters) + } +} diff --git a/Tests/Rules/LinebreakAtEndOfFileTests.swift b/Tests/Rules/LinebreakAtEndOfFileTests.swift new file mode 100644 index 000000000..42ed63bdb --- /dev/null +++ b/Tests/Rules/LinebreakAtEndOfFileTests.swift @@ -0,0 +1,24 @@ +// +// LinebreakAtEndOfFileTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class LinebreakAtEndOfFileTests: XCTestCase { + func testLinebreakAtEndOfFile() { + let input = "foo\nbar" + let output = "foo\nbar\n" + testFormatting(for: input, output, rule: .linebreakAtEndOfFile) + } + + func testNoLinebreakAtEndOfFragment() { + let input = "foo\nbar" + let options = FormatOptions(fragment: true) + testFormatting(for: input, rule: .linebreakAtEndOfFile, options: options) + } +} diff --git a/Tests/Rules/LinebreaksTests.swift b/Tests/Rules/LinebreaksTests.swift new file mode 100644 index 000000000..28a93ee8a --- /dev/null +++ b/Tests/Rules/LinebreaksTests.swift @@ -0,0 +1,36 @@ +// +// LinebreaksTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class LinebreaksTests: XCTestCase { + func testCarriageReturn() { + let input = "foo\rbar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .linebreaks) + } + + func testCarriageReturnLinefeed() { + let input = "foo\r\nbar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .linebreaks) + } + + func testVerticalTab() { + let input = "foo\u{000B}bar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .linebreaks) + } + + func testFormfeed() { + let input = "foo\u{000C}bar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .linebreaks) + } +} diff --git a/Tests/Rules/MarkTypesTests.swift b/Tests/Rules/MarkTypesTests.swift new file mode 100644 index 000000000..e0b006a5c --- /dev/null +++ b/Tests/Rules/MarkTypesTests.swift @@ -0,0 +1,838 @@ +// +// MarkTypesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class MarkTypesTests: XCTestCase { + func testAddsMarkBeforeTypes() { + let input = """ + struct Foo {} + class Bar {} + enum Baz {} + protocol Quux {} + """ + + let output = """ + // MARK: - Foo + + struct Foo {} + + // MARK: - Bar + + class Bar {} + + // MARK: - Baz + + enum Baz {} + + // MARK: - Quux + + protocol Quux {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testDoesntAddMarkBeforeStructWithExistingMark() { + let input = """ + // MARK: - Foo + + struct Foo {} + extension Foo {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testCorrectsTypoInTypeMark() { + let input = """ + // mark: foo + + struct Foo {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + + struct Foo {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testUpdatesMarkAfterTypeIsRenamed() { + let input = """ + // MARK: - FooBarControllerFactory + + struct FooBarControllerBuilder {} + extension FooBarControllerBuilder {} + """ + + let output = """ + // MARK: - FooBarControllerBuilder + + struct FooBarControllerBuilder {} + extension FooBarControllerBuilder {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testAddsMarkBeforeTypeWithDocComment() { + let input = """ + /// This is a doc comment with several + /// lines of prose at the start + /// - And then, after the prose, + /// - a few bullet points just for fun + actor Foo {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + + /// This is a doc comment with several + /// lines of prose at the start + /// - And then, after the prose, + /// - a few bullet points just for fun + actor Foo {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testCustomTypeMark() { + let input = """ + struct Foo {} + extension Foo {} + """ + + let output = """ + // TYPE DEFINITION: Foo + + struct Foo {} + extension Foo {} + """ + + testFormatting( + for: input, output, rule: .markTypes, + options: FormatOptions(typeMarkComment: "TYPE DEFINITION: %t") + ) + } + + func testDoesNothingForExtensionWithoutProtocolConformance() { + let input = """ + extension Foo {} + extension Foo {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func preservesExistingCommentForExtensionWithNoConformances() { + let input = """ + // MARK: Description of extension + + extension Foo {} + extension Foo {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testAddsMarkCommentForExtensionWithConformance() { + let input = """ + extension Foo: BarProtocol {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + BarProtocol + + extension Foo: BarProtocol {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testUpdatesExtensionMarkToCorrectMark() { + let input = """ + // MARK: - BarProtocol + + extension Foo: BarProtocol {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + BarProtocol + + extension Foo: BarProtocol {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testAddsMarkCommentForExtensionWithMultipleConformances() { + let input = """ + extension Foo: BarProtocol, BazProtocol {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + BarProtocol, BazProtocol + + extension Foo: BarProtocol, BazProtocol {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testUpdatesMarkCommentWithCorrectConformances() { + let input = """ + // MARK: - Foo + BarProtocol + + extension Foo: BarProtocol, BazProtocol {} + extension Foo {} + """ + + let output = """ + // MARK: - Foo + BarProtocol, BazProtocol + + extension Foo: BarProtocol, BazProtocol {} + extension Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testCustomExtensionMarkComment() { + let input = """ + struct Foo {} + extension Foo: BarProtocol {} + extension String: BarProtocol {} + """ + + let output = """ + // MARK: - Foo + + struct Foo {} + + // EXTENSION: - BarProtocol + + extension Foo: BarProtocol {} + + // EXTENSION: - String: BarProtocol + + extension String: BarProtocol {} + """ + + testFormatting( + for: input, output, rule: .markTypes, + options: FormatOptions( + extensionMarkComment: "EXTENSION: - %t: %c", + groupedExtensionMarkComment: "EXTENSION: - %c" + ) + ) + } + + func testTypeAndExtensionMarksTogether() { + let input = """ + struct Foo {} + extension Foo: Bar {} + extension String: Bar {} + """ + + let output = """ + // MARK: - Foo + + struct Foo {} + + // MARK: Bar + + extension Foo: Bar {} + + // MARK: - String + Bar + + extension String: Bar {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testFullyQualifiedTypeNames() { + let input = """ + extension MyModule.Foo: MyModule.MyNamespace.BarProtocol, QuuxProtocol {} + extension MyModule.Foo {} + """ + + let output = """ + // MARK: - MyModule.Foo + MyModule.MyNamespace.BarProtocol, QuuxProtocol + + extension MyModule.Foo: MyModule.MyNamespace.BarProtocol, QuuxProtocol {} + extension MyModule.Foo {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testWhereClauseConformanceWithExactConstraint() { + let input = """ + extension Array: BarProtocol where Element == String {} + extension Array {} + """ + + let output = """ + // MARK: - Array + BarProtocol + + extension Array: BarProtocol where Element == String {} + extension Array {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testWhereClauseConformanceWithConformanceConstraint() { + let input = """ + extension Array: BarProtocol where Element: BarProtocol {} + extension Array {} + """ + + let output = """ + // MARK: - Array + BarProtocol + + extension Array: BarProtocol where Element: BarProtocol {} + extension Array {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testWhereClauseWithExactConstraint() { + let input = """ + extension Array where Element == String {} + extension Array {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testWhereClauseWithConformanceConstraint() { + let input = """ + // MARK: [BarProtocol] helpers + + extension Array where Element: BarProtocol {} + extension Rules {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testPlacesMarkAfterImports() { + let input = """ + import Foundation + import os + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + let output = """ + import Foundation + import os + + // MARK: - Rules + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testPlacesMarkAfterFileHeader() { + let input = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + let output = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + // MARK: - Rules + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testPlacesMarkAfterFileHeaderAndImports() { + let input = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + import Foundation + import os + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + let output = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + import Foundation + import os + + // MARK: - Rules + + /// All of SwiftFormat's Rule implementation + class Rules {} + extension Rules {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testDoesNothingIfOnlyOneDeclaration() { + let input = """ + // Created by Nick Lockwood on 12/08/2016. + // Copyright 2016 Nick Lockwood + + import Foundation + import os + + /// All of SwiftFormat's Rule implementation + class Rules {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testMultipleExtensionsOfSameType() { + let input = """ + extension Foo: BarProtocol {} + extension Foo: QuuxProtocol {} + """ + + let output = """ + // MARK: - Foo + BarProtocol + + extension Foo: BarProtocol {} + + // MARK: - Foo + QuuxProtocol + + extension Foo: QuuxProtocol {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testNeverMarkTypes() { + let input = """ + struct EmptyFoo {} + struct EmptyBar { } + struct EmptyBaz { + + } + struct Quux { + let foo = 1 + } + """ + + let options = FormatOptions(markTypes: .never) + testFormatting( + for: input, rule: .markTypes, options: options, + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] + ) + } + + func testMarkTypesIfNotEmpty() { + let input = """ + struct EmptyFoo {} + struct EmptyBar { } + struct EmptyBaz { + + } + struct Quux { + let foo = 1 + } + """ + + let output = """ + struct EmptyFoo {} + struct EmptyBar { } + struct EmptyBaz { + + } + + // MARK: - Quux + + struct Quux { + let foo = 1 + } + """ + + let options = FormatOptions(markTypes: .ifNotEmpty) + testFormatting( + for: input, output, rule: .markTypes, options: options, + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] + ) + } + + func testNeverMarkExtensions() { + let input = """ + extension EmptyFoo: FooProtocol {} + extension EmptyBar: BarProtocol { } + extension EmptyBaz: BazProtocol { + + } + extension Quux: QuuxProtocol { + let foo = 1 + } + """ + + let options = FormatOptions(markExtensions: .never) + testFormatting( + for: input, rule: .markTypes, options: options, + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] + ) + } + + func testMarkExtensionsIfNotEmpty() { + let input = """ + extension EmptyFoo: FooProtocol {} + extension EmptyBar: BarProtocol { } + extension EmptyBaz: BazProtocol { + + } + extension Quux: QuuxProtocol { + let foo = 1 + } + """ + + let output = """ + extension EmptyFoo: FooProtocol {} + extension EmptyBar: BarProtocol { } + extension EmptyBaz: BazProtocol { + + } + + // MARK: - Quux + QuuxProtocol + + extension Quux: QuuxProtocol { + let foo = 1 + } + """ + + let options = FormatOptions(markExtensions: .ifNotEmpty) + testFormatting( + for: input, output, rule: .markTypes, options: options, + exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] + ) + } + + func testMarkExtensionsDisabled() { + let input = """ + extension Foo: FooProtocol {} + + // swiftformat:disable markTypes + + extension Bar: BarProtocol {} + + // swiftformat:enable markTypes + + extension Baz: BazProtocol {} + + extension Quux: QuuxProtocol {} + """ + + let output = """ + // MARK: - Foo + FooProtocol + + extension Foo: FooProtocol {} + + // swiftformat:disable markTypes + + extension Bar: BarProtocol {} + + // MARK: - Baz + BazProtocol + + // swiftformat:enable markTypes + + extension Baz: BazProtocol {} + + // MARK: - Quux + QuuxProtocol + + extension Quux: QuuxProtocol {} + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testExtensionMarkWithImportOfSameName() { + let input = """ + import MagazineLayout + + // MARK: - MagazineLayout + FooProtocol + + extension MagazineLayout: FooProtocol {} + + // MARK: - MagazineLayout + BarProtocol + + extension MagazineLayout: BarProtocol {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testDoesntUseGroupedMarkTemplateWhenSeparatedByOtherType() { + let input = """ + // MARK: - MyComponent + + class MyComponent {} + + // MARK: - MyComponentContent + + struct MyComponentContent {} + + // MARK: - MyComponent + ContentConfigurableView + + extension MyComponent: ContentConfigurableView {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testUsesGroupedMarkTemplateWhenSeparatedByExtensionOfSameType() { + let input = """ + // MARK: - MyComponent + + class MyComponent {} + + // MARK: Equatable + + extension MyComponent: Equatable {} + + // MARK: ContentConfigurableView + + extension MyComponent: ContentConfigurableView {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testDoesntUseGroupedMarkTemplateWhenSeparatedByExtensionOfOtherType() { + let input = """ + // MARK: - MyComponent + + class MyComponent {} + + // MARK: - OtherComponent + Equatable + + extension OtherComponent: Equatable {} + + // MARK: - MyComponent + ContentConfigurableView + + extension MyComponent: ContentConfigurableView {} + """ + + testFormatting(for: input, rule: .markTypes) + } + + func testAddsMarkBeforeTypesWithNoBlankLineAfterMark() { + let input = """ + struct Foo {} + class Bar {} + enum Baz {} + protocol Quux {} + """ + + let output = """ + // MARK: - Foo + struct Foo {} + + // MARK: - Bar + class Bar {} + + // MARK: - Baz + enum Baz {} + + // MARK: - Quux + protocol Quux {} + """ + let options = FormatOptions(lineAfterMarks: false) + testFormatting(for: input, output, rule: .markTypes, options: options) + } + + func testAddsMarkForTypeInExtension() { + let input = """ + enum Foo {} + + extension Foo { + struct Bar { + let baaz: Baaz + } + } + """ + + let output = """ + // MARK: - Foo + + enum Foo {} + + // MARK: Foo.Bar + + extension Foo { + struct Bar { + let baaz: Baaz + } + } + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testDoesntAddsMarkForMultipleTypesInExtension() { + let input = """ + enum Foo {} + + extension Foo { + struct Bar { + let baaz: Baaz + } + + struct Quux { + let baaz: Baaz + } + } + """ + + let output = """ + // MARK: - Foo + + enum Foo {} + + extension Foo { + struct Bar { + let baaz: Baaz + } + + struct Quux { + let baaz: Baaz + } + } + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testAddsMarkForTypeInExtensionNotFollowingTypeBeingExtended() { + let input = """ + struct Baaz {} + + extension Foo { + struct Bar { + let baaz: Baaz + } + } + """ + + let output = """ + // MARK: - Baaz + + struct Baaz {} + + // MARK: - Foo.Bar + + extension Foo { + struct Bar { + let baaz: Baaz + } + } + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testHandlesMultipleLayersOfExtensionNesting() { + let input = """ + enum Foo {} + + extension Foo { + enum Bar {} + } + + extension Foo { + extension Bar { + struct Baaz { + let quux: Quux + } + } + } + """ + + let output = """ + // MARK: - Foo + + enum Foo {} + + // MARK: Foo.Bar + + extension Foo { + enum Bar {} + } + + // MARK: Foo.Bar.Baaz + + extension Foo { + extension Bar { + struct Baaz { + let quux: Quux + } + } + } + """ + + testFormatting(for: input, output, rule: .markTypes) + } + + func testMarkTypeLintReturnsErrorAsExpected() throws { + let input = """ + struct MyStruct {} + + extension MyStruct {} + """ + + // Initialize rule names + let _ = FormatRules.byName + let changes = try lint(input, rules: [.markTypes]) + XCTAssertEqual(changes, [ + .init(line: 1, rule: .markTypes, filePath: nil), + .init(line: 2, rule: .markTypes, filePath: nil), + ]) + } +} diff --git a/Tests/Rules/ModifierOrderTests.swift b/Tests/Rules/ModifierOrderTests.swift new file mode 100644 index 000000000..ac97a12ec --- /dev/null +++ b/Tests/Rules/ModifierOrderTests.swift @@ -0,0 +1,98 @@ +// +// ModifierOrderTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class ModifierOrderTests: XCTestCase { + func testVarModifiersCorrected() { + let input = "unowned private static var foo" + let output = "private unowned static var foo" + let options = FormatOptions(fragment: true) + testFormatting(for: input, output, rule: .modifierOrder, options: options) + } + + func testPrivateSetModifierNotMangled() { + let input = "private(set) public weak lazy var foo" + let output = "public private(set) lazy weak var foo" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testUnownedUnsafeModifierNotMangled() { + let input = "unowned(unsafe) lazy var foo" + let output = "lazy unowned(unsafe) var foo" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testPrivateRequiredStaticFuncModifiers() { + let input = "required static private func foo()" + let output = "private required static func foo()" + let options = FormatOptions(fragment: true) + testFormatting(for: input, output, rule: .modifierOrder, options: options) + } + + func testPrivateConvenienceInit() { + let input = "convenience private init()" + let output = "private convenience init()" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testSpaceInModifiersLeftIntact() { + let input = "weak private(set) /* read-only */\npublic var" + let output = "public private(set) /* read-only */\nweak var" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testSpaceInModifiersLeftIntact2() { + let input = "nonisolated(unsafe) public var foo: String" + let output = "public nonisolated(unsafe) var foo: String" + testFormatting(for: input, output, rule: .modifierOrder) + } + + func testPrefixModifier() { + let input = "prefix public static func - (rhs: Foo) -> Foo" + let output = "public static prefix func - (rhs: Foo) -> Foo" + let options = FormatOptions(fragment: true) + testFormatting(for: input, output, rule: .modifierOrder, options: options) + } + + func testModifierOrder() { + let input = "override public var foo: Int { 5 }" + let output = "public override var foo: Int { 5 }" + let options = FormatOptions(modifierOrder: ["public", "override"]) + testFormatting(for: input, output, rule: .modifierOrder, options: options) + } + + func testConsumingModifierOrder() { + let input = "consuming public func close()" + let output = "public consuming func close()" + let options = FormatOptions(modifierOrder: ["public", "consuming"]) + testFormatting(for: input, output, rule: .modifierOrder, options: options, exclude: [.noExplicitOwnership]) + } + + func testNoConfusePostfixIdentifierWithKeyword() { + let input = "var foo = .postfix\noverride init() {}" + testFormatting(for: input, rule: .modifierOrder) + } + + func testNoConfusePostfixIdentifierWithKeyword2() { + let input = "var foo = postfix\noverride init() {}" + testFormatting(for: input, rule: .modifierOrder) + } + + func testNoConfuseCaseWithModifier() { + let input = """ + enum Foo { + case strong + case weak + public init() {} + } + """ + testFormatting(for: input, rule: .modifierOrder) + } +} diff --git a/Tests/Rules/NoExplicitOwnershipTests.swift b/Tests/Rules/NoExplicitOwnershipTests.swift new file mode 100644 index 000000000..531deee2e --- /dev/null +++ b/Tests/Rules/NoExplicitOwnershipTests.swift @@ -0,0 +1,64 @@ +// +// NoExplicitOwnershipTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class NoExplicitOwnershipTests: XCTestCase { + func testRemovesOwnershipKeywordsFromFunc() { + let input = """ + consuming func myMethod(consuming foo: consuming Foo, borrowing bars: borrowing [Bar]) {} + borrowing func myMethod(consuming foo: consuming Foo, borrowing bars: borrowing [Bar]) {} + """ + + let output = """ + func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} + func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} + """ + + testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: [.unusedArguments]) + } + + func testRemovesOwnershipKeywordsFromClosure() { + let input = """ + foos.map { (foo: consuming Foo) in + foo.bar + } + + foos.map { (foo: borrowing Foo) in + foo.bar + } + """ + + let output = """ + foos.map { (foo: Foo) in + foo.bar + } + + foos.map { (foo: Foo) in + foo.bar + } + """ + + testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: [.unusedArguments]) + } + + func testRemovesOwnershipKeywordsFromType() { + let input = """ + let consuming: (consuming Foo) -> Bar + let borrowing: (borrowing Foo) -> Bar + """ + + let output = """ + let consuming: (Foo) -> Bar + let borrowing: (Foo) -> Bar + """ + + testFormatting(for: input, output, rule: .noExplicitOwnership) + } +} diff --git a/Tests/Rules/NumberFormattingTests.swift b/Tests/Rules/NumberFormattingTests.swift new file mode 100644 index 000000000..49e858437 --- /dev/null +++ b/Tests/Rules/NumberFormattingTests.swift @@ -0,0 +1,203 @@ +// +// NumberFormattingTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class NumberFormattingTests: XCTestCase { + // hex case + + func testLowercaseLiteralConvertedToUpper() { + let input = "let foo = 0xabcd" + let output = "let foo = 0xABCD" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testMixedCaseLiteralConvertedToUpper() { + let input = "let foo = 0xaBcD" + let output = "let foo = 0xABCD" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testUppercaseLiteralConvertedToLower() { + let input = "let foo = 0xABCD" + let output = "let foo = 0xabcd" + let options = FormatOptions(uppercaseHex: false) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testPInExponentialNotConvertedToUpper() { + let input = "let foo = 0xaBcDp5" + let output = "let foo = 0xABCDp5" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testPInExponentialNotConvertedToLower() { + let input = "let foo = 0xaBcDP5" + let output = "let foo = 0xabcdP5" + let options = FormatOptions(uppercaseHex: false, uppercaseExponent: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // exponent case + + func testLowercaseExponent() { + let input = "let foo = 0.456E-5" + let output = "let foo = 0.456e-5" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testUppercaseExponent() { + let input = "let foo = 0.456e-5" + let output = "let foo = 0.456E-5" + let options = FormatOptions(uppercaseExponent: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testUppercaseHexExponent() { + let input = "let foo = 0xFF00p54" + let output = "let foo = 0xFF00P54" + let options = FormatOptions(uppercaseExponent: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testUppercaseGroupedHexExponent() { + let input = "let foo = 0xFF00_AABB_CCDDp54" + let output = "let foo = 0xFF00_AABB_CCDDP54" + let options = FormatOptions(uppercaseExponent: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // decimal grouping + + func testDefaultDecimalGrouping() { + let input = "let foo = 1234_56_78" + let output = "let foo = 12_345_678" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testIgnoreDecimalGrouping() { + let input = "let foo = 1234_5_678" + let options = FormatOptions(decimalGrouping: .ignore) + testFormatting(for: input, rule: .numberFormatting, options: options) + } + + func testNoDecimalGrouping() { + let input = "let foo = 1234_5_678" + let output = "let foo = 12345678" + let options = FormatOptions(decimalGrouping: .none) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testDecimalGroupingThousands() { + let input = "let foo = 1234" + let output = "let foo = 1_234" + let options = FormatOptions(decimalGrouping: .group(3, 3)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testExponentialGrouping() { + let input = "let foo = 1234e5678" + let output = "let foo = 1_234e5678" + let options = FormatOptions(decimalGrouping: .group(3, 3)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testZeroGrouping() { + let input = "let foo = 1234" + let options = FormatOptions(decimalGrouping: .group(0, 0)) + testFormatting(for: input, rule: .numberFormatting, options: options) + } + + // binary grouping + + func testDefaultBinaryGrouping() { + let input = "let foo = 0b11101000_00111111" + let output = "let foo = 0b1110_1000_0011_1111" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testIgnoreBinaryGrouping() { + let input = "let foo = 0b1110_10_00" + let options = FormatOptions(binaryGrouping: .ignore) + testFormatting(for: input, rule: .numberFormatting, options: options) + } + + func testNoBinaryGrouping() { + let input = "let foo = 0b1110_10_00" + let output = "let foo = 0b11101000" + let options = FormatOptions(binaryGrouping: .none) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testBinaryGroupingCustom() { + let input = "let foo = 0b110011" + let output = "let foo = 0b11_00_11" + let options = FormatOptions(binaryGrouping: .group(2, 2)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // hex grouping + + func testDefaultHexGrouping() { + let input = "let foo = 0xFF01FF01AE45" + let output = "let foo = 0xFF01_FF01_AE45" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testCustomHexGrouping() { + let input = "let foo = 0xFF00p54" + let output = "let foo = 0xFF_00p54" + let options = FormatOptions(hexGrouping: .group(2, 2)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // octal grouping + + func testDefaultOctalGrouping() { + let input = "let foo = 0o123456701234" + let output = "let foo = 0o1234_5670_1234" + testFormatting(for: input, output, rule: .numberFormatting) + } + + func testCustomOctalGrouping() { + let input = "let foo = 0o12345670" + let output = "let foo = 0o12_34_56_70" + let options = FormatOptions(octalGrouping: .group(2, 2)) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + // fraction grouping + + func testIgnoreFractionGrouping() { + let input = "let foo = 1.234_5_678" + let options = FormatOptions(decimalGrouping: .ignore, fractionGrouping: true) + testFormatting(for: input, rule: .numberFormatting, options: options) + } + + func testNoFractionGrouping() { + let input = "let foo = 1.234_5_678" + let output = "let foo = 1.2345678" + let options = FormatOptions(decimalGrouping: .none, fractionGrouping: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testFractionGroupingThousands() { + let input = "let foo = 12.34_56_78" + let output = "let foo = 12.345_678" + let options = FormatOptions(decimalGrouping: .group(3, 3), fractionGrouping: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } + + func testHexFractionGrouping() { + let input = "let foo = 0x12.34_56_78p56" + let output = "let foo = 0x12.34_5678p56" + let options = FormatOptions(hexGrouping: .group(4, 4), fractionGrouping: true) + testFormatting(for: input, output, rule: .numberFormatting, options: options) + } +} diff --git a/Tests/Rules/OpaqueGenericParametersTests.swift b/Tests/Rules/OpaqueGenericParametersTests.swift new file mode 100644 index 000000000..d38ec9533 --- /dev/null +++ b/Tests/Rules/OpaqueGenericParametersTests.swift @@ -0,0 +1,683 @@ +// +// OpaqueGenericParametersTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class OpaqueGenericParametersTests: XCTestCase { + func testGenericNotModifiedBelowSwift5_7() { + let input = """ + func foo(_ value: T) { + print(value) + } + """ + + let options = FormatOptions(swiftVersion: "5.6") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithNoConstraint() { + let input = """ + func foo(_ value: T) { + print(value) + } + + init(_ value: T) { + print(value) + } + + subscript(_ value: T) -> Foo { + Foo(value) + } + + subscript(_ value: T) -> Foo { + get { + Foo(value) + } + set { + print(newValue) + } + } + """ + + let output = """ + func foo(_ value: some Any) { + print(value) + } + + init(_ value: some Any) { + print(value) + } + + subscript(_ value: some Any) -> Foo { + Foo(value) + } + + subscript(_ value: some Any) -> Foo { + get { + Foo(value) + } + set { + print(newValue) + } + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testDisableSomeAnyGenericType() { + let input = """ + func foo(_ value: T) { + print(value) + } + """ + + let options = FormatOptions(useSomeAny: false, swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithConstraintInBracket() { + let input = """ + func foo(_ fooable: T, barable: U) -> Baaz { + print(fooable, barable) + } + + init(_ fooable: T, barable: U) { + print(fooable, barable) + } + + subscript(_ fooable: T, barable: U) -> Any { + (fooable, barable) + } + """ + + let output = """ + func foo(_ fooable: some Fooable, barable: some Barable) -> Baaz { + print(fooable, barable) + } + + init(_ fooable: some Fooable, barable: some Barable) { + print(fooable, barable) + } + + subscript(_ fooable: some Fooable, barable: some Barable) -> Any { + (fooable, barable) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithConstraintsInWhereClause() { + let input = """ + func foo(_ t: T, _ u: U) -> Baaz where T: Fooable, T: Barable, U: Baazable { + print(t, u) + } + + init(_ t: T, _ u: U) where T: Fooable, T: Barable, U: Baazable { + print(t, u) + } + """ + + let output = """ + func foo(_ t: some Fooable & Barable, _ u: some Baazable) -> Baaz { + print(t, u) + } + + init(_ t: some Fooable & Barable, _ u: some Baazable) { + print(t, u) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterCanRemoveOneButNotOthers_onOneLine() { + let input = """ + func foo(_ foo: T, bar1: U, bar2: U) where S.AssociatedType == Baaz, T: Quuxable, U: Qaaxable { + print(foo, bar1, bar2) + } + """ + + let output = """ + func foo(_ foo: some Fooable & Quuxable, bar1: U, bar2: U) where S.AssociatedType == Baaz, U: Qaaxable { + print(foo, bar1, bar2) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterCanRemoveOneButNotOthers_onMultipleLines() { + let input = """ + func foo< + S: Baazable, + T: Fooable, + U: Barable + >(_ foo: T, bar1: U, bar2: U) where + S.AssociatedType == Baaz, + T: Quuxable, + U: Qaaxable + { + print(foo, bar1, bar2) + } + """ + + let output = """ + func foo< + S: Baazable, + U: Barable + >(_ foo: some Fooable & Quuxable, bar1: U, bar2: U) where + S.AssociatedType == Baaz, + U: Qaaxable + { + print(foo, bar1, bar2) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithUnknownAssociatedTypeConstraint() { + // If we knew that `T.AssociatedType` was the protocol's primary + // associated type we could update this to `value: some Fooable`, + // but we don't necessarily have that type information available. + // - If primary associated types become very widespread, it may make + // sense to assume (or have an option to assume) that this would work. + let input = """ + func foo(_ value: T) where T.AssociatedType == Bar { + print(value) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithAssociatedTypeConformance() { + // There is no opaque generic parameter syntax that supports this type of constraint + let input = """ + func foo(_ value: T) where T.AssociatedType: Bar { + print(value) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithKnownAssociatedTypeConstraint() { + // For known types (like those in the standard library), + // we are able to know their primary associated types + let input = """ + func foo(_ value: T) where T.Element == Foo { + print(value) + } + """ + + let output = """ + func foo(_ value: some Collection) { + print(value) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParameterWithAssociatedTypeConstraint() { + let input = """ + func foo>(_: T) {} + func bar(_: T) where T: Collection {} + func baaz(_: T) where T == any Collection {} + """ + + let output = """ + func foo(_: some Collection) {} + func bar(_: some Collection) {} + func baaz(_: any Collection) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeUsedInMultipleParameters() { + let input = """ + func foo(_ first: T, second: T) { + print(first, second) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeUsedInClosureMultipleTimes() { + let input = """ + func foo(_ closure: (T) -> T) { + closure(foo) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeUsedAsReturnType() { + // A generic used as a return type is different from an opaque result type (SE-244). + // In `-> T where T: Fooable`, the generic type is caller-specified, but with + // `-> some Fooable` the generic type is specified by the function implementation. + // Because those represent different concepts, we can't convert between them. + let input = """ + func foo() -> T { + // ... + } + + func bar() -> T where T: Barable { + // ... + } + + func baaz() -> Set> { + // ... + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeUsedAsReturnTypeAndParameter() { + // Since we can't change the return value, we can't change any of the use cases of T + let input = """ + func foo(_ value: T) -> T { + value + } + + func bar(_ value: T) -> T where T: Barable { + value + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericTypeWithClosureInWhereClauseDoesntCrash() { + let input = """ + struct Foo { + func bar(_ value: V) where U == @Sendable (V) -> Int {} + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericExtensionSameTypeConstraint() { + let input = """ + func foo(_ u: U) where U == String { + print(u) + } + """ + + let output = """ + func foo(_ u: String) { + print(u) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testGenericExtensionSameTypeGenericConstraint() { + let input = """ + func foo(_ u: U, _ v: V) where U == V { + print(u, v) + } + + func foo(_ u: U, _ v: V) where V == U { + print(u, v) + } + """ + + let output = """ + func foo(_ u: V, _ v: V) { + print(u, v) + } + + func foo(_ u: U, _ v: U) { + print(u, v) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1269() { + let input = """ + func bar( + _ value: V, + _ work: () -> R + ) -> R + where Value == @Sendable () -> V, + V: Sendable + { + work() + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testVariadicParameterNotConvertedToOpaqueGeneric() { + let input = """ + func variadic(_ t: T...) { + print(t) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testNonGenericVariadicParametersDoesntPreventUsingOpaqueGenerics() { + let input = """ + func variadic(t: Any..., u: U, v: Any...) { + print(t, u, v) + } + """ + + let output = """ + func variadic(t: Any..., u: some Any, v: Any...) { + print(t, u, v) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1275() { + let input = """ + func loggedKeypath( + by _: KeyPath..., + actionKeyword _: UserActionKeyword, + identifier _: String + ) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1278() { + let input = """ + public struct Foo { + public func withValue( + _: V, + operation _: () throws -> R + ) rethrows -> R + where Value == @Sendable () -> V, + V: Sendable + {} + + public func withValue( + _: V, + operation _: () async throws -> R + ) async rethrows -> R + where Value == () -> V + {} + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1392() { + let input = """ + public struct Ref {} + + public extension Ref { + static func weak( + _: Base, + _: ReferenceWritableKeyPath + ) -> Ref where T? == Value {} + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testIssue1684() { + let input = """ + @_specialize(where S == Int) + func foo>(t: S) { + print(t) + } + """ + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericSimplifiedInMethodWithAttributeOrMacro() { + let input = """ + @MyResultBuilder + func foo(foo: T, bar: U) -> MyResult { + foo + bar + } + + @MyFunctionBodyMacro(withArgument: true) + func foo(foo: T, bar: U) { + print(foo, bar) + } + """ + + let output = """ + @MyResultBuilder + func foo(foo: some Foo, bar: some Bar) -> MyResult { + foo + bar + } + + @MyFunctionBodyMacro(withArgument: true) + func foo(foo: some Foo, bar: some Bar) { + print(foo, bar) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + // MARK: - genericExtensions + + func testGenericExtensionNotModifiedBeforeSwift5_7() { + let input = "extension Array where Element == Foo {}" + + let options = FormatOptions(swiftVersion: "5.6") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParametersRuleSuccessfullyTerminatesInSampleCode() { + let input = """ + class Service { + public func run() {} + private let foo: Foo + private func a() -> Eventual {} + private func b() -> Eventual {} + private func c() -> Eventual {} + private func d() -> Eventual {} + private func e() -> Eventual {} + private func f() -> Eventual {} + private func g() -> Eventual {} + private func h() -> Eventual {} + private func i() {} + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericParameterUsedInConstraintOfOtherTypeNotChanged() { + let input = """ + func combineResults( + _: Potential, + _: Potential + ) -> Potential where + Success == (Result, Result), + Failure == Never + {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericParameterInheritedFromContextNotRemoved() { + let input = """ + func assign( + on _: DispatchQueue, + to _: AssignTarget, + at _: ReferenceWritableKeyPath + ) where Value: Equatable {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericParameterUsedInBodyNotRemoved() { + let input = """ + func foo(_ value: T) { + typealias TTT = T + let casted = value as TTT + print(casted) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testGenericParameterUsedAsClosureParameterNotRemoved() { + let input = """ + func foo(_: (Foo) -> Void) {} + func bar(_: (Foo) throws -> Void) {} + func baz(_: (Foo) throws(Bar) -> Void) {} + func baaz(_: (Foo) async -> Void) {} + func qux(_: (Foo) async throws -> Void) {} + func quux(_: (Foo) async throws(Bar) -> Void) {} + func qaax(_: ([Foo]) -> Void) {} + func qaax(_: ((Foo, Bar)) -> Void) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testFinalGenericParamRemovedProperlyWithoutHangingComma() { + let input = """ + func foo( + bar _: (Bar) -> Void, + baaz _: Baaz + ) {} + """ + + let output = """ + func foo( + bar _: (Bar) -> Void, + baaz _: some Any + ) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testAddsParensAroundTypeIfNecessary() { + let input = """ + func foo(_: Foo.Type) {} + func bar(_: Foo?) {} + """ + + let output = """ + func foo(_: (some Any).Type) {} + func bar(_: (some Any)?) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testHandlesSingleExactTypeGenericConstraint() { + let input = """ + func foo(with _: T) -> Foo where T == Dependencies {} + """ + + let output = """ + func foo(with _: Dependencies) -> Foo {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testGenericConstraintThatIsGeneric() { + let input = """ + class Foo {} + func foo>(_: T) {} + class Bar {} + func bar>(_: T) {} + """ + + let output = """ + class Foo {} + func foo(_: some Foo) {} + class Bar {} + func bar(_: some Bar) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) + } + + func testDoesntChangeTypeWithConstraintThatReferencesItself() { + // This is a weird one but in the actual code this comes from `ViewModelContext` is both defined + // on the parent type of this declaration (where it has additional important constraints), + // and again in the method itself. Changing this to an opaque parameter breaks the build, because + // it loses the generic constraints applied by the parent type. + let input = """ + func makeSections>(_: ViewModelContext) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + } + + func testOpaqueGenericParametersDoesntleaveTrailingComma() { + let input = "func f(x: U) -> T where T: A, U: B {}" + let output = "func f(x: some B) -> T where T: A {}" + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .opaqueGenericParameters, + options: options, exclude: [.unusedArguments]) + } +} diff --git a/Tests/RulesTests+Organization.swift b/Tests/Rules/OrganizeDeclarationsTests.swift similarity index 51% rename from Tests/RulesTests+Organization.swift rename to Tests/Rules/OrganizeDeclarationsTests.swift index 0c57dc23f..6eedaef66 100644 --- a/Tests/RulesTests+Organization.swift +++ b/Tests/Rules/OrganizeDeclarationsTests.swift @@ -1,17 +1,15 @@ // -// RulesTests+Organization.swift +// OrganizeDeclarationsTests.swift // SwiftFormatTests // -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. // import XCTest @testable import SwiftFormat -class OrganizationTests: RulesTests { - // MARK: organizeDeclarations - +class OrganizeDeclarationsTests: XCTestCase { func testOrganizeClassDeclarationsIntoCategories() { let input = """ class Foo { @@ -2357,2434 +2355,252 @@ class OrganizationTests: RulesTests { testFormatting(for: input, rule: .organizeDeclarations, options: FormatOptions(ifdefIndent: .noIndent), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) } - // MARK: extensionAccessControl .onDeclarations - - func testUpdatesVisibilityOfExtensionMembers() { + func testDoesntConflictWithOrganizeDeclarations() { let input = """ - private extension Foo { - var publicProperty: Int { 10 } - public func publicFunction1() {} - func publicFunction2() {} - internal func internalFunction() {} - private func privateFunction() {} - fileprivate var privateProperty: Int { 10 } - } - """ - - let output = """ - extension Foo { - fileprivate var publicProperty: Int { 10 } - public func publicFunction1() {} - fileprivate func publicFunction2() {} - internal func internalFunction() {} - private func privateFunction() {} - fileprivate var privateProperty: Int { 10 } - } - """ + // swiftformat:sort + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + case upsellB - testFormatting( - for: input, output, rule: .extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations), - exclude: [.redundantInternal] - ) - } + // MARK: Internal - func testUpdatesVisibilityOfExtensionInConditionalCompilationBlock() { - let input = """ - #if DEBUG - public extension Foo { - var publicProperty: Int { 10 } + var anUnsortedProperty: Foo { + Foo() } - #endif - """ - let output = """ - #if DEBUG - extension Foo { - public var publicProperty: Int { 10 } + var unsortedProperty: Foo { + Foo() } - #endif + } """ - testFormatting( - for: input, output, rule: .extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) + testFormatting(for: input, rule: .organizeDeclarations) } - func testUpdatesVisibilityOfExtensionMembersInConditionalCompilationBlock() { + func testSortsWithinOrganizeDeclarations() { let input = """ - public extension Foo { - #if DEBUG - var publicProperty: Int { 10 } - #endif - } - """ + // swiftformat:sort + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA - let output = """ - extension Foo { - #if DEBUG - public var publicProperty: Int { 10 } - #endif - } - """ + // MARK: Internal - testFormatting( - for: input, output, rule: .extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) - } + var sortedProperty: Foo { + Foo() + } - func testDoesntUpdateDeclarationsInsideTypeInsideExtension() { - let input = """ - public extension Foo { - struct Bar { - var baz: Int - var quux: Int + var aSortedProperty: Foo { + Foo() } } """ let output = """ - extension Foo { - public struct Bar { - var baz: Int - var quux: Int + // swiftformat:sort + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + + case upsellB + + // MARK: Internal + + var aSortedProperty: Foo { + Foo() } - } - """ - testFormatting( - for: input, output, rule: .extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) - } + var sortedProperty: Foo { + Foo() + } - func testDoesNothingForInternalExtension() { - let input = """ - extension Foo { - func bar() {} - func baz() {} - public func quux() {} } """ - testFormatting( - for: input, rule: .extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) + testFormatting(for: input, [output], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], + exclude: [.blankLinesAtEndOfScope]) } - func testPlacesVisibilityKeywordAfterAnnotations() { + func testSortsWithinOrganizeDeclarationsByClassName() { let input = """ - public extension Foo { - @discardableResult - func bar() -> Int { 10 } + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA - /// Doc comment - @discardableResult - @available(iOS 10.0, *) - func baz() -> Int { 10 } + // MARK: Internal - @objc func quux() {} - @available(iOS 10.0, *) func quixotic() {} + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } } """ let output = """ - extension Foo { - @discardableResult - public func bar() -> Int { 10 } - - /// Doc comment - @discardableResult - @available(iOS 10.0, *) - public func baz() -> Int { 10 } + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA - @objc public func quux() {} - @available(iOS 10.0, *) public func quixotic() {} - } - """ + case upsellB - testFormatting( - for: input, output, rule: .extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations) - ) - } + // MARK: Internal - func testConvertsExtensionPrivateToMemberFileprivate() { - let input = """ - private extension Foo { - var bar: Int - } + var aSortedProperty: Foo { + Foo() + } - let bar = Foo().bar - """ + var sortedProperty: Foo { + Foo() + } - let output = """ - extension Foo { - fileprivate var bar: Int } - - let bar = Foo().bar """ - testFormatting( - for: input, output, rule: .extensionAccessControl, - options: FormatOptions(extensionACLPlacement: .onDeclarations, swiftVersion: "4"), - exclude: [.propertyType] - ) + testFormatting(for: input, [output], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["FeatureFlags"]), + exclude: [.blankLinesAtEndOfScope]) } - // MARK: extensionAccessControl .onExtension - - func testUpdatedVisibilityOfExtension() { + func testSortsWithinOrganizeDeclarationsByPartialClassName() { let input = """ - extension Foo { - public func bar() {} - public var baz: Int { 10 } + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA + + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } - public struct Foo2 { - var quux: Int + var aSortedProperty: Foo { + Foo() } } """ let output = """ - public extension Foo { - func bar() {} - var baz: Int { 10 } + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + + case upsellB + + // MARK: Internal + + var aSortedProperty: Foo { + Foo() + } - struct Foo2 { - var quux: Int + var sortedProperty: Foo { + Foo() } + } """ - testFormatting(for: input, output, rule: .extensionAccessControl) + testFormatting(for: input, [output], + rules: [.organizeDeclarations, .blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["ureFla"]), + exclude: [.blankLinesAtEndOfScope]) } - func testUpdatedVisibilityOfExtensionWithDeclarationsInConditionalCompilation() { + func testDontSortsWithinOrganizeDeclarationsByClassNameInComment() { let input = """ - extension Foo { - #if DEBUG - public func bar() {} - public var baz: Int { 10 } - #endif - } - """ + /// Comment + enum FeatureFlags { + case fooFeature + case barFeature + case upsellB + case upsellA - let output = """ - public extension Foo { - #if DEBUG - func bar() {} - var baz: Int { 10 } - #endif + // MARK: Internal + + var sortedProperty: Foo { + Foo() + } + + var aSortedProperty: Foo { + Foo() + } } """ - testFormatting(for: input, output, rule: .extensionAccessControl) + testFormatting(for: input, + rules: [.organizeDeclarations, .blankLinesBetweenScopes], + options: .init(alphabeticallySortedDeclarationPatterns: ["Comment"]), + exclude: [.blankLinesAtEndOfScope]) } - func testDoesntUpdateExtensionVisibilityWithoutMajorityBodyVisibility() { + func testOrganizeDeclarationsSortUsesLocalizedCompare() { let input = """ - extension Foo { - public func foo() {} - public func bar() {} - var baz: Int { 10 } - var quux: Int { 5 } + // swiftformat:sort + enum FeatureFlags { + case upsella + case upsellA + case upsellb + case upsellB } """ - testFormatting(for: input, rule: .extensionAccessControl) + testFormatting(for: input, rule: .organizeDeclarations) } - func testUpdateExtensionVisibilityWithMajorityBodyVisibility() { + func testSortDeclarationsSortsExtensionBody() { let input = """ - extension Foo { - public func foo() {} - public func bar() {} - public var baz: Int { 10 } - var quux: Int { 5 } + enum Namespace {} + + // swiftformat:sort + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" } """ let output = """ - public extension Foo { - func foo() {} - func bar() {} - var baz: Int { 10 } - internal var quux: Int { 5 } + enum Namespace {} + + // swiftformat:sort + extension Namespace { + static let baaz = "baaz" + public static let bar = "bar" + static let foo = "foo" } """ - testFormatting(for: input, output, rule: .extensionAccessControl) + // organizeTypes doesn't include "extension". So even though the + // organizeDeclarations rule is enabled, the extension should be + // sorted by the sortDeclarations rule. + let options = FormatOptions(organizeTypes: ["class"]) + testFormatting(for: input, [output], rules: [.sortDeclarations, .organizeDeclarations], options: options) } - func testDoesntUpdateExtensionVisibilityWhenMajorityBodyVisibilityIsntMostVisible() { + func testOrganizeDeclarationsSortsExtensionBody() { let input = """ - extension Foo { - func foo() {} - func bar() {} - public var baz: Int { 10 } - } - """ - - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testDoesntUpdateExtensionVisibilityWithInternalDeclarations() { - let input = """ - extension Foo { - func bar() {} - var baz: Int { 10 } - } - """ - - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testDoesntUpdateExtensionThatAlreadyHasCorrectVisibilityKeyword() { - let input = """ - public extension Foo { - func bar() {} - func baz() {} - } - """ - - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testUpdatesExtensionThatHasHigherACLThanBodyDeclarations() { - let input = """ - public extension Foo { - fileprivate func bar() {} - fileprivate func baz() {} - } - """ - - let output = """ - fileprivate extension Foo { - func bar() {} - func baz() {} - } - """ - - testFormatting(for: input, output, rule: .extensionAccessControl, - exclude: [.redundantFileprivate]) - } - - func testDoesntHoistPrivateVisibilityFromExtensionBodyDeclarations() { - let input = """ - extension Foo { - private var bar() {} - private func baz() {} - } - """ - - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testDoesntUpdatesExtensionThatHasLowerACLThanBodyDeclarations() { - let input = """ - private extension Foo { - public var bar() {} - public func baz() {} - } - """ - - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testDoesntReduceVisibilityOfImplicitInternalDeclaration() { - let input = """ - extension Foo { - fileprivate var bar() {} - func baz() {} - } - """ - - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testUpdatesExtensionThatHasRedundantACLOnBodyDeclarations() { - let input = """ - public extension Foo { - func bar() {} - public func baz() {} - } - """ - - let output = """ - public extension Foo { - func bar() {} - func baz() {} - } - """ - - testFormatting(for: input, output, rule: .extensionAccessControl) - } - - func testNoHoistAccessModifierForOpenMethod() { - let input = """ - extension Foo { - open func bar() {} - } - """ - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testDontChangePrivateExtensionToFileprivate() { - let input = """ - private extension Foo { - func bar() {} - } - """ - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testDontRemoveInternalKeywordFromExtension() { - let input = """ - internal extension Foo { - func bar() {} - } - """ - testFormatting(for: input, rule: .extensionAccessControl, exclude: [.redundantInternal]) - } - - func testNoHoistAccessModifierForExtensionThatAddsProtocolConformance() { - let input = """ - extension Foo: Bar { - public func bar() {} - } - """ - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testProtocolConformanceCheckNotFooledByWhereClause() { - let input = """ - extension Foo where Self: Bar { - public func bar() {} - } - """ - let output = """ - public extension Foo where Self: Bar { - func bar() {} - } - """ - testFormatting(for: input, output, rule: .extensionAccessControl) - } - - func testAccessNotHoistedIfTypeVisibilityIsLower() { - let input = """ - class Foo {} - - extension Foo { - public func bar() {} - } - """ - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testExtensionAccessControlRuleTerminatesInFileWithConditionalCompilation() { - let input = """ - #if os(Linux) - #error("Linux is currently not supported") - #endif - """ - - testFormatting(for: input, rule: .extensionAccessControl) - } - - func testExtensionAccessControlRuleTerminatesInFileWithEmptyType() { - let input = """ - struct Foo { - // This type is empty - } - - extension Foo { - // This extension is empty - } - """ - - testFormatting(for: input, rule: .extensionAccessControl) - } - - // MARK: markTypes - - func testAddsMarkBeforeTypes() { - let input = """ - struct Foo {} - class Bar {} - enum Baz {} - protocol Quux {} - """ - - let output = """ - // MARK: - Foo - - struct Foo {} - - // MARK: - Bar - - class Bar {} - - // MARK: - Baz - - enum Baz {} - - // MARK: - Quux - - protocol Quux {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testDoesntAddMarkBeforeStructWithExistingMark() { - let input = """ - // MARK: - Foo - - struct Foo {} - extension Foo {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testCorrectsTypoInTypeMark() { - let input = """ - // mark: foo - - struct Foo {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo - - struct Foo {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testUpdatesMarkAfterTypeIsRenamed() { - let input = """ - // MARK: - FooBarControllerFactory - - struct FooBarControllerBuilder {} - extension FooBarControllerBuilder {} - """ - - let output = """ - // MARK: - FooBarControllerBuilder - - struct FooBarControllerBuilder {} - extension FooBarControllerBuilder {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testAddsMarkBeforeTypeWithDocComment() { - let input = """ - /// This is a doc comment with several - /// lines of prose at the start - /// - And then, after the prose, - /// - a few bullet points just for fun - actor Foo {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo - - /// This is a doc comment with several - /// lines of prose at the start - /// - And then, after the prose, - /// - a few bullet points just for fun - actor Foo {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testCustomTypeMark() { - let input = """ - struct Foo {} - extension Foo {} - """ - - let output = """ - // TYPE DEFINITION: Foo - - struct Foo {} - extension Foo {} - """ - - testFormatting( - for: input, output, rule: .markTypes, - options: FormatOptions(typeMarkComment: "TYPE DEFINITION: %t") - ) - } - - func testDoesNothingForExtensionWithoutProtocolConformance() { - let input = """ - extension Foo {} - extension Foo {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func preservesExistingCommentForExtensionWithNoConformances() { - let input = """ - // MARK: Description of extension - - extension Foo {} - extension Foo {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testAddsMarkCommentForExtensionWithConformance() { - let input = """ - extension Foo: BarProtocol {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo + BarProtocol - - extension Foo: BarProtocol {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testUpdatesExtensionMarkToCorrectMark() { - let input = """ - // MARK: - BarProtocol - - extension Foo: BarProtocol {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo + BarProtocol - - extension Foo: BarProtocol {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testAddsMarkCommentForExtensionWithMultipleConformances() { - let input = """ - extension Foo: BarProtocol, BazProtocol {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo + BarProtocol, BazProtocol - - extension Foo: BarProtocol, BazProtocol {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testUpdatesMarkCommentWithCorrectConformances() { - let input = """ - // MARK: - Foo + BarProtocol - - extension Foo: BarProtocol, BazProtocol {} - extension Foo {} - """ - - let output = """ - // MARK: - Foo + BarProtocol, BazProtocol - - extension Foo: BarProtocol, BazProtocol {} - extension Foo {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testCustomExtensionMarkComment() { - let input = """ - struct Foo {} - extension Foo: BarProtocol {} - extension String: BarProtocol {} - """ - - let output = """ - // MARK: - Foo - - struct Foo {} - - // EXTENSION: - BarProtocol - - extension Foo: BarProtocol {} - - // EXTENSION: - String: BarProtocol - - extension String: BarProtocol {} - """ - - testFormatting( - for: input, output, rule: .markTypes, - options: FormatOptions( - extensionMarkComment: "EXTENSION: - %t: %c", - groupedExtensionMarkComment: "EXTENSION: - %c" - ) - ) - } - - func testTypeAndExtensionMarksTogether() { - let input = """ - struct Foo {} - extension Foo: Bar {} - extension String: Bar {} - """ - - let output = """ - // MARK: - Foo - - struct Foo {} - - // MARK: Bar - - extension Foo: Bar {} - - // MARK: - String + Bar - - extension String: Bar {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testFullyQualifiedTypeNames() { - let input = """ - extension MyModule.Foo: MyModule.MyNamespace.BarProtocol, QuuxProtocol {} - extension MyModule.Foo {} - """ - - let output = """ - // MARK: - MyModule.Foo + MyModule.MyNamespace.BarProtocol, QuuxProtocol - - extension MyModule.Foo: MyModule.MyNamespace.BarProtocol, QuuxProtocol {} - extension MyModule.Foo {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testWhereClauseConformanceWithExactConstraint() { - let input = """ - extension Array: BarProtocol where Element == String {} - extension Array {} - """ - - let output = """ - // MARK: - Array + BarProtocol - - extension Array: BarProtocol where Element == String {} - extension Array {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testWhereClauseConformanceWithConformanceConstraint() { - let input = """ - extension Array: BarProtocol where Element: BarProtocol {} - extension Array {} - """ - - let output = """ - // MARK: - Array + BarProtocol - - extension Array: BarProtocol where Element: BarProtocol {} - extension Array {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testWhereClauseWithExactConstraint() { - let input = """ - extension Array where Element == String {} - extension Array {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testWhereClauseWithConformanceConstraint() { - let input = """ - // MARK: [BarProtocol] helpers - - extension Array where Element: BarProtocol {} - extension Rules {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testPlacesMarkAfterImports() { - let input = """ - import Foundation - import os - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - let output = """ - import Foundation - import os - - // MARK: - Rules - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testPlacesMarkAfterFileHeader() { - let input = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - let output = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - // MARK: - Rules - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testPlacesMarkAfterFileHeaderAndImports() { - let input = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - import Foundation - import os - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - let output = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - import Foundation - import os - - // MARK: - Rules - - /// All of SwiftFormat's Rule implementation - class Rules {} - extension Rules {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testDoesNothingIfOnlyOneDeclaration() { - let input = """ - // Created by Nick Lockwood on 12/08/2016. - // Copyright 2016 Nick Lockwood - - import Foundation - import os - - /// All of SwiftFormat's Rule implementation - class Rules {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testMultipleExtensionsOfSameType() { - let input = """ - extension Foo: BarProtocol {} - extension Foo: QuuxProtocol {} - """ - - let output = """ - // MARK: - Foo + BarProtocol - - extension Foo: BarProtocol {} - - // MARK: - Foo + QuuxProtocol - - extension Foo: QuuxProtocol {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testNeverMarkTypes() { - let input = """ - struct EmptyFoo {} - struct EmptyBar { } - struct EmptyBaz { - - } - struct Quux { - let foo = 1 - } - """ - - let options = FormatOptions(markTypes: .never) - testFormatting( - for: input, rule: .markTypes, options: options, - exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] - ) - } - - func testMarkTypesIfNotEmpty() { - let input = """ - struct EmptyFoo {} - struct EmptyBar { } - struct EmptyBaz { - - } - struct Quux { - let foo = 1 - } - """ - - let output = """ - struct EmptyFoo {} - struct EmptyBar { } - struct EmptyBaz { - - } - - // MARK: - Quux - - struct Quux { - let foo = 1 - } - """ - - let options = FormatOptions(markTypes: .ifNotEmpty) - testFormatting( - for: input, output, rule: .markTypes, options: options, - exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] - ) - } - - func testNeverMarkExtensions() { - let input = """ - extension EmptyFoo: FooProtocol {} - extension EmptyBar: BarProtocol { } - extension EmptyBaz: BazProtocol { - - } - extension Quux: QuuxProtocol { - let foo = 1 - } - """ - - let options = FormatOptions(markExtensions: .never) - testFormatting( - for: input, rule: .markTypes, options: options, - exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] - ) - } - - func testMarkExtensionsIfNotEmpty() { - let input = """ - extension EmptyFoo: FooProtocol {} - extension EmptyBar: BarProtocol { } - extension EmptyBaz: BazProtocol { - - } - extension Quux: QuuxProtocol { - let foo = 1 - } - """ - - let output = """ - extension EmptyFoo: FooProtocol {} - extension EmptyBar: BarProtocol { } - extension EmptyBaz: BazProtocol { - - } - - // MARK: - Quux + QuuxProtocol - - extension Quux: QuuxProtocol { - let foo = 1 - } - """ - - let options = FormatOptions(markExtensions: .ifNotEmpty) - testFormatting( - for: input, output, rule: .markTypes, options: options, - exclude: [.emptyBraces, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .blankLinesBetweenScopes] - ) - } - - func testMarkExtensionsDisabled() { - let input = """ - extension Foo: FooProtocol {} - - // swiftformat:disable markTypes - - extension Bar: BarProtocol {} - - // swiftformat:enable markTypes - - extension Baz: BazProtocol {} - - extension Quux: QuuxProtocol {} - """ - - let output = """ - // MARK: - Foo + FooProtocol - - extension Foo: FooProtocol {} - - // swiftformat:disable markTypes - - extension Bar: BarProtocol {} - - // MARK: - Baz + BazProtocol - - // swiftformat:enable markTypes - - extension Baz: BazProtocol {} - - // MARK: - Quux + QuuxProtocol - - extension Quux: QuuxProtocol {} - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testExtensionMarkWithImportOfSameName() { - let input = """ - import MagazineLayout - - // MARK: - MagazineLayout + FooProtocol - - extension MagazineLayout: FooProtocol {} - - // MARK: - MagazineLayout + BarProtocol - - extension MagazineLayout: BarProtocol {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testDoesntUseGroupedMarkTemplateWhenSeparatedByOtherType() { - let input = """ - // MARK: - MyComponent - - class MyComponent {} - - // MARK: - MyComponentContent - - struct MyComponentContent {} - - // MARK: - MyComponent + ContentConfigurableView - - extension MyComponent: ContentConfigurableView {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testUsesGroupedMarkTemplateWhenSeparatedByExtensionOfSameType() { - let input = """ - // MARK: - MyComponent - - class MyComponent {} - - // MARK: Equatable - - extension MyComponent: Equatable {} - - // MARK: ContentConfigurableView - - extension MyComponent: ContentConfigurableView {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testDoesntUseGroupedMarkTemplateWhenSeparatedByExtensionOfOtherType() { - let input = """ - // MARK: - MyComponent - - class MyComponent {} - - // MARK: - OtherComponent + Equatable - - extension OtherComponent: Equatable {} - - // MARK: - MyComponent + ContentConfigurableView - - extension MyComponent: ContentConfigurableView {} - """ - - testFormatting(for: input, rule: .markTypes) - } - - func testAddsMarkBeforeTypesWithNoBlankLineAfterMark() { - let input = """ - struct Foo {} - class Bar {} - enum Baz {} - protocol Quux {} - """ - - let output = """ - // MARK: - Foo - struct Foo {} - - // MARK: - Bar - class Bar {} - - // MARK: - Baz - enum Baz {} - - // MARK: - Quux - protocol Quux {} - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, output, rule: .markTypes, options: options) - } - - func testAddsMarkForTypeInExtension() { - let input = """ - enum Foo {} - - extension Foo { - struct Bar { - let baaz: Baaz - } - } - """ - - let output = """ - // MARK: - Foo - - enum Foo {} - - // MARK: Foo.Bar - - extension Foo { - struct Bar { - let baaz: Baaz - } - } - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testDoesntAddsMarkForMultipleTypesInExtension() { - let input = """ - enum Foo {} - - extension Foo { - struct Bar { - let baaz: Baaz - } - - struct Quux { - let baaz: Baaz - } - } - """ - - let output = """ - // MARK: - Foo - - enum Foo {} - - extension Foo { - struct Bar { - let baaz: Baaz - } - - struct Quux { - let baaz: Baaz - } - } - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testAddsMarkForTypeInExtensionNotFollowingTypeBeingExtended() { - let input = """ - struct Baaz {} - - extension Foo { - struct Bar { - let baaz: Baaz - } - } - """ - - let output = """ - // MARK: - Baaz - - struct Baaz {} - - // MARK: - Foo.Bar - - extension Foo { - struct Bar { - let baaz: Baaz - } - } - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testHandlesMultipleLayersOfExtensionNesting() { - let input = """ - enum Foo {} - - extension Foo { - enum Bar {} - } - - extension Foo { - extension Bar { - struct Baaz { - let quux: Quux - } - } - } - """ - - let output = """ - // MARK: - Foo - - enum Foo {} - - // MARK: Foo.Bar - - extension Foo { - enum Bar {} - } - - // MARK: Foo.Bar.Baaz - - extension Foo { - extension Bar { - struct Baaz { - let quux: Quux - } - } - } - """ - - testFormatting(for: input, output, rule: .markTypes) - } - - func testMarkTypeLintReturnsErrorAsExpected() throws { - let input = """ - struct MyStruct {} - - extension MyStruct {} - """ - - // Initialize rule names - let _ = FormatRules.byName - let changes = try lint(input, rules: [.markTypes]) - XCTAssertEqual(changes, [ - .init(line: 1, rule: .markTypes, filePath: nil), - .init(line: 2, rule: .markTypes, filePath: nil), - ]) - } - - // MARK: - sortImports - - func testSortImportsSimpleCase() { - let input = "import Foo\nimport Bar" - let output = "import Bar\nimport Foo" - testFormatting(for: input, output, rule: .sortImports) - } - - func testSortImportsKeepsPreviousCommentWithImport() { - let input = "import Foo\n// important comment\n// (very important)\nimport Bar" - let output = "// important comment\n// (very important)\nimport Bar\nimport Foo" - testFormatting(for: input, output, rule: .sortImports, - exclude: [.blankLineAfterImports]) - } - - func testSortImportsKeepsPreviousCommentWithImport2() { - let input = "// important comment\n// (very important)\nimport Foo\nimport Bar" - let output = "import Bar\n// important comment\n// (very important)\nimport Foo" - testFormatting(for: input, output, rule: .sortImports, - exclude: [.blankLineAfterImports]) - } - - func testSortImportsDoesntMoveHeaderComment() { - let input = "// header comment\n\nimport Foo\nimport Bar" - let output = "// header comment\n\nimport Bar\nimport Foo" - testFormatting(for: input, output, rule: .sortImports) - } - - func testSortImportsDoesntMoveHeaderCommentFollowedByImportComment() { - let input = "// header comment\n\n// important comment\nimport Foo\nimport Bar" - let output = "// header comment\n\nimport Bar\n// important comment\nimport Foo" - testFormatting(for: input, output, rule: .sortImports, - exclude: [.blankLineAfterImports]) - } - - func testSortImportsOnSameLine() { - let input = "import Foo; import Bar\nimport Baz" - let output = "import Baz\nimport Foo; import Bar" - testFormatting(for: input, output, rule: .sortImports) - } - - func testSortImportsWithSemicolonAndCommentOnSameLine() { - let input = "import Foo; // foobar\nimport Bar\nimport Baz" - let output = "import Bar\nimport Baz\nimport Foo; // foobar" - testFormatting(for: input, output, rule: .sortImports, exclude: [.semicolons]) - } - - func testSortImportEnum() { - let input = "import enum Foo.baz\nimport Foo.bar" - let output = "import Foo.bar\nimport enum Foo.baz" - testFormatting(for: input, output, rule: .sortImports) - } - - func testSortImportFunc() { - let input = "import func Foo.baz\nimport Foo.bar" - let output = "import Foo.bar\nimport func Foo.baz" - testFormatting(for: input, output, rule: .sortImports) - } - - func testAlreadySortImportsDoesNothing() { - let input = "import Bar\nimport Foo" - testFormatting(for: input, rule: .sortImports) - } - - func testPreprocessorSortImports() { - let input = "#if os(iOS)\n import Foo2\n import Bar2\n#else\n import Foo1\n import Bar1\n#endif\nimport Foo3\nimport Bar3" - let output = "#if os(iOS)\n import Bar2\n import Foo2\n#else\n import Bar1\n import Foo1\n#endif\nimport Bar3\nimport Foo3" - testFormatting(for: input, output, rule: .sortImports) - } - - func testTestableSortImports() { - let input = "@testable import Foo3\nimport Bar3" - let output = "import Bar3\n@testable import Foo3" - testFormatting(for: input, output, rule: .sortImports) - } - - func testLengthSortImports() { - let input = "import Foo\nimport Module\nimport Bar3" - let output = "import Foo\nimport Bar3\nimport Module" - let options = FormatOptions(importGrouping: .length) - testFormatting(for: input, output, rule: .sortImports, options: options) - } - - func testTestableImportsWithTestableOnPreviousLine() { - let input = "@testable\nimport Foo3\nimport Bar3" - let output = "import Bar3\n@testable\nimport Foo3" - testFormatting(for: input, output, rule: .sortImports) - } - - func testTestableImportsWithGroupingTestableBottom() { - let input = "@testable import Bar\nimport Foo\n@testable import UIKit" - let output = "import Foo\n@testable import Bar\n@testable import UIKit" - let options = FormatOptions(importGrouping: .testableLast) - testFormatting(for: input, output, rule: .sortImports, options: options) - } - - func testTestableImportsWithGroupingTestableTop() { - let input = "@testable import Bar\nimport Foo\n@testable import UIKit" - let output = "@testable import Bar\n@testable import UIKit\nimport Foo" - let options = FormatOptions(importGrouping: .testableFirst) - testFormatting(for: input, output, rule: .sortImports, options: options) - } - - func testCaseInsensitiveSortImports() { - let input = "import Zlib\nimport lib" - let output = "import lib\nimport Zlib" - testFormatting(for: input, output, rule: .sortImports) - } - - func testCaseInsensitiveCaseDifferingSortImports() { - let input = "import c\nimport B\nimport A.a\nimport A.A" - let output = "import A.A\nimport A.a\nimport B\nimport c" - testFormatting(for: input, output, rule: .sortImports) - } - - func testNoDeleteCodeBetweenImports() { - let input = "import Foo\nfunc bar() {}\nimport Bar" - testFormatting(for: input, rule: .sortImports, - exclude: [.blankLineAfterImports]) - } - - func testNoDeleteCodeBetweenImports2() { - let input = "import Foo\nimport Bar\nfoo = bar\nimport Bar" - let output = "import Bar\nimport Foo\nfoo = bar\nimport Bar" - testFormatting(for: input, output, rule: .sortImports, - exclude: [.blankLineAfterImports]) - } - - func testNoDeleteCodeBetweenImports3() { - let input = """ - import Z - - // one - - #if FLAG - print("hi") - #endif - - import A - """ - testFormatting(for: input, rule: .sortImports) - } - - func testSortContiguousImports() { - let input = "import Foo\nimport Bar\nfunc bar() {}\nimport Quux\nimport Baz" - let output = "import Bar\nimport Foo\nfunc bar() {}\nimport Baz\nimport Quux" - testFormatting(for: input, output, rule: .sortImports, - exclude: [.blankLineAfterImports]) - } - - func testNoMangleImportsPrecededByComment() { - let input = """ - // evil comment - - #if canImport(Foundation) - import Foundation - #if canImport(UIKit) && canImport(AVFoundation) - import UIKit - import AVFoundation - #endif - #endif - """ - let output = """ - // evil comment - - #if canImport(Foundation) - import Foundation - #if canImport(UIKit) && canImport(AVFoundation) - import AVFoundation - import UIKit - #endif - #endif - """ - testFormatting(for: input, output, rule: .sortImports) - } - - func testNoMangleFileHeaderNotFollowedByLinebreak() { - let input = """ - // - // Code.swift - // Module - // - // Created by Someone on 4/30/20. - // - import AModuleUI - import AModule - import AModuleHelper - import SomeOtherModule - """ - let output = """ - // - // Code.swift - // Module - // - // Created by Someone on 4/30/20. - // - import AModule - import AModuleHelper - import AModuleUI - import SomeOtherModule - """ - testFormatting(for: input, output, rule: .sortImports) - } - - // MARK: - sortSwitchCases - - func testSortedSwitchCaseNestedSwitchOneCaseDoesNothing() { - let input = """ - switch result { - case let .success(value): - switch result { - case .success: - print("success") - case .value: - print("value") - } - - case .failure: - guard self.bar else { - print(self.bar) - return - } - print(self.bar) - } - """ - - testFormatting(for: input, rule: .sortSwitchCases, exclude: [.redundantSelf]) - } - - func testSortedSwitchCaseMultilineWithOneComment() { - let input = """ - switch self { - case let .type, // something - let .conditionalCompilation: - break - } - """ - let output = """ - switch self { - case let .conditionalCompilation, - let .type: // something - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases) - } - - func testSortedSwitchCaseMultilineWithComments() { - let input = """ - switch self { - case let .type, // typeComment - let .conditionalCompilation: // conditionalCompilationComment - break - } - """ - let output = """ - switch self { - case let .conditionalCompilation, // conditionalCompilationComment - let .type: // typeComment - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases, exclude: [.indent]) - } - - func testSortedSwitchCaseMultilineWithCommentsAndMoreThanOneCasePerLine() { - let input = """ - switch self { - case let .type, // typeComment - let .type1, .type2, - let .conditionalCompilation: // conditionalCompilationComment - break - } - """ - let output = """ - switch self { - case let .conditionalCompilation, // conditionalCompilationComment - let .type, // typeComment - let .type1, - .type2: - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases) - } - - func testSortedSwitchCaseMultiline() { - let input = """ - switch self { - case let .type, - let .conditionalCompilation: - break - } - """ - let output = """ - switch self { - case let .conditionalCompilation, - let .type: - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases) - } - - func testSortedSwitchCaseMultipleAssociatedValues() { - let input = """ - switch self { - case let .b(whatever, whatever2), .a(whatever): - break - } - """ - let output = """ - switch self { - case .a(whatever), let .b(whatever, whatever2): - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: [.wrapSwitchCases]) - } - - func testSortedSwitchCaseOneLineWithoutSpaces() { - let input = """ - switch self { - case .b,.a: - break - } - """ - let output = """ - switch self { - case .a,.b: - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: [.wrapSwitchCases, .spaceAroundOperators]) - } - - func testSortedSwitchCaseLet() { - let input = """ - switch self { - case let .b(whatever), .a(whatever): - break - } - """ - let output = """ - switch self { - case .a(whatever), let .b(whatever): - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: [.wrapSwitchCases]) - } - - func testSortedSwitchCaseOneCaseDoesNothing() { - let input = """ - switch self { - case "a": - break - } - """ - testFormatting(for: input, rule: .sortSwitchCases) - } - - func testSortedSwitchStrings() { - let input = """ - switch self { - case "GET", "POST", "PUT", "DELETE": - break - } - """ - let output = """ - switch self { - case "DELETE", "GET", "POST", "PUT": - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: [.wrapSwitchCases]) - } - - func testSortedSwitchWhereConditionNotLastCase() { - let input = """ - switch self { - case .b, .c, .a where isTrue: - break - } - """ - testFormatting(for: input, - rule: .sortSwitchCases, - exclude: [.wrapSwitchCases]) - } - - func testSortedSwitchWhereConditionLastCase() { - let input = """ - switch self { - case .b, .c where isTrue, .a: - break - } - """ - let output = """ - switch self { - case .a, .b, .c where isTrue: - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: [.wrapSwitchCases]) - } - - func testSortNumericSwitchCases() { - let input = """ - switch foo { - case 12, 3, 5, 7, 8, 10, 1: - break - } - """ - let output = """ - switch foo { - case 1, 3, 5, 7, 8, 10, 12: - break - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: [.wrapSwitchCases]) - } - - func testSortedSwitchTuples() { - let input = """ - switch foo { - case (.foo, _), - (.bar, _), - (.baz, _), - (_, .foo): - } - """ - let output = """ - switch foo { - case (_, .foo), - (.bar, _), - (.baz, _), - (.foo, _): - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases) - } - - func testSortedSwitchTuples2() { - let input = """ - switch self { - case (.quux, .bar), - (_, .foo), - (_, .bar), - (_, .baz), - (.foo, .bar): - } - """ - let output = """ - switch self { - case (_, .bar), - (_, .baz), - (_, .foo), - (.foo, .bar), - (.quux, .bar): - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases) - } - - func testSortSwitchCasesShortestFirst() { - let input = """ - switch foo { - case let .fooAndBar(baz, quux), - let .foo(baz): - } - """ - let output = """ - switch foo { - case let .foo(baz), - let .fooAndBar(baz, quux): - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases) - } - - func testSortHexLiteralCasesInAscendingOrder() { - let input = """ - switch value { - case 0x30 ... 0x39, // 0-9 - 0x0300 ... 0x036F, - 0x1DC0 ... 0x1DFF, - 0x20D0 ... 0x20FF, - 0xFE20 ... 0xFE2F: - return true - default: - return false - } - """ - testFormatting(for: input, rule: .sortSwitchCases) - } - - func testMixedOctalHexIntAndBinaryLiteralCasesInAscendingOrder() { - let input = """ - switch value { - case 0o3, - 0x20, - 110, - 0b1111110: - return true - default: - return false - } - """ - testFormatting(for: input, rule: .sortSwitchCases) - } - - func testSortSwitchCasesNoUnwrapReturn() { - let input = """ - switch self { - case .b, .a, .c, .e, .d: - return nil - } - """ - let output = """ - switch self { - case .a, .b, .c, .d, .e: - return nil - } - """ - testFormatting(for: input, output, rule: .sortSwitchCases, - exclude: [.wrapSwitchCases]) - } - - // MARK: - modifierOrder - - func testVarModifiersCorrected() { - let input = "unowned private static var foo" - let output = "private unowned static var foo" - let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: .modifierOrder, options: options) - } - - func testPrivateSetModifierNotMangled() { - let input = "private(set) public weak lazy var foo" - let output = "public private(set) lazy weak var foo" - testFormatting(for: input, output, rule: .modifierOrder) - } - - func testUnownedUnsafeModifierNotMangled() { - let input = "unowned(unsafe) lazy var foo" - let output = "lazy unowned(unsafe) var foo" - testFormatting(for: input, output, rule: .modifierOrder) - } - - func testPrivateRequiredStaticFuncModifiers() { - let input = "required static private func foo()" - let output = "private required static func foo()" - let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: .modifierOrder, options: options) - } - - func testPrivateConvenienceInit() { - let input = "convenience private init()" - let output = "private convenience init()" - testFormatting(for: input, output, rule: .modifierOrder) - } - - func testSpaceInModifiersLeftIntact() { - let input = "weak private(set) /* read-only */\npublic var" - let output = "public private(set) /* read-only */\nweak var" - testFormatting(for: input, output, rule: .modifierOrder) - } - - func testSpaceInModifiersLeftIntact2() { - let input = "nonisolated(unsafe) public var foo: String" - let output = "public nonisolated(unsafe) var foo: String" - testFormatting(for: input, output, rule: .modifierOrder) - } - - func testPrefixModifier() { - let input = "prefix public static func - (rhs: Foo) -> Foo" - let output = "public static prefix func - (rhs: Foo) -> Foo" - let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: .modifierOrder, options: options) - } - - func testModifierOrder() { - let input = "override public var foo: Int { 5 }" - let output = "public override var foo: Int { 5 }" - let options = FormatOptions(modifierOrder: ["public", "override"]) - testFormatting(for: input, output, rule: .modifierOrder, options: options) - } - - func testConsumingModifierOrder() { - let input = "consuming public func close()" - let output = "public consuming func close()" - let options = FormatOptions(modifierOrder: ["public", "consuming"]) - testFormatting(for: input, output, rule: .modifierOrder, options: options, exclude: [.noExplicitOwnership]) - } - - func testNoConfusePostfixIdentifierWithKeyword() { - let input = "var foo = .postfix\noverride init() {}" - testFormatting(for: input, rule: .modifierOrder) - } - - func testNoConfusePostfixIdentifierWithKeyword2() { - let input = "var foo = postfix\noverride init() {}" - testFormatting(for: input, rule: .modifierOrder) - } - - func testNoConfuseCaseWithModifier() { - let input = """ - enum Foo { - case strong - case weak - public init() {} - } - """ - testFormatting(for: input, rule: .modifierOrder) - } - - // MARK: - sortDeclarations - - func testSortEnumBody() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsellB - case fooFeature( - fooConfiguration: Foo, - barConfiguration: Bar - ) - case barFeature // Trailing comment -- bar feature - /// Leading comment -- upsell A - case upsellA( - fooConfiguration: Foo, - barConfiguration: Bar - ) - } - - enum NextType { - case foo - case bar - } - """ - - let output = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature // Trailing comment -- bar feature - case fooFeature( - fooConfiguration: Foo, - barConfiguration: Bar - ) - /// Leading comment -- upsell A - case upsellA( - fooConfiguration: Foo, - barConfiguration: Bar - ) - case upsellB - } - - enum NextType { - case foo - case bar - } - """ - - testFormatting(for: input, output, rule: .sortDeclarations) - } - - func testSortEnumBodyWithOnlyOneCase() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsellB - } - """ - - testFormatting(for: input, rule: .sortDeclarations) - } - - func testSortEnumBodyWithoutCase() { - let input = """ - // swiftformat:sort - enum FeatureFlags {} - """ - - testFormatting(for: input, rule: .sortDeclarations) - } - - func testNoSortUnannotatedType() { - let input = """ - enum FeatureFlags { - case upsellB - case fooFeature - case barFeature - case upsellA - } - """ - - testFormatting(for: input, rule: .sortDeclarations) - } - - func testPreservesSortedBody() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - case upsellB - } - """ - - testFormatting(for: input, rule: .sortDeclarations) - } - - func testSortsTypeBody() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsellB - case fooFeature - case barFeature - case upsellA - } - """ - - let output = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - case upsellB - } - """ - - testFormatting(for: input, output, rule: .sortDeclarations, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) - } - - func testSortClassWithMixedDeclarationTypes() { - let input = """ - // swiftformat:sort - class Foo { - let quuxProperty = Quux() - let barProperty = Bar() - - var fooComputedProperty: Foo { - Foo() - } - - func baazFunction() -> Baaz { - Baaz() - } - } - """ - - let output = """ - // swiftformat:sort - class Foo { - func baazFunction() -> Baaz { - Baaz() - } - let barProperty = Bar() - - var fooComputedProperty: Foo { - Foo() - } - - let quuxProperty = Quux() - } - """ - - testFormatting(for: input, [output], - rules: [.sortDeclarations, .consecutiveBlankLines], - exclude: [.blankLinesBetweenScopes, .propertyType]) - } - - func testSortBetweenDirectiveCommentsInType() { - let input = """ - enum FeatureFlags { - // swiftformat:sort:begin - case upsellB - case fooFeature - case barFeature - case upsellA - // swiftformat:sort:end - - var anUnsortedProperty: Foo { - Foo() - } - } - """ - - let output = """ - enum FeatureFlags { - // swiftformat:sort:begin - case barFeature - case fooFeature - case upsellA - case upsellB - // swiftformat:sort:end - - var anUnsortedProperty: Foo { - Foo() - } - } - """ - - testFormatting(for: input, output, rule: .sortDeclarations) - } - - func testSortTopLevelDeclarations() { - let input = """ - let anUnsortedGlobal = 0 - - // swiftformat:sort:begin - let sortThisGlobal = 1 - public let thisGlobalIsSorted = 2 - private let anotherSortedGlobal = 5 - let sortAllOfThem = 8 - // swiftformat:sort:end - - let anotherUnsortedGlobal = 9 - """ - - let output = """ - let anUnsortedGlobal = 0 - - // swiftformat:sort:begin - private let anotherSortedGlobal = 5 - let sortAllOfThem = 8 - let sortThisGlobal = 1 - public let thisGlobalIsSorted = 2 - // swiftformat:sort:end - - let anotherUnsortedGlobal = 9 - """ - - testFormatting(for: input, output, rule: .sortDeclarations) - } - - func testDoesntConflictWithOrganizeDeclarations() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - case upsellB - - // MARK: Internal - - var anUnsortedProperty: Foo { - Foo() - } - - var unsortedProperty: Foo { - Foo() - } - } - """ - - testFormatting(for: input, rule: .organizeDeclarations) - } - - func testSortsWithinOrganizeDeclarations() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case fooFeature - case barFeature - case upsellB - case upsellA - - // MARK: Internal - - var sortedProperty: Foo { - Foo() - } - - var aSortedProperty: Foo { - Foo() - } - } - """ - - let output = """ - // swiftformat:sort - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - - case upsellB - - // MARK: Internal - - var aSortedProperty: Foo { - Foo() - } - - var sortedProperty: Foo { - Foo() - } - - } - """ - - testFormatting(for: input, [output], - rules: [.organizeDeclarations, .blankLinesBetweenScopes], - exclude: [.blankLinesAtEndOfScope]) - } - - func testSortsWithinOrganizeDeclarationsByClassName() { - let input = """ - enum FeatureFlags { - case fooFeature - case barFeature - case upsellB - case upsellA - - // MARK: Internal - - var sortedProperty: Foo { - Foo() - } - - var aSortedProperty: Foo { - Foo() - } - } - """ - - let output = """ - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - - case upsellB - - // MARK: Internal - - var aSortedProperty: Foo { - Foo() - } - - var sortedProperty: Foo { - Foo() - } - - } - """ - - testFormatting(for: input, [output], - rules: [.organizeDeclarations, .blankLinesBetweenScopes], - options: .init(alphabeticallySortedDeclarationPatterns: ["FeatureFlags"]), - exclude: [.blankLinesAtEndOfScope]) - } - - func testSortsWithinOrganizeDeclarationsByPartialClassName() { - let input = """ - enum FeatureFlags { - case fooFeature - case barFeature - case upsellB - case upsellA - - // MARK: Internal - - var sortedProperty: Foo { - Foo() - } - - var aSortedProperty: Foo { - Foo() - } - } - """ - - let output = """ - enum FeatureFlags { - case barFeature - case fooFeature - case upsellA - - case upsellB - - // MARK: Internal - - var aSortedProperty: Foo { - Foo() - } - - var sortedProperty: Foo { - Foo() - } - - } - """ - - testFormatting(for: input, [output], - rules: [.organizeDeclarations, .blankLinesBetweenScopes], - options: .init(alphabeticallySortedDeclarationPatterns: ["ureFla"]), - exclude: [.blankLinesAtEndOfScope]) - } - - func testDontSortsWithinOrganizeDeclarationsByClassNameInComment() { - let input = """ - /// Comment - enum FeatureFlags { - case fooFeature - case barFeature - case upsellB - case upsellA - - // MARK: Internal - - var sortedProperty: Foo { - Foo() - } - - var aSortedProperty: Foo { - Foo() - } - } - """ - - testFormatting(for: input, - rules: [.organizeDeclarations, .blankLinesBetweenScopes], - options: .init(alphabeticallySortedDeclarationPatterns: ["Comment"]), - exclude: [.blankLinesAtEndOfScope]) - } - - func testSortDeclarationsSortsByNamePattern() { - let input = """ - enum Namespace {} - - extension Namespace { - static let foo = "foo" - public static let bar = "bar" - static let baaz = "baaz" - } - """ - - let output = """ - enum Namespace {} - - extension Namespace { - static let baaz = "baaz" - public static let bar = "bar" - static let foo = "foo" - } - """ - - let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Namespace"]) - testFormatting(for: input, [output], rules: [.sortDeclarations, .blankLinesBetweenScopes], options: options) - } - - func testSortDeclarationsWontSortByNamePatternInComment() { - let input = """ - enum Namespace {} - - /// Constants - /// enum Constants - extension Namespace { - static let foo = "foo" - public static let bar = "bar" - static let baaz = "baaz" - } - """ - - let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Constants"]) - testFormatting(for: input, rules: [.sortDeclarations, .blankLinesBetweenScopes], options: options) - } - - func testSortDeclarationsUsesLocalizedCompare() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsella - case upsellA - case upsellb - case upsellB - } - """ - - testFormatting(for: input, rule: .sortDeclarations) - } - - func testOrganizeDeclarationsSortUsesLocalizedCompare() { - let input = """ - // swiftformat:sort - enum FeatureFlags { - case upsella - case upsellA - case upsellb - case upsellB - } - """ - - testFormatting(for: input, rule: .organizeDeclarations) - } - - func testSortDeclarationsSortsExtensionBody() { - let input = """ - enum Namespace {} - - // swiftformat:sort - extension Namespace { - static let foo = "foo" - public static let bar = "bar" - static let baaz = "baaz" - } - """ - - let output = """ - enum Namespace {} - - // swiftformat:sort - extension Namespace { - static let baaz = "baaz" - public static let bar = "bar" - static let foo = "foo" - } - """ - - // organizeTypes doesn't include "extension". So even though the - // organizeDeclarations rule is enabled, the extension should be - // sorted by the sortDeclarations rule. - let options = FormatOptions(organizeTypes: ["class"]) - testFormatting(for: input, [output], rules: [.sortDeclarations, .organizeDeclarations], options: options) - } - - func testOrganizeDeclarationsSortsExtensionBody() { - let input = """ - enum Namespace {} - - // swiftformat:sort - extension Namespace { - static let foo = "foo" - public static let bar = "bar" - static let baaz = "baaz" + enum Namespace {} + + // swiftformat:sort + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" } """ @@ -4887,174 +2703,6 @@ class OrganizationTests: RulesTests { options: options, exclude: [.blankLinesAtStartOfScope]) } - // MARK: - sortTypealiases - - func testSortSingleLineTypealias() { - let input = """ - typealias Placeholders = Foo & Bar & Quux & Baaz - """ - - let output = """ - typealias Placeholders = Baaz & Bar & Foo & Quux - """ - - testFormatting(for: input, output, rule: .sortTypealiases) - } - - func testSortMultilineTypealias() { - let input = """ - typealias Placeholders = Foo & Bar - & Quux & Baaz - """ - - let output = """ - typealias Placeholders = Baaz & Bar - & Foo & Quux - """ - - testFormatting(for: input, output, rule: .sortTypealiases) - } - - func testSortMultilineTypealiasWithComments() { - let input = """ - typealias Placeholders = Foo & Bar // Comment about Bar - // Comment about Quux - & Quux & Baaz // Comment about Baaz - """ - - let output = """ - typealias Placeholders = Baaz // Comment about Baaz - & Bar // Comment about Bar - & Foo - // Comment about Quux - & Quux - """ - - testFormatting(for: input, [output], rules: [.sortTypealiases, .indent, .trailingSpace]) - } - - func testSortWrappedMultilineTypealias1() { - let input = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let output = """ - typealias Dependencies = BaazProviding - & BarProviding - & FooProviding - & QuuxProviding - """ - - testFormatting(for: input, output, rule: .sortTypealiases) - } - - func testSortWrappedMultilineTypealias2() { - let input = """ - typealias Dependencies - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let output = """ - typealias Dependencies - = BaazProviding - & BarProviding - & FooProviding - & QuuxProviding - """ - - testFormatting(for: input, output, rule: .sortTypealiases) - } - - func testSortWrappedMultilineTypealiasWithComments() { - let input = """ - typealias Dependencies - // Comment about FooProviding - = FooProviding - // Comment about BarProviding - & BarProviding - & QuuxProviding // Comment about QuuxProviding - // Comment about BaazProviding - & BaazProviding // Comment about BaazProviding - """ - - let output = """ - typealias Dependencies - // Comment about BaazProviding - = BaazProviding // Comment about BaazProviding - // Comment about BarProviding - & BarProviding - // Comment about FooProviding - & FooProviding - & QuuxProviding // Comment about QuuxProviding - """ - - testFormatting(for: input, output, rule: .sortTypealiases) - } - - func testSortTypealiasesWithAssociatedTypes() { - let input = """ - typealias Collections - = Collection - & Collection - & Collection - & Collection - """ - - let output = """ - typealias Collections - = Collection - & Collection - & Collection - & Collection - """ - - testFormatting(for: input, output, rule: .sortTypealiases) - } - - func testSortTypeAliasesAndRemoveDuplicates() { - let input = """ - typealias Placeholders = Foo & Bar & Quux & Baaz & Bar - - typealias Dependencies1 - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - & FooProviding - - typealias Dependencies2 - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - & BaazProviding - """ - - let output = """ - typealias Placeholders = Baaz & Bar & Foo & Quux - - typealias Dependencies1 - = BaazProviding - & BarProviding - & FooProviding - & QuuxProviding - - typealias Dependencies2 - = BaazProviding - & BarProviding - & FooProviding - & QuuxProviding - """ - - testFormatting(for: input, output, rule: .sortTypealiases) - } - func testSortSingleSwiftUIPropertyWrapper() { let input = """ struct ContentView: View { diff --git a/Tests/Rules/PreferForLoopTests.swift b/Tests/Rules/PreferForLoopTests.swift new file mode 100644 index 000000000..6d7fd94dc --- /dev/null +++ b/Tests/Rules/PreferForLoopTests.swift @@ -0,0 +1,391 @@ +// +// PreferForLoopTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class PreferForLoopTests: XCTestCase { + func testConvertSimpleForEachToForLoop() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { string in + print(string) + } + + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { (string: String) in + print(string) + } + """ + + let output = """ + let placeholderStrings = ["foo", "bar", "baaz"] + for string in placeholderStrings { + print(string) + } + + let placeholderStrings = ["foo", "bar", "baaz"] + for string in placeholderStrings { + print(string) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testConvertAnonymousForEachToForLoop() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { + print($0) + } + + potatoes.forEach({ $0.bake() }) + """ + + let output = """ + let placeholderStrings = ["foo", "bar", "baaz"] + for placeholderString in placeholderStrings { + print(placeholderString) + } + + potatoes.forEach({ $0.bake() }) + """ + + testFormatting(for: input, output, rule: .preferForLoop, exclude: [.trailingClosures]) + } + + func testNoConvertAnonymousForEachToForLoop() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { + print($0) + } + + potatoes.forEach({ $0.bake() }) + """ + + let options = FormatOptions(preserveAnonymousForEach: true, preserveSingleLineForEach: false) + testFormatting(for: input, rule: .preferForLoop, options: options, exclude: [.trailingClosures]) + } + + func testConvertSingleLineForEachToForLoop() { + let input = "potatoes.forEach({ item in item.bake() })" + let output = "for item in potatoes { item.bake() }" + + let options = FormatOptions(preserveSingleLineForEach: false) + testFormatting(for: input, output, rule: .preferForLoop, options: options, + exclude: [.wrapLoopBodies]) + } + + func testConvertSingleLineAnonymousForEachToForLoop() { + let input = "potatoes.forEach({ $0.bake() })" + let output = "for potato in potatoes { potato.bake() }" + + let options = FormatOptions(preserveSingleLineForEach: false) + testFormatting(for: input, output, rule: .preferForLoop, options: options, + exclude: [.wrapLoopBodies]) + } + + func testConvertNestedForEach() { + let input = """ + let nestedArrays = [[1, 2], [3, 4]] + nestedArrays.forEach { + $0.forEach { + $0.forEach { + print($0) + } + } + } + """ + + let output = """ + let nestedArrays = [[1, 2], [3, 4]] + for nestedArray in nestedArrays { + for item in nestedArray { + for item in item { + print(item) + } + } + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testDefaultNameAlreadyUsedInLoopBody() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { + let placeholderString = $0.uppercased() + print(placeholderString, $0) + } + """ + + let output = """ + let placeholderStrings = ["foo", "bar", "baaz"] + for item in placeholderStrings { + let placeholderString = item.uppercased() + print(placeholderString, item) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testIgnoreLoopsWithCaptureListForNow() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { [someCapturedValue = fooBar] in + print($0, someCapturedValue) + } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testRemoveAllPrefixFromLoopIdentifier() { + let input = """ + allWindows.forEach { + print($0) + } + """ + + let output = """ + for window in allWindows { + print(window) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testConvertsReturnToContinue() { + let input = """ + let placeholderStrings = ["foo", "bar", "baaz"] + placeholderStrings.forEach { + func capitalize(_ value: String) -> String { + return value.uppercased() + } + + if $0 == "foo" { + return + } else { + print(capitalize($0)) + } + } + """ + + let output = """ + let placeholderStrings = ["foo", "bar", "baaz"] + for placeholderString in placeholderStrings { + func capitalize(_ value: String) -> String { + return value.uppercased() + } + + if placeholderString == "foo" { + continue + } else { + print(capitalize(placeholderString)) + } + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnChainedProperties() { + let input = """ + let bar = foo.bar + bar.baaz.quux.strings.forEach { + print($0) + } + """ + + let output = """ + let bar = foo.bar + for string in bar.baaz.quux.strings { + print(string) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnFunctionCallResult() { + let input = """ + let bar = foo.bar + foo.item().bar[2].baazValues(option: true).forEach { + print($0) + } + """ + + let output = """ + let bar = foo.bar + for baazValue in foo.item().bar[2].baazValues(option: true) { + print(baazValue) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnSubscriptResult() { + let input = """ + let bar = foo.bar + foo.item().bar[2].dictionary["myValue"].forEach { + print($0) + } + """ + + let output = """ + let bar = foo.bar + for item in foo.item().bar[2].dictionary["myValue"] { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnArrayLiteral() { + let input = """ + let quux = foo.bar.baaz.quux + ["foo", "bar", "baaz", quux].forEach { + print($0) + } + """ + + let output = """ + let quux = foo.bar.baaz.quux + for item in ["foo", "bar", "baaz", quux] { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnCurriedFunctionWithSubscript() { + let input = """ + let quux = foo.bar.baaz.quux + foo(bar)(baaz)["item"].forEach { + print($0) + } + """ + + let output = """ + let quux = foo.bar.baaz.quux + for item in foo(bar)(baaz)["item"] { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testHandlesForEachOnArrayLiteralInParens() { + let input = """ + let quux = foo.bar.baaz.quux + (["foo", "bar", "baaz", quux]).forEach { + print($0) + } + """ + + let output = """ + let quux = foo.bar.baaz.quux + for item in (["foo", "bar", "baaz", quux]) { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop, exclude: [.redundantParens]) + } + + func testPreservesForEachAfterMultilineChain() { + let input = """ + placeholderStrings + .filter { $0.style == .fooBar } + .map { $0.uppercased() } + .forEach { print($0) } + + placeholderStrings + .filter({ $0.style == .fooBar }) + .map({ $0.uppercased() }) + .forEach({ print($0) }) + """ + testFormatting(for: input, rule: .preferForLoop, exclude: [.trailingClosures]) + } + + func testPreservesChainWithClosure() { + let input = """ + // Converting this to a for loop would result in unusual looking syntax like + // `for string in strings.map { $0.uppercased() } { print($0) }` + // which causes a warning to be emitted: "trailing closure in this context is + // confusable with the body of the statement; pass as a parenthesized argument + // to silence this warning". + strings.map { $0.uppercased() }.forEach { print($0) } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testForLoopVariableNotUsedIfClashesWithKeyword() { + let input = """ + Foo.allCases.forEach { + print($0) + } + """ + let output = """ + for item in Foo.allCases { + print(item) + } + """ + testFormatting(for: input, output, rule: .preferForLoop) + } + + func testTryNotRemovedInThrowingForEach() { + let input = """ + try list().forEach { + print($0) + } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testOptionalTryNotRemovedInThrowingForEach() { + let input = """ + try? list().forEach { + print($0) + } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testAwaitNotRemovedInAsyncForEach() { + let input = """ + await list().forEach { + print($0) + } + """ + testFormatting(for: input, rule: .preferForLoop) + } + + func testForEachOverDictionary() { + let input = """ + let dict = ["a": "b"] + + dict.forEach { (header: (key: String, value: String)) in + print(header.key) + print(header.value) + } + """ + + let output = """ + let dict = ["a": "b"] + + for header in dict { + print(header.key) + print(header.value) + } + """ + + testFormatting(for: input, output, rule: .preferForLoop) + } +} diff --git a/Tests/Rules/PreferKeyPathTests.swift b/Tests/Rules/PreferKeyPathTests.swift new file mode 100644 index 000000000..fa227a4ed --- /dev/null +++ b/Tests/Rules/PreferKeyPathTests.swift @@ -0,0 +1,113 @@ +// +// PreferKeyPathTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class PreferKeyPathTests: XCTestCase { + func testMapPropertyToKeyPath() { + let input = "let foo = bar.map { $0.foo }" + let output = "let foo = bar.map(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testCompactMapPropertyToKeyPath() { + let input = "let foo = bar.compactMap { $0.foo }" + let output = "let foo = bar.compactMap(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testFlatMapPropertyToKeyPath() { + let input = "let foo = bar.flatMap { $0.foo }" + let output = "let foo = bar.flatMap(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testMapNestedPropertyWithSpacesToKeyPath() { + let input = "let foo = bar.map { $0 . foo . bar }" + let output = "let foo = bar.map(\\ . foo . bar)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options, exclude: [.spaceAroundOperators]) + } + + func testMultilineMapPropertyToKeyPath() { + let input = """ + let foo = bar.map { + $0.foo + } + """ + let output = "let foo = bar.map(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testParenthesizedMapPropertyToKeyPath() { + let input = "let foo = bar.map({ $0.foo })" + let output = "let foo = bar.map(\\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, + options: options) + } + + func testNoMapSelfToKeyPath() { + let input = "let foo = bar.map { $0 }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForSwiftLessThan5_2() { + let input = "let foo = bar.map { $0.foo }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForFunctionCalls() { + let input = "let foo = bar.map { $0.foo() }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForCompoundExpressions() { + let input = "let foo = bar.map { $0.foo || baz }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForOptionalChaining() { + let input = "let foo = bar.map { $0?.foo }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testNoMapPropertyToKeyPathForTrailingContains() { + let input = "let foo = bar.contains { $0.foo }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } + + func testMapPropertyToKeyPathForContainsWhere() { + let input = "let foo = bar.contains(where: { $0.foo })" + let output = "let foo = bar.contains(where: \\.foo)" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, output, rule: .preferKeyPath, options: options) + } + + func testMultipleTrailingClosuresNotConvertedToKeyPath() { + let input = "foo.map { $0.bar } reverse: { $0.bar }" + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .preferKeyPath, options: options) + } +} diff --git a/Tests/Rules/PropertyTypeTests.swift b/Tests/Rules/PropertyTypeTests.swift new file mode 100644 index 000000000..2cdbcede1 --- /dev/null +++ b/Tests/Rules/PropertyTypeTests.swift @@ -0,0 +1,560 @@ +// +// PropertyTypeTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class PropertyTypeTests: XCTestCase { + func testConvertsExplicitTypeToInferredType() { + let input = """ + let foo: Foo = .init() + let bar: Bar = .staticBar + let baaz: Baaz = .Example.default + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + """ + + let output = """ + let foo = Foo() + let bar = Bar.staticBar + let baaz = Baaz.Example.default + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testConvertsInferredTypeToExplicitType() { + let input = """ + let foo = Foo() + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + """ + + let output = """ + let foo: Foo = .init() + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .propertyType, options: options) + } + + func testConvertsTypeMembersToExplicitType() { + let input = """ + struct Foo { + let foo = Foo() + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + } + """ + + let output = """ + struct Foo { + let foo: Foo = .init() + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, output, rule: .propertyType, options: options) + } + + func testConvertsLocalsToImplicitType() { + let input = """ + struct Foo { + let foo = Foo() + + func bar() { + let bar: Bar = .staticBar + let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) + + let dictionary: [Foo: Bar] = .init() + let array: [Foo] = .init() + let genericType: MyGenericType = .init() + } + } + """ + + let output = """ + struct Foo { + let foo: Foo = .init() + + func bar() { + let bar = Bar.staticBar + let quux = Quux.quuxBulder(foo: .foo, bar: .bar) + + let dictionary = [Foo: Bar]() + let array = [Foo]() + let genericType = MyGenericType() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testPreservesInferredTypeFollowingTypeWithDots() { + let input = """ + let baaz = Baaz.Example.default + let color = Color.Theme.default + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesExplicitTypeIfNoRHS() { + let input = """ + let foo: Foo + let bar: Bar + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesImplicitTypeIfNoRHSType() { + let input = """ + let foo = foo() + let bar = bar + let int = 24 + let array = ["string"] + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesImplicitForVoidAndTuples() { + let input = """ + let foo = Void() + let foo = (foo: "foo", bar: "bar").foo + let foo = ["bar", "baz"].quux(quuz) + let foo = [bar].first + let foo = [bar, baaz].first + let foo = ["foo": "bar"].first + let foo = [foo: bar].first + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .propertyType, options: options, exclude: [.void]) + } + + func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { + let input = """ + let foo: Foo = localFoo + let bar: Bar = localBar + let int: Int64 = 1234 + let number: CGFloat = 12.345 + let array: [String] = [] + let dictionary: [String: Int] = [:] + let tuple: (String, Int) = ("foo", 123) + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options, exclude: [.redundantType]) + } + + func testCompatibleWithRedundantTypeInferred() { + let input = """ + let foo: Foo = Foo() + """ + + let output = """ + let foo = Foo() + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType], options: options) + } + + func testCompatibleWithRedundantTypeExplicit() { + let input = """ + let foo: Foo = Foo() + """ + + let output = """ + let foo: Foo = .init() + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType], options: options) + } + + func testCompatibleWithRedundantTypeInferLocalsOnly() { + let input = """ + let foo: Foo = Foo.init() + let foo: Foo = .init() + + func bar() { + let baaz: Baaz = Baaz.init() + let baaz: Baaz = .init() + } + """ + + let output = """ + let foo: Foo = .init() + let foo: Foo = .init() + + func bar() { + let baaz = Baaz() + let baaz = Baaz() + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [.redundantType, .propertyType, .redundantInit], options: options) + } + + func testPropertyTypeWithIfExpressionDisabledByDefault() { + let input = """ + let foo: SomeTypeWithALongGenrericName = + if condition { + .init(bar) + } else { + .init(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPropertyTypeWithIfExpression() { + let input = """ + let foo: Foo = + if condition { + .init(bar) + } else { + .init(baaz) + } + """ + + let output = """ + let foo = + if condition { + Foo(bar) + } else { + Foo(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testPropertyTypeWithSwitchExpression() { + let input = """ + let foo: Foo = + switch condition { + case true: + .init(bar) + case false: + .init(baaz) + } + """ + + let output = """ + let foo = + switch condition { + case true: + Foo(bar) + case false: + Foo(baaz) + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testPreservesNonMatchingIfExpression() { + let input = """ + let foo: Foo = + if condition { + .init(bar) + } else { + [] // e.g. using ExpressibleByArrayLiteral + } + """ + + let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesExplicitOptionalType() { + // `let foo = Foo?.foo` doesn't work if `.foo` is defined on `Foo` but not `Foo?` + let input = """ + let optionalFoo1: Foo? = .foo + let optionalFoo2: Foo? = Foo.foo + let optionalFoo3: Foo! = .foo + let optionalFoo4: Foo! = Foo.foo + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesTypeWithSeparateDeclarationAndProperty() { + let input = """ + var foo: Foo! + foo = Foo(afterDelay: { + print(foo) + }) + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesTypeWithExistentialAny() { + let input = """ + protocol ShapeStyle {} + struct MyShapeStyle: ShapeStyle {} + + extension ShapeStyle where Self == MyShapeStyle { + static var myShape: MyShapeStyle { MyShapeStyle() } + } + + /// This compiles + let myShape1: any ShapeStyle = .myShape + + // This would fail with "error: static member 'myShape' cannot be used on protocol metatype '(any ShapeStyle).Type'" + // let myShape2 = (any ShapeStyle).myShape + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesExplicitRightHandSideWithOperator() { + let input = """ + let value: ClosedRange = .zero ... 10 + let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge + let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() + let dynamicTypeSizeRange: ClosedRange = .convertFromLiteral(.large ... .xxxLarge) + """ + + let output = """ + let value: ClosedRange = .zero ... 10 + let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge + let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() + let dynamicTypeSizeRange = ClosedRange.convertFromLiteral(.large ... .xxxLarge) + """ + + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .propertyType, options: options) + } + + func testPreservesInferredRightHandSideWithOperators() { + let input = """ + let foo = Foo().bar + let foo = Foo.bar.baaz.quux + let foo = Foo.bar ... baaz + """ + + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPreservesUserProvidedSymbolTypes() { + let input = """ + class Foo { + let foo = Foo() + let bar = Bar() + + func bar() { + let foo: Foo = .foo + let bar: Bar = .bar + let baaz: Baaz = .baaz + let quux: Quux = .quux + } + } + """ + + let output = """ + class Foo { + let foo = Foo() + let bar: Bar = .init() + + func bar() { + let foo: Foo = .foo + let bar = Bar.bar + let baaz: Baaz = .baaz + let quux: Quux = .quux + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["Foo", "Baaz", "quux"]) + testFormatting(for: input, output, rule: .propertyType, options: options) + } + + func testPreserveInitIfExplicitlyExcluded() { + let input = """ + class Foo { + let foo = Foo() + let bar = Bar.init() + let baaz = Baaz.baaz() + + func bar() { + let foo: Foo = .init() + let bar: Bar = .init() + let baaz: Baaz = .baaz() + } + } + """ + + let output = """ + class Foo { + let foo = Foo() + let bar = Bar.init() + let baaz: Baaz = .baaz() + + func bar() { + let foo: Foo = .init() + let bar: Bar = .init() + let baaz = Baaz.baaz() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["init"]) + testFormatting(for: input, output, rule: .propertyType, options: options, exclude: [.redundantInit]) + } + + func testClosureBodyIsConsideredLocal() { + let input = """ + foo { + let bar = Bar() + let baaz: Baaz = .init() + } + + foo(bar: bar, baaz: baaz, quux: { + let bar = Bar() + let baaz: Baaz = .init() + }) + + foo { + let bar = Bar() + let baaz: Baaz = .init() + } bar: { + let bar = Bar() + let baaz: Baaz = .init() + } + + class Foo { + let foo = Foo.bar { + let baaz = Baaz() + let baaz: Baaz = .init() + } + } + """ + + let output = """ + foo { + let bar = Bar() + let baaz = Baaz() + } + + foo(bar: bar, baaz: baaz, quux: { + let bar = Bar() + let baaz = Baaz() + }) + + foo { + let bar = Bar() + let baaz = Baaz() + } bar: { + let bar = Bar() + let baaz = Baaz() + } + + class Foo { + let foo: Foo = .bar { + let baaz = Baaz() + let baaz = Baaz() + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) + } + + func testIfGuardConditionsPreserved() { + let input = """ + if let foo = Foo(bar) { + let foo = Foo(bar) + } else if let foo = Foo(bar) { + let foo = Foo(bar) + } else { + let foo = Foo(bar) + } + + guard let foo = Foo(bar) else { + return + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, rule: .propertyType, options: options) + } + + func testPropertyObserversConsideredLocal() { + let input = """ + class Foo { + var foo: Foo { + get { + let foo = Foo(bar) + } + set { + let foo = Foo(bar) + } + willSet { + let foo = Foo(bar) + } + didSet { + let foo = Foo(bar) + } + } + } + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, rule: .propertyType, options: options) + } +} diff --git a/Tests/Rules/RedundantBackticksTests.swift b/Tests/Rules/RedundantBackticksTests.swift new file mode 100644 index 000000000..73942158b --- /dev/null +++ b/Tests/Rules/RedundantBackticksTests.swift @@ -0,0 +1,214 @@ +// +// RedundantBackticksTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantBackticksTests: XCTestCase { + func testRemoveRedundantBackticksInLet() { + let input = "let `foo` = bar" + let output = "let foo = bar" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundKeyword() { + let input = "let `let` = foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundSelf() { + let input = "let `self` = foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundClassSelfInTypealias() { + let input = "typealias `Self` = Foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundClassSelfAsReturnType() { + let input = "func foo(bar: `Self`) { print(bar) }" + let output = "func foo(bar: Self) { print(bar) }" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundClassSelfAsParameterType() { + let input = "func foo() -> `Self` {}" + let output = "func foo() -> Self {}" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundClassSelfArgument() { + let input = "func foo(`Self`: Foo) { print(Self) }" + let output = "func foo(Self: Foo) { print(Self) }" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundKeywordFollowedByType() { + let input = "let `default`: Int = foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundContextualGet() { + let input = "var foo: Int {\n `get`()\n return 5\n}" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundGetArgument() { + let input = "func foo(`get` value: Int) { print(value) }" + let output = "func foo(get value: Int) { print(value) }" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundTypeAtRootLevel() { + let input = "enum `Type` {}" + let output = "enum Type {}" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundTypeInsideType() { + let input = "struct Foo {\n enum `Type` {}\n}" + testFormatting(for: input, rule: .redundantBackticks, exclude: [.enumNamespaces]) + } + + func testNoRemoveBackticksAroundLetArgument() { + let input = "func foo(`let`: Foo) { print(`let`) }" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundTrueArgument() { + let input = "func foo(`true`: Foo) { print(`true`) }" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundTrueArgument() { + let input = "func foo(`true`: Foo) { print(`true`) }" + let output = "func foo(true: Foo) { print(`true`) }" + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantBackticks, options: options) + } + + func testNoRemoveBackticksAroundTypeProperty() { + let input = "var type: Foo.`Type`" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundTypePropertyInsideType() { + let input = "struct Foo {\n enum `Type` {}\n}" + testFormatting(for: input, rule: .redundantBackticks, exclude: [.enumNamespaces]) + } + + func testNoRemoveBackticksAroundTrueProperty() { + let input = "var type = Foo.`true`" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundTrueProperty() { + let input = "var type = Foo.`true`" + let output = "var type = Foo.true" + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantBackticks, options: options, exclude: [.propertyType]) + } + + func testRemoveBackticksAroundProperty() { + let input = "var type = Foo.`bar`" + let output = "var type = Foo.bar" + testFormatting(for: input, output, rule: .redundantBackticks, exclude: [.propertyType]) + } + + func testRemoveBackticksAroundKeywordProperty() { + let input = "var type = Foo.`default`" + let output = "var type = Foo.default" + testFormatting(for: input, output, rule: .redundantBackticks, exclude: [.propertyType]) + } + + func testRemoveBackticksAroundKeypathProperty() { + let input = "var type = \\.`bar`" + let output = "var type = \\.bar" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundKeypathKeywordProperty() { + let input = "var type = \\.`default`" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundKeypathKeywordPropertyInSwift5() { + let input = "var type = \\.`default`" + let output = "var type = \\.default" + let options = FormatOptions(swiftVersion: "5") + testFormatting(for: input, output, rule: .redundantBackticks, options: options) + } + + func testNoRemoveBackticksAroundInitPropertyInSwift5() { + let input = "let foo: Foo = .`init`" + let options = FormatOptions(swiftVersion: "5") + testFormatting(for: input, rule: .redundantBackticks, options: options, exclude: [.propertyType]) + } + + func testNoRemoveBackticksAroundAnyProperty() { + let input = "enum Foo {\n case `Any`\n}" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundGetInSubscript() { + let input = """ + subscript(_ name: String) -> T where T: Equatable { + `get`(name) + } + """ + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundActorProperty() { + let input = "let `actor`: Foo" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundActorRvalue() { + let input = "let foo = `actor`" + let output = "let foo = actor" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundActorLabel() { + let input = "init(`actor`: Foo)" + let output = "init(actor: Foo)" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testRemoveBackticksAroundActorLabel2() { + let input = "init(`actor` foo: Foo)" + let output = "init(actor foo: Foo)" + testFormatting(for: input, output, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundUnderscore() { + let input = "func `_`(_ foo: T) -> T { foo }" + testFormatting(for: input, rule: .redundantBackticks) + } + + func testNoRemoveBackticksAroundShadowedSelf() { + let input = """ + struct Foo { + let `self`: URL + + func printURL() { + print("My URL is \\(self.`self`)") + } + } + """ + let options = FormatOptions(swiftVersion: "4.1") + testFormatting(for: input, rule: .redundantBackticks, options: options) + } + + func testNoRemoveBackticksAroundDollar() { + let input = "@attached(peer, names: prefixed(`$`))" + testFormatting(for: input, rule: .redundantBackticks) + } +} diff --git a/Tests/Rules/RedundantBreakTests.swift b/Tests/Rules/RedundantBreakTests.swift new file mode 100644 index 000000000..b00ce198b --- /dev/null +++ b/Tests/Rules/RedundantBreakTests.swift @@ -0,0 +1,79 @@ +// +// RedundantBreakTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantBreakTests: XCTestCase { + func testRedundantBreaksRemoved() { + let input = """ + switch x { + case foo: + print("hello") + break + case bar: + print("world") + break + default: + print("goodbye") + break + } + """ + let output = """ + switch x { + case foo: + print("hello") + case bar: + print("world") + default: + print("goodbye") + } + """ + testFormatting(for: input, output, rule: .redundantBreak) + } + + func testBreakInEmptyCaseNotRemoved() { + let input = """ + switch x { + case foo: + break + case bar: + break + default: + break + } + """ + testFormatting(for: input, rule: .redundantBreak) + } + + func testConditionalBreakNotRemoved() { + let input = """ + switch x { + case foo: + if bar { + break + } + } + """ + testFormatting(for: input, rule: .redundantBreak) + } + + func testBreakAfterSemicolonNotMangled() { + let input = """ + switch foo { + case 1: print(1); break + } + """ + let output = """ + switch foo { + case 1: print(1); + } + """ + testFormatting(for: input, output, rule: .redundantBreak, exclude: [.semicolons]) + } +} diff --git a/Tests/Rules/RedundantClosureTests.swift b/Tests/Rules/RedundantClosureTests.swift new file mode 100644 index 000000000..c5c20ed3c --- /dev/null +++ b/Tests/Rules/RedundantClosureTests.swift @@ -0,0 +1,1040 @@ +// +// RedundantClosureTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantClosureTests: XCTestCase { + func testClosureAroundConditionalAssignmentNotRedundantForExplicitReturn() { + let input = """ + let myEnum = MyEnum.a + let test: Int = { + switch myEnum { + case .a: + return 0 + case .b: + return 1 + } + }() + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options, + exclude: [.redundantReturn, .propertyType]) + } + + // MARK: redundantClosure + + func testRemoveRedundantClosureInSingleLinePropertyDeclaration() { + let input = """ + let foo = { "Foo" }() + let bar = { "Bar" }() + + let baaz = { "baaz" }() + + let quux = { "quux" }() + """ + + let output = """ + let foo = "Foo" + let bar = "Bar" + + let baaz = "baaz" + + let quux = "quux" + """ + + testFormatting(for: input, output, rule: .redundantClosure) + } + + func testRedundantClosureWithExplicitReturn() { + let input = """ + let foo = { return "Foo" }() + + let bar = { + return if Bool.random() { + "Bar" + } else { + "Baaz" + } + }() + """ + + let output = """ + let foo = "Foo" + + let bar = if Bool.random() { + "Bar" + } else { + "Baaz" + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure], + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureWithExplicitReturn2() { + let input = """ + func foo() -> String { + methodCall() + return { return "Foo" }() + } + + func bar() -> String { + methodCall() + return { "Bar" }() + } + + func baaz() -> String { + { return "Baaz" }() + } + """ + + let output = """ + func foo() -> String { + methodCall() + return "Foo" + } + + func bar() -> String { + methodCall() + return "Bar" + } + + func baaz() -> String { + "Baaz" + } + """ + + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure]) + } + + func testKeepsClosureThatIsNotCalled() { + let input = """ + let foo = { "Foo" } + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsEmptyClosures() { + let input = """ + let foo = {}() + let bar = { /* comment */ }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRemoveRedundantClosureInMultiLinePropertyDeclaration() { + let input = """ + lazy var bar = { + Bar() + }() + """ + + let output = """ + lazy var bar = Bar() + """ + + testFormatting(for: input, output, rule: .redundantClosure, exclude: [.propertyType]) + } + + func testRemoveRedundantClosureInMultiLinePropertyDeclarationWithString() { + let input = #""" + lazy var bar = { + """ + Multiline string literal + """ + }() + """# + + let output = #""" + lazy var bar = """ + Multiline string literal + """ + """# + + testFormatting(for: input, [output], rules: [.redundantClosure, .indent]) + } + + func testRemoveRedundantClosureInMultiLinePropertyDeclarationInClass() { + let input = """ + class Foo { + lazy var bar = { + return Bar(); + }() + } + """ + + let output = """ + class Foo { + lazy var bar = Bar() + } + """ + + testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure, + .semicolons], exclude: [.propertyType]) + } + + func testRemoveRedundantClosureInWrappedPropertyDeclaration_beforeFirst() { + let input = """ + lazy var baaz = { + Baaz( + foo: foo, + bar: bar) + }() + """ + + let output = """ + lazy var baaz = Baaz( + foo: foo, + bar: bar) + """ + + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) + testFormatting(for: input, [output], + rules: [.redundantClosure, .wrapArguments], + options: options, exclude: [.propertyType]) + } + + func testRemoveRedundantClosureInWrappedPropertyDeclaration_afterFirst() { + let input = """ + lazy var baaz = { + Baaz(foo: foo, + bar: bar) + }() + """ + + let output = """ + lazy var baaz = Baaz(foo: foo, + bar: bar) + """ + + let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) + testFormatting(for: input, [output], + rules: [.redundantClosure, .wrapArguments], + options: options, exclude: [.propertyType]) + } + + func testRedundantClosureKeepsMultiStatementClosureThatSetsProperty() { + let input = """ + lazy var baaz = { + let baaz = Baaz(foo: foo, bar: bar) + baaz.foo = foo2 + return baaz + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureKeepsMultiStatementClosureWithMultipleStatements() { + let input = """ + lazy var quux = { + print("hello world") + return "quux" + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureKeepsClosureWithInToken() { + let input = """ + lazy var double = { () -> Double in + 100 + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureKeepsMultiStatementClosureOnSameLine() { + let input = """ + lazy var baaz = { + print("Foo"); return baaz + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureRemovesComplexMultilineClosure() { + let input = """ + lazy var closureInClosure = { + { + print("Foo") + print("Bar"); return baaz + } + }() + """ + + let output = """ + lazy var closureInClosure = { + print("Foo") + print("Bar"); return baaz + } + """ + + testFormatting(for: input, [output], rules: [.redundantClosure, .indent]) + } + + func testKeepsClosureWithIfStatement() { + let input = """ + lazy var baaz = { + if let foo == foo { + return foo + } else { + return Foo() + } + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsClosureWithIfStatementOnSingleLine() { + let input = """ + lazy var baaz = { + if let foo == foo { return foo } else { return Foo() } + }() + """ + + testFormatting(for: input, rule: .redundantClosure, + exclude: [.wrapConditionalBodies]) + } + + func testRemovesClosureWithIfStatementInsideOtherClosure() { + let input = """ + lazy var baaz = { + { + if let foo == foo { + return foo + } else { + return Foo() + } + } + }() + """ + + let output = """ + lazy var baaz = { + if let foo == foo { + return foo + } else { + return Foo() + } + } + """ + + testFormatting(for: input, [output], + rules: [.redundantClosure, .indent]) + } + + func testKeepsClosureWithSwitchStatement() { + let input = """ + lazy var baaz = { + switch foo { + case let .some(foo): + return foo: + case .none: + return Foo() + } + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsClosureWithIfDirective() { + let input = """ + lazy var baaz = { + #if DEBUG + return DebugFoo() + #else + return Foo() + #endif + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsClosureThatCallsMethodThatReturnsNever() { + let input = """ + lazy var foo: String = { fatalError("no default value has been set") }() + lazy var bar: String = { return preconditionFailure("no default value has been set") }() + """ + + testFormatting(for: input, rule: .redundantClosure, + exclude: [.redundantReturn]) + } + + func testRemovesClosureThatHasNestedFatalError() { + let input = """ + lazy var foo = { + Foo(handle: { fatalError() }) + }() + """ + + let output = """ + lazy var foo = Foo(handle: { fatalError() }) + """ + + testFormatting(for: input, output, rule: .redundantClosure, exclude: [.propertyType]) + } + + func testPreservesClosureWithMultipleVoidMethodCalls() { + let input = """ + lazy var triggerSomething: Void = { + logger.trace("log some stuff before Triggering") + TriggerClass.triggerTheThing() + logger.trace("Finished triggering the thing") + }() + """ + + testFormatting(for: input, rule: .redundantClosure) + } + + func testRemovesClosureWithMultipleNestedVoidMethodCalls() { + let input = """ + lazy var foo: Foo = { + Foo(handle: { + logger.trace("log some stuff before Triggering") + TriggerClass.triggerTheThing() + logger.trace("Finished triggering the thing") + }) + }() + """ + + let output = """ + lazy var foo: Foo = Foo(handle: { + logger.trace("log some stuff before Triggering") + TriggerClass.triggerTheThing() + logger.trace("Finished triggering the thing") + }) + """ + + testFormatting(for: input, [output], rules: [.redundantClosure, .indent], exclude: [.redundantType]) + } + + func testKeepsClosureThatThrowsError() { + let input = "let foo = try bar ?? { throw NSError() }()" + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsDiscardableResultClosure() { + let input = """ + @discardableResult + func discardableResult() -> String { "hello world" } + + /// We can't remove this closure, since the method called inline + /// would return a String instead. + let void: Void = { discardableResult() }() + """ + testFormatting(for: input, rule: .redundantClosure) + } + + func testKeepsDiscardableResultClosure2() { + let input = """ + @discardableResult + func discardableResult() -> String { "hello world" } + + /// We can't remove this closure, since the method called inline + /// would return a String instead. + let void: () = { discardableResult() }() + """ + testFormatting(for: input, rule: .redundantClosure) + } + + func testRedundantClosureDoesntLeaveStrayTry() { + let input = """ + let user2: User? = try { + if let data2 = defaults.data(forKey: defaultsKey) { + return try PropertyListDecoder().decode(User.self, from: data2) + } else { + return nil + } + }() + """ + let output = """ + let user2: User? = if let data2 = defaults.data(forKey: defaultsKey) { + try PropertyListDecoder().decode(User.self, from: data2) + } else { + nil + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureDoesntLeaveStrayTryAwait() { + let input = """ + let user2: User? = try await { + if let data2 = defaults.data(forKey: defaultsKey) { + return try await PropertyListDecoder().decode(User.self, from: data2) + } else { + return nil + } + }() + """ + let output = """ + let user2: User? = if let data2 = defaults.data(forKey: defaultsKey) { + try await PropertyListDecoder().decode(User.self, from: data2) + } else { + nil + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], + options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInOperatorChain() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes: Int { + { + switch self { + case .uint8: UInt8.bitWidth + case .uint16: UInt16.bitWidth + } + }() / 8 + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes: Int { + { + if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + }() / 8 + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain2() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes: Int { + 8 / { + if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + }() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain3() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes = 8 / { + if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + }() + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureDoesRemoveRedundantIfStatementClosureInAssignmentPosition() { + let input = """ + private enum Format { + case uint8 + case uint16 + + var bytes = { + if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + }() + } + """ + + let output = """ + private enum Format { + case uint8 + case uint16 + + var bytes = if self == .uint8 { + UInt8.bitWidth + } else { + UInt16.bitWidth + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInArray() { + let input = """ + private func constraint() -> [Int] { + [ + 1, + 2, + { + if Bool.random() { + 3 + } else { + 4 + } + }(), + ] + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testRedundantClosureRemovesClosureAsReturnTryStatement() { + let input = """ + func method() -> Int { + return { + return try! if Bool.random() { + randomThrows() + } else { + randomThrows() + } + }() + } + """ + + let output = """ + func method() -> Int { + return try! if Bool.random() { + randomThrows() + } else { + randomThrows() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsReturnTryStatement2() { + let input = """ + func method() throws -> Int { + return try { + return try if Bool.random() { + randomThrows() + } else { + randomThrows() + } + }() + } + """ + + let output = """ + func method() throws -> Int { + return try if Bool.random() { + randomThrows() + } else { + randomThrows() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsReturnTryStatement3() { + let input = """ + func method() async throws -> Int { + return try await { + return try await if Bool.random() { + randomAsyncThrows() + } else { + randomAsyncThrows() + } + }() + } + """ + + let output = """ + func method() async throws -> Int { + return try await if Bool.random() { + randomAsyncThrows() + } else { + randomAsyncThrows() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsReturnTryStatement4() { + let input = """ + func method() -> Int { + return { + return try! if Bool.random() { + randomThrows() + } else { + randomThrows() + } + }() + } + """ + + let output = """ + func method() -> Int { + return try! if Bool.random() { + randomThrows() + } else { + randomThrows() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsReturnStatement() { + let input = """ + func method() -> Int { + return { + return if Bool.random() { + 42 + } else { + 43 + } + }() + } + """ + + let output = """ + func method() -> Int { + return if Bool.random() { + 42 + } else { + 43 + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.redundantClosure], + options: options, exclude: [.redundantReturn, .indent]) + } + + func testRedundantClosureRemovesClosureAsImplicitReturnStatement() { + let input = """ + func method() -> Int { + { + if Bool.random() { + 42 + } else { + 43 + } + }() + } + """ + + let output = """ + func method() -> Int { + if Bool.random() { + 42 + } else { + 43 + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent]) + } + + func testClosureNotRemovedAroundIfExpressionInGuard() { + let input = """ + guard let foo = { + if condition { + bar() + } + }() else { + return + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testClosureNotRemovedInMethodCall() { + let input = """ + XCTAssert({ + if foo { + bar + } else { + baaz + } + }()) + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testClosureNotRemovedInMethodCall2() { + let input = """ + method("foo", { + if foo { + bar + } else { + baaz + } + }()) + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testClosureNotRemovedInMethodCall3() { + let input = """ + XCTAssert({ + if foo { + bar + } else { + baaz + } + }(), "message") + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testClosureNotRemovedInMethodCall4() { + let input = """ + method( + "foo", + { + if foo { + bar + } else { + baaz + } + }(), + "bar" + ) + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testDoesntRemoveClosureWithIfExpressionConditionalCastInSwift5_9() { + // The following code doesn't compile in Swift 5.9 due to this issue: + // https://github.com/apple/swift/issues/68764 + // + // let result = if condition { + // foo as? String + // } else { + // "bar" + // } + // + let input = """ + let result1: String? = { + if condition { + return foo as? String + } else { + return "bar" + } + }() + + let result1: String? = { + switch condition { + case true: + return foo as! String + case false: + return "bar" + } + }() + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantClosure, options: options) + } + + func testDoesRemoveClosureWithIfExpressionConditionalCastInSwift5_10() { + let input = """ + let result1: String? = { + if condition { + foo as? String + } else { + "bar" + } + }() + + let result2: String? = { + switch condition { + case true: + foo as? String + case false: + "bar" + } + }() + """ + + let output = """ + let result1: String? = if condition { + foo as? String + } else { + "bar" + } + + let result2: String? = switch condition { + case true: + foo as? String + case false: + "bar" + } + """ + + let options = FormatOptions(swiftVersion: "5.10") + testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) + } + + func testRedundantClosureDoesntBreakBuildWithRedundantReturnRuleDisabled() { + let input = """ + enum MyEnum { + case a + case b + } + let myEnum = MyEnum.a + let test: Int = { + return 0 + }() + """ + + let output = """ + enum MyEnum { + case a + case b + } + let myEnum = MyEnum.a + let test: Int = 0 + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantClosure, options: options, + exclude: [.redundantReturn, .blankLinesBetweenScopes, .propertyType]) + } + + func testRedundantClosureWithSwitchExpressionDoesntBreakBuildWithRedundantReturnRuleDisabled() { + // From https://github.com/nicklockwood/SwiftFormat/issues/1565 + let input = """ + enum MyEnum { + case a + case b + } + let myEnum = MyEnum.a + let test: Int = { + switch myEnum { + case .a: + return 0 + case .b: + return 1 + } + }() + """ + + let output = """ + enum MyEnum { + case a + case b + } + let myEnum = MyEnum.a + let test: Int = switch myEnum { + case .a: + 0 + case .b: + 1 + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure], + options: options, + exclude: [.indent, .blankLinesBetweenScopes, .wrapMultilineConditionalAssignment, + .propertyType]) + } + + func testRemovesRedundantClosureWithGenericExistentialTypes() { + let input = """ + let foo: Foo = { DefaultFoo() }() + let foo: any Foo = { DefaultFoo() }() + let foo: any Foo = { DefaultFoo() }() + """ + + let output = """ + let foo: Foo = DefaultFoo() + let foo: any Foo = DefaultFoo() + let foo: any Foo = DefaultFoo() + """ + + testFormatting(for: input, output, rule: .redundantClosure) + } +} diff --git a/Tests/Rules/RedundantExtensionACLTests.swift b/Tests/Rules/RedundantExtensionACLTests.swift new file mode 100644 index 000000000..fd5edda43 --- /dev/null +++ b/Tests/Rules/RedundantExtensionACLTests.swift @@ -0,0 +1,48 @@ +// +// RedundantExtensionACLTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantExtensionACLTests: XCTestCase { + func testPublicExtensionMemberACLStripped() { + let input = """ + public extension Foo { + public var bar: Int { 5 } + private static let baz = "baz" + public func quux() {} + } + """ + let output = """ + public extension Foo { + var bar: Int { 5 } + private static let baz = "baz" + func quux() {} + } + """ + testFormatting(for: input, output, rule: .redundantExtensionACL) + } + + func testPrivateExtensionMemberACLNotStrippedUnlessFileprivate() { + let input = """ + private extension Foo { + fileprivate var bar: Int { 5 } + private static let baz = "baz" + fileprivate func quux() {} + } + """ + let output = """ + private extension Foo { + var bar: Int { 5 } + private static let baz = "baz" + func quux() {} + } + """ + testFormatting(for: input, output, rule: .redundantExtensionACL) + } +} diff --git a/Tests/Rules/RedundantFileprivateTests.swift b/Tests/Rules/RedundantFileprivateTests.swift new file mode 100644 index 000000000..bcbd22b6a --- /dev/null +++ b/Tests/Rules/RedundantFileprivateTests.swift @@ -0,0 +1,575 @@ +// +// RedundantFileprivateTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantFileprivateTests: XCTestCase { + func testFileScopeFileprivateVarChangedToPrivate() { + let input = """ + fileprivate var foo = "foo" + """ + let output = """ + private var foo = "foo" + """ + testFormatting(for: input, output, rule: .redundantFileprivate) + } + + func testFileScopeFileprivateVarNotChangedToPrivateIfFragment() { + let input = """ + fileprivate var foo = "foo" + """ + let options = FormatOptions(fragment: true) + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherType() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + } + """ + let output = """ + struct Foo { + private var foo = "foo" + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherTypeAndFileIncludesImports() { + let input = """ + import Foundation + + struct Foo { + fileprivate var foo = "foo" + } + """ + let output = """ + import Foundation + + struct Foo { + private var foo = "foo" + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAnotherType() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + struct Bar { + func bar() { + print(Foo().foo) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromSubclass() { + let input = """ + class Foo { + fileprivate func foo() {} + } + + class Bar: Foo { + func bar() { + return foo() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAFunction() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + func getFoo() -> String { + return Foo().foo + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAConstant() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + let kFoo = Foo().foo + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAVar() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + var kFoo: String { return Foo().foo } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromCode() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + print(Foo().foo) + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAClosure() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + print({ Foo().foo }()) + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.redundantClosure]) + } + + func testFileprivateVarNotChangedToPrivateIfAccessedFromAnExtensionOnAnotherType() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + extension Bar { + func bar() { + print(Foo().foo) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarChangedToPrivateIfAccessedFromAnExtensionOnSameType() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + extension Foo { + func bar() { + print(foo) + } + } + """ + let output = """ + struct Foo { + private let foo = "foo" + } + + extension Foo { + func bar() { + print(foo) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarChangedToPrivateIfAccessedViaSelfFromAnExtensionOnSameType() { + let input = """ + struct Foo { + fileprivate let foo = "foo" + } + + extension Foo { + func bar() { + print(self.foo) + } + } + """ + let output = """ + struct Foo { + private let foo = "foo" + } + + extension Foo { + func bar() { + print(self.foo) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options, + exclude: [.redundantSelf]) + } + + func testFileprivateMultiLetNotChangedToPrivateIfAccessedOutsideType() { + let input = """ + struct Foo { + fileprivate let foo = "foo", bar = "bar" + } + + extension Foo { + func bar() { + print(foo) + } + } + + extension Bar { + func bar() { + print(Foo().bar) + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInitChangedToPrivateIfConstructorNotCalledOutsideType() { + let input = """ + struct Foo { + fileprivate init() {} + } + """ + let output = """ + struct Foo { + private init() {} + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType() { + let input = """ + struct Foo { + fileprivate init() {} + } + + let foo = Foo() + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType2() { + let input = """ + class Foo { + fileprivate init() {} + } + + struct Bar { + let foo = Foo() + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateStructMemberNotChangedToPrivateIfConstructorCalledOutsideType() { + let input = """ + struct Foo { + fileprivate let bar: String + } + + let foo = Foo(bar: "test") + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateClassMemberChangedToPrivateEvenIfConstructorCalledOutsideType() { + let input = """ + class Foo { + fileprivate let bar: String + } + + let foo = Foo() + """ + let output = """ + class Foo { + private let bar: String + } + + let foo = Foo() + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateExtensionFuncNotChangedToPrivateIfPartOfProtocolConformance() { + let input = """ + private class Foo: Equatable { + fileprivate static func == (_: Foo, _: Foo) -> Bool { + return true + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInnerTypeNotChangedToPrivate() { + let input = """ + struct Foo { + fileprivate enum Bar { + case a, b + } + + fileprivate let bar: Bar + } + + func foo(foo: Foo) { + print(foo.bar) + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, + rule: .redundantFileprivate, + options: options, + exclude: [.wrapEnumCases]) + } + + func testFileprivateClassTypeMemberNotChangedToPrivate() { + let input = """ + class Foo { + fileprivate class var bar = "bar" + } + + func foo() { + print(Foo.bar) + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testOverriddenFileprivateInitNotChangedToPrivate() { + let input = """ + class Foo { + fileprivate init() {} + } + + class Bar: Foo, Equatable { + override public init() { + super.init() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testNonOverriddenFileprivateInitChangedToPrivate() { + let input = """ + class Foo { + fileprivate init() {} + } + + class Bar: Baz { + override public init() { + super.init() + } + } + """ + let output = """ + class Foo { + private init() {} + } + + class Bar: Baz { + override public init() { + super.init() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInitNotChangedToPrivateWhenUsingTypeInferredInits() { + let input = """ + struct Example { + fileprivate init() {} + } + + enum Namespace { + static let example: Example = .init() + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) + } + + func testFileprivateInitNotChangedToPrivateWhenUsingTrailingClosureInit() { + let input = """ + private struct Foo {} + + public struct Bar { + fileprivate let consumeFoo: (Foo) -> Void + } + + public func makeBar() -> Bar { + Bar { _ in } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnContainingType() { + let input = """ + extension Foo.Bar { + fileprivate init() {} + } + + extension Foo { + func baz() -> Foo.Bar { + return Bar() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnNestedType() { + let input = """ + extension Foo { + fileprivate init() {} + } + + extension Foo.Bar { + func baz() -> Foo { + return Foo() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromSubclass() { + let input = """ + class Foo: Bar { + func quux() { + baz() + } + } + + extension Bar { + fileprivate func baz() {} + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInitNotChangedToPrivateWhenAccessedFromSubclass() { + let input = """ + public class Foo { + fileprivate init() {} + } + + private class Bar: Foo { + init(something: String) { + print(something) + super.init() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromExtensionOnSubclass() { + let input = """ + class Foo: Bar {} + + extension Foo { + func quux() { + baz() + } + } + + extension Bar { + fileprivate func baz() {} + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateVarWithPropertWrapperNotChangedToPrivateIfAccessedFromSubclass() { + let input = """ + class Foo { + @Foo fileprivate var foo = 5 + } + + class Bar: Foo { + func bar() { + return $foo + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile() { + let input = """ + extension [String] { + fileprivate func fileprivateMember() {} + } + + extension Namespace { + func testCanAccessFileprivateMember() { + ["string", "array"].fileprivateMember() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, options: options) + } + + func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile2() { + let input = """ + extension Array { + fileprivate func fileprivateMember() {} + } + + extension Namespace { + func testCanAccessFileprivateMember() { + ["string", "array"].fileprivateMember() + } + } + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, rule: .redundantFileprivate, + options: options, exclude: [.typeSugar]) + } +} diff --git a/Tests/Rules/RedundantGetTests.swift b/Tests/Rules/RedundantGetTests.swift new file mode 100644 index 000000000..52aec4610 --- /dev/null +++ b/Tests/Rules/RedundantGetTests.swift @@ -0,0 +1,56 @@ +// +// RedundantGetTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantGetTests: XCTestCase { + func testRemoveSingleLineIsolatedGet() { + let input = "var foo: Int { get { return 5 } }" + let output = "var foo: Int { return 5 }" + testFormatting(for: input, output, rule: .redundantGet) + } + + func testRemoveMultilineIsolatedGet() { + let input = "var foo: Int {\n get {\n return 5\n }\n}" + let output = "var foo: Int {\n return 5\n}" + testFormatting(for: input, [output], rules: [.redundantGet, .indent]) + } + + func testNoRemoveMultilineGetSet() { + let input = "var foo: Int {\n get { return 5 }\n set { foo = newValue }\n}" + testFormatting(for: input, rule: .redundantGet) + } + + func testNoRemoveAttributedGet() { + let input = "var enabled: Bool { @objc(isEnabled) get { true } }" + testFormatting(for: input, rule: .redundantGet) + } + + func testRemoveSubscriptGet() { + let input = "subscript(_ index: Int) {\n get {\n return lookup(index)\n }\n}" + let output = "subscript(_ index: Int) {\n return lookup(index)\n}" + testFormatting(for: input, [output], rules: [.redundantGet, .indent]) + } + + func testGetNotRemovedInFunction() { + let input = "func foo() {\n get {\n self.lookup(index)\n }\n}" + testFormatting(for: input, rule: .redundantGet) + } + + func testEffectfulGetNotRemoved() { + let input = """ + var foo: Int { + get async throws { + try await getFoo() + } + } + """ + testFormatting(for: input, rule: .redundantGet) + } +} diff --git a/Tests/Rules/RedundantInitTests.swift b/Tests/Rules/RedundantInitTests.swift new file mode 100644 index 000000000..39c13287c --- /dev/null +++ b/Tests/Rules/RedundantInitTests.swift @@ -0,0 +1,223 @@ +// +// RedundantInitTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantInitTests: XCTestCase { + func testRemoveRedundantInit() { + let input = "[1].flatMap { String.init($0) }" + let output = "[1].flatMap { String($0) }" + testFormatting(for: input, output, rule: .redundantInit) + } + + func testRemoveRedundantInit2() { + let input = "[String.self].map { Type in Type.init(foo: 1) }" + let output = "[String.self].map { Type in Type(foo: 1) }" + testFormatting(for: input, output, rule: .redundantInit) + } + + func testRemoveRedundantInit3() { + let input = "String.init(\"text\")" + let output = "String(\"text\")" + testFormatting(for: input, output, rule: .redundantInit) + } + + func testDontRemoveInitInSuperCall() { + let input = "class C: NSObject { override init() { super.init() } }" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitInSelfCall() { + let input = "struct S { let n: Int }; extension S { init() { self.init(n: 1) } }" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWhenPassedAsFunction() { + let input = "[1].flatMap(String.init)" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWhenUsedOnMetatype() { + let input = "[String.self].map { type in type.init(1) }" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWhenUsedOnImplicitClosureMetatype() { + let input = "[String.self].map { $0.init(1) }" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWhenUsedOnPossibleMetatype() { + let input = "let something = Foo.bar.init()" + testFormatting(for: input, rule: .redundantInit) + } + + func testDontRemoveInitWithExplicitSignature() { + let input = "[String.self].map(Foo.init(bar:))" + testFormatting(for: input, rule: .redundantInit) + } + + func testRemoveInitWithOpenParenOnFollowingLine() { + let input = """ + var foo: Foo { + Foo.init + ( + bar: bar, + baaz: baaz + ) + } + """ + let output = """ + var foo: Foo { + Foo( + bar: bar, + baaz: baaz + ) + } + """ + testFormatting(for: input, output, rule: .redundantInit) + } + + func testNoRemoveInitWithOpenParenOnFollowingLineAfterComment() { + let input = """ + var foo: Foo { + Foo.init // foo + ( + bar: bar, + baaz: baaz + ) + } + """ + testFormatting(for: input, rule: .redundantInit) + } + + func testNoRemoveInitForLowercaseType() { + let input = """ + let foo = bar.init() + """ + testFormatting(for: input, rule: .redundantInit) + } + + func testNoRemoveInitForLocalLetType() { + let input = """ + let Foo = Foo.self + let foo = Foo.init() + """ + testFormatting(for: input, rule: .redundantInit, exclude: [.propertyType]) + } + + func testNoRemoveInitForLocalLetType2() { + let input = """ + let Foo = Foo.self + if x { + return Foo.init(x) + } else { + return Foo.init(y) + } + """ + testFormatting(for: input, rule: .redundantInit) + } + + func testNoRemoveInitInsideIfdef() { + let input = """ + func myFunc() async throws -> String { + #if DEBUG + .init("foo") + #else + "" + #endif + } + """ + testFormatting(for: input, rule: .redundantInit, exclude: [.indent]) + } + + func testNoRemoveInitInsideIfdef2() { + let input = """ + func myFunc() async throws(Foo) -> String { + #if DEBUG + .init("foo") + #else + "" + #endif + } + """ + testFormatting(for: input, rule: .redundantInit, exclude: [.indent]) + } + + func testRemoveInitAfterCollectionLiterals() { + let input = """ + let array = [String].init() + let arrayElement = [String].Element.init() + let nestedArray = [[String]].init() + let tupleArray = [(key: String, value: Int)].init() + let dictionary = [String: Int].init() + """ + let output = """ + let array = [String]() + let arrayElement = [String].Element() + let nestedArray = [[String]]() + let tupleArray = [(key: String, value: Int)]() + let dictionary = [String: Int]() + """ + testFormatting(for: input, output, rule: .redundantInit, exclude: [.propertyType]) + } + + func testPreservesInitAfterTypeOfCall() { + let input = """ + type(of: oldViewController).init() + """ + + testFormatting(for: input, rule: .redundantInit) + } + + func testRemoveInitAfterOptionalType() { + let input = """ + let someOptional = String?.init("Foo") + // (String!.init("Foo") isn't valid Swift code, so we don't test for it) + """ + let output = """ + let someOptional = String?("Foo") + // (String!.init("Foo") isn't valid Swift code, so we don't test for it) + """ + + testFormatting(for: input, output, rule: .redundantInit, exclude: [.propertyType]) + } + + func testPreservesTryBeforeInit() { + let input = """ + let throwing: Foo = try .init() + let throwingOptional1: Foo = try? .init() + let throwingOptional2: Foo = try! .init() + """ + + testFormatting(for: input, rule: .redundantInit) + } + + func testRemoveInitAfterGenericType() { + let input = """ + let array = Array.init() + let dictionary = Dictionary.init() + let atomicDictionary = Atomic<[String: Int]>.init() + """ + let output = """ + let array = Array() + let dictionary = Dictionary() + let atomicDictionary = Atomic<[String: Int]>() + """ + + testFormatting(for: input, output, rule: .redundantInit, exclude: [.typeSugar, .propertyType]) + } + + func testPreserveNonRedundantInitInTernaryOperator() { + let input = """ + let bar: Bar = (foo.isBar && bar.isBaaz) ? .init() : nil + """ + testFormatting(for: input, rule: .redundantInit) + } +} diff --git a/Tests/Rules/RedundantInternalTests.swift b/Tests/Rules/RedundantInternalTests.swift new file mode 100644 index 000000000..b3f5e8413 --- /dev/null +++ b/Tests/Rules/RedundantInternalTests.swift @@ -0,0 +1,108 @@ +// +// RedundantInternalTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantInternalTests: XCTestCase { + func testRemoveRedundantInternalACL() { + let input = """ + internal class Foo { + internal let bar: String + + internal func baaz() {} + + internal init() { + bar = "bar" + } + } + """ + + let output = """ + class Foo { + let bar: String + + func baaz() {} + + init() { + bar = "bar" + } + } + """ + + testFormatting(for: input, output, rule: .redundantInternal) + } + + func testPreserveInternalInNonInternalExtensionExtension() { + let input = """ + extension Foo { + /// internal is redundant here since the extension is internal + internal func bar() {} + + public func baaz() {} + + /// internal is redundant here since the extension is internal + internal func bar() {} + } + + public extension Foo { + /// internal is not redundant here since the extension is public + internal func bar() {} + + public func baaz() {} + + /// internal is not redundant here since the extension is public + internal func bar() {} + } + """ + + let output = """ + extension Foo { + /// internal is redundant here since the extension is internal + func bar() {} + + public func baaz() {} + + /// internal is redundant here since the extension is internal + func bar() {} + } + + public extension Foo { + /// internal is not redundant here since the extension is public + internal func bar() {} + + public func baaz() {} + + /// internal is not redundant here since the extension is public + internal func bar() {} + } + """ + + testFormatting(for: input, output, rule: .redundantInternal, exclude: [.redundantExtensionACL]) + } + + func testPreserveInternalImport() { + let input = "internal import MyPackage" + testFormatting(for: input, rule: .redundantInternal) + } + + func testPreservesInternalInPublicExtensionWithWhereClause() { + let input = """ + public extension SomeProtocol where SomeAssociatedType == SomeOtherType { + internal func fun1() {} + func fun2() {} + } + + public extension OtherProtocol { + internal func fun1() {} + func fun2() {} + } + """ + testFormatting(for: input, rule: .redundantInternal) + } +} diff --git a/Tests/Rules/RedundantLetErrorTests.swift b/Tests/Rules/RedundantLetErrorTests.swift new file mode 100644 index 000000000..ef88ebcc5 --- /dev/null +++ b/Tests/Rules/RedundantLetErrorTests.swift @@ -0,0 +1,24 @@ +// +// RedundantLetErrorTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantLetErrorTests: XCTestCase { + func testCatchLetError() { + let input = "do {} catch let error {}" + let output = "do {} catch {}" + testFormatting(for: input, output, rule: .redundantLetError) + } + + func testCatchLetErrorWithTypedThrows() { + let input = "do throws(Foo) {} catch let error {}" + let output = "do throws(Foo) {} catch {}" + testFormatting(for: input, output, rule: .redundantLetError) + } +} diff --git a/Tests/Rules/RedundantLetTests.swift b/Tests/Rules/RedundantLetTests.swift new file mode 100644 index 000000000..5f688bb26 --- /dev/null +++ b/Tests/Rules/RedundantLetTests.swift @@ -0,0 +1,136 @@ +// +// RedundantLetTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantLetTests: XCTestCase { + func testRemoveRedundantLet() { + let input = "let _ = bar {}" + let output = "_ = bar {}" + testFormatting(for: input, output, rule: .redundantLet) + } + + func testNoRemoveLetWithType() { + let input = "let _: String = bar {}" + testFormatting(for: input, rule: .redundantLet) + } + + func testRemoveRedundantLetInCase() { + let input = "if case .foo(let _) = bar {}" + let output = "if case .foo(_) = bar {}" + testFormatting(for: input, output, rule: .redundantLet, exclude: [.redundantPattern]) + } + + func testRemoveRedundantVarsInCase() { + let input = "if case .foo(var _, var /* unused */ _) = bar {}" + let output = "if case .foo(_, /* unused */ _) = bar {}" + testFormatting(for: input, output, rule: .redundantLet) + } + + func testNoRemoveLetInIf() { + let input = "if let _ = foo {}" + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInMultiIf() { + let input = "if foo == bar, /* comment! */ let _ = baz {}" + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInGuard() { + let input = "guard let _ = foo else {}" + testFormatting(for: input, rule: .redundantLet, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveLetInWhile() { + let input = "while let _ = foo {}" + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInViewBuilder() { + let input = """ + HStack { + let _ = print("Hi") + Text("Some text") + } + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInViewBuilderModifier() { + let input = """ + VStack { + Text("Some text") + } + .overlay( + HStack { + let _ = print("") + } + ) + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInIfStatementInViewBuilder() { + let input = """ + VStack { + if visible == "YES" { + let _ = print("") + } + } + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetInSwitchStatementInViewBuilder() { + let input = """ + struct TestView: View { + var body: some View { + var foo = "" + switch (self.min, self.max) { + case let (nil, max as Int): + let _ = { + foo = "\\(max)" + }() + + default: + EmptyView() + } + + Text(foo) + } + } + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveAsyncLet() { + let input = "async let _ = foo()" + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetImmediatelyAfterMainActorAttribute() { + let input = """ + let foo = bar { @MainActor + let _ = try await baz() + } + """ + testFormatting(for: input, rule: .redundantLet) + } + + func testNoRemoveLetImmediatelyAfterSendableAttribute() { + let input = """ + let foo = bar { @Sendable + let _ = try await baz() + } + """ + testFormatting(for: input, rule: .redundantLet) + } +} diff --git a/Tests/Rules/RedundantNilInitTests.swift b/Tests/Rules/RedundantNilInitTests.swift new file mode 100644 index 000000000..21aa1a2e2 --- /dev/null +++ b/Tests/Rules/RedundantNilInitTests.swift @@ -0,0 +1,489 @@ +// +// RedundantNilInitTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantNilInitTests: XCTestCase { + func testRemoveRedundantNilInit() { + let input = "var foo: Int? = nil\nlet bar: Int? = nil" + let output = "var foo: Int?\nlet bar: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveLetNilInitAfterVar() { + let input = "var foo: Int; let bar: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNonNilInit() { + let input = "var foo: Int? = 0" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testRemoveRedundantImplicitUnwrapInit() { + let input = "var foo: Int! = nil" + let output = "var foo: Int!" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testRemoveMultipleRedundantNilInitsInSameLine() { + let input = "var foo: Int? = nil, bar: Int? = nil" + let output = "var foo: Int?, bar: Int?" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveLazyVarNilInit() { + let input = "lazy var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveLazyPublicPrivateSetVarNilInit() { + let input = "lazy private(set) public var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, options: options, + exclude: [.modifierOrder]) + } + + func testNoRemoveCodableNilInit() { + let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitWithPropertyWrapper() { + let input = "@Foo var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitWithLowercasePropertyWrapper() { + let input = "@foo var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitWithPropertyWrapperWithArgument() { + let input = "@Foo(bar: baz) var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitWithLowercasePropertyWrapperWithArgument() { + let input = "@foo(bar: baz) var foo: Int? = nil" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testRemoveNilInitWithObjcAttributes() { + let input = "@objc var foo: Int? = nil" + let output = "@objc var foo: Int?" + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitInStructWithDefaultInit() { + let input = """ + struct Foo { + var bar: String? = nil + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testRemoveNilInitInStructWithDefaultInitInSwiftVersion5_2() { + let input = """ + struct Foo { + var bar: String? = nil + } + """ + let output = """ + struct Foo { + var bar: String? + } + """ + let options = FormatOptions(nilInit: .remove, swiftVersion: "5.2") + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testRemoveNilInitInStructWithCustomInit() { + let input = """ + struct Foo { + var bar: String? = nil + init() { + bar = "bar" + } + } + """ + let output = """ + struct Foo { + var bar: String? + init() { + bar = "bar" + } + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitInViewBuilder() { + let input = """ + struct TestView: View { + var body: some View { + var foo: String? = nil + Text(foo ?? "") + } + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitInIfStatementInViewBuilder() { + let input = """ + struct TestView: View { + var body: some View { + if true { + var foo: String? = nil + Text(foo ?? "") + } else { + EmptyView() + } + } + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoRemoveNilInitInSwitchStatementInViewBuilder() { + let input = """ + struct TestView: View { + var body: some View { + switch foo { + case .bar: + var foo: String? = nil + Text(foo ?? "") + + default: + EmptyView() + } + } + } + """ + let options = FormatOptions(nilInit: .remove) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + // --nilInit insert + + func testInsertNilInit() { + let input = "var foo: Int?\nlet bar: Int? = nil" + let output = "var foo: Int? = nil\nlet bar: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitBeforeLet() { + let input = "var foo: Int?; let bar: Int? = nil" + let output = "var foo: Int? = nil; let bar: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitAfterLet() { + let input = "let bar: Int? = nil; var foo: Int?" + let output = "let bar: Int? = nil; var foo: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNonNilInit() { + let input = "var foo: Int? = 0" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testInsertRedundantImplicitUnwrapInit() { + let input = "var foo: Int!" + let output = "var foo: Int! = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testInsertMultipleRedundantNilInitsInSameLine() { + let input = "var foo: Int?, bar: Int?" + let output = "var foo: Int? = nil, bar: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertLazyVarNilInit() { + let input = "lazy var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertLazyPublicPrivateSetVarNilInit() { + let input = "lazy private(set) public var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, options: options, + exclude: [.modifierOrder]) + } + + func testNoInsertCodableNilInit() { + let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitWithPropertyWrapper() { + let input = "@Foo var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitWithLowercasePropertyWrapper() { + let input = "@foo var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitWithPropertyWrapperWithArgument() { + let input = "@Foo(bar: baz) var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitWithLowercasePropertyWrapperWithArgument() { + let input = "@foo(bar: baz) var foo: Int?" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitWithObjcAttributes() { + let input = "@objc var foo: Int?" + let output = "@objc var foo: Int? = nil" + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInStructWithDefaultInit() { + let input = """ + struct Foo { + var bar: String? + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitInStructWithDefaultInitInSwiftVersion5_2() { + let input = """ + struct Foo { + var bar: String? + var foo: String? = nil + } + """ + let output = """ + struct Foo { + var bar: String? = nil + var foo: String? = nil + } + """ + let options = FormatOptions(nilInit: .insert, swiftVersion: "5.2") + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitInStructWithCustomInit() { + let input = """ + struct Foo { + var bar: String? + var foo: String? = nil + init() { + bar = "bar" + foo = "foo" + } + } + """ + let output = """ + struct Foo { + var bar: String? = nil + var foo: String? = nil + init() { + bar = "bar" + foo = "foo" + } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInViewBuilder() { + // Not insert `nil` in result builder + let input = """ + struct TestView: View { + var body: some View { + var foo: String? + Text(foo ?? "") + } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInIfStatementInViewBuilder() { + // Not insert `nil` in result builder + let input = """ + struct TestView: View { + var body: some View { + if true { + var foo: String? + Text(foo ?? "") + } else { + EmptyView() + } + } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInSwitchStatementInViewBuilder() { + // Not insert `nil` in result builder + let input = """ + struct TestView: View { + var body: some View { + switch foo { + case .bar: + var foo: String? + Text(foo ?? "") + + default: + EmptyView() + } + } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInSingleLineComputedProperty() { + let input = """ + var bar: String? { "some string" } + var foo: String? { nil } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInMultilineComputedProperty() { + let input = """ + var foo: String? { + print("some") + } + + var bar: String? { + nil + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInCustomGetterAndSetterProperty() { + let input = """ + var _foo: String? = nil + var foo: String? { + set { _foo = newValue } + get { newValue } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } + + func testInsertNilInitInInstancePropertyWithBody() { + let input = """ + var foo: String? { + didSet { print(foo) } + } + """ + + let output = """ + var foo: String? = nil { + didSet { print(foo) } + } + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, output, rule: .redundantNilInit, + options: options) + } + + func testNoInsertNilInitInAs() { + let input = """ + let json: Any = ["key": 1] + var jsonObject = json as? [String: Int] + """ + let options = FormatOptions(nilInit: .insert) + testFormatting(for: input, rule: .redundantNilInit, + options: options) + } +} diff --git a/Tests/Rules/RedundantObjcTests.swift b/Tests/Rules/RedundantObjcTests.swift new file mode 100644 index 000000000..e7ef19bac --- /dev/null +++ b/Tests/Rules/RedundantObjcTests.swift @@ -0,0 +1,161 @@ +// +// RedundantObjcTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantObjcTests: XCTestCase { + func testRedundantObjcRemovedFromBeforeOutlet() { + let input = "@objc @IBOutlet var label: UILabel!" + let output = "@IBOutlet var label: UILabel!" + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testRedundantObjcRemovedFromAfterOutlet() { + let input = "@IBOutlet @objc var label: UILabel!" + let output = "@IBOutlet var label: UILabel!" + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testRedundantObjcRemovedFromLineBeforeOutlet() { + let input = "@objc\n@IBOutlet var label: UILabel!" + let output = "\n@IBOutlet var label: UILabel!" + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testRedundantObjcCommentNotRemoved() { + let input = "@objc /// an outlet\n@IBOutlet var label: UILabel!" + let output = "/// an outlet\n@IBOutlet var label: UILabel!" + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcNotRemovedFromNSCopying() { + let input = "@objc @NSCopying var foo: String!" + testFormatting(for: input, rule: .redundantObjc) + } + + func testRenamedObjcNotRemoved() { + let input = "@IBOutlet @objc(uiLabel) var label: UILabel!" + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcRemovedOnObjcMembersClass() { + let input = """ + @objcMembers class Foo: NSObject { + @objc var foo: String + } + """ + let output = """ + @objcMembers class Foo: NSObject { + var foo: String + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcRemovedOnRenamedObjcMembersClass() { + let input = """ + @objcMembers @objc(OCFoo) class Foo: NSObject { + @objc var foo: String + } + """ + let output = """ + @objcMembers @objc(OCFoo) class Foo: NSObject { + var foo: String + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcNotRemovedOnNestedClass() { + let input = """ + @objcMembers class Foo: NSObject { + @objc class Bar: NSObject {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcNotRemovedOnRenamedPrivateNestedClass() { + let input = """ + @objcMembers class Foo: NSObject { + @objc private class Bar: NSObject {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcNotRemovedOnNestedEnum() { + let input = """ + @objcMembers class Foo: NSObject { + @objc enum Bar: Int {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcRemovedOnObjcExtensionVar() { + let input = """ + @objc extension Foo { + @objc var foo: String {} + } + """ + let output = """ + @objc extension Foo { + var foo: String {} + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcRemovedOnObjcExtensionFunc() { + let input = """ + @objc extension Foo { + @objc func foo() -> String {} + } + """ + let output = """ + @objc extension Foo { + func foo() -> String {} + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } + + func testObjcNotRemovedOnPrivateFunc() { + let input = """ + @objcMembers class Foo: NSObject { + @objc private func bar() {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcNotRemovedOnFileprivateFunc() { + let input = """ + @objcMembers class Foo: NSObject { + @objc fileprivate func bar() {} + } + """ + testFormatting(for: input, rule: .redundantObjc) + } + + func testObjcRemovedOnPrivateSetFunc() { + let input = """ + @objcMembers class Foo: NSObject { + @objc private(set) func bar() {} + } + """ + let output = """ + @objcMembers class Foo: NSObject { + private(set) func bar() {} + } + """ + testFormatting(for: input, output, rule: .redundantObjc) + } +} diff --git a/Tests/Rules/RedundantOptionalBindingTests.swift b/Tests/Rules/RedundantOptionalBindingTests.swift new file mode 100644 index 000000000..e094ed5e2 --- /dev/null +++ b/Tests/Rules/RedundantOptionalBindingTests.swift @@ -0,0 +1,183 @@ +// +// RedundantOptionalBindingTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantOptionalBindingTests: XCTestCase { + func testRemovesRedundantOptionalBindingsInSwift5_7() { + let input = """ + if let foo = foo { + print(foo) + } + + else if var bar = bar { + print(bar) + } + + guard let self = self else { + return + } + + while var quux = quux { + break + } + """ + + let output = """ + if let foo { + print(foo) + } + + else if var bar { + print(bar) + } + + guard let self else { + return + } + + while var quux { + break + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options, exclude: [.elseOnSameLine]) + } + + func testRemovesMultipleOptionalBindings() { + let input = """ + if let foo = foo, let bar = bar, let baaz = baaz { + print(foo, bar, baaz) + } + + guard let foo = foo, let bar = bar, let baaz = baaz else { + return + } + """ + + let output = """ + if let foo, let bar, let baaz { + print(foo, bar, baaz) + } + + guard let foo, let bar, let baaz else { + return + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options) + } + + func testRemovesMultipleOptionalBindingsOnSeparateLines() { + let input = """ + if + let foo = foo, + let bar = bar, + let baaz = baaz + { + print(foo, bar, baaz) + } + + guard + let foo = foo, + let bar = bar, + let baaz = baaz + else { + return + } + """ + + let output = """ + if + let foo, + let bar, + let baaz + { + print(foo, bar, baaz) + } + + guard + let foo, + let bar, + let baaz + else { + return + } + """ + + let options = FormatOptions(indent: " ", swiftVersion: "5.7") + testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options) + } + + func testKeepsRedundantOptionalBeforeSwift5_7() { + let input = """ + if let foo = foo { + print(foo) + } + """ + + let options = FormatOptions(swiftVersion: "5.6") + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) + } + + func testKeepsNonRedundantOptional() { + let input = """ + if let foo = bar { + print(foo) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) + } + + func testKeepsOptionalNotEligibleForShorthand() { + let input = """ + if let foo = self.foo, let bar = bar(), let baaz = baaz[0] { + print(foo, bar, baaz) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .redundantOptionalBinding, options: options, exclude: [.redundantSelf]) + } + + func testRedundantSelfAndRedundantOptionalTogether() { + let input = """ + if let foo = self.foo { + print(foo) + } + """ + + let output = """ + if let foo { + print(foo) + } + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, [output], rules: [.redundantOptionalBinding, .redundantSelf], options: options) + } + + func testDoesntRemoveShadowingOutsideOfOptionalBinding() { + let input = """ + let foo = foo + + if let bar = baaz({ + let foo = foo + print(foo) + }) {} + """ + + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .redundantOptionalBinding, options: options) + } +} diff --git a/Tests/RulesTests+Parens.swift b/Tests/Rules/RedundantParensTests.swift similarity index 99% rename from Tests/RulesTests+Parens.swift rename to Tests/Rules/RedundantParensTests.swift index b5b8d0978..9fe62fbc0 100644 --- a/Tests/RulesTests+Parens.swift +++ b/Tests/Rules/RedundantParensTests.swift @@ -1,17 +1,15 @@ // -// RulesTests+Parens.swift +// RedundantParensTests.swift // SwiftFormatTests // -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. // import XCTest @testable import SwiftFormat -class ParensTests: RulesTests { - // MARK: - redundantParens - +class RedundantParensTests: XCTestCase { // around expressions func testRedundantParensRemoved() { diff --git a/Tests/Rules/RedundantPatternTests.swift b/Tests/Rules/RedundantPatternTests.swift new file mode 100644 index 000000000..d9eda08f7 --- /dev/null +++ b/Tests/Rules/RedundantPatternTests.swift @@ -0,0 +1,55 @@ +// +// RedundantPatternTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantPatternTests: XCTestCase { + func testRemoveRedundantPatternInIfCase() { + let input = "if case let .foo(_, _) = bar {}" + let output = "if case .foo = bar {}" + testFormatting(for: input, output, rule: .redundantPattern) + } + + func testNoRemoveRequiredPatternInIfCase() { + let input = "if case (_, _) = bar {}" + testFormatting(for: input, rule: .redundantPattern) + } + + func testRemoveRedundantPatternInSwitchCase() { + let input = "switch foo {\ncase let .bar(_, _): break\ndefault: break\n}" + let output = "switch foo {\ncase .bar: break\ndefault: break\n}" + testFormatting(for: input, output, rule: .redundantPattern) + } + + func testNoRemoveRequiredPatternLetInSwitchCase() { + let input = "switch foo {\ncase let .bar(_, a): break\ndefault: break\n}" + testFormatting(for: input, rule: .redundantPattern) + } + + func testNoRemoveRequiredPatternInSwitchCase() { + let input = "switch foo {\ncase (_, _): break\ndefault: break\n}" + testFormatting(for: input, rule: .redundantPattern) + } + + func testSimplifyLetPattern() { + let input = "let(_, _) = bar" + let output = "let _ = bar" + testFormatting(for: input, output, rule: .redundantPattern, exclude: [.redundantLet]) + } + + func testNoRemoveVoidFunctionCall() { + let input = "if case .foo() = bar {}" + testFormatting(for: input, rule: .redundantPattern) + } + + func testNoRemoveMethodSignature() { + let input = "func foo(_, _) {}" + testFormatting(for: input, rule: .redundantPattern) + } +} diff --git a/Tests/Rules/RedundantPropertyTests.swift b/Tests/Rules/RedundantPropertyTests.swift new file mode 100644 index 000000000..c499aac8a --- /dev/null +++ b/Tests/Rules/RedundantPropertyTests.swift @@ -0,0 +1,182 @@ +// +// RedundantPropertyTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantPropertyTests: XCTestCase { + func testRemovesRedundantProperty() { + let input = """ + func foo() -> Foo { + let foo = Foo(bar: bar, baaz: baaz) + return foo + } + """ + + let output = """ + func foo() -> Foo { + return Foo(bar: bar, baaz: baaz) + } + """ + + testFormatting(for: input, output, rule: .redundantProperty, exclude: [.redundantReturn]) + } + + func testRemovesRedundantPropertyWithIfExpression() { + let input = """ + func foo() -> Foo { + let foo = + if condition { + Foo.foo() + } else { + Foo.bar() + } + + return foo + } + """ + + let output = """ + func foo() -> Foo { + if condition { + Foo.foo() + } else { + Foo.bar() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.redundantProperty, .redundantReturn, .indent], options: options) + } + + func testRemovesRedundantPropertyWithSwitchExpression() { + let input = """ + func foo() -> Foo { + let foo: Foo + switch condition { + case true: + foo = Foo(bar) + case false: + foo = Foo(baaz) + } + + return foo + } + """ + + let output = """ + func foo() -> Foo { + switch condition { + case true: + Foo(bar) + case false: + Foo(baaz) + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], rules: [.conditionalAssignment, .redundantProperty, .redundantReturn, .indent], options: options) + } + + func testRemovesRedundantPropertyWithPreferInferredType() { + let input = """ + func bar() -> Bar { + let bar: Bar = .init(baaz: baaz, quux: quux) + return bar + } + """ + + let output = """ + func bar() -> Bar { + return Bar(baaz: baaz, quux: quux) + } + """ + + testFormatting(for: input, [output], rules: [.propertyType, .redundantProperty, .redundantInit], exclude: [.redundantReturn]) + } + + func testRemovesRedundantPropertyWithComments() { + let input = """ + func foo() -> Foo { + // There's a comment before this property + let foo = Foo(bar: bar, baaz: baaz) + // And there's a comment after the property + return foo + } + """ + + let output = """ + func foo() -> Foo { + // There's a comment before this property + return Foo(bar: bar, baaz: baaz) + // And there's a comment after the property + } + """ + + testFormatting(for: input, output, rule: .redundantProperty, exclude: [.redundantReturn]) + } + + func testRemovesRedundantPropertyFollowingOtherProperty() { + let input = """ + func foo() -> Foo { + let bar = Bar(baaz: baaz) + let foo = Foo(bar: bar) + return foo + } + """ + + let output = """ + func foo() -> Foo { + let bar = Bar(baaz: baaz) + return Foo(bar: bar) + } + """ + + testFormatting(for: input, output, rule: .redundantProperty) + } + + func testPreservesPropertyWhereReturnIsNotRedundant() { + let input = """ + func foo() -> Foo { + let foo = Foo(bar: bar, baaz: baaz) + return foo.with(quux: quux) + } + + func bar() -> Foo { + let bar = Bar(baaz: baaz) + return bar.baaz + } + + func baaz() -> Foo { + let bar = Bar(baaz: baaz) + print(bar) + return bar + } + """ + + testFormatting(for: input, rule: .redundantProperty) + } + + func testPreservesUnwrapConditionInIfStatement() { + let input = """ + func foo() -> Foo { + let foo = Foo(bar: bar, baaz: baaz) + + if let foo = foo.nestedFoo { + print(foo) + } + + return foo + } + """ + + testFormatting(for: input, rule: .redundantProperty) + } +} diff --git a/Tests/Rules/RedundantRawValuesTests.swift b/Tests/Rules/RedundantRawValuesTests.swift new file mode 100644 index 000000000..a8e4132ea --- /dev/null +++ b/Tests/Rules/RedundantRawValuesTests.swift @@ -0,0 +1,37 @@ +// +// RedundantRawValuesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantRawValuesTests: XCTestCase { + func testRemoveRedundantRawString() { + let input = "enum Foo: String {\n case bar = \"bar\"\n case baz = \"baz\"\n}" + let output = "enum Foo: String {\n case bar\n case baz\n}" + testFormatting(for: input, output, rule: .redundantRawValues) + } + + func testRemoveCommaDelimitedCaseRawStringCases() { + let input = "enum Foo: String { case bar = \"bar\", baz = \"baz\" }" + let output = "enum Foo: String { case bar, baz }" + testFormatting(for: input, output, rule: .redundantRawValues, + exclude: [.wrapEnumCases]) + } + + func testRemoveBacktickCaseRawStringCases() { + let input = "enum Foo: String { case `as` = \"as\", `let` = \"let\" }" + let output = "enum Foo: String { case `as`, `let` }" + testFormatting(for: input, output, rule: .redundantRawValues, + exclude: [.wrapEnumCases]) + } + + func testNoRemoveRawStringIfNameDoesntMatch() { + let input = "enum Foo: String {\n case bar = \"foo\"\n}" + testFormatting(for: input, rule: .redundantRawValues) + } +} diff --git a/Tests/Rules/RedundantReturnTests.swift b/Tests/Rules/RedundantReturnTests.swift new file mode 100644 index 000000000..152c71a83 --- /dev/null +++ b/Tests/Rules/RedundantReturnTests.swift @@ -0,0 +1,1264 @@ +// +// RedundantReturnTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantReturnTests: XCTestCase { + func testRemoveRedundantReturnInClosure() { + let input = "foo(with: { return 5 })" + let output = "foo(with: { 5 })" + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.trailingClosures]) + } + + func testRemoveRedundantReturnInClosureWithArgs() { + let input = "foo(with: { foo in return foo })" + let output = "foo(with: { foo in foo })" + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.trailingClosures]) + } + + func testRemoveRedundantReturnInMap() { + let input = "let foo = bar.map { return 1 }" + let output = "let foo = bar.map { 1 }" + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testNoRemoveReturnInComputedVar() { + let input = "var foo: Int { return 5 }" + testFormatting(for: input, rule: .redundantReturn) + } + + func testRemoveReturnInComputedVar() { + let input = "var foo: Int { return 5 }" + let output = "var foo: Int { 5 }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInGet() { + let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" + testFormatting(for: input, rule: .redundantReturn) + } + + func testRemoveReturnInGet() { + let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" + let output = "var foo: Int {\n get { 5 }\n set { _foo = newValue }\n}" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInGetClosure() { + let input = "let foo = get { return 5 }" + let output = "let foo = get { 5 }" + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testRemoveReturnInVarClosure() { + let input = "var foo = { return 5 }()" + let output = "var foo = { 5 }()" + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.redundantClosure]) + } + + func testRemoveReturnInParenthesizedClosure() { + let input = "var foo = ({ return 5 }())" + let output = "var foo = ({ 5 }())" + testFormatting(for: input, output, rule: .redundantReturn, exclude: [.redundantParens, .redundantClosure]) + } + + func testNoRemoveReturnInFunction() { + let input = "func foo() -> Int { return 5 }" + testFormatting(for: input, rule: .redundantReturn) + } + + func testRemoveReturnInFunction() { + let input = "func foo() -> Int { return 5 }" + let output = "func foo() -> Int { 5 }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInOperatorFunction() { + let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" + testFormatting(for: input, rule: .redundantReturn, exclude: [.unusedArguments]) + } + + func testRemoveReturnInOperatorFunction() { + let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" + let output = "func + (lhs: Int, rhs: Int) -> Int { 5 }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options, + exclude: [.unusedArguments]) + } + + func testNoRemoveReturnInFailableInit() { + let input = "init?() { return nil }" + testFormatting(for: input, rule: .redundantReturn) + } + + func testNoRemoveReturnInFailableInitWithConditional() { + let input = """ + init?(optionalHex: String?) { + if let optionalHex { + self.init(hex: optionalHex) + } else { + return nil + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInFailableInitWithNestedConditional() { + let input = """ + init?(optionalHex: String?) { + if let optionalHex { + self.init(hex: optionalHex) + } else { + switch foo { + case .foo: + self.init() + case .bar: + return nil + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRemoveReturnInFailableInit() { + let input = "init?() { return nil }" + let output = "init?() { nil }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInSubscript() { + let input = "subscript(index: Int) -> String { return nil }" + testFormatting(for: input, rule: .redundantReturn, exclude: [.unusedArguments]) + } + + func testRemoveReturnInSubscript() { + let input = "subscript(index: Int) -> String { return nil }" + let output = "subscript(index: Int) -> String { nil }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options, + exclude: [.unusedArguments]) + } + + func testNoRemoveReturnInDoCatch() { + let input = """ + func foo() -> Int { + do { + return try Bar() + } catch { + return -1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInDoThrowsCatch() { + let input = """ + func foo() -> Int { + do throws(Foo) { + return try Bar() + } catch { + return -1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInDoCatchLet() { + let input = """ + func foo() -> Int { + do { + return try Bar() + } catch let e as Error { + return -1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInDoThrowsCatchLet() { + let input = """ + func foo() -> Int { + do throws(Foo) { + return try Bar() + } catch let e as Error { + return -1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInForIn() { + let input = "for foo in bar { return 5 }" + testFormatting(for: input, rule: .redundantReturn, exclude: [.wrapLoopBodies]) + } + + func testNoRemoveReturnInForWhere() { + let input = "for foo in bar where baz { return 5 }" + testFormatting(for: input, rule: .redundantReturn, exclude: [.wrapLoopBodies]) + } + + func testNoRemoveReturnInIfLetTry() { + let input = "if let foo = try? bar() { return 5 }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveReturnInMultiIfLetTry() { + let input = "if let foo = bar, let bar = baz { return 5 }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveReturnAfterMultipleAs() { + let input = "if foo as? bar as? baz { return 5 }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.wrapConditionalBodies]) + } + + func testRemoveVoidReturn() { + let input = "{ _ in return }" + let output = "{ _ in }" + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testNoRemoveReturnAfterKeyPath() { + let input = "func foo() { if bar == #keyPath(baz) { return 5 } }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveReturnAfterParentheses() { + let input = "if let foo = (bar as? String) { return foo }" + testFormatting(for: input, rule: .redundantReturn, + exclude: [.redundantParens, .wrapConditionalBodies]) + } + + func testRemoveReturnInTupleVarGetter() { + let input = "var foo: (Int, Int) { return (1, 2) }" + let output = "var foo: (Int, Int) { (1, 2) }" + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testNoRemoveReturnInIfLetWithNoSpaceAfterParen() { + let input = """ + var foo: String? { + if let bar = baz(){ + return bar + } else { + return nil + } + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.spaceAroundBraces, .spaceAroundParens]) + } + + func testNoRemoveReturnInIfWithUnParenthesizedClosure() { + let input = """ + if foo { $0.bar } { + return true + } + """ + testFormatting(for: input, rule: .redundantReturn) + } + + func testRemoveBlankLineWithReturn() { + let input = """ + foo { + return + bar + } + """ + let output = """ + foo { + bar + } + """ + testFormatting(for: input, output, rule: .redundantReturn, + exclude: [.indent]) + } + + func testRemoveRedundantReturnInFunctionWithWhereClause() { + let input = """ + func foo(_ name: String) -> T where T: Equatable { + return name + } + """ + let output = """ + func foo(_ name: String) -> T where T: Equatable { + name + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, + options: options) + } + + func testRemoveRedundantReturnInSubscriptWithWhereClause() { + let input = """ + subscript(_ name: String) -> T where T: Equatable { + return name + } + """ + let output = """ + subscript(_ name: String) -> T where T: Equatable { + name + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, + options: options) + } + + func testNoRemoveReturnFollowedByMoreCode() { + let input = """ + var foo: Bar = { + return foo + let bar = baz + return bar + }() + """ + testFormatting(for: input, rule: .redundantReturn, exclude: [.redundantProperty]) + } + + func testNoRemoveReturnInForWhereLoop() { + let input = """ + func foo() -> Bool { + for bar in baz where !bar { + return false + } + return true + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantReturnInVoidFunction() { + let input = """ + func foo() { + return + } + """ + let output = """ + func foo() { + } + """ + testFormatting(for: input, output, rule: .redundantReturn, + exclude: [.emptyBraces]) + } + + func testRedundantReturnInVoidFunction2() { + let input = """ + func foo() { + print("") + return + } + """ + let output = """ + func foo() { + print("") + } + """ + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testRedundantReturnInVoidFunction3() { + let input = """ + func foo() { + // empty + return + } + """ + let output = """ + func foo() { + // empty + } + """ + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testRedundantReturnInVoidFunction4() { + let input = """ + func foo() { + return // empty + } + """ + let output = """ + func foo() { + // empty + } + """ + testFormatting(for: input, output, rule: .redundantReturn) + } + + func testNoRemoveVoidReturnInCatch() { + let input = """ + func foo() { + do { + try Foo() + } catch Feature.error { + print("feature error") + return + } + print("foo") + } + """ + testFormatting(for: input, rule: .redundantReturn) + } + + func testNoRemoveReturnInIfCase() { + let input = """ + var isSessionDeinitializedError: Bool { + if case .sessionDeinitialized = self { return true } + return false + } + """ + testFormatting(for: input, rule: .redundantReturn, + options: FormatOptions(swiftVersion: "5.1"), + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveReturnInForCasewhere() { + let input = """ + for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] + where names.contains(name) + { + return true + } + """ + testFormatting(for: input, rule: .redundantReturn, + options: FormatOptions(swiftVersion: "5.1")) + } + + func testNoRemoveRequiredReturnInFunctionInsideClosure() { + let input = """ + foo { + func bar() -> Bar { + let bar = Bar() + return bar + } + } + """ + testFormatting(for: input, rule: .redundantReturn, + options: FormatOptions(swiftVersion: "5.1"), exclude: [.redundantProperty]) + } + + func testNoRemoveRequiredReturnInIfClosure() { + let input = """ + func findButton() -> Button? { + let btns = [top, content, bottom] + if let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { + return btn + } + return btns.first + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNoRemoveRequiredReturnInIfClosure2() { + let input = """ + func findButton() -> Button? { + let btns = [top, content, bottom] + if let foo, let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { + return btn + } + return btns.first + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRemoveRedundantReturnInIfClosure() { + let input = """ + func findButton() -> Button? { + let btns = [top, content, bottom] + if let btn = btns.first { return !$0.isHidden && $0.alpha > 0.01 } { + print("hello") + } + return btns.first + } + """ + let output = """ + func findButton() -> Button? { + let btns = [top, content, bottom] + if let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { + print("hello") + } + return btns.first + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testDisableNextRedundantReturn() { + let input = """ + func foo() -> Foo { + // swiftformat:disable:next redundantReturn + return Foo() + } + """ + let options = FormatOptions(swiftVersion: "5.1") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantIfStatementReturnSwift5_8() { + let input = """ + func foo(condition: Bool) -> String { + if condition { + return "foo" + } else { + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantReturn, + options: options) + } + + func testNonRedundantIfStatementReturnSwift5_9() { + let input = """ + func foo(condition: Bool) -> String { + if condition { + return "foo" + } else if !condition { + return "bar" + } + return "baaz" + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantIfStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + if condition { + return "foo" + } else if otherCondition { + if anotherCondition { + return "bar" + } else { + return "baaz" + } + } else { + return "quux" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + if condition { + "foo" + } else if otherCondition { + if anotherCondition { + "bar" + } else { + "baaz" + } + } else { + "quux" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNoRemoveRedundantIfStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + if condition { + return "foo" + } else if otherCondition { + if anotherCondition { + return "bar" + } else { + return "baaz" + } + } else { + return "quux" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.conditionalAssignment]) + } + + func testRedundantIfStatementReturnInClosure() { + let input = """ + let closure: (Bool) -> String = { condition in + if condition { + return "foo" + } else { + return "bar" + } + } + """ + let output = """ + let closure: (Bool) -> String = { condition in + if condition { + "foo" + } else { + "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNoRemoveRedundantIfStatementReturnInClosure() { + let input = """ + let closure: (Bool) -> String = { condition in + if condition { + return "foo" + } else { + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.conditionalAssignment]) + } + + func testNoRemoveReturnInConsecutiveIfStatements() { + let input = """ + func foo() -> String? { + if bar { + return nil + } + if baz { + return "baz" + } else { + return "quux" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantIfStatementReturnInRedundantClosure() { + let input = """ + let value = { + if condition { + return "foo" + } else { + return "bar" + } + }() + """ + let output = """ + let value = if condition { + "foo" + } else { + "bar" + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment, + .redundantClosure, .indent], + options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testRedundantSwitchStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + case false: + return "bar" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + "foo" + case false: + "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNoRemoveRedundantSwitchStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + case false: + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.conditionalAssignment]) + } + + func testNonRedundantSwitchStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + case false: + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantSwitchStatementReturnInFunctionWithDefault() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + default: + return "bar" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + "foo" + default: + "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRedundantSwitchStatementReturnInFunctionWithComment() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + // foo + return "foo" + + default: + /* bar */ + return "bar" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + // foo + "foo" + + default: + /* bar */ + "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNonRedundantSwitchStatementReturnInFunctionWithDefault() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + return "foo" + default: + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testNonRedundantSwitchStatementReturnInFunctionWithFallthrough() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + fallthrough + case false: + return "bar" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testVoidReturnNotStrippedFromSwitch() { + let input = """ + func foo(condition: Bool) { + switch condition { + case true: + print("foo") + case false: + return + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRedundantNestedSwitchStatementReturnInFunction() { + let input = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + switch condition { + case true: + return "foo" + case false: + if condition { + return "bar" + } else { + return "baaz" + } + } + + case false: + return "quux" + } + } + """ + let output = """ + func foo(condition: Bool) -> String { + switch condition { + case true: + switch condition { + case true: + "foo" + case false: + if condition { + "bar" + } else { + "baaz" + } + } + + case false: + "quux" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRedundantSwitchStatementReturnWithAssociatedValueMatchingInFunction() { + let input = """ + func test(_ value: SomeEnum) -> String { + switch value { + case let .first(str): + return "first \\(str)" + case .second("str"): + return "second" + default: + return "default" + } + } + """ + let output = """ + func test(_ value: SomeEnum) -> String { + switch value { + case let .first(str): + "first \\(str)" + case .second("str"): + "second" + default: + "default" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRedundantReturnDoesntFailToTerminateOnLongSwitch() { + let input = """ + func test(_ value: SomeEnum) -> String { + switch value { + case .one: + return "" + case .two: + return "" + case .three: + return "" + case .four: + return "" + case .five: + return "" + case .six: + return "" + case .seven: + return "" + case .eight: + return "" + case .nine: + return "" + case .ten: + return "" + case .eleven: + return "" + case .twelve: + return "" + case .thirteen: + return "" + case .fourteen: + return "" + case .fifteen: + return "" + case .sixteen: + return "" + case .seventeen: + return "" + case .eighteen: + return "" + case .nineteen: + return "" + } + } + """ + let output = """ + func test(_ value: SomeEnum) -> String { + switch value { + case .one: + "" + case .two: + "" + case .three: + "" + case .four: + "" + case .five: + "" + case .six: + "" + case .seven: + "" + case .eight: + "" + case .nine: + "" + case .ten: + "" + case .eleven: + "" + case .twelve: + "" + case .thirteen: + "" + case .fourteen: + "" + case .fifteen: + "" + case .sixteen: + "" + case .seventeen: + "" + case .eighteen: + "" + case .nineteen: + "" + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testNoRemoveDebugReturnFollowedBySwitch() { + let input = """ + func swiftFormatBug() -> Foo { + return .foo + + switch state { + case .foo, .bar: + return state + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options, + exclude: [.wrapSwitchCases, .sortSwitchCases]) + } + + func testDoesntRemoveReturnFromIfExpressionConditionalCastInSwift5_9() { + // The following code doesn't compile in Swift 5.9 due to this issue: + // https://github.com/apple/swift/issues/68764 + // + // var result: String { + // if condition { + // foo as? String + // } else { + // "bar" + // } + // } + // + let input = """ + var result1: String { + if condition { + return foo as? String + } else { + return "bar" + } + } + + var result2: String { + switch condition { + case true: + return foo as? String + case false: + return "bar" + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantReturn, options: options) + } + + func testRemovseReturnFromIfExpressionNestedConditionalCastInSwift5_9() { + let input = """ + var result1: String { + if condition { + return method(foo as? String) + } else { + return "bar" + } + } + """ + + let output = """ + var result1: String { + if condition { + method(foo as? String) + } else { + "bar" + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRemovesReturnFromIfExpressionConditionalCastInSwift5_10() { + let input = """ + var result: String { + if condition { + return foo as? String + } else { + return "bar" + } + } + """ + + let output = """ + var result: String { + if condition { + foo as? String + } else { + "bar" + } + } + """ + + let options = FormatOptions(swiftVersion: "5.10") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRemovesRedundantReturnBeforeIfExpression() { + let input = """ + func foo() -> Foo { + return if condition { + Foo.foo() + } else { + Foo.bar() + } + } + """ + + let output = """ + func foo() -> Foo { + if condition { + Foo.foo() + } else { + Foo.bar() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testRemovesRedundantReturnBeforeSwitchExpression() { + let input = """ + func foo() -> Foo { + return switch condition { + case true: + Foo.foo() + case false: + Foo.bar() + } + } + """ + + let output = """ + func foo() -> Foo { + switch condition { + case true: + Foo.foo() + case false: + Foo.bar() + } + } + """ + + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantReturn, options: options) + } + + func testRedundantSwitchStatementReturnInFunctionWithMultipleWhereClauses() { + // https://github.com/nicklockwood/SwiftFormat/issues/1554 + let input = """ + func foo(cases: FooCases, count: Int) -> String? { + switch cases { + case .fooCase1 where count == 0: + return "foo" + case .fooCase2 where count < 100, + .fooCase3 where count < 100, + .fooCase4: + return "bar" + default: + return nil + } + } + """ + let output = """ + func foo(cases: FooCases, count: Int) -> String? { + switch cases { + case .fooCase1 where count == 0: + "foo" + case .fooCase2 where count < 100, + .fooCase3 where count < 100, + .fooCase4: + "bar" + default: + nil + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } + + func testRedundantSwitchStatementReturnInFunctionWithSingleWhereClause() { + // https://github.com/nicklockwood/SwiftFormat/issues/1554 + let input = """ + func anotherFoo(cases: FooCases, count: Int) -> String? { + switch cases { + case .fooCase1 where count == 0: + return "foo" + case .fooCase2 where count < 100, + .fooCase4: + return "bar" + default: + return nil + } + } + """ + let output = """ + func anotherFoo(cases: FooCases, count: Int) -> String? { + switch cases { + case .fooCase1 where count == 0: + "foo" + case .fooCase2 where count < 100, + .fooCase4: + "bar" + default: + nil + } + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, [output], + rules: [.redundantReturn, .conditionalAssignment], + options: options) + } +} diff --git a/Tests/Rules/RedundantSelfTests.swift b/Tests/Rules/RedundantSelfTests.swift new file mode 100644 index 000000000..9f97447c8 --- /dev/null +++ b/Tests/Rules/RedundantSelfTests.swift @@ -0,0 +1,3393 @@ +// +// RedundantSelfTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantSelfTests: XCTestCase { + // explicitSelf = .remove + + func testSimpleRemoveRedundantSelf() { + let input = "func foo() { self.bar() }" + let output = "func foo() { bar() }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInsideStringInterpolation() { + let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" + let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfForArgument() { + let input = "func foo(bar: Int) { self.bar = bar }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForLocalVariable() { + let input = "func foo() { var bar = self.bar }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfForLocalVariableOn5_4() { + let input = "func foo() { var bar = self.bar }" + let output = "func foo() { var bar = bar }" + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantSelf, + options: options) + } + + func testNoRemoveSelfForCommaDelimitedLocalVariables() { + let input = "func foo() { let foo = self.foo, bar = self.bar }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfForCommaDelimitedLocalVariablesOn5_4() { + let input = "func foo() { let foo = self.foo, bar = self.bar }" + let output = "func foo() { let foo = self.foo, bar = bar }" + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantSelf, + options: options) + } + + func testNoRemoveSelfForCommaDelimitedLocalVariables2() { + let input = "func foo() {\n let foo: Foo, bar: Bar\n foo = self.foo\n bar = self.bar\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForTupleAssignedVariables() { + let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" + testFormatting(for: input, rule: .redundantSelf) + } + + // TODO: make this work +// func testRemoveSelfForTupleAssignedVariablesOn5_4() { +// let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" +// let output = "func foo() { let (bar, baz) = (bar, baz) }" +// let options = FormatOptions(swiftVersion: "5.4") +// testFormatting(for: input, output, rule: .redundantSelf, +// options: options) +// } + + func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularVariable() { + let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar), baz = self.baz\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularLet() { + let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar)\n let baz = self.baz\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveNonRedundantNestedFunctionSelf() { + let input = "func foo() { func bar() { self.bar() } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveNonRedundantNestedFunctionSelf2() { + let input = "func foo() {\n func bar() {}\n self.bar()\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveNonRedundantNestedFunctionSelf3() { + let input = "func foo() { let bar = 5; func bar() { self.bar = bar } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveClosureSelf() { + let input = "func foo() { bar { self.bar = 5 } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfAfterOptionalReturn() { + let input = "func foo() -> String? {\n var index = startIndex\n if !matching(self[index]) {\n break\n }\n index = self.index(after: index)\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveRequiredSelfInExtensions() { + let input = "extension Foo {\n func foo() {\n var index = 5\n if true {\n break\n }\n index = self.index(after: index)\n }\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfBeforeInit() { + let input = "convenience init() { self.init(5) }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfInsideSwitch() { + let input = "func foo() {\n switch self.bar {\n case .foo:\n self.baz()\n }\n}" + let output = "func foo() {\n switch bar {\n case .foo:\n baz()\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInsideSwitchWhere() { + let input = "func foo() {\n switch self.bar {\n case .foo where a == b:\n self.baz()\n }\n}" + let output = "func foo() {\n switch bar {\n case .foo where a == b:\n baz()\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInsideSwitchWhereAs() { + let input = "func foo() {\n switch self.bar {\n case .foo where a == b as C:\n self.baz()\n }\n}" + let output = "func foo() {\n switch bar {\n case .foo where a == b as C:\n baz()\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInsideClassInit() { + let input = "class Foo {\n var bar = 5\n init() { self.bar = 6 }\n}" + let output = "class Foo {\n var bar = 5\n init() { bar = 6 }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureInsideIf() { + let input = "if foo { bar { self.baz() } }" + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveSelfForErrorInCatch() { + let input = "do {} catch { self.error = error }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForErrorInDoThrowsCatch() { + let input = "do throws(Foo) {} catch { self.error = error }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForNewValueInSet() { + let input = "var foo: Int { set { self.newValue = newValue } get { return 0 } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForCustomNewValueInSet() { + let input = "var foo: Int { set(n00b) { self.n00b = n00b } get { return 0 } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForNewValueInWillSet() { + let input = "var foo: Int { willSet { self.newValue = newValue } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForCustomNewValueInWillSet() { + let input = "var foo: Int { willSet(n00b) { self.n00b = n00b } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForOldValueInDidSet() { + let input = "var foo: Int { didSet { self.oldValue = oldValue } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForCustomOldValueInDidSet() { + let input = "var foo: Int { didSet(oldz) { self.oldz = oldz } }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForIndexVarInFor() { + let input = "for foo in bar { self.foo = foo }" + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) + } + + func testNoRemoveSelfForKeyValueTupleInFor() { + let input = "for (foo, bar) in baz { self.foo = foo; self.bar = bar }" + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) + } + + func testRemoveSelfFromComputedVar() { + let input = "var foo: Int { return self.bar }" + let output = "var foo: Int { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromOptionalComputedVar() { + let input = "var foo: Int? { return self.bar }" + let output = "var foo: Int? { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromNamespacedComputedVar() { + let input = "var foo: Swift.String { return self.bar }" + let output = "var foo: Swift.String { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromGenericComputedVar() { + let input = "var foo: Foo { return self.bar }" + let output = "var foo: Foo { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromComputedArrayVar() { + let input = "var foo: [Int] { return self.bar }" + let output = "var foo: [Int] { return bar }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromVarSetter() { + let input = "var foo: Int { didSet { self.bar() } }" + let output = "var foo: Int { didSet { bar() } }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarClosure() { + let input = "var foo = { self.bar }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfFromLazyVar() { + let input = "lazy var foo = self.bar" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfFromLazyVar() { + let input = "lazy var foo = self.bar" + let output = "lazy var foo = bar" + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { + let input = """ + var baz = bar + lazy var foo = self.bar + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { + let input = """ + var baz = bar + lazy var foo = self.bar + """ + let output = """ + var baz = bar + lazy var foo = bar + """ + let options = FormatOptions(swiftVersion: "4") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfFromLazyVarClosure() { + let input = "lazy var foo = { self.bar }()" + testFormatting(for: input, rule: .redundantSelf, exclude: [.redundantClosure]) + } + + func testNoRemoveSelfFromLazyVarClosure2() { + let input = "lazy var foo = { let bar = self.baz }()" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfFromLazyVarClosure3() { + let input = "lazy var foo = { [unowned self] in let bar = self.baz }()" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfFromVarInFuncWithUnusedArgument() { + let input = "func foo(bar _: Int) { self.baz = 5 }" + let output = "func foo(bar _: Int) { baz = 5 }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfFromVarMatchingUnusedArgument() { + let input = "func foo(bar _: Int) { self.bar = 5 }" + let output = "func foo(bar _: Int) { bar = 5 }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarMatchingRenamedArgument() { + let input = "func foo(bar baz: Int) { self.baz = baz }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarRedeclaredInSubscope() { + let input = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = self.bar\n}" + let output = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = bar\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarDeclaredLaterInScope() { + let input = "func foo() {\n let bar = self.baz\n let baz = quux\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfFromVarDeclaredLaterInOuterScope() { + let input = "func foo() {\n if quux {\n let bar = self.baz\n }\n let baz = 6\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInWhilePreceededByVarDeclaration() { + let input = "var index = start\nwhile index < end {\n index = self.index(after: index)\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInLocalVarPrecededByLocalVarFollowedByIfComma() { + let input = "func foo() {\n let bar = Bar()\n let baz = Baz()\n self.baz = baz\n if let bar = bar, bar > 0 {}\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInLocalVarPrecededByIfLetContainingClosure() { + let input = "func foo() {\n if let bar = 5 { baz { _ in } }\n let quux = self.quux\n}" + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveSelfForVarCreatedInGuardScope() { + let input = "func foo() {\n guard let bar = 5 else {}\n let baz = self.bar\n}" + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testRemoveSelfForVarCreatedInIfScope() { + let input = "func foo() {\n if let bar = bar {}\n let baz = self.bar\n}" + let output = "func foo() {\n if let bar = bar {}\n let baz = bar\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfForVarDeclaredInWhileCondition() { + let input = "while let foo = bar { self.foo = foo }" + testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) + } + + func testRemoveSelfForVarNotDeclaredInWhileCondition() { + let input = "while let foo == bar { self.baz = 5 }" + let output = "while let foo == bar { baz = 5 }" + testFormatting(for: input, output, rule: .redundantSelf, exclude: [.wrapLoopBodies]) + } + + func testNoRemoveSelfForVarDeclaredInSwitchCase() { + let input = "switch foo {\ncase bar: let baz = self.baz\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfAfterGenericInit() { + let input = "init(bar: Int) {\n self = Foo()\n self.bar(bar)\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRemoveSelfInClassFunction() { + let input = "class Foo {\n class func foo() {\n func bar() { self.foo() }\n }\n}" + let output = "class Foo {\n class func foo() {\n func bar() { foo() }\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfInStaticFunction() { + let input = "struct Foo {\n static func foo() {\n func bar() { self.foo() }\n }\n}" + let output = "struct Foo {\n static func foo() {\n func bar() { foo() }\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf, exclude: [.enumNamespaces]) + } + + func testRemoveSelfInClassFunctionWithModifiers() { + let input = "class Foo {\n class private func foo() {\n func bar() { self.foo() }\n }\n}" + let output = "class Foo {\n class private func foo() {\n func bar() { foo() }\n }\n}" + testFormatting(for: input, output, rule: .redundantSelf, + exclude: [.modifierOrder]) + } + + func testNoRemoveSelfInClassFunction() { + let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { self.foo() }\n }\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForVarDeclaredAfterRepeatWhile() { + let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n let foo = 6\n self.foo()\n }\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfForVarInClosureAfterRepeatWhile() { + let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n ({ self.foo() })()\n }\n}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterVar() { + let input = "var foo: String\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterNamespacedVar() { + let input = "var foo: Swift.String\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterOptionalVar() { + let input = "var foo: String?\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterGenericVar() { + let input = "var foo: Foo\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureAfterArray() { + let input = "var foo: [Int]\nbar { self.baz() }" + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInExpectFunction() { // Special case to support the Nimble framework + let input = """ + class FooTests: XCTestCase { + let foo = 1 + func testFoo() { + expect(self.foo) == 1 + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveNestedSelfInExpectFunction() { + let input = """ + func testFoo() { + expect(Foo.validate(bar: self.bar)).to(equal(1)) + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveNestedSelfInArrayInExpectFunction() { + let input = """ + func testFoo() { + expect(Foo.validate(bar: [self.bar])).to(equal(1)) + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveNestedSelfInSubscriptInExpectFunction() { + let input = """ + func testFoo() { + expect(Foo.validations[self.bar]).to(equal(1)) + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInOSLogFunction() { + let input = """ + func testFoo() { + os_log("error: \\(self.bar) is nil") + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInExcludedFunction() { + let input = """ + class Foo { + let foo = 1 + func testFoo() { + log(self.foo) + } + } + """ + let options = FormatOptions(selfRequired: ["log"]) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfForExcludedFunction() { + let input = """ + class Foo { + let foo = 1 + func testFoo() { + self.log(foo) + } + } + """ + let options = FormatOptions(selfRequired: ["log"]) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInInterpolatedStringInExcludedFunction() { + let input = """ + class Foo { + let foo = 1 + func testFoo() { + log("\\(self.foo)") + } + } + """ + let options = FormatOptions(selfRequired: ["log"]) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInExcludedInitializer() { + let input = """ + let vc = UIHostingController(rootView: InspectionView(inspection: self.inspection)) + """ + let options = FormatOptions(selfRequired: ["InspectionView"]) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.propertyType]) + } + + func testSelfRemovedFromSwitchCaseWhere() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo where self.bar.baz: + return self.bar + default: + return nil + } + } + } + """ + let output = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo where bar.baz: + return bar + default: + return nil + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testSwitchCaseLetVarRecognized() { + let input = """ + switch foo { + case .bar: + baz = nil + case let baz: + self.baz = baz + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSwitchCaseHoistedLetVarRecognized() { + let input = """ + switch foo { + case .bar: + baz = nil + case let .foo(baz): + self.baz = baz + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSwitchCaseWhereMemberNotTreatedAsVar() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let bar where self.bar.baz: + return self.bar + default: + return nil + } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedInClosureAfterSwitch() { + let input = """ + switch x { + default: + break + } + let foo = { y in + switch y { + default: + self.bar() + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedInClosureInCaseWithWhereClause() { + let input = """ + switch foo { + case bar where baz: + quux = { self.foo } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovedInDidSet() { + let input = """ + class Foo { + var bar = false { + didSet { + self.bar = !self.bar + } + } + } + """ + let output = """ + class Foo { + var bar = false { + didSet { + bar = !bar + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testSelfNotRemovedInGetter() { + let input = """ + class Foo { + var bar: Int { + return self.bar + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedInIfdef() { + let input = """ + func foo() { + #if os(macOS) + let bar = self.bar + #endif + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfRemovedWhenFollowedBySwitchContainingIfdef() { + let input = """ + struct Foo { + func bar() { + self.method(self.value) + switch x { + #if BAZ + case .baz: + break + #endif + default: + break + } + } + } + """ + let output = """ + struct Foo { + func bar() { + method(value) + switch x { + #if BAZ + case .baz: + break + #endif + default: + break + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfRemovedInsideConditionalCase() { + let input = """ + struct Foo { + func bar() { + let method2 = () -> Void + switch x { + #if BAZ + case .baz: + self.method1(self.value) + #else + case .quux: + self.method2(self.value) + #endif + default: + break + } + } + } + """ + let output = """ + struct Foo { + func bar() { + let method2 = () -> Void + switch x { + #if BAZ + case .baz: + method1(value) + #else + case .quux: + self.method2(value) + #endif + default: + break + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfRemovedAfterConditionalLet() { + let input = """ + class Foo { + var bar: Int? + var baz: Bool + + func foo() { + if let bar = bar, self.baz { + // ... + } + } + } + """ + let output = """ + class Foo { + var bar: Int? + var baz: Bool + + func foo() { + if let bar = bar, baz { + // ... + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNestedClosureInNotMistakenForForLoop() { + let input = """ + func f() { + let str = "hello" + try! str.withCString(encodedAs: UTF8.self) { _ throws in + try! str.withCString(encodedAs: UTF8.self) { _ throws in } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testTypedThrowingNestedClosureInNotMistakenForForLoop() { + let input = """ + func f() { + let str = "hello" + try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in + try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfPreservesSelfInClosureWithExplicitStrongCaptureBefore5_3() { + let input = """ + class Foo { + let bar: Int + + func baaz() { + closure { [self] in + print(self.bar) + } + } + } + """ + + let options = FormatOptions(swiftVersion: "5.2") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRemovesSelfInClosureWithExplicitStrongCapture() { + let input = """ + class Foo { + let foo: Int + + func baaz() { + closure { [self, bar] baaz, quux in + print(self.foo) + } + } + } + """ + + let output = """ + class Foo { + let foo: Int + + func baaz() { + closure { [self, bar] baaz, quux in + print(foo) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .redundantSelf, options: options, exclude: [.unusedArguments]) + } + + func testRedundantSelfRemovesSelfInClosureWithNestedExplicitStrongCapture() { + let input = """ + class Foo { + let bar: Int + + func baaz() { + closure { + print(self.bar) + closure { [self] in + print(self.bar) + } + print(self.bar) + } + } + } + """ + + let output = """ + class Foo { + let bar: Int + + func baaz() { + closure { + print(self.bar) + closure { [self] in + print(bar) + } + print(self.bar) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRedundantSelfKeepsSelfInNestedClosureWithNoExplicitStrongCapture() { + let input = """ + class Foo { + let bar: Int + let baaz: Int? + + func baaz() { + closure { [self] in + print(self.bar) + closure { + print(self.bar) + if let baaz = self.baaz { + print(baaz) + } + } + print(self.bar) + if let baaz = self.baaz { + print(baaz) + } + } + } + } + """ + + let output = """ + class Foo { + let bar: Int + let baaz: Int? + + func baaz() { + closure { [self] in + print(bar) + closure { + print(self.bar) + if let baaz = self.baaz { + print(baaz) + } + } + print(bar) + if let baaz = baaz { + print(baaz) + } + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRemovesSelfInClosureCapturingStruct() { + let input = """ + struct Foo { + let bar: Int + + func baaz() { + closure { + print(self.bar) + } + } + } + """ + + let output = """ + struct Foo { + let bar: Int + + func baaz() { + closure { + print(bar) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRemovesSelfInClosureCapturingSelfWeakly() { + let input = """ + class Foo { + let bar: Int + + func baaz() { + closure { [weak self] in + print(self?.bar) + guard let self else { + return + } + print(self.bar) + closure { + print(self.bar) + } + closure { [self] in + print(self.bar) + } + print(self.bar) + } + + closure { [weak self] in + guard let self = self else { + return + } + + print(self.bar) + } + + closure { [weak self] in + guard let self = self ?? somethingElse else { + return + } + + print(self.bar) + } + } + } + """ + + let output = """ + class Foo { + let bar: Int + + func baaz() { + closure { [weak self] in + print(self?.bar) + guard let self else { + return + } + print(bar) + closure { + print(self.bar) + } + closure { [self] in + print(bar) + } + print(bar) + } + + closure { [weak self] in + guard let self = self else { + return + } + + print(bar) + } + + closure { [weak self] in + guard let self = self ?? somethingElse else { + return + } + + print(self.bar) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, output, rule: .redundantSelf, + options: options, exclude: [.redundantOptionalBinding]) + } + + func testWeakSelfNotRemovedIfNotUnwrapped() { + let input = """ + class A { + weak var delegate: ADelegate? + + func testFunction() { + DispatchQueue.main.async { [weak self] in + self.flatMap { $0.delegate?.aDidSomething($0) } + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testClosureParameterListShadowingPropertyOnSelf() { + let input = """ + class Foo { + var bar = "bar" + + func method() { + closure { [self] bar in + self.bar = bar + } + } + } + """ + + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testClosureParameterListShadowingPropertyOnSelfInStruct() { + let input = """ + struct Foo { + var bar = "bar" + + func method() { + closure { bar in + self.bar = bar + } + } + } + """ + + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testClosureCaptureListShadowingPropertyOnSelf() { + let input = """ + class Foo { + var bar = "bar" + var baaz = "baaz" + + func method() { + closure { [self, bar, baaz = bar] in + self.bar = bar + self.baaz = baaz + } + } + } + """ + + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfKeepsSelfInClosureCapturingSelfWeaklyBefore5_8() { + let input = """ + class Foo { + let bar: Int + + func baaz() { + closure { [weak self] in + print(self?.bar) + guard let self else { + return + } + print(self.bar) + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.7") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNonRedundantSelfNotRemovedAfterConditionalLet() { + let input = """ + class Foo { + var bar: Int? + var baz: Bool + + func foo() { + let baz = 5 + if let bar = bar, self.baz { + // ... + } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfDoesntGetStuckIfNoParensFound() { + let input = "init_ foo: T {}" + testFormatting(for: input, rule: .redundantSelf, + exclude: [.spaceAroundOperators]) + } + + func testNoRemoveSelfInIfLetSelf() { + let input = """ + func foo() { + if let self = self as? Foo { + self.bar() + } + self.bar() + } + """ + let output = """ + func foo() { + if let self = self as? Foo { + self.bar() + } + bar() + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfInIfLetEscapedSelf() { + let input = """ + func foo() { + if let `self` = self as? Foo { + self.bar() + } + self.bar() + } + """ + let output = """ + func foo() { + if let `self` = self as? Foo { + self.bar() + } + bar() + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testNoRemoveSelfAfterGuardLetSelf() { + let input = """ + func foo() { + guard let self = self as? Foo else { + return + } + self.bar() + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInClosureInIfCondition() { + let input = """ + class Foo { + func foo() { + if bar({ self.baz() }) {} + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInTrailingClosureInVarAssignment() { + let input = """ + func broken() { + var bad = abc { + self.foo() + self.bar + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedWhenPropertyIsKeyword() { + let input = """ + class Foo { + let `default` = 5 + func foo() { + print(self.default) + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedWhenPropertyIsContextualKeyword() { + let input = """ + class Foo { + let `self` = 5 + func foo() { + print(self.self) + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovedForContextualKeywordThatRequiresNoEscaping() { + let input = """ + class Foo { + let get = 5 + func foo() { + print(self.get) + } + } + """ + let output = """ + class Foo { + let get = 5 + func foo() { + print(get) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveSelfForMemberNamedLazy() { + let input = "func foo() { self.lazy() }" + let output = "func foo() { lazy() }" + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveRedundantSelfInArrayLiteral() { + let input = """ + class Foo { + func foo() { + print([self.bar.x, self.bar.y]) + } + } + """ + let output = """ + class Foo { + func foo() { + print([bar.x, bar.y]) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveRedundantSelfInArrayLiteralVar() { + let input = """ + class Foo { + func foo() { + var bars = [self.bar.x, self.bar.y] + print(bars) + } + } + """ + let output = """ + class Foo { + func foo() { + var bars = [bar.x, bar.y] + print(bars) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemoveRedundantSelfInGuardLet() { + let input = """ + class Foo { + func foo() { + guard let bar = self.baz else { + return + } + } + } + """ + let output = """ + class Foo { + func foo() { + guard let bar = baz else { + return + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testSelfNotRemovedInClosureInIf() { + let input = """ + if let foo = bar(baz: { [weak self] in + guard let self = self else { return } + _ = self.myVar + }) {} + """ + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testStructSelfRemovedInTrailingClosureInIfCase() { + let input = """ + struct A { + func doSomething() { + B.method { mode in + if case .edit = mode { + self.doA() + } else { + self.doB() + } + } + } + + func doA() {} + func doB() {} + } + """ + let output = """ + struct A { + func doSomething() { + B.method { mode in + if case .edit = mode { + doA() + } else { + doB() + } + } + } + + func doA() {} + func doB() {} + } + """ + testFormatting(for: input, output, rule: .redundantSelf, + options: FormatOptions(swiftVersion: "5.8")) + } + + func testSelfNotRemovedInDynamicMemberLookup() { + let input = """ + @dynamicMemberLookup + struct Foo { + subscript(dynamicMember foo: String) -> String { + foo + "bar" + } + + func bar() { + if self.foo == "foobar" { + return + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfNotRemovedInDeclarationWithDynamicMemberLookup() { + let input = """ + @dynamicMemberLookup + struct Foo { + subscript(dynamicMember foo: String) -> String { + foo + "bar" + } + + func bar() { + let foo = self.foo + print(foo) + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfNotRemovedInExtensionOfTypeWithDynamicMemberLookup() { + let input = """ + @dynamicMemberLookup + struct Foo {} + + extension Foo { + subscript(dynamicMember foo: String) -> String { + foo + "bar" + } + + func bar() { + if self.foo == "foobar" { + return + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfRemovedInNestedExtensionOfTypeWithDynamicMemberLookup() { + let input = """ + @dynamicMemberLookup + struct Foo { + var foo: Int + struct Foo {} + extension Foo { + func bar() { + if self.foo == "foobar" { + return + } + } + } + } + """ + let output = """ + @dynamicMemberLookup + struct Foo { + var foo: Int + struct Foo {} + extension Foo { + func bar() { + if foo == "foobar" { + return + } + } + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantSelf, + options: options) + } + + func testNoRemoveSelfAfterGuardCaseLetWithExplicitNamespace() { + let input = """ + class Foo { + var name: String? + + func bug(element: Something) { + guard case let Something.a(name) = element + else { return } + self.name = name + } + } + """ + testFormatting(for: input, rule: .redundantSelf, + exclude: [.wrapConditionalBodies]) + } + + func testNoRemoveSelfInAssignmentInsideIfAsStatement() { + let input = """ + if let foo = foo as? Foo, let bar = baz { + self.bar = bar + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testNoRemoveSelfInAssignmentInsideIfLetWithPostfixOperator() { + let input = """ + if let foo = baz?.foo, let bar = baz?.bar { + self.foo = foo + self.bar = bar + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfParsingBug() { + let input = """ + private class Foo { + mutating func bar() -> Statement? { + let start = self + guard case Token.identifier(let name)? = self.popFirst() else { + self = start + return nil + } + return Statement.declaration(name: name) + } + } + """ + let output = """ + private class Foo { + mutating func bar() -> Statement? { + let start = self + guard case Token.identifier(let name)? = popFirst() else { + self = start + return nil + } + return Statement.declaration(name: name) + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf, + exclude: [.hoistPatternLet]) + } + + func testRedundantSelfParsingBug2() { + let input = """ + extension Foo { + private enum NonHashableEnum: RawRepresentable { + case foo + case bar + + var rawValue: RuntimeTypeTests.TestStruct { + return TestStruct(foo: 0) + } + + init?(rawValue: RuntimeTypeTests.TestStruct) { + switch rawValue.foo { + case 0: + self = .foo + case 1: + self = .bar + default: + return nil + } + } + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfWithStaticMethodAfterForLoop() { + let input = """ + struct Foo { + init() { + for foo in self.bar {} + } + + static func foo() {} + } + + """ + let output = """ + struct Foo { + init() { + for foo in bar {} + } + + static func foo() {} + } + + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfWithStaticMethodAfterForWhereLoop() { + let input = """ + struct Foo { + init() { + for foo in self.bar where !bar.isEmpty {} + } + + static func foo() {} + } + + """ + let output = """ + struct Foo { + init() { + for foo in bar where !bar.isEmpty {} + } + + static func foo() {} + } + + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfRuleDoesntErrorInForInTryLoop() { + let input = "for foo in try bar() {}" + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfInInitWithActorLabel() { + let input = """ + class Foo { + init(actor: Actor, bar: Bar) { + self.actor = actor + self.bar = bar + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfRuleFailsInGuardWithParenthesizedClosureAfterComma() { + let input = """ + guard let foo = bar, foo.bar(baz: { $0 }) else { + return nil + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testMinSelfNotRemoved() { + let input = """ + extension Array where Element: Comparable { + func foo() -> Int { + self.min() + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testMinSelfNotRemovedOnSwift5_4() { + let input = """ + extension Array where Element == Foo { + func smallest() -> Foo? { + let bar = self.min(by: { rect1, rect2 -> Bool in + rect1.perimeter < rect2.perimeter + }) + return bar + } + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) + } + + func testDisableRedundantSelfDirective() { + let input = """ + func smallest() -> Foo? { + // swiftformat:disable:next redundantSelf + let bar = self.foo { rect1, rect2 -> Bool in + rect1.perimeter < rect2.perimeter + } + return bar + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) + } + + func testDisableRedundantSelfDirective2() { + let input = """ + func smallest() -> Foo? { + let bar = + // swiftformat:disable:next redundantSelf + self.foo { rect1, rect2 -> Bool in + rect1.perimeter < rect2.perimeter + } + return bar + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) + } + + func testSelfInsertDirective() { + let input = """ + func smallest() -> Foo? { + // swiftformat:options:next --self insert + let bar = self.foo { rect1, rect2 -> Bool in + rect1.perimeter < rect2.perimeter + } + return bar + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) + } + + func testNoRemoveVariableShadowedLaterInScopeInOlderSwiftVersions() { + let input = """ + func foo() -> Bar? { + guard let baz = self.bar else { + return nil + } + + let bar = Foo() + return Bar(baz) + } + """ + let options = FormatOptions(swiftVersion: "4.2") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testStillRemoveVariableShadowedInSameDecalarationInOlderSwiftVersions() { + let input = """ + func foo() -> Bar? { + guard let bar = self.bar else { + return nil + } + return bar + } + """ + let output = """ + func foo() -> Bar? { + guard let bar = bar else { + return nil + } + return bar + } + """ + let options = FormatOptions(swiftVersion: "5.0") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testShadowedSelfRemovedInGuardLet() { + let input = """ + func foo() { + guard let optional = self.optional else { + return + } + print(optional) + } + """ + let output = """ + func foo() { + guard let optional = optional else { + return + } + print(optional) + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testShadowedStringValueNotRemovedInInit() { + let input = """ + init() { + let value = "something" + self.value = value + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testShadowedIntValueNotRemovedInInit() { + let input = """ + init() { + let value = 5 + self.value = value + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testShadowedPropertyValueNotRemovedInInit() { + let input = """ + init() { + let value = foo + self.value = value + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testShadowedFuncCallValueNotRemovedInInit() { + let input = """ + init() { + let value = foo() + self.value = value + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testShadowedFuncParamRemovedInInit() { + let input = """ + init() { + let value = foo(self.value) + } + """ + let output = """ + init() { + let value = foo(value) + } + """ + let options = FormatOptions(swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoRemoveSelfInMacro() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: self.__myVar) + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + // explicitSelf = .insert + + func testInsertSelf() { + let input = "class Foo {\n let foo: Int\n init() { foo = 5 }\n}" + let output = "class Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testInsertSelfInActor() { + let input = "actor Foo {\n let foo: Int\n init() { foo = 5 }\n}" + let output = "actor Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testInsertSelfAfterReturn() { + let input = "class Foo {\n let foo: Int\n func bar() -> Int { return foo }\n}" + let output = "class Foo {\n let foo: Int\n func bar() -> Int { return self.foo }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testInsertSelfInsideStringInterpolation() { + let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" + let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInterpretGenericTypesAsMembers() { + let input = "class Foo {\n let foo: Bar\n init() { self.foo = Int(5) }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testInsertSelfForStaticMemberInClassFunction() { + let input = "class Foo {\n static var foo: Int\n class func bar() { foo = 5 }\n}" + let output = "class Foo {\n static var foo: Int\n class func bar() { self.foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForInstanceMemberInClassFunction() { + let input = "class Foo {\n var foo: Int\n class func bar() { foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForStaticMemberInInstanceFunction() { + let input = "class Foo {\n static var foo: Int\n func bar() { foo = 5 }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForShadowedClassMemberInClassFunction() { + let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { foo = 5 }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInForLoopTuple() { + let input = "class Foo {\n var bar: Int\n func foo() { for (bar, baz) in quux {} }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForTupleTypeMembers() { + let input = "class Foo {\n var foo: (Int, UIColor) {\n let bar = UIColor.red\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForArrayElements() { + let input = "class Foo {\n var foo = [1, 2, nil]\n func bar() { baz(nil) }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForNestedVarReference() { + let input = "class Foo {\n func bar() {\n var bar = 5\n repeat { bar = 6 } while true\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.wrapLoopBodies]) + } + + func testNoInsertSelfInSwitchCaseLet() { + let input = "class Foo {\n var foo: Bar? {\n switch bar {\n case let .baz(foo, _):\n return nil\n }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInFuncAfterImportedClass() { + let input = "import class Foo.Bar\nfunc foo() {\n var bar = 5\n if true {\n bar = 6\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, + exclude: [.blankLineAfterImports]) + } + + func testNoInsertSelfForSubscriptGetSet() { + let input = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return get(key) }\n set { set(key, newValue) }\n }\n}" + let output = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return self.get(key) }\n set { self.set(key, newValue) }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInIfCaseLet() { + let input = "enum Foo {\n case bar(Int)\n var value: Int? {\n if case let .bar(value) = self { return value }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, + exclude: [.wrapConditionalBodies]) + } + + func testNoInsertSelfForPatternLet() { + let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForPatternLet2() { + let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case let .foo(baz): print(baz)\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForTypeOf() { + let input = "class Foo {\n var type: String?\n func bar() {\n print(\"\\(type(of: self))\")\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForConditionalLocal() { + let input = "class Foo {\n func foo() {\n #if os(watchOS)\n var foo: Int\n #else\n var foo: Float\n #endif\n print(foo)\n }\n}" + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testInsertSelfInExtension() { + let input = """ + struct Foo { + var bar = 5 + } + + extension Foo { + func baz() { + bar = 6 + } + } + """ + let output = """ + struct Foo { + var bar = 5 + } + + extension Foo { + func baz() { + self.bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testGlobalAfterTypeNotTreatedAsMember() { + let input = """ + struct Foo { + var foo = 1 + } + + var bar = 5 + + extension Foo { + func baz() { + bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testForWhereVarNotTreatedAsMember() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + for bar in self where bar.baz { + return bar + } + return nil + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSwitchCaseWhereVarNotTreatedAsMember() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let bar where bar.baz: + return bar + default: + return nil + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSwitchCaseVarDoesntLeak() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let bar: + return bar + default: + return bar + } + } + } + """ + let output = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let bar: + return bar + default: + return self.bar + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInSwitchCaseLet() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo: + return bar + default: + return bar + } + } + } + """ + let output = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo: + return self.bar + default: + return self.bar + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInSwitchCaseWhere() { + let input = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo where bar.baz: + return bar + default: + return bar + } + } + } + """ + let output = """ + class Foo { + var bar: Bar + var bazziestBar: Bar? { + switch x { + case let foo where self.bar.baz: + return self.bar + default: + return self.bar + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInDidSet() { + let input = """ + class Foo { + var bar = false { + didSet { + bar = !bar + } + } + } + """ + let output = """ + class Foo { + var bar = false { + didSet { + self.bar = !self.bar + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedAfterLet() { + let input = """ + struct Foo { + let foo = "foo" + func bar() { + let x = foo + baz(x) + } + + func baz(_: String) {} + } + """ + let output = """ + struct Foo { + let foo = "foo" + func bar() { + let x = self.foo + self.baz(x) + } + + func baz(_: String) {} + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfNotInsertedInParameterNames() { + let input = """ + class Foo { + let a: String + + func bar() { + foo(a: a) + } + } + """ + let output = """ + class Foo { + let a: String + + func bar() { + foo(a: self.a) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfNotInsertedInCaseLet() { + let input = """ + class Foo { + let a: String? + let b: String + + func bar() { + if case let .some(a) = self.a, case var .some(b) = self.b {} + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfNotInsertedInCaseLet2() { + let input = """ + class Foo { + let a: String? + let b: String + + func baz() { + if case let .foos(a, b) = foo, case let .bars(a, b) = bar {} + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInTupleAssignment() { + let input = """ + class Foo { + let a: String? + let b: String + + func bar() { + (a, b) = ("foo", "bar") + } + } + """ + let output = """ + class Foo { + let a: String? + let b: String + + func bar() { + (self.a, self.b) = ("foo", "bar") + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfNotInsertedInTupleAssignment() { + let input = """ + class Foo { + let a: String? + let b: String + + func bar() { + let (a, b) = (self.a, self.b) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testInsertSelfForMemberNamedLazy() { + let input = """ + class Foo { + var lazy = "foo" + func foo() { + print(lazy) + } + } + """ + let output = """ + class Foo { + var lazy = "foo" + func foo() { + print(self.lazy) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForVarDefinedInIfCaseLet() { + let input = """ + struct A { + var localVar = "" + + var B: String { + if case let .c(localVar) = self.d, localVar == .e { + print(localVar) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForVarDefinedInUnhoistedIfCaseLet() { + let input = """ + struct A { + var localVar = "" + + var B: String { + if case .c(let localVar) = self.d, localVar == .e { + print(localVar) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, + exclude: [.hoistPatternLet]) + } + + func testNoInsertSelfForVarDefinedInFor() { + let input = """ + struct A { + var localVar = "" + + var B: String { + for localVar in 0 ..< 6 where localVar < 5 { + print(localVar) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfForVarDefinedInWhileLet() { + let input = """ + struct A { + var localVar = "" + + var B: String { + while let localVar = self.localVar, localVar < 5 { + print(localVar) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInCaptureList() { + let input = """ + class Thing { + var a: String? { nil } + + func foo() { + let b = "" + { [weak a = b] _ in } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInCaptureList2() { + let input = """ + class Thing { + var a: String? { nil } + + func foo() { + { [weak a] _ in } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInCaptureList3() { + let input = """ + class A { + var thing: B? { fatalError() } + + func foo() { + let thing2 = B() + let _: (Bool) -> Void = { [weak thing = thing2] _ in + thing?.bar() + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testBodilessFunctionDoesntBreakParser() { + let input = """ + @_silgen_name("foo") + func foo(_: CFString, _: CFTypeRef) -> Int? + + enum Bar { + static func baz() { + fatalError() + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfBeforeSet() { + let input = """ + class Foo { + var foo: Bool + + var bar: Bool { + get { self.foo } + set { self.foo = newValue } + } + + required init() {} + + func set() {} + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInMacro() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: __myVar) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfBeforeBinding() { + let input = """ + struct MyView: View { + @Environment(ViewModel.self) var viewModel + + var body: some View { + @Bindable var viewModel = self.viewModel + ZStack { + MySubview( + navigationPath: $viewModel.navigationPath + ) + } + } + } + """ + let options = FormatOptions(explicitSelf: .insert, swiftVersion: "5.10") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + // explicitSelf = .initOnly + + func testPreserveSelfInsideClassInit() { + let input = """ + class Foo { + var bar = 5 + init() { + self.bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRemoveSelfIfNotInsideClassInit() { + let input = """ + class Foo { + var bar = 5 + func baz() { + self.bar = 6 + } + } + """ + let output = """ + class Foo { + var bar = 5 + func baz() { + bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testInsertSelfInsideClassInit() { + let input = """ + class Foo { + var bar = 5 + init() { + bar = 6 + } + } + """ + let output = """ + class Foo { + var bar = 5 + init() { + self.bar = 6 + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testNoInsertSelfInsideClassInitIfNotLvalue() { + let input = """ + class Foo { + var bar = 5 + let baz = 6 + init() { + bar = baz + } + } + """ + let output = """ + class Foo { + var bar = 5 + let baz = 6 + init() { + self.bar = baz + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRemoveSelfInsideClassInitIfNotLvalue() { + let input = """ + class Foo { + var bar = 5 + let baz = 6 + init() { + self.bar = self.baz + } + } + """ + let output = """ + class Foo { + var bar = 5 + let baz = 6 + init() { + self.bar = baz + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfDotTypeInsideClassInitEdgeCase() { + let input = """ + class Foo { + let type: Int + + init() { + self.type = 5 + } + + func baz() { + switch type {} + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfInsertedInTupleInInit() { + let input = """ + class Foo { + let a: String? + let b: String + + init() { + (a, b) = ("foo", "bar") + } + } + """ + let output = """ + class Foo { + let a: String? + let b: String + + init() { + (self.a, self.b) = ("foo", "bar") + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testSelfInsertedAfterLetInInit() { + let input = """ + class Foo { + var foo: String + init(bar: Bar) { + let baz = bar.quux + foo = baz + } + } + """ + let output = """ + class Foo { + var foo: String + init(bar: Bar) { + let baz = bar.quux + self.foo = baz + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRuleDoesntErrorForStaticFuncInProtocolWithWhere() { + let input = """ + protocol Foo where Self: Bar { + static func baz() -> Self + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRuleDoesntErrorForStaticFuncInStructWithWhere() { + let input = """ + struct Foo where T: Bar { + static func baz() -> Foo {} + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRuleDoesntErrorForClassFuncInClassWithWhere() { + let input = """ + class Foo where T: Bar { + class func baz() -> Foo {} + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfRuleFailsInInitOnlyMode() { + let input = """ + class Foo { + func foo() -> Foo? { + guard let bar = { nil }() else { + return nil + } + } + + static func baz() -> String? {} + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantClosure]) + } + + func testRedundantSelfRuleFailsInInitOnlyMode2() { + let input = """ + struct Mesh { + var storage: Storage + init(vertices: [Vertex]) { + let isConvex = pointsAreConvex(vertices) + storage = Storage(vertices: vertices) + } + } + """ + let output = """ + struct Mesh { + var storage: Storage + init(vertices: [Vertex]) { + let isConvex = pointsAreConvex(vertices) + self.storage = Storage(vertices: vertices) + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, output, rule: .redundantSelf, + options: options) + } + + func testSelfNotRemovedInInitForSwift5_4() { + let input = """ + init() { + let foo = 1234 + self.bar = foo + } + """ + let options = FormatOptions(explicitSelf: .initOnly, swiftVersion: "5.4") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testPropertyInitNotInterpretedAsTypeInit() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: __myVar) + init(initialValue) { + __myVar = initialValue + } + set { + __myVar = newValue + } + get { + __myVar + } + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testPropertyInitNotInterpretedAsTypeInit2() { + let input = """ + struct MyStruct { + private var __myVar: String + var myVar: String { + @storageRestrictions(initializes: __myVar) + init { + __myVar = newValue + } + set { + __myVar = newValue + } + get { + __myVar + } + } + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + // parsing bugs + + func testSelfRemovalParsingBug() { + let input = """ + extension Dictionary where Key == String { + func requiredValue(for keyPath: String) throws -> T { + return keyPath as! T + } + + func optionalValue(for keyPath: String) throws -> T? { + guard let anyValue = self[keyPath] else { + return nil + } + guard let value = anyValue as? T else { + return nil + } + return value + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovalParsingBug2() { + let input = """ + if let test = value()["hi"] { + print("hi") + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovalParsingBug3() { + let input = """ + func handleGenericError(_ error: Error) { + if let requestableError = error as? RequestableError, + case let .underlying(error as NSError) = requestableError, + error.code == NSURLErrorNotConnectedToInternet + {} + } + """ + let options = FormatOptions(explicitSelf: .initOnly) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfRemovalParsingBug4() { + let input = """ + struct Foo { + func bar() { + for flag in [] where [].filter({ true }) {} + } + + static func baz() {} + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfRemovalParsingBug5() { + let input = """ + extension Foo { + func method(foo: Bar) { + self.foo = foo + + switch foo { + case let .foo(bar): + closure { + Foo.draw() + } + } + } + + private static func draw() {} + } + """ + + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfRemovalParsingBug6() { + let input = """ + something.do(onSuccess: { result in + if case .success((let d, _)) = result { + self.relay.onNext(d) + } + }) + """ + testFormatting(for: input, rule: .redundantSelf, + exclude: [.hoistPatternLet]) + } + + func testSelfRemovalParsingBug7() { + let input = """ + extension Dictionary where Key == String { + func requiredValue(for keyPath: String) throws(Foo) -> T { + return keyPath as! T + } + + func optionalValue(for keyPath: String) throws(Foo) -> T? { + guard let anyValue = self[keyPath] else { + return nil + } + guard let value = anyValue as? T else { + return nil + } + return value + } + } + """ + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfNotRemovedInCaseIfElse() { + let input = """ + class Foo { + let bar = true + let someOptionalBar: String? = "bar" + + func test() { + guard let bar: String = someOptionalBar else { + return + } + + let result = Result.success(bar) + switch result { + case let .success(value): + if self.bar { + if self.bar { + print(self.bar) + } + } else { + if self.bar { + print(self.bar) + } + } + + case .failure: + if self.bar { + print(self.bar) + } + } + } + } + """ + + testFormatting(for: input, rule: .redundantSelf) + } + + func testSelfCallAfterIfStatementInSwitchStatement() { + let input = """ + closure { [weak self] in + guard let self else { + return + } + + switch result { + case let .success(value): + if value != nil { + if value != nil { + self.method() + } + } + self.method() + + case .failure: + break + } + } + """ + + let options = FormatOptions(swiftVersion: "5.3") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testSelfNotRemovedFollowingNestedSwitchStatements() { + let input = """ + class Foo { + let bar = true + let someOptionalBar: String? = "bar" + + func test() { + guard let bar: String = someOptionalBar else { + return + } + + let result = Result.success(bar) + switch result { + case let .success(value): + switch result { + case .success: + print("success") + case .value: + print("value") + } + + case .failure: + guard self.bar else { + print(self.bar) + return + } + print(self.bar) + } + } + } + """ + + testFormatting(for: input, rule: .redundantSelf) + } + + func testRedundantSelfWithStaticAsyncSendableClosureFunction() { + let input = """ + class Foo: Bar { + static func bar( + _ closure: @escaping @Sendable () async -> Foo + ) -> @Sendable () async -> Foo { + self.foo = closure + return closure + } + + static func bar() {} + } + """ + let output = """ + class Foo: Bar { + static func bar( + _ closure: @escaping @Sendable () async -> Foo + ) -> @Sendable () async -> Foo { + foo = closure + return closure + } + + static func bar() {} + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + // enable/disable + + func testDisableRemoveSelf() { + let input = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable redundantSelf + self.bar = 1 + // swiftformat:enable redundantSelf + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable redundantSelf + self.bar = 1 + // swiftformat:enable redundantSelf + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testDisableRemoveSelfCaseInsensitive() { + let input = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable redundantself + self.bar = 1 + // swiftformat:enable RedundantSelf + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable redundantself + self.bar = 1 + // swiftformat:enable RedundantSelf + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testDisableNextRemoveSelf() { + let input = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable:next redundantSelf + self.bar = 1 + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + // swiftformat:disable:next redundantSelf + self.bar = 1 + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testMultilineDisableRemoveSelf() { + let input = """ + class Foo { + var bar: Int + func baz() { + /* swiftformat:disable redundantSelf */ self.bar = 1 /* swiftformat:enable all */ + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + /* swiftformat:disable redundantSelf */ self.bar = 1 /* swiftformat:enable all */ + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testMultilineDisableNextRemoveSelf() { + let input = """ + class Foo { + var bar: Int + func baz() { + /* swiftformat:disable:next redundantSelf */ + self.bar = 1 + self.bar = 2 + } + } + """ + let output = """ + class Foo { + var bar: Int + func baz() { + /* swiftformat:disable:next redundantSelf */ + self.bar = 1 + bar = 2 + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRemovesSelfInNestedFunctionInStrongSelfClosure() { + let input = """ + class Test { + func doWork(_ escaping: @escaping () -> Void) { + escaping() + } + + func test() { + doWork { [self] in + doWork { + // Not allowed. Warning in Swift 5 and error in Swift 6. + self.test() + } + + func innerFunc() { + // Allowed: https://forums.swift.org/t/why-does-se-0269-have-different-rules-for-inner-closures-vs-inner-functions/64334/2 + self.test() + } + + innerFunc() + } + } + } + """ + + let output = """ + class Test { + func doWork(_ escaping: @escaping () -> Void) { + escaping() + } + + func test() { + doWork { [self] in + doWork { + // Not allowed. Warning in Swift 5 and error in Swift 6. + self.test() + } + + func innerFunc() { + // Allowed: https://forums.swift.org/t/why-does-se-0269-have-different-rules-for-inner-closures-vs-inner-functions/64334/2 + test() + } + + innerFunc() + } + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf, options: FormatOptions(swiftVersion: "5.8")) + } + + func testPreservesSelfInNestedFunctionInWeakSelfClosure() { + let input = """ + class Test { + func doWork(_ escaping: @escaping () -> Void) { + escaping() + } + + func test() { + doWork { [weak self] in + func innerFunc() { + self?.test() + } + + guard let self else { + return + } + + self.test() + + func innerFunc() { + self.test() + } + + self.test() + } + } + } + """ + + let output = """ + class Test { + func doWork(_ escaping: @escaping () -> Void) { + escaping() + } + + func test() { + doWork { [weak self] in + func innerFunc() { + self?.test() + } + + guard let self else { + return + } + + test() + + func innerFunc() { + self.test() + } + + test() + } + } + } + """ + + testFormatting(for: input, output, rule: .redundantSelf, + options: FormatOptions(swiftVersion: "5.8")) + } + + func testRedundantSelfAfterScopedImport() { + let input = """ + import struct Foundation.Date + + struct Foo { + let foo: String + init(bar: String) { + self.foo = bar + } + } + """ + let output = """ + import struct Foundation.Date + + struct Foo { + let foo: String + init(bar: String) { + foo = bar + } + } + """ + testFormatting(for: input, output, rule: .redundantSelf) + } + + func testRedundantSelfNotConfusedByParameterPack() { + let input = """ + func pairUp(firstPeople: repeat each T, secondPeople: repeat each U) -> (repeat (first: each T, second: each U)) { + (repeat (each firstPeople, each secondPeople)) + } + """ + let options = FormatOptions(swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testRedundantSelfNotConfusedByStaticAfterSwitch() { + let input = """ + public final class MyClass { + private static func privateStaticFunction1() -> Bool { + switch Result(catching: { try someThrowingFunction() }) { + case .success: + return true + case .failure: + return false + } + } + + private static func privateStaticFunction2() -> Bool { + return false + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.enumNamespaces]) + } + + func testRedundantSelfNotConfusedByMainActor() { + let input = """ + class Test { + private var p: Int + + func f() { + self.f2( + closure: { @MainActor [weak self] p in + print(p) + } + ) + } + } + """ + let options = FormatOptions(explicitSelf: .insert) + testFormatting(for: input, rule: .redundantSelf, options: options) + } + + func testNoMistakeProtocolClassModifierForClassFunction() { + let input = "protocol Foo: class {}\nfunc bar() {}" + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + XCTAssertNoThrow(try format(input, rules: FormatRules.all)) + } + + func testRedundantSelfParsingBug3() { + let input = """ + final class ViewController { + private func bottomBarModels() -> [BarModeling] { + if let url = URL(string: "..."){ + // ... + } + + models.append( + Footer.barModel( + content: FooterContent( + primaryTitleText: "..."), + style: style) + .setBehaviors { context in + context.view.primaryButtonState = self.isLoading ? .waiting : .normal + context.view.primaryActionHandler = { [weak self] _ in + self?.acceptButtonWasTapped() + } + }) + } + + } + """ + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + } + + func testRedundantSelfParsingBug4() { + let input = """ + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let row: Row = promotionSections[indexPath.section][indexPath.row] else { return UITableViewCell() } + let cell = tableView.dequeueReusable(RowTableViewCell.self, forIndexPath: indexPath) + cell.update(row: row) + return cell + } + """ + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + } + + func testRedundantSelfParsingBug5() { + let input = """ + Button.primary( + title: "Title", + tapHandler: { [weak self] in + self?.dismissBlock? { + // something + } + } + ) + """ + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + } + + func testRedundantSelfParsingBug6() { + let input = """ + if let foo = bar, foo.tracking[jsonDict: "something"] != nil {} + """ + XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) + } +} diff --git a/Tests/Rules/RedundantStaticSelfTests.swift b/Tests/Rules/RedundantStaticSelfTests.swift new file mode 100644 index 000000000..67a960959 --- /dev/null +++ b/Tests/Rules/RedundantStaticSelfTests.swift @@ -0,0 +1,226 @@ +// +// RedundantStaticSelfTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantStaticSelfTests: XCTestCase { + func testRedundantStaticSelfInStaticVar() { + let input = "enum E { static var x: Int { Self.y } }" + let output = "enum E { static var x: Int { y } }" + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfInStaticMethod() { + let input = "enum E { static func foo() { Self.bar() } }" + let output = "enum E { static func foo() { bar() } }" + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfOnNextLine() { + let input = """ + enum E { + static func foo() { + Self + .bar() + } + } + """ + let output = """ + enum E { + static func foo() { + bar() + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfWithReturn() { + let input = "enum E { static func foo() { return Self.bar() } }" + let output = "enum E { static func foo() { return bar() } }" + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfInConditional() { + let input = """ + enum E { + static func foo() { + if Bool.random() { + Self.bar() + } + } + } + """ + let output = """ + enum E { + static func foo() { + if Bool.random() { + bar() + } + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfInNestedFunction() { + let input = """ + enum E { + static func foo() { + func bar() { + Self.foo() + } + } + } + """ + let output = """ + enum E { + static func foo() { + func bar() { + foo() + } + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testRedundantStaticSelfInNestedType() { + let input = """ + enum Outer { + enum Inner { + static func foo() {} + static func bar() { Self.foo() } + } + } + """ + let output = """ + enum Outer { + enum Inner { + static func foo() {} + static func bar() { foo() } + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testStaticSelfNotRemovedWhenUsedAsImplicitInitializer() { + let input = "enum E { static func foo() { Self().bar() } }" + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testStaticSelfNotRemovedWhenUsedAsExplicitInitializer() { + let input = "enum E { static func foo() { Self.init().bar() } }" + testFormatting(for: input, rule: .redundantStaticSelf, exclude: [.redundantInit]) + } + + func testPreservesStaticSelfInFunctionAfterStaticVar() { + let input = """ + enum MyFeatureCacheStrategy { + case networkOnly + case cacheFirst + + static let defaultCacheAge = TimeInterval.minutes(5) + + func requestStrategy() -> SingleRequestStrategy { + switch self { + case .networkOnly: + return .networkOnly(writeResultToCache: true) + case .cacheFirst: + return .cacheFirst(maxCacheAge: Self.defaultCacheAge) + } + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf, exclude: [.propertyType]) + } + + func testPreserveStaticSelfInInstanceFunction() { + let input = """ + enum Foo { + static var value = 0 + + func f() { + Self.value = value + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testPreserveStaticSelfForShadowedProperty() { + let input = """ + enum Foo { + static var value = 0 + + static func f(value: Int) { + Self.value = value + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testPreserveStaticSelfInGetter() { + let input = """ + enum Foo { + static let foo: String = "foo" + + var sharedFoo: String { + Self.foo + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testRemoveStaticSelfInStaticGetter() { + let input = """ + public enum Foo { + static let foo: String = "foo" + + static var getFoo: String { + Self.foo + } + } + """ + let output = """ + public enum Foo { + static let foo: String = "foo" + + static var getFoo: String { + foo + } + } + """ + testFormatting(for: input, output, rule: .redundantStaticSelf) + } + + func testPreserveStaticSelfInGuardLet() { + let input = """ + class LocationDeeplink: Deeplink { + convenience init?(warnRegion: String) { + guard let value = Self.location(for: warnRegion) else { + return nil + } + self.init(location: value) + } + } + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } + + func testPreserveStaticSelfInSingleLineClassInit() { + let input = """ + class A { static let defaultName = "A"; let name: String; init() { name = Self.defaultName }} + """ + testFormatting(for: input, rule: .redundantStaticSelf) + } +} diff --git a/Tests/Rules/RedundantTypeTests.swift b/Tests/Rules/RedundantTypeTests.swift new file mode 100644 index 000000000..43f990c76 --- /dev/null +++ b/Tests/Rules/RedundantTypeTests.swift @@ -0,0 +1,701 @@ +// +// RedundantTypeTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantTypeTests: XCTestCase { + func testVarRedundantTypeRemoval() { + let input = "var view: UIView = UIView()" + let output = "var view = UIView()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testVarRedundantArrayTypeRemoval() { + let input = "var foo: [String] = [String]()" + let output = "var foo = [String]()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testVarRedundantDictionaryTypeRemoval() { + let input = "var foo: [String: Int] = [String: Int]()" + let output = "var foo = [String: Int]()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testLetRedundantGenericTypeRemoval() { + let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" + let output = "let relay = BehaviourRelay(value: nil)" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testVarNonRedundantTypeDoesNothing() { + let input = "var view: UIView = UINavigationBar()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testLetRedundantTypeRemoval() { + let input = "let view: UIView = UIView()" + let output = "let view = UIView()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testLetNonRedundantTypeDoesNothing() { + let input = "let view: UIView = UINavigationBar()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testTypeNoRedundancyDoesNothing() { + let input = "let foo: Bar = 5" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testClassTwoVariablesNoRedundantTypeDoesNothing() { + let input = """ + final class LGWebSocketClient: WebSocketClient, WebSocketLibraryDelegate { + var webSocket: WebSocketLibraryProtocol + var timeoutIntervalForRequest: TimeInterval = LGCoreKitConstants.websocketTimeOutTimeInterval + } + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testRedundantTypeRemovedIfValueOnNextLine() { + let input = """ + let view: UIView + = UIView() + """ + let output = """ + let view + = UIView() + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testRedundantTypeRemovedIfValueOnNextLine2() { + let input = """ + let view: UIView = + UIView() + """ + let output = """ + let view = + UIView() + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testAllRedundantTypesRemovedInCommaDelimitedDeclaration() { + let input = "var foo: Int = 0, bar: Int = 0" + let output = "var foo = 0, bar = 0" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testRedundantTypeRemovalWithComment() { + let input = "var view: UIView /* view */ = UIView()" + let output = "var view /* view */ = UIView()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testRedundantTypeRemovalWithComment2() { + let input = "var view: UIView = /* view */ UIView()" + let output = "var view = /* view */ UIView()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testNonRedundantTernaryConditionTypeNotRemoved() { + let input = "let foo: Bar = Bar.baz() ? .bar1 : .bar2" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testTernaryConditionAfterLetNotTreatedAsPartOfExpression() { + let input = """ + let foo: Bar = Bar.baz() + baz ? bar2() : bar2() + """ + let output = """ + let foo = Bar.baz() + baz ? bar2() : bar2() + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testNoRemoveRedundantTypeIfVoid() { + let input = "let foo: Void = Void()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, + options: options, exclude: [.void]) + } + + func testNoRemoveRedundantTypeIfVoid2() { + let input = "let foo: () = ()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, + options: options, exclude: [.void]) + } + + func testNoRemoveRedundantTypeIfVoid3() { + let input = "let foo: [Void] = [Void]()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testNoRemoveRedundantTypeIfVoid4() { + let input = "let foo: Array = Array()" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, + options: options, exclude: [.typeSugar]) + } + + func testNoRemoveRedundantTypeIfVoid5() { + let input = "let foo: Void? = Void?.none" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testNoRemoveRedundantTypeIfVoid6() { + let input = "let foo: Optional = Optional.none" + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, + options: options, exclude: [.typeSugar]) + } + + func testRedundantTypeWithLiterals() { + let input = """ + let a1: Bool = true + let a2: Bool = false + + let b1: String = "foo" + let b2: String = "\\(b1)" + + let c1: Int = 1 + let c2: Int = 1.0 + + let d1: Double = 3.14 + let d2: Double = 3 + + let e1: [Double] = [3.14] + let e2: [Double] = [3] + + let f1: [String: Int] = ["foo": 5] + let f2: [String: Int?] = ["foo": nil] + """ + let output = """ + let a1 = true + let a2 = false + + let b1 = "foo" + let b2 = "\\(b1)" + + let c1 = 1 + let c2: Int = 1.0 + + let d1 = 3.14 + let d2: Double = 3 + + let e1 = [3.14] + let e2: [Double] = [3] + + let f1 = ["foo": 5] + let f2: [String: Int?] = ["foo": nil] + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, output, rule: .redundantType, + options: options) + } + + func testRedundantTypePreservesLiteralRepresentableTypes() { + let input = """ + let a: MyBoolRepresentable = true + let b: MyStringRepresentable = "foo" + let c: MyIntRepresentable = 1 + let d: MyDoubleRepresentable = 3.14 + let e: MyArrayRepresentable = ["bar"] + let f: MyDictionaryRepresentable = ["baz": 1] + """ + let options = FormatOptions(redundantType: .inferred) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testPreservesTypeWithIfExpressionInSwift5_8() { + let input = """ + let foo: Foo + if condition { + foo = Foo("foo") + } else { + foo = Foo("bar") + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.8") + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testPreservesNonRedundantTypeWithIfExpression() { + let input = """ + let foo: Foo = if condition { + Foo("foo") + } else { + FooSubclass("bar") + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testRedundantTypeWithIfExpression_inferred() { + let input = """ + let foo: Foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let output = """ + let foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testRedundantTypeWithIfExpression_explicit() { + let input = """ + let foo: Foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + let output = """ + let foo: Foo = if condition { + .init("foo") + } else { + .init("bar") + } + """ + let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment, .propertyType]) + } + + func testRedundantTypeWithNestedIfExpression_inferred() { + let input = """ + let foo: Foo = if condition { + switch condition { + case true: + if condition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + Foo("baaz") + } + } else { + Foo("quux") + } + """ + let output = """ + let foo = if condition { + switch condition { + case true: + if condition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + Foo("baaz") + } + } else { + Foo("quux") + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + func testRedundantTypeWithNestedIfExpression_explicit() { + let input = """ + let foo: Foo = if condition { + switch condition { + case true: + if condition { + Foo("foo") + } else { + Foo("bar") + } + + case false: + Foo("baaz") + } + } else { + Foo("quux") + } + """ + let output = """ + let foo: Foo = if condition { + switch condition { + case true: + if condition { + .init("foo") + } else { + .init("bar") + } + + case false: + .init("baaz") + } + } else { + .init("quux") + } + """ + let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment, .propertyType]) + } + + func testRedundantTypeWithLiteralsInIfExpression() { + let input = """ + let foo: String = if condition { + "foo" + } else { + "bar" + } + """ + let output = """ + let foo = if condition { + "foo" + } else { + "bar" + } + """ + let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) + } + + // --redundanttype explicit + + func testVarRedundantTypeRemovalExplicitType() { + let input = "var view: UIView = UIView()" + let output = "var view: UIView = .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testVarRedundantTypeRemovalExplicitType2() { + let input = "var view: UIView = UIView /* foo */()" + let output = "var view: UIView = .init /* foo */()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.spaceAroundComments, .propertyType]) + } + + func testLetRedundantGenericTypeRemovalExplicitType() { + let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" + let output = "let relay: BehaviourRelay = .init(value: nil)" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testLetRedundantGenericTypeRemovalExplicitTypeIfValueOnNextLine() { + let input = "let relay: Foo = Foo\n .default" + let output = "let relay: Foo = \n .default" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.trailingSpace, .propertyType]) + } + + func testVarNonRedundantTypeDoesNothingExplicitType() { + let input = "var view: UIView = UINavigationBar()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options) + } + + func testLetRedundantTypeRemovalExplicitType() { + let input = "let view: UIView = UIView()" + let output = "let view: UIView = .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovedIfValueOnNextLineExplicitType() { + let input = """ + let view: UIView + = UIView() + """ + let output = """ + let view: UIView + = .init() + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovedIfValueOnNextLine2ExplicitType() { + let input = """ + let view: UIView = + UIView() + """ + let output = """ + let view: UIView = + .init() + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovalWithCommentExplicitType() { + let input = "var view: UIView /* view */ = UIView()" + let output = "var view: UIView /* view */ = .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovalWithComment2ExplicitType() { + let input = "var view: UIView = /* view */ UIView()" + let output = "var view: UIView = /* view */ .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovalWithStaticMember() { + let input = """ + let session: URLSession = URLSession.default + + init(foo: Foo, bar: Bar) { + self.foo = foo + self.bar = bar + } + """ + let output = """ + let session: URLSession = .default + + init(foo: Foo, bar: Bar) { + self.foo = foo + self.bar = bar + } + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovalWithStaticFunc() { + let input = """ + let session: URLSession = URLSession.default() + + init(foo: Foo, bar: Bar) { + self.foo = foo + self.bar = bar + } + """ + let output = """ + let session: URLSession = .default() + + init(foo: Foo, bar: Bar) { + self.foo = foo + self.bar = bar + } + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingWithChainedMember() { + let input = "let session: URLSession = URLSession.default.makeCopy()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantRedundantChainedMemberTypeRemovedOnSwift5_4() { + let input = "let session: URLSession = URLSession.default.makeCopy()" + let output = "let session: URLSession = .default.makeCopy()" + let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingWithChainedMember2() { + let input = "let color: UIColor = UIColor.red.withAlphaComponent(0.5)" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingWithChainedMember3() { + let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeRemovedWithChainedMemberOnSwift5_4() { + let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" + let output = "let url: URL = .init(fileURLWithPath: #file).deletingLastPathComponent()" + let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") + testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingIfLet() { + let input = "if let foo: Foo = Foo() {}" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingGuardLet() { + let input = "guard let foo: Foo = Foo() else {}" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeDoesNothingIfLetAfterComma() { + let input = "if check == true, let foo: Foo = Foo() {}" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) + } + + func testRedundantTypeWorksAfterIf() { + let input = """ + if foo {} + let foo: Foo = Foo() + """ + let output = """ + if foo {} + let foo: Foo = .init() + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeIfVoid() { + let input = "let foo: [Void] = [Void]()" + let output = "let foo: [Void] = .init()" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } + + func testRedundantTypeWithIntegerLiteralNotMangled() { + let input = "let foo: Int = 1.toFoo" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, + options: options) + } + + func testRedundantTypeWithFloatLiteralNotMangled() { + let input = "let foo: Double = 1.0.toFoo" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, + options: options) + } + + func testRedundantTypeWithArrayLiteralNotMangled() { + let input = "let foo: [Int] = [1].toFoo" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, + options: options) + } + + func testRedundantTypeWithBoolLiteralNotMangled() { + let input = "let foo: Bool = false.toFoo" + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, + options: options) + } + + func testRedundantTypeInModelClassNotStripped() { + // See: https://github.com/nicklockwood/SwiftFormat/issues/1649 + let input = """ + @Model + class FooBar { + var created: Date = Date.now + } + """ + let options = FormatOptions(redundantType: .explicit) + testFormatting(for: input, rule: .redundantType, options: options) + } + + // --redundanttype infer-locals-only + + func testRedundantTypeinferLocalsOnly() { + let input = """ + let globalFoo: Foo = Foo() + + struct SomeType { + let instanceFoo: Foo = Foo() + + func method() { + let localFoo: Foo = Foo() + let localString: String = "foo" + } + + let instanceString: String = "foo" + } + + let globalString: String = "foo" + """ + + let output = """ + let globalFoo: Foo = .init() + + struct SomeType { + let instanceFoo: Foo = .init() + + func method() { + let localFoo = Foo() + let localString = "foo" + } + + let instanceString: String = "foo" + } + + let globalString: String = "foo" + """ + + let options = FormatOptions(redundantType: .inferLocalsOnly) + testFormatting(for: input, output, rule: .redundantType, + options: options, exclude: [.propertyType]) + } +} diff --git a/Tests/Rules/RedundantTypedThrowsTests.swift b/Tests/Rules/RedundantTypedThrowsTests.swift new file mode 100644 index 000000000..a63dea186 --- /dev/null +++ b/Tests/Rules/RedundantTypedThrowsTests.swift @@ -0,0 +1,61 @@ +// +// RedundantTypedThrowsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantTypedThrowsTests: XCTestCase { + func testRemovesRedundantNeverTypeThrows() { + let input = """ + func foo() throws(Never) -> Int { + 0 + } + """ + + let output = """ + func foo() -> Int { + 0 + } + """ + + let options = FormatOptions(swiftVersion: "6.0") + testFormatting(for: input, output, rule: .redundantTypedThrows, options: options) + } + + func testRemovesRedundantAnyErrorTypeThrows() { + let input = """ + func foo() throws(any Error) -> Int { + throw MyError.foo + } + """ + + let output = """ + func foo() throws -> Int { + throw MyError.foo + } + """ + + let options = FormatOptions(swiftVersion: "6.0") + testFormatting(for: input, output, rule: .redundantTypedThrows, options: options) + } + + func testDontRemovesNonRedundantErrorTypeThrows() { + let input = """ + func bar() throws(BarError) -> Foo { + throw .foo + } + + func foo() throws(Error) -> Int { + throw MyError.foo + } + """ + + let options = FormatOptions(swiftVersion: "6.0") + testFormatting(for: input, rule: .redundantTypedThrows, options: options) + } +} diff --git a/Tests/Rules/RedundantVoidReturnTypeTests.swift b/Tests/Rules/RedundantVoidReturnTypeTests.swift new file mode 100644 index 000000000..34f10c168 --- /dev/null +++ b/Tests/Rules/RedundantVoidReturnTypeTests.swift @@ -0,0 +1,94 @@ +// +// RedundantVoidReturnTypeTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class RedundantVoidReturnTypeTests: XCTestCase { + func testRemoveRedundantVoidReturnType() { + let input = "func foo() -> Void {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantVoidReturnType2() { + let input = "func foo() ->\n Void {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantSwiftDotVoidReturnType() { + let input = "func foo() -> Swift.Void {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantSwiftDotVoidReturnType2() { + let input = "func foo() -> Swift\n .Void {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantEmptyReturnType() { + let input = "func foo() -> () {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantVoidTupleReturnType() { + let input = "func foo() -> (Void) {}" + let output = "func foo() {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testNoRemoveCommentFollowingRedundantVoidReturnType() { + let input = "func foo() -> Void /* void */ {}" + let output = "func foo() /* void */ {}" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testNoRemoveRequiredVoidReturnType() { + let input = "typealias Foo = () -> Void" + testFormatting(for: input, rule: .redundantVoidReturnType) + } + + func testNoRemoveChainedVoidReturnType() { + let input = "func foo() -> () -> Void {}" + testFormatting(for: input, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantVoidInClosureArguments() { + let input = "{ (foo: Bar) -> Void in foo() }" + let output = "{ (foo: Bar) in foo() }" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantEmptyReturnTypeInClosureArguments() { + let input = "{ (foo: Bar) -> () in foo() }" + let output = "{ (foo: Bar) in foo() }" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantVoidInClosureArguments2() { + let input = "methodWithTrailingClosure { foo -> Void in foo() }" + let output = "methodWithTrailingClosure { foo in foo() }" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testRemoveRedundantSwiftDotVoidInClosureArguments2() { + let input = "methodWithTrailingClosure { foo -> Swift.Void in foo() }" + let output = "methodWithTrailingClosure { foo in foo() }" + testFormatting(for: input, output, rule: .redundantVoidReturnType) + } + + func testNoRemoveRedundantVoidInClosureArgument() { + let input = "{ (foo: Bar) -> Void in foo() }" + let options = FormatOptions(closureVoidReturn: .preserve) + testFormatting(for: input, rule: .redundantVoidReturnType, options: options) + } +} diff --git a/Tests/Rules/SemicolonsTests.swift b/Tests/Rules/SemicolonsTests.swift new file mode 100644 index 000000000..13044aaab --- /dev/null +++ b/Tests/Rules/SemicolonsTests.swift @@ -0,0 +1,77 @@ +// +// SemicolonsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SemicolonsTests: XCTestCase { + func testSemicolonRemovedAtEndOfLine() { + let input = "print(\"hello\");\n" + let output = "print(\"hello\")\n" + testFormatting(for: input, output, rule: .semicolons) + } + + func testSemicolonRemovedAtStartOfLine() { + let input = "\n;print(\"hello\")" + let output = "\nprint(\"hello\")" + testFormatting(for: input, output, rule: .semicolons) + } + + func testSemicolonRemovedAtEndOfProgram() { + let input = "print(\"hello\");" + let output = "print(\"hello\")" + testFormatting(for: input, output, rule: .semicolons) + } + + func testSemicolonRemovedAtStartOfProgram() { + let input = ";print(\"hello\")" + let output = "print(\"hello\")" + testFormatting(for: input, output, rule: .semicolons) + } + + func testIgnoreInlineSemicolon() { + let input = "print(\"hello\"); print(\"goodbye\")" + let options = FormatOptions(allowInlineSemicolons: true) + testFormatting(for: input, rule: .semicolons, options: options) + } + + func testReplaceInlineSemicolon() { + let input = "print(\"hello\"); print(\"goodbye\")" + let output = "print(\"hello\")\nprint(\"goodbye\")" + let options = FormatOptions(allowInlineSemicolons: false) + testFormatting(for: input, output, rule: .semicolons, options: options) + } + + func testReplaceSemicolonFollowedByComment() { + let input = "print(\"hello\"); // comment\nprint(\"goodbye\")" + let output = "print(\"hello\") // comment\nprint(\"goodbye\")" + let options = FormatOptions(allowInlineSemicolons: true) + testFormatting(for: input, output, rule: .semicolons, options: options) + } + + func testSemicolonNotReplacedAfterReturn() { + let input = "return;\nfoo()" + testFormatting(for: input, rule: .semicolons) + } + + func testSemicolonReplacedAfterReturnIfEndOfScope() { + let input = "do { return; }" + let output = "do { return }" + testFormatting(for: input, output, rule: .semicolons) + } + + func testRequiredSemicolonNotRemovedAfterInferredVar() { + let input = """ + func foo() { + @Environment(\\.colorScheme) var colorScheme; + print(colorScheme) + } + """ + testFormatting(for: input, rule: .semicolons) + } +} diff --git a/Tests/Rules/SortDeclarationsTests.swift b/Tests/Rules/SortDeclarationsTests.swift new file mode 100644 index 000000000..3f2d046a0 --- /dev/null +++ b/Tests/Rules/SortDeclarationsTests.swift @@ -0,0 +1,289 @@ +// +// SortDeclarationsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SortDeclarationsTests: XCTestCase { + func testSortEnumBody() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsellB + case fooFeature( + fooConfiguration: Foo, + barConfiguration: Bar + ) + case barFeature // Trailing comment -- bar feature + /// Leading comment -- upsell A + case upsellA( + fooConfiguration: Foo, + barConfiguration: Bar + ) + } + + enum NextType { + case foo + case bar + } + """ + + let output = """ + // swiftformat:sort + enum FeatureFlags { + case barFeature // Trailing comment -- bar feature + case fooFeature( + fooConfiguration: Foo, + barConfiguration: Bar + ) + /// Leading comment -- upsell A + case upsellA( + fooConfiguration: Foo, + barConfiguration: Bar + ) + case upsellB + } + + enum NextType { + case foo + case bar + } + """ + + testFormatting(for: input, output, rule: .sortDeclarations) + } + + func testSortEnumBodyWithOnlyOneCase() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsellB + } + """ + + testFormatting(for: input, rule: .sortDeclarations) + } + + func testSortEnumBodyWithoutCase() { + let input = """ + // swiftformat:sort + enum FeatureFlags {} + """ + + testFormatting(for: input, rule: .sortDeclarations) + } + + func testNoSortUnannotatedType() { + let input = """ + enum FeatureFlags { + case upsellB + case fooFeature + case barFeature + case upsellA + } + """ + + testFormatting(for: input, rule: .sortDeclarations) + } + + func testPreservesSortedBody() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + case upsellB + } + """ + + testFormatting(for: input, rule: .sortDeclarations) + } + + func testSortsTypeBody() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsellB + case fooFeature + case barFeature + case upsellA + } + """ + + let output = """ + // swiftformat:sort + enum FeatureFlags { + case barFeature + case fooFeature + case upsellA + case upsellB + } + """ + + testFormatting(for: input, output, rule: .sortDeclarations, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + func testSortClassWithMixedDeclarationTypes() { + let input = """ + // swiftformat:sort + class Foo { + let quuxProperty = Quux() + let barProperty = Bar() + + var fooComputedProperty: Foo { + Foo() + } + + func baazFunction() -> Baaz { + Baaz() + } + } + """ + + let output = """ + // swiftformat:sort + class Foo { + func baazFunction() -> Baaz { + Baaz() + } + let barProperty = Bar() + + var fooComputedProperty: Foo { + Foo() + } + + let quuxProperty = Quux() + } + """ + + testFormatting(for: input, [output], + rules: [.sortDeclarations, .consecutiveBlankLines], + exclude: [.blankLinesBetweenScopes, .propertyType]) + } + + func testSortBetweenDirectiveCommentsInType() { + let input = """ + enum FeatureFlags { + // swiftformat:sort:begin + case upsellB + case fooFeature + case barFeature + case upsellA + // swiftformat:sort:end + + var anUnsortedProperty: Foo { + Foo() + } + } + """ + + let output = """ + enum FeatureFlags { + // swiftformat:sort:begin + case barFeature + case fooFeature + case upsellA + case upsellB + // swiftformat:sort:end + + var anUnsortedProperty: Foo { + Foo() + } + } + """ + + testFormatting(for: input, output, rule: .sortDeclarations) + } + + func testSortTopLevelDeclarations() { + let input = """ + let anUnsortedGlobal = 0 + + // swiftformat:sort:begin + let sortThisGlobal = 1 + public let thisGlobalIsSorted = 2 + private let anotherSortedGlobal = 5 + let sortAllOfThem = 8 + // swiftformat:sort:end + + let anotherUnsortedGlobal = 9 + """ + + let output = """ + let anUnsortedGlobal = 0 + + // swiftformat:sort:begin + private let anotherSortedGlobal = 5 + let sortAllOfThem = 8 + let sortThisGlobal = 1 + public let thisGlobalIsSorted = 2 + // swiftformat:sort:end + + let anotherUnsortedGlobal = 9 + """ + + testFormatting(for: input, output, rule: .sortDeclarations) + } + + func testSortDeclarationsSortsByNamePattern() { + let input = """ + enum Namespace {} + + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" + } + """ + + let output = """ + enum Namespace {} + + extension Namespace { + static let baaz = "baaz" + public static let bar = "bar" + static let foo = "foo" + } + """ + + let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Namespace"]) + testFormatting(for: input, [output], rules: [.sortDeclarations, .blankLinesBetweenScopes], options: options) + } + + func testSortDeclarationsWontSortByNamePatternInComment() { + let input = """ + enum Namespace {} + + /// Constants + /// enum Constants + extension Namespace { + static let foo = "foo" + public static let bar = "bar" + static let baaz = "baaz" + } + """ + + let options = FormatOptions(alphabeticallySortedDeclarationPatterns: ["Constants"]) + testFormatting(for: input, rules: [.sortDeclarations, .blankLinesBetweenScopes], options: options) + } + + func testSortDeclarationsUsesLocalizedCompare() { + let input = """ + // swiftformat:sort + enum FeatureFlags { + case upsella + case upsellA + case upsellb + case upsellB + } + """ + + testFormatting(for: input, rule: .sortDeclarations) + } +} diff --git a/Tests/Rules/SortImportsTests.swift b/Tests/Rules/SortImportsTests.swift new file mode 100644 index 000000000..60fc97b70 --- /dev/null +++ b/Tests/Rules/SortImportsTests.swift @@ -0,0 +1,214 @@ +// +// SortImportsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SortImportsTests: XCTestCase { + func testSortImportsSimpleCase() { + let input = "import Foo\nimport Bar" + let output = "import Bar\nimport Foo" + testFormatting(for: input, output, rule: .sortImports) + } + + func testSortImportsKeepsPreviousCommentWithImport() { + let input = "import Foo\n// important comment\n// (very important)\nimport Bar" + let output = "// important comment\n// (very important)\nimport Bar\nimport Foo" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testSortImportsKeepsPreviousCommentWithImport2() { + let input = "// important comment\n// (very important)\nimport Foo\nimport Bar" + let output = "import Bar\n// important comment\n// (very important)\nimport Foo" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testSortImportsDoesntMoveHeaderComment() { + let input = "// header comment\n\nimport Foo\nimport Bar" + let output = "// header comment\n\nimport Bar\nimport Foo" + testFormatting(for: input, output, rule: .sortImports) + } + + func testSortImportsDoesntMoveHeaderCommentFollowedByImportComment() { + let input = "// header comment\n\n// important comment\nimport Foo\nimport Bar" + let output = "// header comment\n\nimport Bar\n// important comment\nimport Foo" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testSortImportsOnSameLine() { + let input = "import Foo; import Bar\nimport Baz" + let output = "import Baz\nimport Foo; import Bar" + testFormatting(for: input, output, rule: .sortImports) + } + + func testSortImportsWithSemicolonAndCommentOnSameLine() { + let input = "import Foo; // foobar\nimport Bar\nimport Baz" + let output = "import Bar\nimport Baz\nimport Foo; // foobar" + testFormatting(for: input, output, rule: .sortImports, exclude: [.semicolons]) + } + + func testSortImportEnum() { + let input = "import enum Foo.baz\nimport Foo.bar" + let output = "import Foo.bar\nimport enum Foo.baz" + testFormatting(for: input, output, rule: .sortImports) + } + + func testSortImportFunc() { + let input = "import func Foo.baz\nimport Foo.bar" + let output = "import Foo.bar\nimport func Foo.baz" + testFormatting(for: input, output, rule: .sortImports) + } + + func testAlreadySortImportsDoesNothing() { + let input = "import Bar\nimport Foo" + testFormatting(for: input, rule: .sortImports) + } + + func testPreprocessorSortImports() { + let input = "#if os(iOS)\n import Foo2\n import Bar2\n#else\n import Foo1\n import Bar1\n#endif\nimport Foo3\nimport Bar3" + let output = "#if os(iOS)\n import Bar2\n import Foo2\n#else\n import Bar1\n import Foo1\n#endif\nimport Bar3\nimport Foo3" + testFormatting(for: input, output, rule: .sortImports) + } + + func testTestableSortImports() { + let input = "@testable import Foo3\nimport Bar3" + let output = "import Bar3\n@testable import Foo3" + testFormatting(for: input, output, rule: .sortImports) + } + + func testLengthSortImports() { + let input = "import Foo\nimport Module\nimport Bar3" + let output = "import Foo\nimport Bar3\nimport Module" + let options = FormatOptions(importGrouping: .length) + testFormatting(for: input, output, rule: .sortImports, options: options) + } + + func testTestableImportsWithTestableOnPreviousLine() { + let input = "@testable\nimport Foo3\nimport Bar3" + let output = "import Bar3\n@testable\nimport Foo3" + testFormatting(for: input, output, rule: .sortImports) + } + + func testTestableImportsWithGroupingTestableBottom() { + let input = "@testable import Bar\nimport Foo\n@testable import UIKit" + let output = "import Foo\n@testable import Bar\n@testable import UIKit" + let options = FormatOptions(importGrouping: .testableLast) + testFormatting(for: input, output, rule: .sortImports, options: options) + } + + func testTestableImportsWithGroupingTestableTop() { + let input = "@testable import Bar\nimport Foo\n@testable import UIKit" + let output = "@testable import Bar\n@testable import UIKit\nimport Foo" + let options = FormatOptions(importGrouping: .testableFirst) + testFormatting(for: input, output, rule: .sortImports, options: options) + } + + func testCaseInsensitiveSortImports() { + let input = "import Zlib\nimport lib" + let output = "import lib\nimport Zlib" + testFormatting(for: input, output, rule: .sortImports) + } + + func testCaseInsensitiveCaseDifferingSortImports() { + let input = "import c\nimport B\nimport A.a\nimport A.A" + let output = "import A.A\nimport A.a\nimport B\nimport c" + testFormatting(for: input, output, rule: .sortImports) + } + + func testNoDeleteCodeBetweenImports() { + let input = "import Foo\nfunc bar() {}\nimport Bar" + testFormatting(for: input, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testNoDeleteCodeBetweenImports2() { + let input = "import Foo\nimport Bar\nfoo = bar\nimport Bar" + let output = "import Bar\nimport Foo\nfoo = bar\nimport Bar" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testNoDeleteCodeBetweenImports3() { + let input = """ + import Z + + // one + + #if FLAG + print("hi") + #endif + + import A + """ + testFormatting(for: input, rule: .sortImports) + } + + func testSortContiguousImports() { + let input = "import Foo\nimport Bar\nfunc bar() {}\nimport Quux\nimport Baz" + let output = "import Bar\nimport Foo\nfunc bar() {}\nimport Baz\nimport Quux" + testFormatting(for: input, output, rule: .sortImports, + exclude: [.blankLineAfterImports]) + } + + func testNoMangleImportsPrecededByComment() { + let input = """ + // evil comment + + #if canImport(Foundation) + import Foundation + #if canImport(UIKit) && canImport(AVFoundation) + import UIKit + import AVFoundation + #endif + #endif + """ + let output = """ + // evil comment + + #if canImport(Foundation) + import Foundation + #if canImport(UIKit) && canImport(AVFoundation) + import AVFoundation + import UIKit + #endif + #endif + """ + testFormatting(for: input, output, rule: .sortImports) + } + + func testNoMangleFileHeaderNotFollowedByLinebreak() { + let input = """ + // + // Code.swift + // Module + // + // Created by Someone on 4/30/20. + // + import AModuleUI + import AModule + import AModuleHelper + import SomeOtherModule + """ + let output = """ + // + // Code.swift + // Module + // + // Created by Someone on 4/30/20. + // + import AModule + import AModuleHelper + import AModuleUI + import SomeOtherModule + """ + testFormatting(for: input, output, rule: .sortImports) + } +} diff --git a/Tests/Rules/SortSwitchCasesTests.swift b/Tests/Rules/SortSwitchCasesTests.swift new file mode 100644 index 000000000..9459ba2c3 --- /dev/null +++ b/Tests/Rules/SortSwitchCasesTests.swift @@ -0,0 +1,340 @@ +// +// SortSwitchCasesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SortSwitchCasesTests: XCTestCase { + func testSortedSwitchCaseNestedSwitchOneCaseDoesNothing() { + let input = """ + switch result { + case let .success(value): + switch result { + case .success: + print("success") + case .value: + print("value") + } + + case .failure: + guard self.bar else { + print(self.bar) + return + } + print(self.bar) + } + """ + + testFormatting(for: input, rule: .sortSwitchCases, exclude: [.redundantSelf]) + } + + func testSortedSwitchCaseMultilineWithOneComment() { + let input = """ + switch self { + case let .type, // something + let .conditionalCompilation: + break + } + """ + let output = """ + switch self { + case let .conditionalCompilation, + let .type: // something + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortedSwitchCaseMultilineWithComments() { + let input = """ + switch self { + case let .type, // typeComment + let .conditionalCompilation: // conditionalCompilationComment + break + } + """ + let output = """ + switch self { + case let .conditionalCompilation, // conditionalCompilationComment + let .type: // typeComment + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, exclude: [.indent]) + } + + func testSortedSwitchCaseMultilineWithCommentsAndMoreThanOneCasePerLine() { + let input = """ + switch self { + case let .type, // typeComment + let .type1, .type2, + let .conditionalCompilation: // conditionalCompilationComment + break + } + """ + let output = """ + switch self { + case let .conditionalCompilation, // conditionalCompilationComment + let .type, // typeComment + let .type1, + .type2: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortedSwitchCaseMultiline() { + let input = """ + switch self { + case let .type, + let .conditionalCompilation: + break + } + """ + let output = """ + switch self { + case let .conditionalCompilation, + let .type: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortedSwitchCaseMultipleAssociatedValues() { + let input = """ + switch self { + case let .b(whatever, whatever2), .a(whatever): + break + } + """ + let output = """ + switch self { + case .a(whatever), let .b(whatever, whatever2): + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchCaseOneLineWithoutSpaces() { + let input = """ + switch self { + case .b,.a: + break + } + """ + let output = """ + switch self { + case .a,.b: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases, .spaceAroundOperators]) + } + + func testSortedSwitchCaseLet() { + let input = """ + switch self { + case let .b(whatever), .a(whatever): + break + } + """ + let output = """ + switch self { + case .a(whatever), let .b(whatever): + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchCaseOneCaseDoesNothing() { + let input = """ + switch self { + case "a": + break + } + """ + testFormatting(for: input, rule: .sortSwitchCases) + } + + func testSortedSwitchStrings() { + let input = """ + switch self { + case "GET", "POST", "PUT", "DELETE": + break + } + """ + let output = """ + switch self { + case "DELETE", "GET", "POST", "PUT": + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchWhereConditionNotLastCase() { + let input = """ + switch self { + case .b, .c, .a where isTrue: + break + } + """ + testFormatting(for: input, + rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchWhereConditionLastCase() { + let input = """ + switch self { + case .b, .c where isTrue, .a: + break + } + """ + let output = """ + switch self { + case .a, .b, .c where isTrue: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortNumericSwitchCases() { + let input = """ + switch foo { + case 12, 3, 5, 7, 8, 10, 1: + break + } + """ + let output = """ + switch foo { + case 1, 3, 5, 7, 8, 10, 12: + break + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } + + func testSortedSwitchTuples() { + let input = """ + switch foo { + case (.foo, _), + (.bar, _), + (.baz, _), + (_, .foo): + } + """ + let output = """ + switch foo { + case (_, .foo), + (.bar, _), + (.baz, _), + (.foo, _): + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortedSwitchTuples2() { + let input = """ + switch self { + case (.quux, .bar), + (_, .foo), + (_, .bar), + (_, .baz), + (.foo, .bar): + } + """ + let output = """ + switch self { + case (_, .bar), + (_, .baz), + (_, .foo), + (.foo, .bar), + (.quux, .bar): + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortSwitchCasesShortestFirst() { + let input = """ + switch foo { + case let .fooAndBar(baz, quux), + let .foo(baz): + } + """ + let output = """ + switch foo { + case let .foo(baz), + let .fooAndBar(baz, quux): + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases) + } + + func testSortHexLiteralCasesInAscendingOrder() { + let input = """ + switch value { + case 0x30 ... 0x39, // 0-9 + 0x0300 ... 0x036F, + 0x1DC0 ... 0x1DFF, + 0x20D0 ... 0x20FF, + 0xFE20 ... 0xFE2F: + return true + default: + return false + } + """ + testFormatting(for: input, rule: .sortSwitchCases) + } + + func testMixedOctalHexIntAndBinaryLiteralCasesInAscendingOrder() { + let input = """ + switch value { + case 0o3, + 0x20, + 110, + 0b1111110: + return true + default: + return false + } + """ + testFormatting(for: input, rule: .sortSwitchCases) + } + + func testSortSwitchCasesNoUnwrapReturn() { + let input = """ + switch self { + case .b, .a, .c, .e, .d: + return nil + } + """ + let output = """ + switch self { + case .a, .b, .c, .d, .e: + return nil + } + """ + testFormatting(for: input, output, rule: .sortSwitchCases, + exclude: [.wrapSwitchCases]) + } +} diff --git a/Tests/Rules/SortTypealiasesTests.swift b/Tests/Rules/SortTypealiasesTests.swift new file mode 100644 index 000000000..7df63378a --- /dev/null +++ b/Tests/Rules/SortTypealiasesTests.swift @@ -0,0 +1,178 @@ +// +// SortTypealiasesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SortTypealiasesTests: XCTestCase { + func testSortSingleLineTypealias() { + let input = """ + typealias Placeholders = Foo & Bar & Quux & Baaz + """ + + let output = """ + typealias Placeholders = Baaz & Bar & Foo & Quux + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortMultilineTypealias() { + let input = """ + typealias Placeholders = Foo & Bar + & Quux & Baaz + """ + + let output = """ + typealias Placeholders = Baaz & Bar + & Foo & Quux + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortMultilineTypealiasWithComments() { + let input = """ + typealias Placeholders = Foo & Bar // Comment about Bar + // Comment about Quux + & Quux & Baaz // Comment about Baaz + """ + + let output = """ + typealias Placeholders = Baaz // Comment about Baaz + & Bar // Comment about Bar + & Foo + // Comment about Quux + & Quux + """ + + testFormatting(for: input, [output], rules: [.sortTypealiases, .indent, .trailingSpace]) + } + + func testSortWrappedMultilineTypealias1() { + let input = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let output = """ + typealias Dependencies = BaazProviding + & BarProviding + & FooProviding + & QuuxProviding + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortWrappedMultilineTypealias2() { + let input = """ + typealias Dependencies + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let output = """ + typealias Dependencies + = BaazProviding + & BarProviding + & FooProviding + & QuuxProviding + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortWrappedMultilineTypealiasWithComments() { + let input = """ + typealias Dependencies + // Comment about FooProviding + = FooProviding + // Comment about BarProviding + & BarProviding + & QuuxProviding // Comment about QuuxProviding + // Comment about BaazProviding + & BaazProviding // Comment about BaazProviding + """ + + let output = """ + typealias Dependencies + // Comment about BaazProviding + = BaazProviding // Comment about BaazProviding + // Comment about BarProviding + & BarProviding + // Comment about FooProviding + & FooProviding + & QuuxProviding // Comment about QuuxProviding + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortTypealiasesWithAssociatedTypes() { + let input = """ + typealias Collections + = Collection + & Collection + & Collection + & Collection + """ + + let output = """ + typealias Collections + = Collection + & Collection + & Collection + & Collection + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } + + func testSortTypeAliasesAndRemoveDuplicates() { + let input = """ + typealias Placeholders = Foo & Bar & Quux & Baaz & Bar + + typealias Dependencies1 + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + & FooProviding + + typealias Dependencies2 + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + & BaazProviding + """ + + let output = """ + typealias Placeholders = Baaz & Bar & Foo & Quux + + typealias Dependencies1 + = BaazProviding + & BarProviding + & FooProviding + & QuuxProviding + + typealias Dependencies2 + = BaazProviding + & BarProviding + & FooProviding + & QuuxProviding + """ + + testFormatting(for: input, output, rule: .sortTypealiases) + } +} diff --git a/Tests/Rules/SpaceAroundBracesTests.swift b/Tests/Rules/SpaceAroundBracesTests.swift new file mode 100644 index 000000000..426145fe6 --- /dev/null +++ b/Tests/Rules/SpaceAroundBracesTests.swift @@ -0,0 +1,64 @@ +// +// SpaceAroundBracesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundBracesTests: XCTestCase { + func testSpaceAroundTrailingClosure() { + let input = "if x{ y }else{ z }" + let output = "if x { y } else { z }" + testFormatting(for: input, output, rule: .spaceAroundBraces, + exclude: [.wrapConditionalBodies]) + } + + func testNoSpaceAroundClosureInsiderParens() { + let input = "foo({ $0 == 5 })" + testFormatting(for: input, rule: .spaceAroundBraces, + exclude: [.trailingClosures]) + } + + func testNoExtraSpaceAroundBracesAtStartOrEndOfFile() { + let input = "{ foo }" + testFormatting(for: input, rule: .spaceAroundBraces) + } + + func testNoSpaceAfterPrefixOperator() { + let input = "let foo = ..{ bar }" + testFormatting(for: input, rule: .spaceAroundBraces) + } + + func testNoSpaceBeforePostfixOperator() { + let input = "let foo = { bar }.." + testFormatting(for: input, rule: .spaceAroundBraces) + } + + func testSpaceAroundBracesAfterOptionalProperty() { + let input = "var: Foo?{}" + let output = "var: Foo? {}" + testFormatting(for: input, output, rule: .spaceAroundBraces) + } + + func testSpaceAroundBracesAfterImplicitlyUnwrappedProperty() { + let input = "var: Foo!{}" + let output = "var: Foo! {}" + testFormatting(for: input, output, rule: .spaceAroundBraces) + } + + func testSpaceAroundBracesAfterNumber() { + let input = "if x = 5{}" + let output = "if x = 5 {}" + testFormatting(for: input, output, rule: .spaceAroundBraces) + } + + func testSpaceAroundBracesAfterString() { + let input = "if x = \"\"{}" + let output = "if x = \"\" {}" + testFormatting(for: input, output, rule: .spaceAroundBraces) + } +} diff --git a/Tests/Rules/SpaceAroundBracketsTests.swift b/Tests/Rules/SpaceAroundBracketsTests.swift new file mode 100644 index 000000000..74c5578fe --- /dev/null +++ b/Tests/Rules/SpaceAroundBracketsTests.swift @@ -0,0 +1,100 @@ +// +// SpaceAroundBracketsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundBracketsTests: XCTestCase { + func testSubscriptNoAddSpacing() { + let input = "foo[bar] = baz" + testFormatting(for: input, rule: .spaceAroundBrackets) + } + + func testSubscriptRemoveSpacing() { + let input = "foo [bar] = baz" + let output = "foo[bar] = baz" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testArrayLiteralSpacing() { + let input = "foo = [bar, baz]" + testFormatting(for: input, rule: .spaceAroundBrackets) + } + + func testAsArrayCastingSpacing() { + let input = "foo as[String]" + let output = "foo as [String]" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testAsOptionalArrayCastingSpacing() { + let input = "foo as? [String]" + testFormatting(for: input, rule: .spaceAroundBrackets) + } + + func testIsArrayTestingSpacing() { + let input = "if foo is[String] {}" + let output = "if foo is [String] {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testKeywordAsIdentifierBracketSpacing() { + let input = "if foo.is[String] {}" + testFormatting(for: input, rule: .spaceAroundBrackets) + } + + func testSpaceBeforeTupleIndexSubscript() { + let input = "foo.1 [2]" + let output = "foo.1[2]" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testRemoveSpaceBetweenBracketAndParen() { + let input = "let foo = bar[5] ()" + let output = "let foo = bar[5]()" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testRemoveSpaceBetweenBracketAndParenInsideClosure() { + let input = "let foo = bar { [Int] () }" + let output = "let foo = bar { [Int]() }" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testAddSpaceBetweenCaptureListAndParen() { + let input = "let foo = bar { [self](foo: Int) in foo }" + let output = "let foo = bar { [self] (foo: Int) in foo }" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testAddSpaceBetweenInoutAndStringArray() { + let input = "func foo(arg _: inout[String]) {}" + let output = "func foo(arg _: inout [String]) {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } + + func testAddSpaceBetweenConsumingAndStringArray() { + let input = "func foo(arg _: consuming[String]) {}" + let output = "func foo(arg _: consuming [String]) {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets, + exclude: [.noExplicitOwnership]) + } + + func testAddSpaceBetweenBorrowingAndStringArray() { + let input = "func foo(arg _: borrowing[String]) {}" + let output = "func foo(arg _: borrowing [String]) {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets, + exclude: [.noExplicitOwnership]) + } + + func testAddSpaceBetweenSendingAndStringArray() { + let input = "func foo(arg _: sending[String]) {}" + let output = "func foo(arg _: sending [String]) {}" + testFormatting(for: input, output, rule: .spaceAroundBrackets) + } +} diff --git a/Tests/Rules/SpaceAroundCommentsTests.swift b/Tests/Rules/SpaceAroundCommentsTests.swift new file mode 100644 index 000000000..30769365e --- /dev/null +++ b/Tests/Rules/SpaceAroundCommentsTests.swift @@ -0,0 +1,36 @@ +// +// SpaceAroundCommentsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundCommentsTests: XCTestCase { + func testSpaceAroundCommentInParens() { + let input = "(/* foo */)" + let output = "( /* foo */ )" + testFormatting(for: input, output, rule: .spaceAroundComments, + exclude: [.redundantParens]) + } + + func testNoSpaceAroundCommentAtStartAndEndOfFile() { + let input = "/* foo */" + testFormatting(for: input, rule: .spaceAroundComments) + } + + func testNoSpaceAroundCommentBeforeComma() { + let input = "(foo /* foo */ , bar)" + let output = "(foo /* foo */, bar)" + testFormatting(for: input, output, rule: .spaceAroundComments) + } + + func testSpaceAroundSingleLineComment() { + let input = "func foo() {// comment\n}" + let output = "func foo() { // comment\n}" + testFormatting(for: input, output, rule: .spaceAroundComments) + } +} diff --git a/Tests/Rules/SpaceAroundGenericsTests.swift b/Tests/Rules/SpaceAroundGenericsTests.swift new file mode 100644 index 000000000..077a6a573 --- /dev/null +++ b/Tests/Rules/SpaceAroundGenericsTests.swift @@ -0,0 +1,28 @@ +// +// SpaceAroundGenericsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundGenericsTests: XCTestCase { + func testSpaceAroundGenerics() { + let input = "Foo >" + let output = "Foo>" + testFormatting(for: input, output, rule: .spaceAroundGenerics) + } + + func testSpaceAroundGenericsFollowedByAndOperator() { + let input = "if foo is Foo && baz {}" + testFormatting(for: input, rule: .spaceAroundGenerics, exclude: [.andOperator]) + } + + func testSpaceAroundGenericResultBuilder() { + let input = "func foo(@SomeResultBuilder builder: () -> Void) {}" + testFormatting(for: input, rule: .spaceAroundGenerics) + } +} diff --git a/Tests/Rules/SpaceAroundOperatorsTests.swift b/Tests/Rules/SpaceAroundOperatorsTests.swift new file mode 100644 index 000000000..5e20fcde9 --- /dev/null +++ b/Tests/Rules/SpaceAroundOperatorsTests.swift @@ -0,0 +1,754 @@ +// +// SpaceAroundOperatorsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class SpaceAroundOperatorsTests: XCTestCase { + func testSpaceAfterColon() { + let input = "let foo:Bar = 5" + let output = "let foo: Bar = 5" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceBetweenOptionalAndDefaultValue() { + let input = "let foo: String?=nil" + let output = "let foo: String? = nil" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceBetweenImplictlyUnwrappedOptionalAndDefaultValue() { + let input = "let foo: String!=nil" + let output = "let foo: String! = nil" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpacePreservedBetweenOptionalTryAndDot() { + let input = "let foo: Int = try? .init()" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpacePreservedBetweenForceTryAndDot() { + let input = "let foo: Int = try! .init()" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpaceBetweenOptionalAndDefaultValueInFunction() { + let input = "func foo(bar _: String?=nil) {}" + let output = "func foo(bar _: String? = nil) {}" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testNoSpaceAddedAfterColonInSelector() { + let input = "@objc(foo:bar:)" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpaceAfterColonInSwitchCase() { + let input = "switch x { case .y:break }" + let output = "switch x { case .y: break }" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceAfterColonInSwitchDefault() { + let input = "switch x { default:break }" + let output = "switch x { default: break }" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceAfterComma() { + let input = "let foo = [1,2,3]" + let output = "let foo = [1, 2, 3]" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceBetweenColonAndEnumValue() { + let input = "[.Foo:.Bar]" + let output = "[.Foo: .Bar]" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceBetweenCommaAndEnumValue() { + let input = "[.Foo,.Bar]" + let output = "[.Foo, .Bar]" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testNoRemoveSpaceAroundEnumInBrackets() { + let input = "[ .red ]" + testFormatting(for: input, rule: .spaceAroundOperators, + exclude: [.spaceInsideBrackets]) + } + + func testSpaceBetweenSemicolonAndEnumValue() { + let input = "statement;.Bar" + let output = "statement; .Bar" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpacePreservedBetweenEqualsAndEnumValue() { + let input = "foo = .Bar" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testNoSpaceBeforeColon() { + let input = "let foo : Bar = 5" + let output = "let foo: Bar = 5" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpacePreservedBeforeColonInTernary() { + let input = "foo ? bar : baz" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpacePreservedAroundEnumValuesInTernary() { + let input = "foo ? .Bar : .Baz" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpaceBeforeColonInNestedTernary() { + let input = "foo ? (hello + a ? b: c) : baz" + let output = "foo ? (hello + a ? b : c) : baz" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testNoSpaceBeforeComma() { + let input = "let foo = [1 , 2 , 3]" + let output = "let foo = [1, 2, 3]" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testSpaceAtStartOfLine() { + let input = "print(foo\n ,bar)" + let output = "print(foo\n , bar)" + testFormatting(for: input, output, rule: .spaceAroundOperators, + exclude: [.leadingDelimiters]) + } + + func testSpaceAroundInfixMinus() { + let input = "foo-bar" + let output = "foo - bar" + testFormatting(for: input, output, rule: .spaceAroundOperators) + } + + func testNoSpaceAroundPrefixMinus() { + let input = "foo + -bar" + testFormatting(for: input, rule: .spaceAroundOperators) + } + + func testSpaceAroundLessThan() { + let input = "foo [ + String: String + ] + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToTypeDeclaration4() { + let input = """ + func foo() -> [String: [ + String: Int + ]] + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToTypeDeclaration5() { + let input = """ + let foo = [String: [ + String: Int + ]]() + """ + testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) + } + + func testTrailingCommaNotAddedToTypeDeclaration6() { + let input = """ + let foo = [String: [ + (Foo<[ + String + ]>, [ + Int + ]) + ]]() + """ + testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) + } + + func testTrailingCommaNotAddedToTypeDeclaration7() { + let input = """ + func foo() -> Foo<[String: [ + String: Int + ]]> + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToTypeDeclaration8() { + let input = """ + extension Foo { + var bar: [ + Int + ] { + fatalError() + } + } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToTypealias() { + let input = """ + typealias Foo = [ + Int + ] + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToCaptureList() { + let input = """ + let foo = { [ + self + ] in } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToCaptureListWithComment() { + let input = """ + let foo = { [ + self // captures self + ] in } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + func testTrailingCommaNotAddedToCaptureListWithMainActor() { + let input = """ + let closure = { @MainActor [ + foo = state.foo, + baz = state.baz + ] _ in } + """ + testFormatting(for: input, rule: .trailingCommas) + } + + // trailingCommas = false + + func testCommaNotAddedToLastItem() { + let input = "[\n foo,\n bar\n]" + let options = FormatOptions(trailingCommas: false) + testFormatting(for: input, rule: .trailingCommas, options: options) + } + + func testCommaRemovedFromLastItem() { + let input = "[\n foo,\n bar,\n]" + let output = "[\n foo,\n bar\n]" + let options = FormatOptions(trailingCommas: false) + testFormatting(for: input, output, rule: .trailingCommas, options: options) + } +} diff --git a/Tests/Rules/TrailingSpaceTests.swift b/Tests/Rules/TrailingSpaceTests.swift new file mode 100644 index 000000000..ffdff7bb6 --- /dev/null +++ b/Tests/Rules/TrailingSpaceTests.swift @@ -0,0 +1,58 @@ +// +// TrailingSpaceTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class TrailingSpaceTests: XCTestCase { + // truncateBlankLines = true + + func testTrailingSpace() { + let input = "foo \nbar" + let output = "foo\nbar" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTrailingSpaceAtEndOfFile() { + let input = "foo " + let output = "foo" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTrailingSpaceInMultilineComments() { + let input = "/* foo \n bar */" + let output = "/* foo\n bar */" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTrailingSpaceInSingleLineComments() { + let input = "// foo \n// bar " + let output = "// foo\n// bar" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTruncateBlankLine() { + let input = "foo {\n // bar\n \n // baz\n}" + let output = "foo {\n // bar\n\n // baz\n}" + testFormatting(for: input, output, rule: .trailingSpace) + } + + func testTrailingSpaceInArray() { + let input = "let foo = [\n 1,\n \n 2,\n]" + let output = "let foo = [\n 1,\n\n 2,\n]" + testFormatting(for: input, output, rule: .trailingSpace, exclude: [.redundantSelf]) + } + + // truncateBlankLines = false + + func testNoTruncateBlankLine() { + let input = "foo {\n // bar\n \n // baz\n}" + let options = FormatOptions(truncateBlankLines: false) + testFormatting(for: input, rule: .trailingSpace, options: options) + } +} diff --git a/Tests/Rules/TypeSugarTests.swift b/Tests/Rules/TypeSugarTests.swift new file mode 100644 index 000000000..6d164bd62 --- /dev/null +++ b/Tests/Rules/TypeSugarTests.swift @@ -0,0 +1,238 @@ +// +// TypeSugarTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class TypeSugarTests: XCTestCase { + // arrays + + func testArrayTypeConvertedToSugar() { + let input = "var foo: Array" + let output = "var foo: [String]" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testSwiftArrayTypeConvertedToSugar() { + let input = "var foo: Swift.Array" + let output = "var foo: [String]" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testArrayNestedTypeAliasNotConvertedToSugar() { + let input = "typealias Indices = Array.Indices" + testFormatting(for: input, rule: .typeSugar) + } + + func testArrayTypeReferenceConvertedToSugar() { + let input = "let type = Array.Type" + let output = "let type = [Foo].Type" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testSwiftArrayTypeReferenceConvertedToSugar() { + let input = "let type = Swift.Array.Type" + let output = "let type = [Foo].Type" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testArraySelfReferenceConvertedToSugar() { + let input = "let type = Array.self" + let output = "let type = [Foo].self" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testSwiftArraySelfReferenceConvertedToSugar() { + let input = "let type = Swift.Array.self" + let output = "let type = [Foo].self" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testArrayDeclarationNotConvertedToSugar() { + let input = "struct Array {}" + testFormatting(for: input, rule: .typeSugar) + } + + func testExtensionTypeSugar() { + let input = """ + extension Array {} + extension Optional {} + extension Dictionary {} + extension Optional>>> {} + """ + + let output = """ + extension [Foo] {} + extension Foo? {} + extension [Foo: Bar] {} + extension [[Foo: [Bar]]]? {} + """ + testFormatting(for: input, output, rule: .typeSugar) + } + + // dictionaries + + func testDictionaryTypeConvertedToSugar() { + let input = "var foo: Dictionary" + let output = "var foo: [String: Int]" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testSwiftDictionaryTypeConvertedToSugar() { + let input = "var foo: Swift.Dictionary" + let output = "var foo: [String: Int]" + testFormatting(for: input, output, rule: .typeSugar) + } + + // optionals + + func testOptionalPropertyTypeNotConvertedToSugarByDefault() { + let input = "var bar: Optional" + testFormatting(for: input, rule: .typeSugar) + } + + func testOptionalTypeConvertedToSugar() { + let input = "var foo: Optional" + let output = "var foo: String?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testSwiftOptionalTypeConvertedToSugar() { + let input = "var foo: Swift.Optional" + let output = "var foo: String?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testOptionalClosureParenthesizedConvertedToSugar() { + let input = "var foo: Optional<(Int) -> String>" + let output = "var foo: ((Int) -> String)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testOptionalTupleWrappedInParensConvertedToSugar() { + let input = "let foo: Optional<(foo: Int, bar: String)>" + let output = "let foo: (foo: Int, bar: String)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testOptionalComposedProtocolWrappedInParensConvertedToSugar() { + let input = "let foo: Optional" + let output = "let foo: (UIView & Foo)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testSwiftOptionalClosureParenthesizedConvertedToSugar() { + let input = "var foo: Swift.Optional<(Int) -> String>" + let output = "var foo: ((Int) -> String)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testStrippingSwiftNamespaceInOptionalTypeWhenConvertedToSugar() { + let input = "Swift.Optional" + let output = "String?" + testFormatting(for: input, output, rule: .typeSugar) + } + + func testStrippingSwiftNamespaceDoesNotStripPreviousSwiftNamespaceReferences() { + let input = "let a: Swift.String = Optional" + let output = "let a: Swift.String = String?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testOptionalTypeInsideCaseConvertedToSugar() { + let input = "if case .some(Optional.some(let foo)) = bar else {}" + let output = "if case .some(Any?.some(let foo)) = bar else {}" + testFormatting(for: input, output, rule: .typeSugar, exclude: [.hoistPatternLet]) + } + + func testSwitchCaseOptionalNotReplaced() { + let input = """ + switch foo { + case Optional.none: + } + """ + testFormatting(for: input, rule: .typeSugar) + } + + func testCaseOptionalNotReplaced2() { + let input = "if case Optional.none = foo {}" + testFormatting(for: input, rule: .typeSugar) + } + + func testUnwrappedOptionalSomeParenthesized() { + let input = "func foo() -> Optional> {}" + let output = "func foo() -> (some Publisher)? {}" + testFormatting(for: input, output, rule: .typeSugar) + } + + // swift parser bug + + func testAvoidSwiftParserBugWithClosuresInsideArrays() { + let input = "var foo = Array<(_ image: Data?) -> Void>()" + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) + } + + func testAvoidSwiftParserBugWithClosuresInsideDictionaries() { + let input = "var foo = Dictionary Void>()" + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) + } + + func testAvoidSwiftParserBugWithClosuresInsideOptionals() { + let input = "var foo = Optional<(_ image: Data?) -> Void>()" + testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) + } + + func testDontOverApplyBugWorkaround() { + let input = "var foo: Array<(_ image: Data?) -> Void>" + let output = "var foo: [(_ image: Data?) -> Void]" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testDontOverApplyBugWorkaround2() { + let input = "var foo: Dictionary Void>" + let output = "var foo: [String: (_ image: Data?) -> Void]" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testDontOverApplyBugWorkaround3() { + let input = "var foo: Optional<(_ image: Data?) -> Void>" + let output = "var foo: ((_ image: Data?) -> Void)?" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options) + } + + func testDontOverApplyBugWorkaround4() { + let input = "var foo = Array<(image: Data?) -> Void>()" + let output = "var foo = [(image: Data?) -> Void]()" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) + } + + func testDontOverApplyBugWorkaround5() { + let input = "var foo = Array<(Data?) -> Void>()" + let output = "var foo = [(Data?) -> Void]()" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) + } + + func testDontOverApplyBugWorkaround6() { + let input = "var foo = Dictionary Void>>()" + let output = "var foo = [Int: Array<(_ image: Data?) -> Void>]()" + let options = FormatOptions(shortOptionals: .always) + testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) + } +} diff --git a/Tests/Rules/UnusedArgumentsTests.swift b/Tests/Rules/UnusedArgumentsTests.swift new file mode 100644 index 000000000..170623d83 --- /dev/null +++ b/Tests/Rules/UnusedArgumentsTests.swift @@ -0,0 +1,1117 @@ +// +// UnusedArgumentsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class UnusedArgumentsTests: XCTestCase { + // closures + + func testUnusedTypedClosureArguments() { + let input = "let foo = { (bar: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" + let output = "let foo = { (_: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedUntypedClosureArguments() { + let input = "let foo = { bar, baz in\n print(\"Hello \\(baz)\")\n}" + let output = "let foo = { _, baz in\n print(\"Hello \\(baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testNoRemoveClosureReturnType() { + let input = "let foo = { () -> Foo.Bar in baz() }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureThrows() { + let input = "let foo = { () throws in }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureTypedThrows() { + let input = "let foo = { () throws(Foo) in }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureGenericReturnTypes() { + let input = "let foo = { () -> Promise in bar }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureTupleReturnTypes() { + let input = "let foo = { () -> (Int, Int) in (5, 6) }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveClosureGenericArgumentTypes() { + let input = "let foo = { (_: Foo) in }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testNoRemoveFunctionNameBeforeForLoop() { + let input = "{\n func foo() -> Int {}\n for a in b {}\n}" + testFormatting(for: input, rule: .unusedArguments) + } + + func testClosureTypeInClosureArgumentsIsNotMangled() { + let input = "{ (foo: (Int) -> Void) in }" + let output = "{ (_: (Int) -> Void) in }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedUnnamedClosureArguments() { + let input = "{ (_ foo: Int, _ bar: Int) in }" + let output = "{ (_: Int, _: Int) in }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedInoutClosureArgumentsNotMangled() { + let input = "{ (foo: inout Foo, bar: inout Bar) in }" + let output = "{ (_: inout Foo, _: inout Bar) in }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMalformedFunctionNotMisidentifiedAsClosure() { + let input = "func foo() { bar(5) {} in }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArguments() { + let input = """ + forEach { foo, bar in + guard let foo = foo, let bar = bar else { + return + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedPartUsedArguments() { + let input = """ + forEach { foo, bar in + guard let foo = baz, bar == baz else { + return + } + } + """ + let output = """ + forEach { _, bar in + guard let foo = baz, bar == baz else { + return + } + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testShadowedParameterUsedInSameGuard() { + let input = """ + forEach { foo in + guard let foo = bar, baz = foo else { + return + } + } + """ + let output = """ + forEach { _ in + guard let foo = bar, baz = foo else { + return + } + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testParameterUsedInForIn() { + let input = """ + forEach { foos in + for foo in foos { + print(foo) + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testParameterUsedInWhereClause() { + let input = """ + forEach { foo in + if bar where foo { + print(bar) + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testParameterUsedInSwitchCase() { + let input = """ + forEach { foo in + switch bar { + case let baz: + foo = baz + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testParameterUsedInStringInterpolation() { + let input = """ + forEach { foo in + print("\\(foo)") + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedClosureArgument() { + let input = """ + _ = Parser { input in + let parser = Parser.with(input) + return parser + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty, .propertyType]) + } + + func testShadowedClosureArgument2() { + let input = """ + _ = foo { input in + let input = ["foo": "Foo", "bar": "Bar"][input] + return input + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testUnusedPropertyWrapperArgument() { + let input = """ + ForEach($list.notes) { $note in + Text(note.foobar) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedThrowingClosureArgument() { + let input = "foo = { bar throws in \"\" }" + let output = "foo = { _ throws in \"\" }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedTypedThrowingClosureArgument() { + let input = "foo = { bar throws(Foo) in \"\" }" + let output = "foo = { _ throws(Foo) in \"\" }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUsedThrowingClosureArgument() { + let input = "let foo = { bar throws in bar + \"\" }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testUsedTypedThrowingClosureArgument() { + let input = "let foo = { bar throws(Foo) in bar + \"\" }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedTrailingAsyncClosureArgument() { + let input = """ + app.get { foo async in + print("No foo") + } + """ + let output = """ + app.get { _ async in + print("No foo") + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedTrailingAsyncClosureArgument2() { + let input = """ + app.get { foo async -> String in + "No foo" + } + """ + let output = """ + app.get { _ async -> String in + "No foo" + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedTrailingAsyncClosureArgument3() { + let input = """ + app.get { (foo: String) async -> String in + "No foo" + } + """ + let output = """ + app.get { (_: String) async -> String in + "No foo" + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUsedTrailingAsyncClosureArgument() { + let input = """ + app.get { foo async -> String in + "\\(foo)" + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testTrailingAsyncClosureArgumentAlreadyMarkedUnused() { + let input = "app.get { _ async in 5 }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedTrailingClosureArgumentCalledAsync() { + let input = """ + app.get { async -> String in + "No async" + } + """ + let output = """ + app.get { _ -> String in + "No async" + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testClosureArgumentUsedInGuardNotRemoved() { + let input = """ + bar(for: quux) { _, _, foo in + guard + let baz = quux.baz, + foo.contains(where: { $0.baz == baz }) + else { + return + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testClosureArgumentUsedInIfNotRemoved() { + let input = """ + foo = { reservations, _ in + if let reservations, eligibleToShow( + reservations, + accountService: accountService + ) { + coordinator.startFlow() + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + // init + + func testParameterUsedInInit() { + let input = """ + init(m: Rotation) { + let x = sqrt(max(0, m)) / 2 + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedParametersShadowedInTupleAssignment() { + let input = """ + init(x: Int, y: Int, v: Vector) { + let (x, y) = v + } + """ + let output = """ + init(x _: Int, y _: Int, v: Vector) { + let (x, y) = v + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUsedParametersShadowedInAssignmentFromFunctionCall() { + let input = """ + init(r: Double) { + let r = max(abs(r), epsilon) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArgumentInSwitch() { + let input = """ + init(_ action: Action, hub: Hub) { + switch action { + case let .get(hub, key): + self = .get(key, hub) + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testParameterUsedInSwitchCaseAfterShadowing() { + let input = """ + func issue(name: String) -> String { + switch self { + case .b(let name): return name + case .a: return name + } + } + """ + testFormatting(for: input, rule: .unusedArguments, + exclude: [.hoistPatternLet]) + } + + // functions + + func testMarkUnusedFunctionArgument() { + let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" + let output = "func foo(bar _: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedArgumentsInNonVoidFunction() { + let input = "func foo(bar: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" + let output = "func foo(bar _: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedArgumentsInThrowsFunction() { + let input = "func foo(bar: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" + let output = "func foo(bar _: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedArgumentsInOptionalReturningFunction() { + let input = "func foo(bar: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" + let output = "func foo(bar _: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testNoMarkUnusedArgumentsInProtocolFunction() { + let input = "protocol Foo {\n func foo(bar: Int) -> Int\n var bar: Int { get }\n}" + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedUnnamedFunctionArgument() { + let input = "func foo(_ foo: Int) {}" + let output = "func foo(_: Int) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedInoutFunctionArgumentIsNotMangled() { + let input = "func foo(_ foo: inout Foo) {}" + let output = "func foo(_: inout Foo) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedInternallyRenamedFunctionArgument() { + let input = "func foo(foo bar: Int) {}" + let output = "func foo(foo _: Int) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testNoMarkProtocolFunctionArgument() { + let input = "func foo(foo bar: Int)\nvar bar: Bool { get }" + testFormatting(for: input, rule: .unusedArguments) + } + + func testMembersAreNotArguments() { + let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(bar.baz)\")\n}" + let output = "func foo(bar: Int, baz _: String) {\n print(\"Hello \\(bar.baz)\")\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testLabelsAreNotArguments() { + let input = "func foo(bar: Int, baz: String) {\n bar: while true { print(baz) }\n}" + let output = "func foo(bar _: Int, baz: String) {\n bar: while true { print(baz) }\n}" + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.wrapLoopBodies]) + } + + func testDictionaryLiteralsRuinEverything() { + let input = "func foo(bar: Int, baz: Int) {\n let quux = [bar: 1, baz: 2]\n}" + testFormatting(for: input, rule: .unusedArguments) + } + + func testOperatorArgumentsAreUnnamed() { + let input = "func == (lhs: Int, rhs: Int) { false }" + let output = "func == (_: Int, _: Int) { false }" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedtFailableInitArgumentsAreNotMangled() { + let input = "init?(foo: Bar) {}" + let output = "init?(foo _: Bar) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testTreatEscapedArgumentsAsUsed() { + let input = "func foo(default: Int) -> Int {\n return `default`\n}" + testFormatting(for: input, rule: .unusedArguments) + } + + func testPartiallyMarkedUnusedArguments() { + let input = "func foo(bar: Bar, baz _: Baz) {}" + let output = "func foo(bar _: Bar, baz _: Baz) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testPartiallyMarkedUnusedArguments2() { + let input = "func foo(bar _: Bar, baz: Baz) {}" + let output = "func foo(bar _: Bar, baz _: Baz) {}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnownedUnsafeNotStripped() { + let input = """ + func foo() { + var num = 0 + Just(1) + .sink { [unowned(unsafe) self] in + num += $0 + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUnusedArguments() { + let input = """ + func foo(bar: String, baz: Int) { + let bar = "bar", baz = 5 + print(bar, baz) + } + """ + let output = """ + func foo(bar _: String, baz _: Int) { + let bar = "bar", baz = 5 + print(bar, baz) + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testShadowedUsedArguments2() { + let input = """ + func foo(things: [String], form: Form) { + let form = FormRequest( + things: things, + form: form + ) + print(form) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArguments3() { + let input = """ + func zoomTo(locations: [Foo], count: Int) { + let num = count + guard num > 0, locations.count >= count else { + return + } + print(locations) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArguments4() { + let input = """ + func foo(bar: Int) { + if let bar = baz { + return + } + print(bar) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArguments5() { + let input = """ + func doSomething(with number: Int) { + if let number = Int?(123), + number == 456 + { + print("Not likely") + } + + if number == 180 { + print("Bullseye!") + } + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedUsedArgumentInSwitchCase() { + let input = """ + func foo(bar baz: Foo) -> Foo? { + switch (a, b) { + case (0, _), + (_, nil): + return .none + case let (1, baz?): + return .bar(baz) + default: + return baz + } + } + """ + testFormatting(for: input, rule: .unusedArguments, + exclude: [.sortSwitchCases]) + } + + func testTryArgumentNotMarkedUnused() { + let input = """ + func foo(bar: String) throws -> String? { + let bar = + try parse(bar) + return bar + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testTryAwaitArgumentNotMarkedUnused() { + let input = """ + func foo(bar: String) async throws -> String? { + let bar = try + await parse(bar) + return bar + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testTypedTryAwaitArgumentNotMarkedUnused() { + let input = """ + func foo(bar: String) async throws(Foo) -> String? { + let bar = try + await parse(bar) + return bar + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } + + func testConditionalIfLetMarkedAsUnused() { + let input = """ + func foo(bar: UIViewController) { + if let bar = baz { + bar.loadViewIfNeeded() + } + } + """ + let output = """ + func foo(bar _: UIViewController) { + if let bar = baz { + bar.loadViewIfNeeded() + } + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testConditionAfterIfCaseHoistedLetNotMarkedUnused() { + let input = """ + func isLoadingFirst(for tabID: String) -> Bool { + if case let .loading(.first(loadingTabID, _)) = requestState.status, loadingTabID == tabID { + return true + } else { + return false + } + + print(tabID) + } + """ + let options = FormatOptions(hoistPatternLet: true) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testConditionAfterIfCaseInlineLetNotMarkedUnused2() { + let input = """ + func isLoadingFirst(for tabID: String) -> Bool { + if case .loading(.first(let loadingTabID, _)) = requestState.status, loadingTabID == tabID { + return true + } else { + return false + } + + print(tabID) + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testConditionAfterIfCaseInlineLetNotMarkedUnused3() { + let input = """ + private func isFocusedView(formDataID: FormDataID) -> Bool { + guard + case .selected(let selectedFormDataID) = currentState.selectedFormItemAction, + selectedFormDataID == formDataID + else { + return false + } + + return true + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testConditionAfterIfCaseInlineLetNotMarkedUnused4() { + let input = """ + private func totalRowContent(priceItemsCount: Int, priceBreakdownStyle: PriceBreakdownStyle) { + if + case .all(let shouldCollapseByDefault, _) = priceBreakdownStyle, + priceItemsCount > 0 + { + // .. + } + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testConditionAfterIfCaseInlineLetNotMarkedUnused5() { + let input = """ + private mutating func clearPendingRemovals(itemIDs: Set) { + for change in changes { + if case .removal(itemID: let itemID) = change, !itemIDs.contains(itemID) { + // .. + } + } + } + """ + let options = FormatOptions(hoistPatternLet: false) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testSecondConditionAfterTupleMarkedUnused() { + let input = """ + func foobar(bar: Int) { + let (foo, baz) = (1, 2), bar = 3 + print(foo, bar, baz) + } + """ + let output = """ + func foobar(bar _: Int) { + let (foo, baz) = (1, 2), bar = 3 + print(foo, bar, baz) + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedParamsInTupleAssignment() { + let input = """ + func foobar(_ foo: Int, _ bar: Int, _ baz: Int, _ quux: Int) { + let ((foo, bar), baz) = ((foo, quux), bar) + print(foo, bar, baz, quux) + } + """ + let output = """ + func foobar(_ foo: Int, _ bar: Int, _: Int, _ quux: Int) { + let ((foo, bar), baz) = ((foo, quux), bar) + print(foo, bar, baz, quux) + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testShadowedIfLetNotMarkedAsUnused() { + let input = """ + func method(_ foo: Int?, _ bar: String?) { + if let foo = foo, let bar = bar {} + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShorthandIfLetNotMarkedAsUnused() { + let input = """ + func method(_ foo: Int?, _ bar: String?) { + if let foo, let bar {} + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShorthandLetMarkedAsUnused() { + let input = """ + func method(_ foo: Int?, _ bar: Int?) { + var foo, bar: Int? + } + """ + let output = """ + func method(_: Int?, _: Int?) { + var foo, bar: Int? + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testShadowedClosureNotMarkedUnused() { + let input = """ + func foo(bar: () -> Void) { + let bar = { + print("log") + bar() + } + bar() + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testShadowedClosureMarkedUnused() { + let input = """ + func foo(bar: () -> Void) { + let bar = { + print("log") + } + bar() + } + """ + let output = """ + func foo(bar _: () -> Void) { + let bar = { + print("log") + } + bar() + } + """ + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testViewBuilderAnnotationDoesntBreakUnusedArgDetection() { + let input = """ + struct Foo { + let content: View + + public init( + responsibleFileID: StaticString = #fileID, + @ViewBuilder content: () -> View) + { + self.content = content() + } + } + """ + let output = """ + struct Foo { + let content: View + + public init( + responsibleFileID _: StaticString = #fileID, + @ViewBuilder content: () -> View) + { + self.content = content() + } + } + """ + testFormatting(for: input, output, rule: .unusedArguments, + exclude: [.braces, .wrapArguments]) + } + + func testArgumentUsedInDictionaryLiteral() { + let input = """ + class MyClass { + func testMe(value: String) { + let value = [ + "key": value + ] + print(value) + } + } + """ + testFormatting(for: input, rule: .unusedArguments, + exclude: [.trailingCommas]) + } + + func testArgumentUsedAfterIfDefInsideSwitchBlock() { + let input = """ + func test(string: String) { + let number = 5 + switch number { + #if DEBUG + case 1: + print("ONE") + #endif + default: + print("NOT ONE") + } + print(string) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUsedConsumingArgument() { + let input = """ + func close(file: consuming FileHandle) { + file.close() + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.noExplicitOwnership]) + } + + func testUsedConsumingBorrowingArguments() { + let input = """ + func foo(a: consuming Foo, b: borrowing Bar) { + consume(a) + borrow(b) + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.noExplicitOwnership]) + } + + func testUnusedConsumingArgument() { + let input = """ + func close(file: consuming FileHandle) { + print("no-op") + } + """ + let output = """ + func close(file _: consuming FileHandle) { + print("no-op") + } + """ + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.noExplicitOwnership]) + } + + func testUnusedConsumingBorrowingArguments() { + let input = """ + func foo(a: consuming Foo, b: borrowing Bar) { + print("no-op") + } + """ + let output = """ + func foo(a _: consuming Foo, b _: borrowing Bar) { + print("no-op") + } + """ + testFormatting(for: input, output, rule: .unusedArguments, exclude: [.noExplicitOwnership]) + } + + func testFunctionArgumentUsedInGuardNotRemoved() { + let input = """ + func scrollViewDidEndDecelerating(_ visibleDayRange: DayRange) { + guard + store.state.request.isIdle, + let nextDayToLoad = store.state.request.nextCursor?.lowerBound, + visibleDayRange.upperBound.distance(to: nextDayToLoad) < 30 + else { + return + } + + store.handle(.loadNext) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testFunctionArgumentUsedInGuardNotRemoved2() { + let input = """ + func convert( + filter: Filter, + accounts: [Account], + outgoingTotal: MulticurrencyTotal? + ) -> History? { + guard + let firstParameter = incomingTotal?.currency, + let secondParameter = outgoingTotal?.currency, + isFilter(filter, accounts: accounts) + else { + return nil + } + return History(firstParameter, secondParameter) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testFunctionArgumentUsedInGuardNotRemoved3() { + let input = """ + public func flagMessage(_ message: Message) { + model.withState { state in + guard + let flagMessageFeature, + shouldAllowFlaggingMessage( + message, + thread: state.thread) + else { return } + } + } + """ + testFormatting(for: input, rule: .unusedArguments, + exclude: [.wrapArguments, .wrapConditionalBodies, .indent]) + } + + // functions (closure-only) + + func testNoMarkFunctionArgument() { + let input = "func foo(_ bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" + let options = FormatOptions(stripUnusedArguments: .closureOnly) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + // functions (unnamed-only) + + func testNoMarkNamedFunctionArgument() { + let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" + let options = FormatOptions(stripUnusedArguments: .unnamedOnly) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + func testRemoveUnnamedFunctionArgument() { + let input = "func foo(_ foo: Int) {}" + let output = "func foo(_: Int) {}" + let options = FormatOptions(stripUnusedArguments: .unnamedOnly) + testFormatting(for: input, output, rule: .unusedArguments, options: options) + } + + func testNoRemoveInternalFunctionArgumentName() { + let input = "func foo(foo bar: Int) {}" + let options = FormatOptions(stripUnusedArguments: .unnamedOnly) + testFormatting(for: input, rule: .unusedArguments, options: options) + } + + // init + + func testMarkUnusedInitArgument() { + let input = "init(bar: Int, baz: String) {\n self.baz = baz\n}" + let output = "init(bar _: Int, baz: String) {\n self.baz = baz\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + // subscript + + func testMarkUnusedSubscriptArgument() { + let input = "subscript(foo: Int, baz: String) -> String {\n return get(baz)\n}" + let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedUnnamedSubscriptArgument() { + let input = "subscript(_ foo: Int, baz: String) -> String {\n return get(baz)\n}" + let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testMarkUnusedNamedSubscriptArgument() { + let input = "subscript(foo foo: Int, baz: String) -> String {\n return get(baz)\n}" + let output = "subscript(foo _: Int, baz: String) -> String {\n return get(baz)\n}" + testFormatting(for: input, output, rule: .unusedArguments) + } + + func testUnusedArgumentWithClosureShadowingParamName() { + let input = """ + func test(foo: Foo) { + let foo = { + if foo.bar { + baaz + } else { + bar + } + }() + print(foo) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedArgumentWithConditionalAssignmentShadowingParamName() { + let input = """ + func test(foo: Foo) { + let foo = + if foo.bar { + baaz + } else { + bar + } + print(foo) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedArgumentWithSwitchAssignmentShadowingParamName() { + let input = """ + func test(foo: Foo) { + let foo = + switch foo.bar { + case true: + baaz + case false: + bar + } + print(foo) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testUnusedArgumentWithConditionalAssignmentNotShadowingParamName() { + let input = """ + func test(bar: Bar) { + let quux = + if foo { + bar + } else { + baaz + } + print(quux) + } + """ + testFormatting(for: input, rule: .unusedArguments) + } + + func testIssue1694() { + let input = """ + listenForUpdates() { [weak self] update, error in + guard let update, error == nil else { + return + } + self?.configure(update) + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantParens]) + } + + func testIssue1696() { + let input = """ + func someFunction(with parameter: Int) -> Int { + let parameter = max( + 200, + parameter + ) + return parameter + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) + } +} diff --git a/Tests/Rules/UnusedPrivateDeclarationTests.swift b/Tests/Rules/UnusedPrivateDeclarationTests.swift new file mode 100644 index 000000000..13851c40f --- /dev/null +++ b/Tests/Rules/UnusedPrivateDeclarationTests.swift @@ -0,0 +1,355 @@ +// +// UnusedPrivateDeclarationTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class UnusedPrivateDeclarationTests: XCTestCase { + func testRemoveUnusedPrivate() { + let input = """ + struct Foo { + private var foo = "foo" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testRemoveUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveUsedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemoveMultipleUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + fileprivate var baz = "baz" + var bar = "bar" + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testRemoveMixedUsedAndUnusedFilePrivate() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + fileprivate var baz = "baz" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + let output = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + } + + struct Hello { + let localFoo = Foo().foo + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveFilePrivateUsedInSameStruct() { + let input = """ + struct Foo { + fileprivate var foo = "foo" + var bar = "bar" + + func useFoo() { + print(foo) + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemoveUnusedFilePrivateInNestedStruct() { + let input = """ + struct Foo { + var bar = "bar" + + struct Inner { + fileprivate var foo = "foo" + } + } + """ + let output = """ + struct Foo { + var bar = "bar" + + struct Inner { + } + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: [.emptyBraces]) + } + + func testDoNotRemoveFilePrivateUsedInNestedStruct() { + let input = """ + struct Foo { + var bar = "bar" + + struct Inner { + fileprivate var foo = "foo" + func useFoo() { + print(foo) + } + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemoveUnusedFileprivateFunction() { + let input = """ + struct Foo { + var bar = "bar" + + fileprivate func sayHi() { + print("hi") + } + } + """ + let output = """ + struct Foo { + var bar = "bar" + } + """ + testFormatting(for: input, [output], rules: [.unusedPrivateDeclaration, .blankLinesAtEndOfScope]) + } + + func testDoNotRemoveUnusedFileprivateOperatorDefinition() { + let input = """ + private class Foo: Equatable { + fileprivate static func == (_: Foo, _: Foo) -> Bool { + return true + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testRemovePrivateDeclarationButDoNotRemoveUnusedPrivateType() { + let input = """ + private struct Foo { + private func bar() { + print("test") + } + } + """ + let output = """ + private struct Foo { + } + """ + + testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: [.emptyBraces]) + } + + func testRemovePrivateDeclarationButDoNotRemovePrivateExtension() { + let input = """ + private extension Foo { + private func doSomething() {} + func anotherFunction() {} + } + """ + let output = """ + private extension Foo { + func anotherFunction() {} + } + """ + + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testRemovesPrivateTypealias() { + let input = """ + enum Foo { + struct Bar {} + private typealias Baz = Bar + } + """ + let output = """ + enum Foo { + struct Bar {} + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } + + func testDoesntRemoveFileprivateInit() { + let input = """ + struct Foo { + fileprivate init() {} + static let foo = Foo() + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: [.propertyType]) + } + + func testCanDisableUnusedPrivateDeclarationRule() { + let input = """ + private enum Foo { + // swiftformat:disable:next unusedPrivateDeclaration + fileprivate static func bar() {} + } + """ + + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoesNotRemovePropertyWrapperPrefixesIfUsed() { + let input = """ + struct ContentView: View { + public init() { + _showButton = .init(initialValue: false) + } + + @State private var showButton: Bool + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoesNotRemoveUnderscoredDeclarationIfUsed() { + let input = """ + struct Foo { + private var _showButton: Bool = true + print(_showButton) + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoesNotRemoveBacktickDeclarationIfUsed() { + let input = """ + struct Foo { + fileprivate static var `default`: Bool = true + func printDefault() { + print(Foo.default) + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoesNotRemoveBacktickUsage() { + let input = """ + struct Foo { + fileprivate static var foo = true + func printDefault() { + print(Foo.`foo`) + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: [.redundantBackticks]) + } + + func testDoNotRemovePreservedPrivateDeclarations() { + let input = """ + enum Foo { + private static let registryAssociation = false + } + """ + let options = FormatOptions(preservedPrivateDeclarations: ["registryAssociation", "hello"]) + testFormatting(for: input, rule: .unusedPrivateDeclaration, options: options) + } + + func testDoNotRemoveOverridePrivateMethodDeclarations() { + let input = """ + class Poodle: Dog { + override private func makeNoise() { + print("Yip!") + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveOverridePrivatePropertyDeclarations() { + let input = """ + class Poodle: Dog { + override private var age: Int { + 7 + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveObjcPrivatePropertyDeclaration() { + let input = """ + struct Foo { + @objc + private var bar = "bar" + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveObjcPrivateFunctionDeclaration() { + let input = """ + struct Foo { + @objc + private func doSomething() {} + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } + + func testDoNotRemoveIBActionPrivateFunctionDeclaration() { + let input = """ + class FooViewController: UIViewController { + @IBAction private func buttonPressed(_: UIButton) { + print("Button pressed!") + } + } + """ + testFormatting(for: input, rule: .unusedPrivateDeclaration) + } +} diff --git a/Tests/Rules/VoidTests.swift b/Tests/Rules/VoidTests.swift new file mode 100644 index 000000000..a29aac168 --- /dev/null +++ b/Tests/Rules/VoidTests.swift @@ -0,0 +1,261 @@ +// +// VoidTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class VoidTests: XCTestCase { + func testEmptyParensReturnValueConvertedToVoid() { + let input = "() -> ()" + let output = "() -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testSpacedParensReturnValueConvertedToVoid() { + let input = "() -> ( \n)" + let output = "() -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testParensContainingCommentNotConvertedToVoid() { + let input = "() -> ( /* Hello World */ )" + testFormatting(for: input, rule: .void) + } + + func testParensNotConvertedToVoidIfLocalOverrideExists() { + let input = """ + struct Void {} + let foo = () -> () + print(foo) + """ + testFormatting(for: input, rule: .void) + } + + func testParensRemovedAroundVoid() { + let input = "() -> (Void)" + let output = "() -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testVoidArgumentConvertedToEmptyParens() { + let input = "Void -> Void" + let output = "() -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testVoidArgumentInParensNotConvertedToEmptyParens() { + let input = "(Void) -> Void" + testFormatting(for: input, rule: .void) + } + + func testAnonymousVoidArgumentNotConvertedToEmptyParens() { + let input = "{ (_: Void) -> Void in }" + testFormatting(for: input, rule: .void, exclude: [.redundantVoidReturnType]) + } + + func testFuncWithAnonymousVoidArgumentNotStripped() { + let input = "func foo(_: Void) -> Void" + testFormatting(for: input, rule: .void) + } + + func testFunctionThatReturnsAFunction() { + let input = "(Void) -> Void -> ()" + let output = "(Void) -> () -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testFunctionThatReturnsAFunctionThatThrows() { + let input = "(Void) -> Void throws -> ()" + let output = "(Void) -> () throws -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testFunctionThatReturnsAFunctionThatHasTypedThrows() { + let input = "(Void) -> Void throws(Foo) -> ()" + let output = "(Void) -> () throws(Foo) -> Void" + testFormatting(for: input, output, rule: .void) + } + + func testChainOfFunctionsIsNotChanged() { + let input = "() -> () -> () -> Void" + testFormatting(for: input, rule: .void) + } + + func testChainOfFunctionsWithThrowsIsNotChanged() { + let input = "() -> () throws -> () throws -> Void" + testFormatting(for: input, rule: .void) + } + + func testChainOfFunctionsWithTypedThrowsIsNotChanged() { + let input = "() -> () throws(Foo) -> () throws(Foo) -> Void" + testFormatting(for: input, rule: .void) + } + + func testVoidThrowsIsNotMangled() { + let input = "(Void) throws -> Void" + testFormatting(for: input, rule: .void) + } + + func testVoidTypedThrowsIsNotMangled() { + let input = "(Void) throws(Foo) -> Void" + testFormatting(for: input, rule: .void) + } + + func testEmptyClosureArgsNotMangled() { + let input = "{ () in }" + testFormatting(for: input, rule: .void) + } + + func testEmptyClosureReturnValueConvertedToVoid() { + let input = "{ () -> () in }" + let output = "{ () -> Void in }" + testFormatting(for: input, output, rule: .void, exclude: [.redundantVoidReturnType]) + } + + func testAnonymousVoidClosureNotChanged() { + let input = "{ (_: Void) in }" + testFormatting(for: input, rule: .void, exclude: [.unusedArguments]) + } + + func testVoidLiteralConvertedToParens() { + let input = "foo(Void())" + let output = "foo(())" + testFormatting(for: input, output, rule: .void) + } + + func testVoidLiteralConvertedToParens2() { + let input = "let foo = Void()" + let output = "let foo = ()" + testFormatting(for: input, output, rule: .void) + } + + func testVoidLiteralReturnValueConvertedToParens() { + let input = """ + func foo() { + return Void() + } + """ + let output = """ + func foo() { + return () + } + """ + testFormatting(for: input, output, rule: .void) + } + + func testVoidLiteralReturnValueConvertedToParens2() { + let input = "{ _ in Void() }" + let output = "{ _ in () }" + testFormatting(for: input, output, rule: .void) + } + + func testNamespacedVoidLiteralNotConverted() { + // TODO: it should actually be safe to convert Swift.Void - only unsafe for other namespaces + let input = "let foo = Swift.Void()" + testFormatting(for: input, rule: .void) + } + + func testMalformedFuncDoesNotCauseInvalidOutput() throws { + let input = "func baz(Void) {}" + testFormatting(for: input, rule: .void) + } + + func testEmptyParensInGenericsConvertedToVoid() { + let input = "Foo<(), ()>" + let output = "Foo" + testFormatting(for: input, output, rule: .void) + } + + func testCaseVoidNotUnwrapped() { + let input = "case some(Void)" + testFormatting(for: input, rule: .void) + } + + func testLocalVoidTypeNotConverted() { + let input = """ + struct Void {} + let foo = Void() + print(foo) + """ + testFormatting(for: input, rule: .void) + } + + func testLocalVoidTypeForwardReferenceNotConverted() { + let input = """ + let foo = Void() + print(foo) + struct Void {} + """ + testFormatting(for: input, rule: .void) + } + + func testLocalVoidTypealiasNotConverted() { + let input = """ + typealias Void = MyVoid + let foo = Void() + print(foo) + """ + testFormatting(for: input, rule: .void) + } + + func testLocalVoidTypealiasForwardReferenceNotConverted() { + let input = """ + let foo = Void() + print(foo) + typealias Void = MyVoid + """ + testFormatting(for: input, rule: .void) + } + + // useVoid = false + + func testUseVoidOptionFalse() { + let input = "(Void) -> Void" + let output = "(()) -> ()" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, output, rule: .void, options: options) + } + + func testNamespacedVoidNotConverted() { + let input = "() -> Swift.Void" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, rule: .void, options: options) + } + + func testTypealiasVoidNotConverted() { + let input = "public typealias Void = ()" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, rule: .void, options: options) + } + + func testVoidClosureReturnValueConvertedToEmptyTuple() { + let input = "{ () -> Void in }" + let output = "{ () -> () in }" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, output, rule: .void, options: options, exclude: [.redundantVoidReturnType]) + } + + func testNoConvertVoidSelfToTuple() { + let input = "Void.self" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, rule: .void, options: options) + } + + func testNoConvertVoidTypeToTuple() { + let input = "Void.Type" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, rule: .void, options: options) + } + + func testCaseVoidConvertedToTuple() { + let input = "case some(Void)" + let output = "case some(())" + let options = FormatOptions(useVoid: false) + testFormatting(for: input, output, rule: .void, options: options) + } +} diff --git a/Tests/Rules/WrapArgumentsTests.swift b/Tests/Rules/WrapArgumentsTests.swift new file mode 100644 index 000000000..3fe31d58b --- /dev/null +++ b/Tests/Rules/WrapArgumentsTests.swift @@ -0,0 +1,2447 @@ +// +// WrapArgumentsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapArgumentsTests: XCTestCase { + func testIndentFirstElementWhenApplyingWrap() { + let input = """ + let foo = Set([ + Thing(), + Thing(), + ]) + """ + let output = """ + let foo = Set([ + Thing(), + Thing(), + ]) + """ + testFormatting(for: input, output, rule: .wrapArguments, exclude: [.propertyType]) + } + + func testWrapArgumentsDoesntIndentTrailingComment() { + let input = """ + foo( // foo + bar: Int + ) + """ + let output = """ + foo( // foo + bar: Int + ) + """ + testFormatting(for: input, output, rule: .wrapArguments) + } + + func testWrapArgumentsDoesntIndentClosingBracket() { + let input = """ + [ + "foo": [ + ], + ] + """ + testFormatting(for: input, rule: .wrapArguments) + } + + func testWrapParametersDoesNotAffectFunctionDeclaration() { + let input = "foo(\n bar _: Int,\n baz _: String\n)" + let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapParametersClosureAfterParameterListDoesNotWrapClosureArguments() { + let input = """ + func foo() {} + bar = (baz: 5, quux: 7, + quuz: 10) + """ + let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapParametersNotSetWrapArgumentsAfterFirstDefaultsToAfterFirst() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersNotSetWrapArgumentsBeforeFirstDefaultsToBeforeFirst() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersNotSetWrapArgumentsPreserveDefaultsToPreserve() { + let input = "func foo(\n bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapArguments: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionDeclarationClosingParenOnSameLine() { + let input = """ + func foo( + bar _: Int, + baz _: String + ) {} + """ + let output = """ + func foo( + bar _: Int, + baz _: String) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionDeclarationClosingParenOnNextLine() { + let input = """ + func foo( + bar _: Int, + baz _: String) {} + """ + let output = """ + func foo( + bar _: Int, + baz _: String + ) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionDeclarationClosingParenOnSameLineAndForce() { + let input = """ + func foo( + bar _: Int, + baz _: String + ) {} + """ + let output = """ + func foo( + bar _: Int, + baz _: String) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine, callSiteClosingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionDeclarationClosingParenOnNextLineAndForce() { + let input = """ + func foo( + bar _: Int, + baz _: String) {} + """ + let output = """ + func foo( + bar _: Int, + baz _: String + ) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersFunctionCallClosingParenOnNextLineAndForce() { + let input = """ + foo( + bar: 42, + baz: "foo" + ) + """ + let output = """ + foo( + bar: 42, + baz: "foo") + """ + let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testIndentMultilineStringWhenWrappingArguments() { + let input = """ + foobar(foo: \"\"" + baz + \"\"", + bar: \"\"" + baz + \"\"") + """ + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testHandleXcodeTokenApplyingWrap() { + let input = """ + test(image: \u{003c}#T##UIImage#>, name: "Name") + """ + + let output = """ + test( + image: \u{003c}#T##UIImage#>, + name: "Name" + ) + """ + let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testIssue1530() { + let input = """ + extension DRAutoWeatherReadRequestResponse { + static let mock = DRAutoWeatherReadRequestResponse( + offlineFirstWeather: DRAutoWeatherReadRequestResponse.DROfflineFirstWeather( + daily: .mockWeatherID, hourly: [] + ) + ) + } + """ + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) + } + + // MARK: wrapParameters + + // MARK: preserve + + func testAfterFirstPreserved() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testAfterFirstPreservedIndentFixed() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testAfterFirstPreservedNewlineRemoved() { + let input = "func foo(bar _: Int,\n baz _: String\n) {}" + let output = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testBeforeFirstPreserved() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testBeforeFirstPreservedIndentFixed() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testBeforeFirstPreservedNewlineAdded() { + let input = "func foo(\n bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersAfterMultilineComment() { + let input = """ + /** + Some function comment. + */ + func barFunc( + _ firstParam: FirstParamType, + secondParam: SecondParamType + ) + """ + let options = FormatOptions(wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: afterFirst + + func testBeforeFirstConvertedToAfterFirst() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testNoWrapInnerArguments() { + let input = "func foo(\n bar _: Int,\n baz _: foo(bar, baz)\n) {}" + let output = "func foo(bar _: Int,\n baz _: foo(bar, baz)) {}" + let options = FormatOptions(wrapParameters: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + // MARK: afterFirst, maxWidth + + func testWrapAfterFirstIfMaxLengthExceeded() { + let input = """ + func foo(bar: Int, baz: String) -> Bool {} + """ + let output = """ + func foo(bar: Int, + baz: String) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments, .wrap]) + } + + func testWrapAfterFirstIfMaxLengthExceeded2() { + let input = """ + func foo(bar: Int, baz: String, quux: Bool) -> Bool {} + """ + let output = """ + func foo(bar: Int, + baz: String, + quux: Bool) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments, .wrap]) + } + + func testWrapAfterFirstIfMaxLengthExceeded3() { + let input = """ + func foo(bar: Int, baz: String, aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} + """ + let output = """ + func foo(bar: Int, baz: String, + aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments, .wrap]) + } + + func testWrapAfterFirstIfMaxLengthExceeded3WithWrap() { + let input = """ + func foo(bar: Int, baz: String, aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} + """ + let output = """ + func foo(bar: Int, baz: String, + aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) + -> Bool {} + """ + let output2 = """ + func foo(bar: Int, baz: String, + aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) + -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) + testFormatting(for: input, [output, output2], + rules: [.wrapArguments, .wrap], + options: options, exclude: [.unusedArguments]) + } + + func testWrapAfterFirstIfMaxLengthExceeded4WithWrap() { + let input = """ + func foo(bar: String, baz: String, quux: Bool) -> Bool {} + """ + let output = """ + func foo(bar: String, + baz: String, + quux: Bool) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) + testFormatting(for: input, [output], + rules: [.wrapArguments, .wrap], + options: options, exclude: [.unusedArguments]) + } + + func testWrapAfterFirstIfMaxLengthExceededInClassScopeWithWrap() { + let input = """ + class TestClass { + func foo(bar: String, baz: String, quux: Bool) -> Bool {} + } + """ + let output = """ + class TestClass { + func foo(bar: String, + baz: String, + quux: Bool) + -> Bool {} + } + """ + let output2 = """ + class TestClass { + func foo(bar: String, + baz: String, + quux: Bool) + -> Bool {} + } + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) + testFormatting(for: input, [output, output2], + rules: [.wrapArguments, .wrap], + options: options, exclude: [.unusedArguments]) + } + + func testWrapParametersListInClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: (Int, + Int, + String) -> Int = { _, _, _ in + 0 + } + """ + let output2 = """ + var mathFunction: (Int, + Int, + String) + -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 30) + testFormatting(for: input, [output, output2], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersAfterFirstIfMaxLengthExceededInReturnType() { + let input = """ + func foo(bar: Int, baz: String, quux: Bool) -> LongReturnType {} + """ + let output2 = """ + func foo(bar: Int, baz: String, + quux: Bool) -> LongReturnType {} + """ + let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 50) + testFormatting(for: input, [input, output2], rules: [.wrapArguments], + options: options, exclude: [.unusedArguments]) + } + + func testWrapParametersAfterFirstWithSeparatedArgumentLabels() { + let input = """ + func foo(with + bar: Int, and + baz: String, and + quux: Bool + ) -> LongReturnType {} + """ + let output = """ + func foo(with bar: Int, + and baz: String, + and quux: Bool) -> LongReturnType {} + """ + let options = FormatOptions(wrapParameters: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, + options: options, exclude: [.unusedArguments]) + } + + // MARK: beforeFirst + + func testWrapAfterFirstConvertedToWrapBefore() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testLinebreakInsertedAtEndOfWrappedFunction() { + let input = "func foo(\n bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testAfterFirstConvertedToBeforeFirst() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapParametersListBeforeFirstInClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersListBeforeFirstInThrowingClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) throws -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) throws -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersListBeforeFirstInTypedThrowingClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) throws(Foo) -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) throws(Foo) -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersListBeforeFirstInRethrowingClosureType() { + let input = """ + var mathFunction: (Int, + Int, String) rethrows -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) rethrows -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options) + } + + func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameter() { + let input = """ + func foo(bar: (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParams() { + let input = """ + func foo(bar: Int, baz: (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: Int, baz: ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParamsAfterWrappedClosure() { + let input = """ + func foo(bar: Int, baz: (Int, + Bool, String) -> Int, quux: String) -> Int {} + """ + let output = """ + func foo(bar: Int, baz: ( + Int, + Bool, + String + ) -> Int, quux: String) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInEscapingClosureTypeAsFunctionParameter() { + let input = """ + func foo(bar: @escaping (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: @escaping ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInNoEscapeClosureTypeAsFunctionParameter() { + let input = """ + func foo(bar: @noescape (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: @noescape ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInEscapingAutoclosureTypeAsFunctionParameter() { + let input = """ + func foo(bar: @escaping @autoclosure (Int, + Bool, String) -> Int) -> Int {} + """ + let output = """ + func foo(bar: @escaping @autoclosure ( + Int, + Bool, + String + ) -> Int) -> Int {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, [output], + rules: [.wrapArguments], + options: options, + exclude: [.unusedArguments]) + } + + // MARK: beforeFirst, maxWidth + + func testWrapBeforeFirstIfMaxLengthExceeded() { + let input = """ + func foo(bar: Int, baz: String) -> Bool {} + """ + let output = """ + func foo( + bar: Int, + baz: String + ) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testNoWrapBeforeFirstIfMaxLengthNotExceeded() { + let input = """ + func foo(bar: Int, baz: String) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 42) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testNoWrapGenericsIfClosingBracketWithinMaxWidth() { + let input = """ + func foo(bar: Int, baz: String) -> Bool {} + """ + let output = """ + func foo( + bar: Int, + baz: String + ) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testWrapAlreadyWrappedArgumentsIfMaxLengthExceeded() { + let input = """ + func foo( + bar: Int, baz: String, quux: Bool + ) -> Bool {} + """ + let output = """ + func foo( + bar: Int, baz: String, + quux: Bool + ) -> Bool {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 26) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testWrapParametersBeforeFirstIfMaxLengthExceededInReturnType() { + let input = """ + func foo(bar: Int, baz: String, quux: Bool) -> LongReturnType {} + """ + let output2 = """ + func foo( + bar: Int, + baz: String, + quux: Bool + ) -> LongReturnType {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 50) + testFormatting(for: input, [input, output2], rules: [.wrapArguments], + options: options, exclude: [.unusedArguments]) + } + + func testWrapParametersBeforeFirstWithSeparatedArgumentLabels() { + let input = """ + func foo(with + bar: Int, and + baz: String + ) -> LongReturnType {} + """ + let output = """ + func foo( + with bar: Int, + and baz: String + ) -> LongReturnType {} + """ + let options = FormatOptions(wrapParameters: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, + options: options, exclude: [.unusedArguments]) + } + + func testWrapParametersListBeforeFirstInClosureTypeWithMaxWidth() { + let input = """ + var mathFunction: (Int, Int, String) -> Int = { _, _, _ in + 0 + } + """ + let output = """ + var mathFunction: ( + Int, + Int, + String + ) -> Int = { _, _, _ in + 0 + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) + testFormatting(for: input, [output], rules: [.wrapArguments], + options: options) + } + + func testNoWrapBeforeFirstMaxWidthNotExceededWithLineBreakSinceLastEndOfArgumentScope() { + let input = """ + class Foo { + func foo() { + bar() + } + + func bar(foo: String, bar: Int) { + quux() + } + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 37) + testFormatting(for: input, rule: .wrapArguments, + options: options, exclude: [.unusedArguments]) + } + + func testNoWrapSubscriptWithSingleElement() { + let input = "guard let foo = bar[0] {}" + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 20) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testNoWrapArrayWithSingleElement() { + let input = "let foo = [0]" + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 11) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testNoWrapDictionaryWithSingleElement() { + let input = "let foo = [bar: baz]" + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 15) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testNoWrapImageLiteral() { + let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testNoWrapColorLiteral() { + let input = """ + if let color = #colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1) {} + """ + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap]) + } + + func testWrapArgumentsNoIndentBlankLines() { + let input = """ + let foo = [ + + bar, + + ] + """ + let options = FormatOptions(wrapCollections: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.wrap, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) + } + + // MARK: closingParenPosition = true + + func testParenOnSameLineWhenWrapAfterFirstConvertedToWrapBefore() { + let input = "func foo(bar _: Int,\n baz _: String) {}" + let output = "func foo(\n bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testParenOnSameLineWhenWrapBeforeFirstUnchanged() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(\n bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testParenOnSameLineWhenWrapBeforeFirstPreserved() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let output = "func foo(\n bar _: Int,\n baz _: String) {}" + let options = FormatOptions(wrapParameters: .preserve, closingParenPosition: .sameLine) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + // MARK: indent with tabs + + func testTabIndentWrappedFunctionWithSmartTabs() { + let input = """ + func foo(bar: Int, + baz: Int) {} + """ + let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, tabWidth: 2) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + func testTabIndentWrappedFunctionWithoutSmartTabs() { + let input = """ + func foo(bar: Int, + baz: Int) {} + """ + let output = """ + func foo(bar: Int, + \t\t\t\t baz: Int) {} + """ + let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, + tabWidth: 2, smartTabs: false) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments]) + } + + // MARK: - wrapArguments --wrapArguments + + func testWrapArgumentsDoesNotAffectFunctionDeclaration() { + let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapArgumentsDoesNotAffectInit() { + let input = "init(\n bar _: Int,\n baz _: String\n) {}" + let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapArgumentsDoesNotAffectSubscript() { + let input = "subscript(\n bar _: Int,\n baz _: String\n) -> Int {}" + let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: afterFirst + + func testWrapArgumentsConvertBeforeFirstToAfterFirst() { + let input = """ + foo( + bar _: Int, + baz _: String + ) + """ + let output = """ + foo(bar _: Int, + baz _: String) + """ + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testCorrectWrapIndentForNestedArguments() { + let input = "foo(\nbar: (\nx: 0,\ny: 0\n),\nbaz: (\nx: 0,\ny: 0\n)\n)" + let output = "foo(bar: (x: 0,\n y: 0),\n baz: (x: 0,\n y: 0))" + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testNoRemoveLinebreakAfterCommentInArguments() { + let input = "a(b // comment\n)" + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoRemoveLinebreakAfterCommentInArguments2() { + let input = """ + foo(bar: bar + // , + // baz: baz + ) {} + """ + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.indent]) + } + + func testConsecutiveCodeCommentsNotIndented() { + let input = """ + foo(bar: bar, + // bar, + // baz, + quux) + """ + let options = FormatOptions(wrapArguments: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: afterFirst maxWidth + + func testWrapArgumentsAfterFirst() { + let input = """ + foo(bar: Int, baz: String, quux: Bool) + """ + let output = """ + foo(bar: Int, + baz: String, + quux: Bool) + """ + let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 20) + testFormatting(for: input, output, rule: .wrapArguments, options: options, + exclude: [.unusedArguments, .wrap]) + } + + // MARK: beforeFirst + + func testClosureInsideParensNotWrappedOntoNextLine() { + let input = "foo({\n bar()\n})" + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.trailingClosures]) + } + + func testNoMangleCommentedLinesWhenWrappingArguments() { + let input = """ + foo(bar: bar + // , + // baz: baz + ) {} + """ + let output = """ + foo( + bar: bar + // , + // baz: baz + ) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testNoMangleCommentedLinesWhenWrappingArgumentsWithNoCommas() { + let input = """ + foo(bar: bar + // baz: baz + ) {} + """ + let output = """ + foo( + bar: bar + // baz: baz + ) {} + """ + let options = FormatOptions(wrapArguments: .beforeFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + // MARK: preserve + + func testWrapArgumentsDoesNotAffectLessThanOperator() { + let input = """ + func foo() { + guard foo < bar.count else { return nil } + } + """ + let options = FormatOptions(wrapArguments: .preserve) + testFormatting(for: input, rule: .wrapArguments, + options: options, exclude: [.wrapConditionalBodies]) + } + + // MARK: - --wrapArguments, --wrapParameter + + // MARK: beforeFirst + + func testNoMistakeTernaryExpressionForArguments() { + let input = """ + (foo ? + bar : + baz) + """ + let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options, + exclude: [.redundantParens]) + } + + // MARK: beforeFirst, maxWidth : string interpolation + + func testNoWrapBeforeFirstArgumentInStringInterpolation() { + let input = """ + "a very long string literal with \\(interpolation) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 40) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapBeforeFirstArgumentInStringInterpolation2() { + let input = """ + "a very long string literal with \\(interpolation) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 50) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapBeforeFirstArgumentInStringInterpolation3() { + let input = """ + "a very long string literal with \\(interpolated, variables) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 40) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapBeforeNestedFirstArgumentInStringInterpolation() { + let input = """ + "a very long string literal with \\(foo(interpolated)) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 45) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapBeforeNestedFirstArgumentInStringInterpolation2() { + let input = """ + "a very long string literal with \\(foo(interpolated, variables)) inside" + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapParameters: .beforeFirst, + maxWidth: 45) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapProtocolFuncParametersBeforeFirst() { + let input = """ + protocol Foo { + public func stringify(_ value: T, label: String) -> (T, String) + } + """ + let output = """ + protocol Foo { + public func stringify( + _ value: T, + label: String + ) -> (T, String) + } + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) + testFormatting(for: input, output, rule: .wrapArguments, + options: options) + } + + // MARK: afterFirst maxWidth : string interpolation + + func testNoWrapAfterFirstArgumentInStringInterpolation() { + let input = """ + "a very long string literal with \\(interpolated) inside" + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapParameters: .afterFirst, + maxWidth: 46) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapAfterFirstArgumentInStringInterpolation2() { + let input = """ + "a very long string literal with \\(interpolated, variables) inside" + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapParameters: .afterFirst, + maxWidth: 50) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testNoWrapAfterNestedFirstArgumentInStringInterpolation() { + let input = """ + "a very long string literal with \\(foo(interpolated, variables)) inside" + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapParameters: .afterFirst, + maxWidth: 55) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // macros + + func testWrapMacroParametersBeforeFirst() { + let input = """ + @freestanding(expression) + public macro stringify(_ value: T, label: String) -> (T, String) + """ + let output = """ + @freestanding(expression) + public macro stringify( + _ value: T, + label: String + ) -> (T, String) + """ + let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) + testFormatting(for: input, output, rule: .wrapArguments, + options: options) + } + + // MARK: - wrapArguments --wrapCollections + + // MARK: beforeFirst + + func testNoDoubleSpaceAddedToWrappedArray() { + let input = "[ foo,\n bar ]" + let output = "[\n foo,\n bar\n]" + let options = FormatOptions(trailingCommas: false, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .spaceInsideBrackets], + options: options) + } + + func testTrailingCommasAddedToWrappedArray() { + let input = "[foo,\n bar]" + let output = "[\n foo,\n bar,\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testTrailingCommasAddedToWrappedNestedDictionary() { + let input = "[foo: [bar: baz,\n bar2: baz2]]" + let output = "[foo: [\n bar: baz,\n bar2: baz2,\n]]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testTrailingCommasAddedToSingleLineNestedDictionary() { + let input = "[\n foo: [bar: baz, bar2: baz2]]" + let output = "[\n foo: [bar: baz, bar2: baz2],\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testTrailingCommasAddedToWrappedNestedDictionaries() { + let input = "[foo: [bar: baz,\n bar2: baz2],\n foo2: [bar: baz,\n bar2: baz2]]" + let output = "[\n foo: [\n bar: baz,\n bar2: baz2,\n ],\n foo2: [\n bar: baz,\n bar2: baz2,\n ],\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testSpaceAroundEnumValuesInArray() { + let input = "[\n .foo,\n .bar, .baz,\n]" + let options = FormatOptions(wrapCollections: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: beforeFirst maxWidth + + func testWrapCollectionOnOneLineBeforeFirstWidthExceededInChainedFunctionCallAfterCollection() { + let input = """ + let foo = ["bar", "baz"].quux(quuz) + """ + let output2 = """ + let foo = ["bar", "baz"] + .quux(quuz) + """ + let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 26) + testFormatting(for: input, [input, output2], + rules: [.wrapArguments], options: options) + } + + // MARK: afterFirst + + func testTrailingCommaRemovedInWrappedArray() { + let input = "[\n .foo,\n .bar,\n .baz,\n]" + let output = "[.foo,\n .bar,\n .baz]" + let options = FormatOptions(wrapCollections: .afterFirst) + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testNoRemoveLinebreakAfterCommentInElements() { + let input = "[a, // comment\n]" + let options = FormatOptions(wrapCollections: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapCollectionsConsecutiveCodeCommentsNotIndented() { + let input = """ + let a = [foo, + // bar, + // baz, + quux] + """ + let options = FormatOptions(wrapCollections: .afterFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapCollectionsConsecutiveCodeCommentsNotIndentedInWrapBeforeFirst() { + let input = """ + let a = [ + foo, + // bar, + // baz, + quux, + ] + """ + let options = FormatOptions(wrapCollections: .beforeFirst) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: preserve + + func testNoBeforeFirstPreservedAndTrailingCommaIgnoredInMultilineNestedDictionary() { + let input = "[foo: [bar: baz,\n bar2: baz2]]" + let output = "[foo: [bar: baz,\n bar2: baz2]]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testBeforeFirstPreservedAndTrailingCommaAddedInSingleLineNestedDictionary() { + let input = "[\n foo: [bar: baz, bar2: baz2]]" + let output = "[\n foo: [bar: baz, bar2: baz2],\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + func testBeforeFirstPreservedAndTrailingCommaAddedInSingleLineNestedDictionaryWithOneNestedItem() { + let input = "[\n foo: [bar: baz]]" + let output = "[\n foo: [bar: baz],\n]" + let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) + testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], + options: options) + } + + // MARK: - wrapArguments --wrapCollections & --wrapArguments + + // MARK: beforeFirst maxWidth + + func testWrapArgumentsBeforeFirstWhenArgumentsExceedMaxWidthAndArgumentIsCollection() { + let input = """ + foo(bar: ["baz", "quux"], quuz: corge) + """ + let output = """ + foo( + bar: ["baz", "quux"], + quuz: corge + ) + """ + let options = FormatOptions(wrapArguments: .beforeFirst, + wrapCollections: .beforeFirst, + maxWidth: 26) + testFormatting(for: input, [output], + rules: [.wrapArguments], options: options) + } + + // MARK: afterFirst maxWidth + + func testWrapArgumentsAfterFirstWhenArgumentsExceedMaxWidthAndArgumentIsCollection() { + let input = """ + foo(bar: ["baz", "quux"], quuz: corge) + """ + let output = """ + foo(bar: ["baz", "quux"], + quuz: corge) + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapCollections: .beforeFirst, + maxWidth: 26) + testFormatting(for: input, [output], + rules: [.wrapArguments], options: options) + } + + // MARK: - wrapArguments Multiple Wraps On Same Line + + func testWrapAfterFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { + let input = """ + foo.bar(baz: [qux, quux]).quuz([corge: grault], garply: waldo) + """ + let output = """ + foo.bar(baz: [qux, quux]) + .quuz([corge: grault], + garply: waldo) + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapCollections: .afterFirst, + maxWidth: 28) + testFormatting(for: input, [output], + rules: [.wrapArguments, .wrap], options: options) + } + + func testWrapAfterFirstWrapCollectionsBeforeFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { + let input = """ + foo.bar(baz: [qux, quux]).quuz([corge: grault], garply: waldo) + """ + let output = """ + foo.bar(baz: [qux, quux]) + .quuz([corge: grault], + garply: waldo) + """ + let options = FormatOptions(wrapArguments: .afterFirst, + wrapCollections: .beforeFirst, + maxWidth: 28) + testFormatting(for: input, [output], + rules: [.wrapArguments, .wrap], options: options) + } + + func testNoMangleNestedFunctionCalls() { + let input = """ + points.append(.curve( + quadraticBezier(p0.position.x, Double(p1.x), Double(p2.x), t), + quadraticBezier(p0.position.y, Double(p1.y), Double(p2.y), t) + )) + """ + let output = """ + points.append(.curve( + quadraticBezier( + p0.position.x, + Double(p1.x), + Double(p2.x), + t + ), + quadraticBezier( + p0.position.y, + Double(p1.y), + Double(p2.y), + t + ) + )) + """ + let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 40) + testFormatting(for: input, [output], + rules: [.wrapArguments, .wrap], options: options) + } + + func testWrapArguments_typealias_beforeFirst() { + let input = """ + typealias Dependencies = FooProviding & BarProviding & BaazProviding & QuuxProviding + """ + + let output = """ + typealias Dependencies + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 40) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_multipleTypealiases_beforeFirst() { + let input = """ + enum Namespace { + typealias DependenciesA = FooProviding & BarProviding + typealias DependenciesB = BaazProviding & QuuxProviding + } + """ + + let output = """ + enum Namespace { + typealias DependenciesA + = FooProviding + & BarProviding + typealias DependenciesB + = BaazProviding + & QuuxProviding + } + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 45) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_afterFirst() { + let input = """ + typealias Dependencies = FooProviding & BarProviding & BaazProviding & QuuxProviding + """ + + let output = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 40) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_multipleTypealiases_afterFirst() { + let input = """ + enum Namespace { + typealias DependenciesA = FooProviding & BarProviding + typealias DependenciesB = BaazProviding & QuuxProviding + } + """ + + let output = """ + enum Namespace { + typealias DependenciesA = FooProviding + & BarProviding + typealias DependenciesB = BaazProviding + & QuuxProviding + } + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 45) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth() { + let input = """ + typealias Dependencies = FooProviding & BarProviding & BaazProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 100) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently() { + let input = """ + typealias Dependencies = FooProviding & BarProviding & + BaazProviding & QuuxProviding + """ + + let output = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently2() { + let input = """ + enum Namespace { + typealias Dependencies = FooProviding & BarProviding + & BaazProviding & QuuxProviding + } + """ + + let output = """ + enum Namespace { + typealias Dependencies + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + } + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently3() { + let input = """ + typealias Dependencies + = FooProviding & BarProviding & + BaazProviding & QuuxProviding + """ + + let output = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently4() { + let input = """ + typealias Dependencies + = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let output = """ + typealias Dependencies = FooProviding + & BarProviding + & BaazProviding + & QuuxProviding + """ + + let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistentlyWithComment() { + let input = """ + typealias Dependencies = FooProviding & BarProviding // trailing comment 1 + // Inline Comment 1 + & BaazProviding & QuuxProviding // trailing comment 2 + """ + + let output = """ + typealias Dependencies + = FooProviding + & BarProviding // trailing comment 1 + // Inline Comment 1 + & BaazProviding + & QuuxProviding // trailing comment 2 + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_singleTypePreserved() { + let input = """ + typealias Dependencies = FooProviding + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 10) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.wrap]) + } + + func testWrapArguments_typealias_preservesCommentsBetweenTypes() { + let input = """ + typealias Dependencies + // We use `FooProviding` because `FooFeature` depends on `Foo` + = FooProviding + // We use `BarProviding` because `BarFeature` depends on `Bar` + & BarProviding + // We use `BaazProviding` because `BaazFeature` depends on `Baaz` + & BaazProviding + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_preservesCommentsAfterTypes() { + let input = """ + typealias Dependencies + = FooProviding // We use `FooProviding` because `FooFeature` depends on `Foo` + & BarProviding // We use `BarProviding` because `BarFeature` depends on `Bar` + & BaazProviding // We use `BaazProviding` because `BaazFeature` depends on `Baaz` + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + func testWrapArguments_typealias_withAssociatedType() { + let input = """ + typealias Collections = Collection & Collection & Collection & Collection + """ + + let output = """ + typealias Collections + = Collection + & Collection + & Collection + & Collection + """ + + let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 50) + testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) + } + + // MARK: - -return wrap-if-multiline + + func testWrapReturnOnMultilineFunctionDeclaration() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) -> String {} + """ + + let output = """ + func multilineFunction( + foo _: String, + bar _: String) + -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapReturnAndEffectOnMultilineFunctionDeclaration() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) async -> String {} + """ + + let output = """ + func multilineFunction( + foo _: String, + bar _: String) + async -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testDoesntWrapReturnAndEffectOnSingleLineFunctionDeclaration() { + let input = """ + func singleLineFunction() async throws -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testDoesntWrapReturnAndTypedEffectOnSingleLineFunctionDeclaration() { + let input = """ + func singleLineFunction() async throws(Foo) -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapEffectOnMultilineFunctionDeclaration() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) async throws + -> String {} + """ + + let output = """ + func multilineFunction( + foo _: String, + bar _: String) + async throws -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testUnwrapEffectOnMultilineFunctionDeclaration() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) + async throws -> String {} + """ + + let output = """ + func multilineFunction( + foo _: String, + bar _: String) async throws + -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .never + ) + + testFormatting(for: input, output, rule: .wrapArguments, options: options) + } + + func testWrapArgumentsDoesntBreakFunctionDeclaration_issue_1776() { + let input = """ + struct OpenAPIController: RouteCollection { + let info = InfoObject(title: "Swagger {{cookiecutter.service_name}} - OpenAPI", + description: "{{cookiecutter.description}}", + contact: .init(email: "{{cookiecutter.email}}"), + version: Version(0, 0, 1)) + func boot(routes: RoutesBuilder) throws { + routes.get("swagger", "swagger.json") { + $0.application.routes.openAPI(info: info) + } + .excludeFromOpenAPI() + } + } + """ + + let options = FormatOptions(wrapEffects: .never) + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) + } + + func testWrapEffectsNeverPreservesComments() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) + // Comment here between the parameters and effects + async throws -> String {} + """ + + let options = FormatOptions(closingParenPosition: .sameLine, wrapEffects: .never) + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testWrapReturnOnMultilineFunctionDeclarationWithAfterFirst() { + let input = """ + func multilineFunction(foo _: String, + bar _: String) -> String {} + """ + + let output = """ + func multilineFunction(foo _: String, + bar _: String) + -> String {} + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting( + for: input, output, rule: .wrapArguments, options: options, + exclude: [.indent] + ) + } + + func testWrapReturnOnMultilineThrowingFunctionDeclarationWithAfterFirst() { + let input = """ + func multilineFunction(foo _: String, + bar _: String) throws -> String {} + """ + + let output = """ + func multilineFunction(foo _: String, + bar _: String) throws + -> String {} + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting( + for: input, output, rule: .wrapArguments, options: options, + exclude: [.indent] + ) + } + + func testWrapReturnAndEffectOnMultilineThrowingFunctionDeclarationWithAfterFirst() { + let input = """ + func multilineFunction(foo _: String, + bar _: String) throws -> String {} + """ + + let output = """ + func multilineFunction(foo _: String, + bar _: String) + throws -> String {} + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + + testFormatting( + for: input, output, rule: .wrapArguments, options: options, + exclude: [.indent] + ) + } + + func testDoesntWrapReturnOnMultilineThrowingFunction() { + let input = """ + func multilineFunction(foo _: String, + bar _: String) + throws -> String {} + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting( + for: input, rule: .wrapArguments, options: options, + exclude: [.indent] + ) + } + + func testDoesntWrapReturnOnSingleLineFunctionDeclaration() { + let input = """ + func multilineFunction(foo _: String, bar _: String) -> String {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineArray() { + let input = """ + final class Foo { + private static let array = [ + "one", + ] + + private func singleLine() -> String {} + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineMethodCall() { + let input = """ + public final class Foo { + public var multiLineMethodCall = Foo.multiLineMethodCall( + bar: bar, + baz: baz) + + func singleLine() -> String { + return "method body" + } + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline + ) + + testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) + } + + func testPreserveReturnOnMultilineFunctionDeclarationByDefault() { + let input = """ + func multilineFunction( + foo _: String, + bar _: String) -> String + {} + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + + testFormatting(for: input, rule: .wrapArguments, options: options) + } + + // MARK: wrapConditions before-first + + func testWrapConditionsBeforeFirstPreservesMultilineStatements() { + let input = """ + if + let unwrappedFoo = Foo( + bar: bar, + baz: baz), + unwrappedFoo.elements + .compactMap({ $0 }) + .filter({ + if $0.matchesCondition { + return true + } else { + return false + } + }).isEmpty, + let bar = unwrappedFoo.bar, + let baz = unwrappedFoo.bar? + .first(where: { $0.isBaz }), + let unwrappedFoo2 = Foo( + bar: bar2, + baz: baz2), + let quux = baz.quux + {} + """ + testFormatting( + for: input, rules: [.wrapArguments, .indent], + options: FormatOptions(closingParenPosition: .sameLine, wrapConditions: .beforeFirst), + exclude: [.propertyType] + ) + } + + func testWrapConditionsBeforeFirst() { + let input = """ + if let foo = foo, + let bar = bar, + foo == bar {} + + else if foo != bar, + let quux = quux {} + + if let baz = baz {} + + guard baz.filter({ $0 == foo }), + let bar = bar else {} + + while let foo = foo, + let bar = bar {} + """ + let output = """ + if + let foo = foo, + let bar = bar, + foo == bar {} + + else if + foo != bar, + let quux = quux {} + + if let baz = baz {} + + guard + baz.filter({ $0 == foo }), + let bar = bar else {} + + while + let foo = foo, + let bar = bar {} + """ + testFormatting( + for: input, output, rule: .wrapArguments, + options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), + exclude: [.wrapConditionalBodies] + ) + } + + func testWrapConditionsBeforeFirstWhereShouldPreserveExisting() { + let input = """ + else {} + + else + {} + + if foo == bar + {} + + guard let foo = bar else + {} + + guard let foo = bar + else {} + """ + testFormatting( + for: input, rule: .wrapArguments, + options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), + exclude: [.elseOnSameLine, .wrapConditionalBodies] + ) + } + + func testWrapConditionsAfterFirst() { + let input = """ + if + let foo = foo, + let bar = bar, + foo == bar {} + + else if + foo != bar, + let quux = quux {} + + else {} + + if let baz = baz {} + + guard + baz.filter({ $0 == foo }), + let bar = bar else {} + + while + let foo = foo, + let bar = bar {} + """ + let output = """ + if let foo = foo, + let bar = bar, + foo == bar {} + + else if foo != bar, + let quux = quux {} + + else {} + + if let baz = baz {} + + guard baz.filter({ $0 == foo }), + let bar = bar else {} + + while let foo = foo, + let bar = bar {} + """ + testFormatting( + for: input, output, rule: .wrapArguments, + options: FormatOptions(indent: " ", wrapConditions: .afterFirst), + exclude: [.wrapConditionalBodies] + ) + } + + func testWrapConditionsAfterFirstWhenFirstLineIsComment() { + let input = """ + guard + // Apply this rule to any function-like declaration + ["func", "init", "subscript"].contains(keyword.string), + // Opaque generic parameter syntax is only supported in Swift 5.7+ + formatter.options.swiftVersion >= "5.7", + // Validate that this is a generic method using angle bracket syntax, + // and find the indices for all of the key tokens + let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), + let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), + let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), + let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), + genericSignatureStartIndex < paramListStartIndex, + genericSignatureEndIndex < paramListStartIndex, + let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), + let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) + else { return } + """ + let output = """ + guard // Apply this rule to any function-like declaration + ["func", "init", "subscript"].contains(keyword.string), + // Opaque generic parameter syntax is only supported in Swift 5.7+ + formatter.options.swiftVersion >= "5.7", + // Validate that this is a generic method using angle bracket syntax, + // and find the indices for all of the key tokens + let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), + let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), + let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), + let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), + genericSignatureStartIndex < paramListStartIndex, + genericSignatureEndIndex < paramListStartIndex, + let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), + let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) + else { return } + """ + testFormatting( + for: input, [output], rules: [.wrapArguments, .indent], + options: FormatOptions(wrapConditions: .afterFirst), + exclude: [.wrapConditionalBodies] + ) + } + + // MARK: conditionsWrap auto + + func testConditionsWrapAutoForLongGuard() { + let input = """ + guard let foo = foo, let bar = bar, let third = third else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoForLongGuardWithoutChanges() { + let input = """ + guard let foo = foo, let bar = bar, let third = third else {} + """ + testFormatting( + for: input, + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120) + ) + } + + func testConditionsWrapAutoForMultilineGuard() { + let input = """ + guard let foo = foo, + let bar = bar, let third = third else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments, .indent], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoOptionForGuardStyledAsBeforeArgument() { + let input = """ + guard + let foo = foo, + let bar = bar, + let third = third + else {} + + guard + let foo = foo, + let bar = bar, + let third = third + else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoOptionForGuardWhenElseOnNewLine() { + let input = """ + guard let foo = foo, let bar = bar, let third = third + else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoOptionForGuardWhenElseOnNewLineAndNotAligned() { + let input = """ + guard let foo = foo, let bar = bar, let third = third + else {} + + guard let foo = foo, let bar = bar, let third = third + + else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) + ) + } + + func testConditionsWrapAutoOptionForGuardInMethod() { + let input = """ + func doSmth() { + let a = smth as? SmthElse + + guard + let foo = foo, + let bar = bar, + let third = third + else { + return nil + } + + let value = a.doSmth() + } + """ + + let output = """ + func doSmth() { + let a = smth as? SmthElse + + guard let foo = foo, + let bar = bar, + let third = third + else { + return nil + } + + let value = a.doSmth() + } + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120) + ) + } + + func testConditionsWrapAutoOptionForIfInsideMethod() { + let input = """ + func doSmth() { + let a = smth as? SmthElse + + if + let foo = foo, + let bar = bar, + let third = third { + return nil + } + + let value = a.doSmth() + } + """ + + let output = """ + func doSmth() { + let a = smth as? SmthElse + + if let foo = foo, + let bar = bar, + let third = third { + return nil + } + + let value = a.doSmth() + } + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120), + exclude: [.wrapMultilineStatementBraces] + ) + } + + func testConditionsWrapAutoOptionForLongIf() { + let input = """ + if let foo = foo, let bar = bar, let third = third {} + """ + + let output = """ + if let foo = foo, + let bar = bar, + let third = third {} + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments, .indent], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 25) + ) + } + + func testConditionsWrapAutoOptionForLongMultilineIf() { + let input = """ + if let foo = foo, + let bar = bar, let third = third {} + """ + + let output = """ + if let foo = foo, + let bar = bar, + let third = third {} + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments, .indent], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 25) + ) + } + + // MARK: conditionsWrap always + + func testConditionWrapAlwaysOptionForLongGuard() { + let input = """ + guard let foo = foo, let bar = bar, let third = third else {} + """ + + let output = """ + guard let foo = foo, + let bar = bar, + let third = third + else {} + """ + + testFormatting( + for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .always, maxWidth: 120) + ) + } +} diff --git a/Tests/Rules/WrapAttributesTests.swift b/Tests/Rules/WrapAttributesTests.swift new file mode 100644 index 000000000..c01a46d56 --- /dev/null +++ b/Tests/Rules/WrapAttributesTests.swift @@ -0,0 +1,645 @@ +// +// WrapAttributesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapAttributesTests: XCTestCase { + func testPreserveWrappedFuncAttributeByDefault() { + let input = """ + @objc + func foo() {} + """ + testFormatting(for: input, rule: .wrapAttributes) + } + + func testPreserveUnwrappedFuncAttributeByDefault() { + let input = """ + @objc func foo() {} + """ + testFormatting(for: input, rule: .wrapAttributes) + } + + func testWrapFuncAttribute() { + let input = """ + @available(iOS 14.0, *) func foo() {} + """ + let output = """ + @available(iOS 14.0, *) + func foo() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapInitAttribute() { + let input = """ + @available(iOS 14.0, *) init() {} + """ + let output = """ + @available(iOS 14.0, *) + init() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testMultipleAttributesNotSeparated() { + let input = """ + @objc @IBAction func foo {} + """ + let output = """ + @objc @IBAction + func foo {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, + options: options, exclude: [.redundantObjc]) + } + + func testFuncAttributeStaysWrapped() { + let input = """ + @available(iOS 14.0, *) + func foo() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testUnwrapFuncAttribute() { + let input = """ + @available(iOS 14.0, *) + func foo() {} + """ + let output = """ + @available(iOS 14.0, *) func foo() {} + """ + let options = FormatOptions(funcAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testUnwrapFuncAttribute2() { + let input = """ + class MyClass: NSObject { + @objc + func myFunction() { + print("Testing") + } + } + """ + let output = """ + class MyClass: NSObject { + @objc func myFunction() { + print("Testing") + } + } + """ + let options = FormatOptions(funcAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testFuncAttributeStaysUnwrapped() { + let input = """ + @objc func foo() {} + """ + let options = FormatOptions(funcAttributes: .sameLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testVarAttributeIsNotWrapped() { + let input = """ + @IBOutlet var foo: UIView? + + @available(iOS 14.0, *) + func foo() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapTypeAttribute() { + let input = """ + @available(iOS 14.0, *) class Foo {} + """ + let output = """ + @available(iOS 14.0, *) + class Foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting( + for: input, + output, + rule: .wrapAttributes, + options: options + ) + } + + func testWrapExtensionAttribute() { + let input = """ + @available(iOS 14.0, *) extension Foo {} + """ + let output = """ + @available(iOS 14.0, *) + extension Foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting( + for: input, + output, + rule: .wrapAttributes, + options: options + ) + } + + func testTypeAttributeStaysWrapped() { + let input = """ + @available(iOS 14.0, *) + struct Foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testUnwrapTypeAttribute() { + let input = """ + @available(iOS 14.0, *) + enum Foo {} + """ + let output = """ + @available(iOS 14.0, *) enum Foo {} + """ + let options = FormatOptions(typeAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testTypeAttributeStaysUnwrapped() { + let input = """ + @objc class Foo {} + """ + let options = FormatOptions(typeAttributes: .sameLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testTestableImportIsNotWrapped() { + let input = """ + @testable import Framework + + @available(iOS 14.0, *) + class Foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testModifiersDontAffectAttributeWrapping() { + let input = """ + @objc override public func foo {} + """ + let output = """ + @objc + override public func foo {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testClassFuncAttributeTreatedAsFunction() { + let input = """ + @objc class func foo {} + """ + let output = """ + @objc + class func foo {} + """ + let options = FormatOptions(funcAttributes: .prevLine, fragment: true) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testClassFuncAttributeNotTreatedAsType() { + let input = """ + @objc class func foo {} + """ + let options = FormatOptions(typeAttributes: .prevLine, fragment: true) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testClassAttributeNotMistakenForClassLet() { + let input = """ + @objc final class MyClass: NSObject {} + let myClass = MyClass() + """ + let output = """ + @objc + final class MyClass: NSObject {} + let myClass = MyClass() + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testClassImportAttributeNotTreatedAsType() { + let input = """ + @testable import class Framework.Foo + """ + let options = FormatOptions(typeAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapPrivateSetComputedVarAttributes() { + let input = """ + @objc private(set) dynamic var foo = Foo() + """ + let output = """ + @objc + private(set) dynamic var foo = Foo() + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testWrapPrivateSetVarAttributes() { + let input = """ + @objc private(set) dynamic var foo = Foo() + """ + let output = """ + @objc + private(set) dynamic var foo = Foo() + """ + let options = FormatOptions(varAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testDontWrapPrivateSetVarAttributes() { + let input = """ + @objc + private(set) dynamic var foo = Foo() + """ + let output = """ + @objc private(set) dynamic var foo = Foo() + """ + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testWrapConvenienceInitAttribute() { + let input = """ + @objc public convenience init() {} + """ + let output = """ + @objc + public convenience init() {} + """ + let options = FormatOptions(funcAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapPropertyWrapperAttributeVarAttributes() { + let input = """ + @OuterType.Wrapper var foo: Int + """ + let output = """ + @OuterType.Wrapper + var foo: Int + """ + let options = FormatOptions(varAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapPropertyWrapperAttribute() { + let input = """ + @OuterType.Wrapper var foo: Int + """ + let output = """ + @OuterType.Wrapper + var foo: Int + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testDontWrapPropertyWrapperAttribute() { + let input = """ + @OuterType.Wrapper + var foo: Int + """ + let output = """ + @OuterType.Wrapper var foo: Int + """ + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapGenericPropertyWrapperAttribute() { + let input = """ + @OuterType.Generic var foo: WrappedType + """ + let output = """ + @OuterType.Generic + var foo: WrappedType + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapGenericPropertyWrapperAttribute2() { + let input = """ + @OuterType.Generic.Foo var foo: WrappedType + """ + let output = """ + @OuterType.Generic.Foo + var foo: WrappedType + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testAttributeOnComputedProperty() { + let input = """ + extension SectionContainer: ContentProviding where Section: ContentProviding { + @_disfavoredOverload + public var content: Section.Content { + section.content + } + } + """ + + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapAvailableAttributeUnderMaxWidth() { + let input = """ + @available(*, unavailable, message: "This property is deprecated.") + var foo: WrappedType + """ + let output = """ + @available(*, unavailable, message: "This property is deprecated.") var foo: WrappedType + """ + let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testDoesntWrapAvailableAttributeWithLongMessage() { + // Unwrapping this attribute would just cause it to wrap in a different way: + // + // @available( + // *, + // unavailable, + // message: "This property is deprecated. It has a really long message." + // ) var foo: WrappedType + // + // so instead leave it un-wrapped to preserve the existing formatting. + let input = """ + @available(*, unavailable, message: "This property is deprecated. It has a really long message.") + var foo: WrappedType + """ + let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testDoesntWrapComplexAttribute() { + let input = """ + @Option( + name: ["myArgument"], + help: "Long help text for my example arg from Swift argument parser") + var foo: WrappedType + """ + let options = FormatOptions(closingParenPosition: .sameLine, varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testDoesntWrapComplexMultilineAttribute() { + let input = """ + @available(*, deprecated, message: "Deprecated!") + var foo: WrappedType + """ + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapsComplexAttribute() { + let input = """ + @available(*, deprecated, message: "Deprecated!") var foo: WrappedType + """ + + let output = """ + @available(*, deprecated, message: "Deprecated!") + var foo: WrappedType + """ + let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testWrapAttributesIndentsLineCorrectly() { + let input = """ + class Foo { + @objc var foo = Foo() + } + """ + let output = """ + class Foo { + @objc + var foo = Foo() + } + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testComplexAttributesException() { + let input = """ + @Environment(\\.myEnvironmentVar) var foo: Foo + + @SomeCustomAttr(argument: true) var foo: Foo + + @available(*, deprecated) var foo: Foo + """ + + let output = """ + @Environment(\\.myEnvironmentVar) var foo: Foo + + @SomeCustomAttr(argument: true) var foo: Foo + + @available(*, deprecated) + var foo: Foo + """ + + let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine, complexAttributesExceptions: ["@SomeCustomAttr"]) + testFormatting(for: input, output, rule: .wrapAttributes, options: options) + } + + func testMixedComplexAndSimpleAttributes() { + let input = """ + /// Simple attributes stay on a single line: + @State private var warpDriveEnabled: Bool + + @ObservedObject private var lifeSupportService: LifeSupportService + + @Environment(\\.controlPanelStyle) private var controlPanelStyle + + @AppStorage("ControlsConfig") private var controlsConfig: ControlConfiguration + + /// Complex attributes are wrapped: + @AppStorage("ControlPanelState", store: myCustomUserDefaults) private var controlPanelState: ControlPanelState + + @Tweak(name: "Aspect ratio") private var aspectRatio = AspectRatio.stretch + + @available(*, unavailable) var saturn5Builder: Saturn5Builder + + @available(*, unavailable, message: "No longer in production") var saturn5Builder: Saturn5Builder + """ + + let output = """ + /// Simple attributes stay on a single line: + @State private var warpDriveEnabled: Bool + + @ObservedObject private var lifeSupportService: LifeSupportService + + @Environment(\\.controlPanelStyle) private var controlPanelStyle + + @AppStorage("ControlsConfig") private var controlsConfig: ControlConfiguration + + /// Complex attributes are wrapped: + @AppStorage("ControlPanelState", store: myCustomUserDefaults) + private var controlPanelState: ControlPanelState + + @Tweak(name: "Aspect ratio") + private var aspectRatio = AspectRatio.stretch + + @available(*, unavailable) + var saturn5Builder: Saturn5Builder + + @available(*, unavailable, message: "No longer in production") + var saturn5Builder: Saturn5Builder + """ + + let options = FormatOptions(storedVarAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testEscapingClosureNotMistakenForComplexAttribute() { + let input = """ + func foo(_ fooClosure: @escaping () throws -> Void) { + try fooClosure() + } + """ + + let options = FormatOptions(complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testEscapingTypedThrowClosureNotMistakenForComplexAttribute() { + let input = """ + func foo(_ fooClosure: @escaping () throws(Foo) -> Void) { + try fooClosure() + } + """ + + let options = FormatOptions(complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapOrDontWrapMultipleDeclarationsInClass() { + let input = """ + class Foo { + @objc + var foo = Foo() + + @available(*, unavailable) + var bar: Bar + + @available(*, unavailable) + var myComputedFoo: String { + "myComputedFoo" + } + + @Environment(\\.myEnvironmentVar) + var foo + + @State + var myStoredFoo: String = "myStoredFoo" { + didSet { + print(newValue) + } + } + } + """ + let output = """ + class Foo { + @objc var foo = Foo() + + @available(*, unavailable) + var bar: Bar + + @available(*, unavailable) + var myComputedFoo: String { + "myComputedFoo" + } + + @Environment(\\.myEnvironmentVar) var foo + + @State var myStoredFoo: String = "myStoredFoo" { + didSet { + print(newValue) + } + } + } + """ + let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine) + testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) + } + + func testWrapOrDontAttributesInSwiftUIView() { + let input = """ + struct MyView: View { + @State var textContent: String + + var body: some View { + childView + } + + @ViewBuilder + var childView: some View { + Text(verbatim: textContent) + } + } + """ + + let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testWrapAttributesInSwiftUIView() { + let input = """ + struct MyView: View { + @State var textContent: String + @Environment(\\.myEnvironmentVar) var environmentVar + + var body: some View { + childView + } + + @ViewBuilder var childView: some View { + Text(verbatim: textContent) + } + } + """ + + let options = FormatOptions(varAttributes: .sameLine, complexAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } + + func testInlineMainActorAttributeNotWrapped() { + let input = """ + var foo: @MainActor (Foo) -> Void + var bar: @MainActor (Bar) -> Void + """ + let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) + testFormatting(for: input, rule: .wrapAttributes, options: options) + } +} diff --git a/Tests/Rules/WrapConditionalBodiesTests.swift b/Tests/Rules/WrapConditionalBodiesTests.swift new file mode 100644 index 000000000..3e761e73c --- /dev/null +++ b/Tests/Rules/WrapConditionalBodiesTests.swift @@ -0,0 +1,259 @@ +// +// WrapConditionalBodiesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapConditionalBodiesTests: XCTestCase { + func testGuardReturnWraps() { + let input = "guard let foo = bar else { return }" + let output = """ + guard let foo = bar else { + return + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testEmptyGuardReturnWithSpaceDoesNothing() { + let input = "guard let foo = bar else { }" + testFormatting(for: input, rule: .wrapConditionalBodies, + exclude: [.emptyBraces]) + } + + func testEmptyGuardReturnWithoutSpaceDoesNothing() { + let input = "guard let foo = bar else {}" + testFormatting(for: input, rule: .wrapConditionalBodies, + exclude: [.emptyBraces]) + } + + func testGuardReturnWithValueWraps() { + let input = "guard let foo = bar else { return baz }" + let output = """ + guard let foo = bar else { + return baz + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardBodyWithClosingBraceAlreadyOnNewlineWraps() { + let input = """ + guard foo else { return + } + """ + let output = """ + guard foo else { + return + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardContinueWithNoSpacesToCleanupWraps() { + let input = "guard let foo = bar else {continue}" + let output = """ + guard let foo = bar else { + continue + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardReturnWrapsSemicolonDelimitedStatements() { + let input = "guard let foo = bar else { var baz = 0; let boo = 1; fatalError() }" + let output = """ + guard let foo = bar else { + var baz = 0; let boo = 1; fatalError() + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardReturnWrapsSemicolonDelimitedStatementsWithNoSpaces() { + let input = "guard let foo = bar else {var baz=0;let boo=1;fatalError()}" + let output = """ + guard let foo = bar else { + var baz=0;let boo=1;fatalError() + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies, + exclude: [.spaceAroundOperators]) + } + + func testGuardReturnOnNewlineUnchanged() { + let input = """ + guard let foo = bar else { + return + } + """ + testFormatting(for: input, rule: .wrapConditionalBodies) + } + + func testGuardCommentSameLineUnchanged() { + let input = """ + guard let foo = bar else { // Test comment + return + } + """ + testFormatting(for: input, rule: .wrapConditionalBodies) + } + + func testGuardMultilineCommentSameLineUnchanged() { + let input = "guard let foo = bar else { /* Test comment */ return }" + let output = """ + guard let foo = bar else { /* Test comment */ + return + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardTwoMultilineCommentsSameLine() { + let input = "guard let foo = bar else { /* Test comment 1 */ return /* Test comment 2 */ }" + let output = """ + guard let foo = bar else { /* Test comment 1 */ + return /* Test comment 2 */ + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testNestedGuardElseIfStatementsPutOnNewline() { + let input = "guard let foo = bar else { if qux { return quux } else { return quuz } }" + let output = """ + guard let foo = bar else { + if qux { + return quux + } else { + return quuz + } + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testNestedGuardElseGuardStatementPutOnNewline() { + let input = "guard let foo = bar else { guard qux else { return quux } }" + let output = """ + guard let foo = bar else { + guard qux else { + return quux + } + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testGuardWithClosureOnlyWrapsElseBody() { + let input = "guard foo { $0.bar } else { return true }" + let output = """ + guard foo { $0.bar } else { + return true + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testIfElseReturnsWrap() { + let input = "if foo { return bar } else if baz { return qux } else { return quux }" + let output = """ + if foo { + return bar + } else if baz { + return qux + } else { + return quux + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testIfElseBodiesWrap() { + let input = "if foo { bar } else if baz { qux } else { quux }" + let output = """ + if foo { + bar + } else if baz { + qux + } else { + quux + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testIfElsesWithClosuresDontWrapClosures() { + let input = "if foo { $0.bar } { baz } else if qux { $0.quux } { quuz } else { corge }" + let output = """ + if foo { $0.bar } { + baz + } else if qux { $0.quux } { + quuz + } else { + corge + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies) + } + + func testEmptyIfElseBodiesWithSpaceDoNothing() { + let input = "if foo { } else if baz { } else { }" + testFormatting(for: input, rule: .wrapConditionalBodies, + exclude: [.emptyBraces]) + } + + func testEmptyIfElseBodiesWithoutSpaceDoNothing() { + let input = "if foo {} else if baz {} else {}" + testFormatting(for: input, rule: .wrapConditionalBodies, + exclude: [.emptyBraces]) + } + + func testGuardElseBraceStartingOnDifferentLine() { + let input = """ + guard foo else + { return bar } + """ + let output = """ + guard foo else + { + return bar + } + """ + + testFormatting(for: input, output, rule: .wrapConditionalBodies, + exclude: [.braces, .indent, .elseOnSameLine]) + } + + func testIfElseBracesStartingOnDifferentLines() { + let input = """ + if foo + { return bar } + else if baz + { return qux } + else + { return quux } + """ + let output = """ + if foo + { + return bar + } + else if baz + { + return qux + } + else + { + return quux + } + """ + testFormatting(for: input, output, rule: .wrapConditionalBodies, + exclude: [.braces, .indent, .elseOnSameLine]) + } +} diff --git a/Tests/Rules/WrapEnumCasesTests.swift b/Tests/Rules/WrapEnumCasesTests.swift new file mode 100644 index 000000000..039325aa0 --- /dev/null +++ b/Tests/Rules/WrapEnumCasesTests.swift @@ -0,0 +1,229 @@ +// +// WrapEnumCasesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapEnumCasesTests: XCTestCase { + func testMultilineEnumCases() { + let input = """ + enum Enum1: Int { + case a = 0, p = 2, c, d + case e, k + case m(String, String) + } + """ + let output = """ + enum Enum1: Int { + case a = 0 + case p = 2 + case c + case d + case e + case k + case m(String, String) + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases) + } + + func testMultilineEnumCasesWithNestedEnumsDoesNothing() { + let input = """ + public enum SearchTerm: Decodable, Equatable { + case term(name: String) + case category(category: Category) + + enum CodingKeys: String, CodingKey { + case name + case type + case categoryID = "category_id" + case attributes + } + } + """ + testFormatting(for: input, rule: .wrapEnumCases) + } + + func testEnumCaseSplitOverMultipleLines() { + let input = """ + enum Foo { + case bar( + x: String, + y: Int + ), baz + } + """ + let output = """ + enum Foo { + case bar( + x: String, + y: Int + ) + case baz + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases) + } + + func testEnumCasesAlreadyWrappedOntoMultipleLines() { + let input = """ + enum Foo { + case bar, + baz, + quux + } + """ + let output = """ + enum Foo { + case bar + case baz + case quux + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases) + } + + func testEnumCasesIfValuesWithoutValuesDoesNothing() { + let input = """ + enum Foo { + case bar, baz, quux + } + """ + testFormatting(for: input, rule: .wrapEnumCases, + options: FormatOptions(wrapEnumCases: .withValues)) + } + + func testEnumCasesIfValuesWithRawValuesAndNestedEnum() { + let input = """ + enum Foo { + case bar = 1, baz, quux + + enum Foo2 { + case bar, baz, quux + } + } + """ + let output = """ + enum Foo { + case bar = 1 + case baz + case quux + + enum Foo2 { + case bar, baz, quux + } + } + """ + testFormatting( + for: input, + output, + rule: .wrapEnumCases, + options: FormatOptions(wrapEnumCases: .withValues) + ) + } + + func testEnumCasesIfValuesWithAssociatedValues() { + let input = """ + enum Foo { + case bar(a: Int), baz, quux + } + """ + let output = """ + enum Foo { + case bar(a: Int) + case baz + case quux + } + """ + testFormatting( + for: input, + output, + rule: .wrapEnumCases, + options: FormatOptions(wrapEnumCases: .withValues) + ) + } + + func testEnumCasesWithCommentsAlreadyWrappedOntoMultipleLines() { + let input = """ + enum Foo { + case bar, // bar + baz, // baz + quux // quux + } + """ + let output = """ + enum Foo { + case bar // bar + case baz // baz + case quux // quux + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases) + } + + func testNoWrapEnumStatementAllOnOneLine() { + let input = "enum Foo { bar, baz }" + testFormatting(for: input, rule: .wrapEnumCases) + } + + func testNoConfuseIfCaseWithEnum() { + let input = """ + enum Foo { + case foo + case bar(value: [Int]) + } + + func baz() { + if case .foo = foo, + case .bar(let value) = bar, + value.isEmpty + { + print("") + } + } + """ + testFormatting(for: input, rule: .wrapEnumCases, + exclude: [.hoistPatternLet]) + } + + func testNoMangleUnindentedEnumCases() { + let input = """ + enum Foo { + case foo, bar + } + """ + let output = """ + enum Foo { + case foo + case bar + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases, exclude: [.indent]) + } + + func testNoMangleEnumCaseOnOpeningLine() { + let input = """ + enum SortOrder { case + asc(String), desc(String) + } + """ + // TODO: improve formatting here + let output = """ + enum SortOrder { case + asc(String) + case desc(String) + } + """ + testFormatting(for: input, output, rule: .wrapEnumCases, exclude: [.indent]) + } + + func testNoWrapSingleLineEnumCases() { + let input = "enum Foo { case foo, bar }" + testFormatting(for: input, rule: .wrapEnumCases) + } +} diff --git a/Tests/Rules/WrapLoopBodiesTests.swift b/Tests/Rules/WrapLoopBodiesTests.swift new file mode 100644 index 000000000..523424be8 --- /dev/null +++ b/Tests/Rules/WrapLoopBodiesTests.swift @@ -0,0 +1,42 @@ +// +// WrapLoopBodiesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapLoopBodiesTests: XCTestCase { + func testWrapForLoop() { + let input = "for foo in bar { print(foo) }" + let output = """ + for foo in bar { + print(foo) + } + """ + testFormatting(for: input, output, rule: .wrapLoopBodies) + } + + func testWrapWhileLoop() { + let input = "while let foo = bar.next() { print(foo) }" + let output = """ + while let foo = bar.next() { + print(foo) + } + """ + testFormatting(for: input, output, rule: .wrapLoopBodies) + } + + func testWrapRepeatWhileLoop() { + let input = "repeat { print(foo) } while condition()" + let output = """ + repeat { + print(foo) + } while condition() + """ + testFormatting(for: input, output, rule: .wrapLoopBodies) + } +} diff --git a/Tests/Rules/WrapMultilineConditionalAssignmentTests.swift b/Tests/Rules/WrapMultilineConditionalAssignmentTests.swift new file mode 100644 index 000000000..a0e751717 --- /dev/null +++ b/Tests/Rules/WrapMultilineConditionalAssignmentTests.swift @@ -0,0 +1,148 @@ +// +// WrapMultilineConditionalAssignmentTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapMultilineConditionalAssignmentTests: XCTestCase { + func testWrapIfExpressionAssignment() { + let input = """ + let foo = if let bar { + bar + } else { + baaz + } + """ + + let output = """ + let foo = + if let bar { + bar + } else { + baaz + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testUnwrapsAssignmentOperatorInIfExpressionAssignment() { + let input = """ + let foo + = if let bar { + bar + } else { + baaz + } + """ + + let output = """ + let foo = + if let bar { + bar + } else { + baaz + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testUnwrapsAssignmentOperatorInIfExpressionFollowingComment() { + let input = """ + let foo + // In order to unwrap the `=` here it has to move it to + // before the comment, rather than simply unwrapping it. + = if let bar { + bar + } else { + baaz + } + """ + + let output = """ + let foo = + // In order to unwrap the `=` here it has to move it to + // before the comment, rather than simply unwrapping it. + if let bar { + bar + } else { + baaz + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testWrapIfAssignmentWithoutIntroducer() { + let input = """ + property = if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + + let output = """ + property = + if condition { + Foo("foo") + } else { + Foo("bar") + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testWrapSwitchAssignmentWithoutIntroducer() { + let input = """ + property = switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let output = """ + property = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } + + func testWrapSwitchAssignmentWithComplexLValue() { + let input = """ + property?.foo!.bar["baaz"] = switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + let output = """ + property?.foo!.bar["baaz"] = + switch condition { + case true: + Foo("foo") + case false: + Foo("bar") + } + """ + + testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) + } +} diff --git a/Tests/Rules/WrapMultilineStatementBracesTests.swift b/Tests/Rules/WrapMultilineStatementBracesTests.swift new file mode 100644 index 000000000..bf9b97f84 --- /dev/null +++ b/Tests/Rules/WrapMultilineStatementBracesTests.swift @@ -0,0 +1,706 @@ +// +// WrapMultilineStatementBracesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapMultilineStatementBracesTests: XCTestCase { + func testMultilineIfBraceOnNextLine() { + let input = """ + if firstConditional, + array.contains(where: { secondConditional }) { + print("statement body") + } + """ + let output = """ + if firstConditional, + array.contains(where: { secondConditional }) + { + print("statement body") + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) + } + + func testMultilineFuncBraceOnNextLine() { + let input = """ + func method( + foo: Int, + bar: Int) { + print("function body") + } + """ + let output = """ + func method( + foo: Int, + bar: Int) + { + print("function body") + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + exclude: [.wrapArguments, .unusedArguments]) + } + + func testMultilineInitBraceOnNextLine() { + let input = """ + init(foo: Int, + bar: Int) { + print("function body") + } + """ + let output = """ + init(foo: Int, + bar: Int) + { + print("function body") + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + exclude: [.wrapArguments, .unusedArguments]) + } + + func testMultilineForLoopBraceOnNextLine() { + let input = """ + for foo in + [1, 2] { + print(foo) + } + """ + let output = """ + for foo in + [1, 2] + { + print(foo) + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) + } + + func testMultilineForLoopBraceOnNextLine2() { + let input = """ + for foo in [ + 1, + 2, + ] { + print(foo) + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testMultilineForWhereLoopBraceOnNextLine() { + let input = """ + for foo in bar + where foo != baz { + print(foo) + } + """ + let output = """ + for foo in bar + where foo != baz + { + print(foo) + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) + } + + func testMultilineGuardBraceOnNextLine() { + let input = """ + guard firstConditional, + array.contains(where: { secondConditional }) else { + print("statement body") + } + """ + let output = """ + guard firstConditional, + array.contains(where: { secondConditional }) else + { + print("statement body") + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + exclude: [.braces, .elseOnSameLine]) + } + + func testInnerMultilineIfBraceOnNextLine() { + let input = """ + if outerConditional { + if firstConditional, + array.contains(where: { secondConditional }) { + print("statement body") + } + } + """ + let output = """ + if outerConditional { + if firstConditional, + array.contains(where: { secondConditional }) + { + print("statement body") + } + } + """ + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) + } + + func testMultilineIfBraceOnSameLine() { + let input = """ + if let object = Object([ + foo, + bar, + ]) { + print("statement body") + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces, exclude: [.propertyType]) + } + + func testSingleLineIfBraceOnSameLine() { + let input = """ + if firstConditional { + print("statement body") + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testSingleLineGuardBrace() { + let input = """ + guard firstConditional else { + print("statement body") + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testGuardElseOnOwnLineBraceNotWrapped() { + let input = """ + guard let foo = bar, + bar == baz + else { + print("statement body") + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testMultilineGuardClosingBraceOnSameLine() { + let input = """ + guard let foo = bar, + let baz = quux else { return } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces, + exclude: [.wrapConditionalBodies]) + } + + func testMultilineGuardBraceOnSameLineAsElse() { + let input = """ + guard let foo = bar, + let baz = quux + else { + return + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testMultilineClassBrace() { + let input = """ + class Foo: BarProtocol, + BazProtocol + { + init() {} + } + """ + testFormatting(for: input, rule: .wrapMultilineStatementBraces) + } + + func testMultilineClassBraceNotAppliedForXcodeIndentationMode() { + let input = """ + class Foo: BarProtocol, + BazProtocol { + init() {} + } + """ + let options = FormatOptions(xcodeIndentation: true) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, options: options) + } + + func testMultilineBraceAppliedToTrailingClosure_wrapBeforeFirst() { + let input = """ + UIView.animate( + duration: 10, + options: []) { + print() + } + """ + + let output = """ + UIView.animate( + duration: 10, + options: []) + { + print() + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + options: options, exclude: [.indent]) + } + + func testMultilineBraceAppliedToTrailingClosure2_wrapBeforeFirst() { + let input = """ + moveGradient( + to: defaultPosition, + isTouchDown: false, + animated: animated) { + self.isTouchDown = false + } + """ + + let output = """ + moveGradient( + to: defaultPosition, + isTouchDown: false, + animated: animated) + { + self.isTouchDown = false + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .indent, .braces, + ], options: options) + } + + func testMultilineBraceAppliedToGetterBody_wrapBeforeFirst() { + let input = """ + var items = Adaptive.adaptive( + compact: Sizes.horizontalPaddingTiny_8, + regular: Sizes.horizontalPaddingLarge_64) { + didSet { updateAccessoryViewSpacing() } + } + """ + + let output = """ + var items = Adaptive.adaptive( + compact: Sizes.horizontalPaddingTiny_8, + regular: Sizes.horizontalPaddingLarge_64) + { + didSet { updateAccessoryViewSpacing() } + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .indent, + ], options: options, exclude: [.propertyType]) + } + + func testMultilineBraceAppliedToTrailingClosure_wrapAfterFirst() { + let input = """ + UIView.animate(duration: 10, + options: []) { + print() + } + """ + + let output = """ + UIView.animate(duration: 10, + options: []) + { + print() + } + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, + options: options, exclude: [.indent]) + } + + func testMultilineBraceAppliedToGetterBody_wrapAfterFirst() { + let input = """ + var items = Adaptive.adaptive(compact: Sizes.horizontalPaddingTiny_8, + regular: Sizes.horizontalPaddingLarge_64) + { + didSet { updateAccessoryViewSpacing() } + } + """ + + let options = FormatOptions( + wrapArguments: .afterFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, [], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options, exclude: [.propertyType]) + } + + func testMultilineBraceAppliedToSubscriptBody() { + let input = """ + public subscript( + key: Foo) + -> ServerDrivenLayoutContentPresenter? + { + get { foo[key] } + set { foo[key] = newValue } + } + """ + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, + options: options, exclude: [.trailingClosures]) + } + + func testWrapsMultilineStatementConsistently() { + let input = """ + func aFunc( + one _: Int, + two _: Int) -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int) + -> String + { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistentlyWithEffects() { + let input = """ + func aFunc( + one _: Int, + two _: Int) async throws -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int) + async throws -> String + { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistentlyWithArrayReturnType() { + let input = """ + public func aFunc( + one _: Int, + two _: Int) -> [String] { + ["one"] + } + """ + + let output = """ + public func aFunc( + one _: Int, + two _: Int) + -> [String] + { + ["one"] + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistentlyWithComplexGenericReturnType() { + let input = """ + public func aFunc( + one _: Int, + two _: Int) throws -> some Collection { + ["one"] + } + """ + + let output = """ + public func aFunc( + one _: Int, + two _: Int) + throws -> some Collection + { + ["one"] + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistentlyWithTuple() { + let input = """ + public func aFunc( + one: Int, + two: Int) -> (one: String, two: String) { + (one: String(one), two: String(two)) + } + """ + + let output = """ + public func aFunc( + one: Int, + two: Int) + -> (one: String, two: String) + { + (one: String(one), two: String(two)) + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine, + wrapReturnType: .ifMultiline, + wrapEffects: .ifMultiline + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently2() { + let input = """ + func aFunc( + one _: Int, + two _: Int) -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int + ) -> String { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .balanced + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently2_withEffects() { + let input = """ + func aFunc( + one _: Int, + two _: Int) async throws -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int + ) async throws -> String { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .balanced, + wrapEffects: .never + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently2_withTypedEffects() { + let input = """ + func aFunc( + one _: Int, + two _: Int) async throws(Foo) -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int + ) async throws(Foo) -> String { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .balanced, + wrapEffects: .never + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently3() { + let input = """ + func aFunc( + one _: Int, + two _: Int + ) -> String { + "one" + } + """ + + let options = FormatOptions( + // wrapMultilineStatementBraces: true, + wrapArguments: .beforeFirst, + closingParenPosition: .balanced + ) + + testFormatting(for: input, [], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapsMultilineStatementConsistently4() { + let input = """ + func aFunc( + one _: Int, + two _: Int + ) -> String { + "one" + } + """ + + let output = """ + func aFunc( + one _: Int, + two _: Int) -> String + { + "one" + } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, [output], rules: [ + .wrapMultilineStatementBraces, + .wrapArguments, + ], options: options) + } + + func testWrapMultilineStatementConsistently5() { + let input = """ + foo( + one: 1, + two: 2).bar({ _ in + "one" + }) + """ + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, rule: .wrapMultilineStatementBraces, + options: options, exclude: [.trailingClosures]) + } + + func testOpenBraceAfterEqualsInGuardNotWrapped() { + let input = """ + guard + let foo = foo, + let bar: String = { + nil + }() + else { return } + """ + + let options = FormatOptions( + wrapArguments: .beforeFirst, + closingParenPosition: .sameLine + ) + testFormatting(for: input, rules: [.wrapMultilineStatementBraces, .wrap], + options: options, exclude: [.indent, .redundantClosure, .wrapConditionalBodies]) + } +} diff --git a/Tests/Rules/WrapSingleLineCommentsTests.swift b/Tests/Rules/WrapSingleLineCommentsTests.swift new file mode 100644 index 000000000..fa588d56a --- /dev/null +++ b/Tests/Rules/WrapSingleLineCommentsTests.swift @@ -0,0 +1,160 @@ +// +// WrapSingleLineCommentsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapSingleLineCommentsTests: XCTestCase { + func testWrapSingleLineComment() { + let input = """ + // a b cde fgh + """ + let output = """ + // a b + // cde + // fgh + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 6)) + } + + func testWrapSingleLineCommentThatOverflowsByOneCharacter() { + let input = """ + // a b cde fg h + """ + let output = """ + // a b cde fg + // h + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 14)) + } + + func testNoWrapSingleLineCommentThatExactlyFits() { + let input = """ + // a b cde fg h + """ + + testFormatting(for: input, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 15)) + } + + func testWrapSingleLineCommentWithNoLeadingSpace() { + let input = """ + //a b cde fgh + """ + let output = """ + //a b + //cde + //fgh + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 6), + exclude: [.spaceInsideComments]) + } + + func testWrapDocComment() { + let input = """ + /// a b cde fgh + """ + let output = """ + /// a b + /// cde + /// fgh + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 7), exclude: [.docComments]) + } + + func testWrapDocLineCommentWithNoLeadingSpace() { + let input = """ + ///a b cde fgh + """ + let output = """ + ///a b + ///cde + ///fgh + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 6), + exclude: [.spaceInsideComments, .docComments]) + } + + func testWrapSingleLineCommentWithIndent() { + let input = """ + func f() { + // a b cde fgh + let x = 1 + } + """ + let output = """ + func f() { + // a b cde + // fgh + let x = 1 + } + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 14), exclude: [.docComments]) + } + + func testWrapSingleLineCommentAfterCode() { + let input = """ + func f() { + foo.bar() // this comment is much much much too long + } + """ + let output = """ + func f() { + foo.bar() // this comment + // is much much much too + // long + } + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 29), exclude: [.wrap]) + } + + func testWrapDocCommentWithLongURL() { + let input = """ + /// See [Link](https://www.domain.com/pathextension/pathextension/pathextension/pathextension/pathextension/pathextension). + """ + + testFormatting(for: input, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 100), exclude: [.docComments]) + } + + func testWrapDocCommentWithLongURL2() { + let input = """ + /// Link to SDK documentation - https://docs.adyen.com/checkout/3d-secure/native-3ds2/api-integration#collect-the-3d-secure-2-device-fingerprint-from-an-ios-app + """ + + testFormatting(for: input, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 80)) + } + + func testWrapDocCommentWithMultipleLongURLs() { + let input = """ + /// Link to http://a-very-long-url-that-wont-fit-on-one-line, http://another-very-long-url-that-wont-fit-on-one-line + """ + let output = """ + /// Link to http://a-very-long-url-that-wont-fit-on-one-line, + /// http://another-very-long-url-that-wont-fit-on-one-line + """ + + testFormatting(for: input, output, rule: .wrapSingleLineComments, + options: FormatOptions(maxWidth: 40), exclude: [.docComments]) + } +} diff --git a/Tests/Rules/WrapSwitchCasesTests.swift b/Tests/Rules/WrapSwitchCasesTests.swift new file mode 100644 index 000000000..c2113c142 --- /dev/null +++ b/Tests/Rules/WrapSwitchCasesTests.swift @@ -0,0 +1,53 @@ +// +// WrapSwitchCasesTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapSwitchCasesTests: XCTestCase { + func testMultilineSwitchCases() { + let input = """ + func foo() { + switch bar { + case .a(_), .b, "c": + print("") + case .d: + print("") + } + } + """ + let output = """ + func foo() { + switch bar { + case .a(_), + .b, + "c": + print("") + case .d: + print("") + } + } + """ + testFormatting(for: input, output, rule: .wrapSwitchCases) + } + + func testIfAfterSwitchCaseNotWrapped() { + let input = """ + switch foo { + case "foo": + print("") + default: + print("") + } + if let foo = bar, foo != .baz { + throw error + } + """ + testFormatting(for: input, rule: .wrapSwitchCases) + } +} diff --git a/Tests/Rules/WrapTests.swift b/Tests/Rules/WrapTests.swift new file mode 100644 index 000000000..d190126d9 --- /dev/null +++ b/Tests/Rules/WrapTests.swift @@ -0,0 +1,725 @@ +// +// WrapTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class WrapTests: XCTestCase { + func testWrapIfStatement() { + let input = """ + if let foo = foo, let bar = bar, let baz = baz {} + """ + let output = """ + if let foo = foo, + let bar = bar, + let baz = baz {} + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapIfElseStatement() { + let input = """ + if let foo = foo {} else if let bar = bar {} + """ + let output = """ + if let foo = foo {} + else if let bar = + bar {} + """ + let output2 = """ + if let foo = foo {} + else if let bar = + bar {} + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapGuardStatement() { + let input = """ + guard let foo = foo, let bar = bar else { + break + } + """ + let output = """ + guard let foo = foo, + let bar = bar + else { + break + } + """ + let output2 = """ + guard let foo = foo, + let bar = bar + else { + break + } + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapClosure() { + let input = """ + let foo = { () -> Bool in true } + """ + let output = """ + let foo = + { () -> Bool in + true } + """ + let output2 = """ + let foo = + { () -> Bool in + true + } + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapClosure2() { + let input = """ + let foo = { bar, _ in bar } + """ + let output = """ + let foo = + { bar, _ in + bar } + """ + let output2 = """ + let foo = + { bar, _ in + bar + } + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapClosureWithAllmanBraces() { + let input = """ + let foo = { bar, _ in bar } + """ + let output = """ + let foo = + { bar, _ in + bar } + """ + let output2 = """ + let foo = + { bar, _ in + bar + } + """ + let options = FormatOptions(allmanBraces: true, maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapClosure3() { + let input = "let foo = bar { $0.baz }" + let output = """ + let foo = bar { + $0.baz } + """ + let output2 = """ + let foo = bar { + $0.baz + } + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth() { + let input = """ + func testFunc() -> ReturnType { + doSomething() + doSomething() + } + """ + let output = """ + func testFunc() + -> ReturnType { + doSomething() + doSomething() + } + """ + let options = FormatOptions(maxWidth: 25) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidthWithXcodeIndentation() { + let input = """ + func testFunc() -> ReturnType { + doSomething() + doSomething() + } + """ + let output = """ + func testFunc() + -> ReturnType { + doSomething() + doSomething() + } + """ + let output2 = """ + func testFunc() + -> ReturnType { + doSomething() + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 25) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth2() { + let input = """ + func testFunc() -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output = """ + func testFunc() + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 35) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation() { + let input = """ + func testFunc() throws -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output = """ + func testFunc() throws + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output2 = """ + func testFunc() throws + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation2() { + let input = """ + func testFunc() throws(Foo) -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output = """ + func testFunc() throws(Foo) + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let output2 = """ + func testFunc() throws(Foo) + -> (ReturnType, ReturnType2) { + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth3() { + let input = """ + func testFunc() -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc() + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 35) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth3WithXcodeIndentation() { + let input = """ + func testFunc() -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc() + -> (Bool, String) -> String? { + doSomething() + } + """ + let output2 = """ + func testFunc() + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth4() { + let input = """ + func testFunc(_: () -> Void) -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc(_: () -> Void) + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 35) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapFunctionIfReturnTypeExceedsMaxWidth4WithXcodeIndentation() { + let input = """ + func testFunc(_: () -> Void) -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc(_: () -> Void) + -> (Bool, String) -> String? { + doSomething() + } + """ + let output2 = """ + func testFunc(_: () -> Void) + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) + testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapChainedFunctionAfterSubscriptCollection() { + let input = """ + let foo = bar["baz"].quuz() + """ + let output = """ + let foo = bar["baz"] + .quuz() + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapChainedFunctionInSubscriptCollection() { + let input = """ + let foo = bar[baz.quuz()] + """ + let output = """ + let foo = + bar[baz.quuz()] + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapThrowingFunctionIfReturnTypeExceedsMaxWidth() { + let input = """ + func testFunc(_: () -> Void) throws -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc(_: () -> Void) throws + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 42) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testWrapTypedThrowingFunctionIfReturnTypeExceedsMaxWidth() { + let input = """ + func testFunc(_: () -> Void) throws(Foo) -> (Bool, String) -> String? { + doSomething() + } + """ + let output = """ + func testFunc(_: () -> Void) throws(Foo) + -> (Bool, String) -> String? { + doSomething() + } + """ + let options = FormatOptions(maxWidth: 42) + testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) + } + + func testNoWrapInterpolatedStringLiteral() { + let input = """ + "a very long \\(string) literal" + """ + let options = FormatOptions(maxWidth: 20) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testNoWrapAtUnspacedOperator() { + let input = "let foo = bar+baz+quux" + let output = "let foo =\n bar+baz+quux" + let options = FormatOptions(maxWidth: 15) + testFormatting(for: input, output, rule: .wrap, options: options, + exclude: [.spaceAroundOperators]) + } + + func testNoWrapAtUnspacedEquals() { + let input = "let foo=bar+baz+quux" + let options = FormatOptions(maxWidth: 15) + testFormatting(for: input, rule: .wrap, options: options, + exclude: [.spaceAroundOperators]) + } + + func testNoWrapSingleParameter() { + let input = "let fooBar = try unkeyedContainer.decode(FooBar.self)" + let output = """ + let fooBar = try unkeyedContainer + .decode(FooBar.self) + """ + let options = FormatOptions(maxWidth: 50) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapSingleParameter() { + let input = "let fooBar = try unkeyedContainer.decode(FooBar.self)" + let output = """ + let fooBar = try unkeyedContainer.decode( + FooBar.self + ) + """ + let options = FormatOptions(maxWidth: 50, noWrapOperators: [".", "="]) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapFunctionArrow() { + let input = "func foo() -> Int {}" + let output = """ + func foo() + -> Int {} + """ + let options = FormatOptions(maxWidth: 14) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testNoWrapFunctionArrow() { + let input = "func foo() -> Int {}" + let output = """ + func foo( + ) -> Int {} + """ + let options = FormatOptions(maxWidth: 14, noWrapOperators: ["->"]) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testNoCrashWrap() { + let input = """ + struct Foo { + func bar(a: Set, c: D) {} + } + """ + let output = """ + struct Foo { + func bar( + a: Set< + B + >, + c: D + ) {} + } + """ + let options = FormatOptions(maxWidth: 10) + testFormatting(for: input, output, rule: .wrap, options: options, + exclude: [.unusedArguments]) + } + + func testNoCrashWrap2() { + let input = """ + struct Test { + func webView(_: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + authenticationChallengeProcessor.process(challenge: challenge, completionHandler: completionHandler) + } + } + """ + let output = """ + struct Test { + func webView( + _: WKWebView, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, + URLCredential?) -> Void + ) { + authenticationChallengeProcessor.process( + challenge: challenge, + completionHandler: completionHandler + ) + } + } + """ + let options = FormatOptions(wrapParameters: .preserve, maxWidth: 80) + testFormatting(for: input, output, rule: .wrap, options: options, + exclude: [.indent, .wrapArguments]) + } + + func testWrapColorLiteral() throws { + let input = """ + button.setTitleColor(#colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1), for: .normal) + """ + let options = FormatOptions(maxWidth: 80, assetLiteralWidth: .visualWidth) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testWrapImageLiteral() { + let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" + let options = FormatOptions(maxWidth: 40, assetLiteralWidth: .visualWidth) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testNoWrapBeforeFirstArgumentInSingleLineStringInterpolation() { + let input = """ + "a very long string literal with \\(interpolation) inside" + """ + let options = FormatOptions(maxWidth: 40) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testWrapBeforeFirstArgumentInMultineStringInterpolation() { + let input = """ + \""" + a very long string literal with \\(interpolation) inside + \""" + """ + let output = """ + \""" + a very long string literal with \\( + interpolation + ) inside + \""" + """ + let options = FormatOptions(maxWidth: 40) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + // ternary expressions + + func testWrapSimpleTernaryOperator() { + let input = """ + let foo = fooCondition ? longValueThatContainsFoo : longValueThatContainsBar + """ + + let output = """ + let foo = fooCondition + ? longValueThatContainsFoo + : longValueThatContainsBar + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testRewrapsSimpleTernaryOperator() { + let input = """ + let foo = fooCondition ? longValueThatContainsFoo : + longValueThatContainsBar + """ + + let output = """ + let foo = fooCondition + ? longValueThatContainsFoo + : longValueThatContainsBar + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapComplexTernaryOperator() { + let input = """ + let foo = fooCondition ? Foo(property: value) : barContainer.getBar(using: barProvider) + """ + + let output = """ + let foo = fooCondition + ? Foo(property: value) + : barContainer.getBar(using: barProvider) + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testRewrapsComplexTernaryOperator() { + let input = """ + let foo = fooCondition ? Foo(property: value) : + barContainer.getBar(using: barProvider) + """ + + let output = """ + let foo = fooCondition + ? Foo(property: value) + : barContainer.getBar(using: barProvider) + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapsSimpleNestedTernaryOperator() { + let input = """ + let foo = fooCondition ? (barCondition ? a : b) : (baazCondition ? c : d) + """ + + let output = """ + let foo = fooCondition + ? (barCondition ? a : b) + : (baazCondition ? c : d) + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapsDoubleNestedTernaryOperation() { + let input = """ + let foo = fooCondition ? barCondition ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult + """ + + let output = """ + let foo = fooCondition + ? barCondition + ? longTrueBarResult + : longFalseBarResult + : baazCondition + ? longTrueBaazResult + : longFalseBaazResult + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testWrapsTripleNestedTernaryOperation() { + let input = """ + let foo = fooCondition ? barCondition ? quuxCondition ? longTrueQuuxResult : longFalseQuuxResult : barCondition2 ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult + """ + + let output = """ + let foo = fooCondition + ? barCondition + ? quuxCondition + ? longTrueQuuxResult + : longFalseQuuxResult + : barCondition2 + ? longTrueBarResult + : longFalseBarResult + : baazCondition + ? longTrueBaazResult + : longFalseBaazResult + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testNoWrapTernaryWrappedWithinChildExpression() { + let input = """ + func foo() { + return _skipString(string) ? .token( + string, Location(source: input, range: startIndex ..< index) + ) : nil + } + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testNoWrapTernaryWrappedWithinChildExpression2() { + let input = """ + let types: [PolygonType] = plane.isEqual(to: plane) ? [] : vertices.map { + let t = plane.normal.dot($0.position) - plane.w + let type: PolygonType = (t < -epsilon) ? .back : (t > epsilon) ? .front : .coplanar + polygonType = PolygonType(rawValue: polygonType.rawValue | type.rawValue)! + return type + } + """ + + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testNoWrapTernaryInsideStringLiteral() { + let input = """ + "\\(true ? "Some string literal" : "Some other string")" + """ + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) + testFormatting(for: input, rule: .wrap, options: options) + } + + func testWrapTernaryInsideMultilineStringLiteral() { + let input = """ + let foo = \""" + \\(true ? "Some string literal" : "Some other string")" + \""" + """ + let output = """ + let foo = \""" + \\(true + ? "Some string literal" + : "Some other string")" + \""" + """ + let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) + testFormatting(for: input, output, rule: .wrap, options: options) + } + + func testNoCrashWrap3() throws { + let input = """ + override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { + let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext + context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size + return context + } + """ + let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 100) + let rules: [FormatRule] = [.wrap, .wrapArguments] + XCTAssertNoThrow(try format(input, rules: rules, options: options)) + } + + func testErrorNotReportedOnBlankLineAfterWrap() throws { + let input = """ + [ + abagdiasiudbaisndoanosdasdasdasdasdnaosnooanso(), + + bar(), + ] + """ + let options = FormatOptions(truncateBlankLines: false, maxWidth: 40) + let changes = try lint(input, rules: [.wrap, .indent], options: options) + XCTAssertEqual(changes, [.init(line: 2, rule: .wrap, filePath: nil)]) + } +} diff --git a/Tests/Rules/YodaConditionsTests.swift b/Tests/Rules/YodaConditionsTests.swift new file mode 100644 index 000000000..139fc9a6d --- /dev/null +++ b/Tests/Rules/YodaConditionsTests.swift @@ -0,0 +1,293 @@ +// +// YodaConditionsTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class YodaConditionsTests: XCTestCase { + func testNumericLiteralEqualYodaCondition() { + let input = "5 == foo" + let output = "foo == 5" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNumericLiteralGreaterYodaCondition() { + let input = "5.1 > foo" + let output = "foo < 5.1" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testStringLiteralNotEqualYodaCondition() { + let input = "\"foo\" != foo" + let output = "foo != \"foo\"" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNilNotEqualYodaCondition() { + let input = "nil != foo" + let output = "foo != nil" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testTrueNotEqualYodaCondition() { + let input = "true != foo" + let output = "foo != true" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testEnumCaseNotEqualYodaCondition() { + let input = ".foo != foo" + let output = "foo != .foo" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testArrayLiteralNotEqualYodaCondition() { + let input = "[5, 6] != foo" + let output = "foo != [5, 6]" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNestedArrayLiteralNotEqualYodaCondition() { + let input = "[5, [6, 7]] != foo" + let output = "foo != [5, [6, 7]]" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testDictionaryLiteralNotEqualYodaCondition() { + let input = "[foo: 5, bar: 6] != foo" + let output = "foo != [foo: 5, bar: 6]" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testSubscriptNotTreatedAsYodaCondition() { + let input = "foo[5] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfParenthesizedExpressionNotTreatedAsYodaCondition() { + let input = "(foo + bar)[5] != baz" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfUnwrappedValueNotTreatedAsYodaCondition() { + let input = "foo![5] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { + let input = "foo /* foo */ [5] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfCollectionNotTreatedAsYodaCondition() { + let input = "[foo][5] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfTrailingClosureNotTreatedAsYodaCondition() { + let input = "foo { [5] }[0] != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testSubscriptOfRhsNotMangledInYodaCondition() { + let input = "[1] == foo[0]" + let output = "foo[0] == [1]" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testTupleYodaCondition() { + let input = "(5, 6) != bar" + let output = "bar != (5, 6)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testLabeledTupleYodaCondition() { + let input = "(foo: 5, bar: 6) != baz" + let output = "baz != (foo: 5, bar: 6)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNestedTupleYodaCondition() { + let input = "(5, (6, 7)) != baz" + let output = "baz != (5, (6, 7))" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testFunctionCallNotTreatedAsYodaCondition() { + let input = "foo(5) != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testCallOfParenthesizedExpressionNotTreatedAsYodaCondition() { + let input = "(foo + bar)(5) != baz" + testFormatting(for: input, rule: .yodaConditions) + } + + func testCallOfUnwrappedValueNotTreatedAsYodaCondition() { + let input = "foo!(5) != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testCallOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { + let input = "foo /* foo */ (5) != bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testCallOfRhsNotMangledInYodaCondition() { + let input = "(1, 2) == foo(0)" + let output = "foo(0) == (1, 2)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testTrailingClosureOnRhsNotMangledInYodaCondition() { + let input = "(1, 2) == foo { $0 }" + let output = "foo { $0 } == (1, 2)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionInIfStatement() { + let input = "if 5 != foo {}" + let output = "if foo != 5 {}" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testSubscriptYodaConditionInIfStatementWithBraceOnNextLine() { + let input = "if [0] == foo.bar[0]\n{ baz() }" + let output = "if foo.bar[0] == [0]\n{ baz() }" + testFormatting(for: input, output, rule: .yodaConditions, + exclude: [.wrapConditionalBodies]) + } + + func testYodaConditionInSecondClauseOfIfStatement() { + let input = "if foo, 5 != bar {}" + let output = "if foo, bar != 5 {}" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionInExpression() { + let input = "let foo = 5 < bar\nbaz()" + let output = "let foo = bar > 5\nbaz()" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionInExpressionWithTrailingClosure() { + let input = "let foo = 5 < bar { baz() }" + let output = "let foo = bar { baz() } > 5" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionInFunctionCall() { + let input = "foo(5 < bar)" + let output = "foo(bar > 5)" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testYodaConditionFollowedByExpression() { + let input = "5 == foo + 6" + let output = "foo + 6 == 5" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testPrefixExpressionYodaCondition() { + let input = "!false == foo" + let output = "foo == !false" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testPrefixExpressionYodaCondition2() { + let input = "true == !foo" + let output = "!foo == true" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testPostfixExpressionYodaCondition() { + let input = "5<*> == foo" + let output = "foo == 5<*>" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testDoublePostfixExpressionYodaCondition() { + let input = "5!! == foo" + let output = "foo == 5!!" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testPostfixExpressionNonYodaCondition() { + let input = "5 == 5<*>" + testFormatting(for: input, rule: .yodaConditions) + } + + func testPostfixExpressionNonYodaCondition2() { + let input = "5<*> == 5" + testFormatting(for: input, rule: .yodaConditions) + } + + func testStringEqualsStringNonYodaCondition() { + let input = "\"foo\" == \"bar\"" + testFormatting(for: input, rule: .yodaConditions) + } + + func testConstantAfterNullCoalescingNonYodaCondition() { + let input = "foo.last ?? -1 < bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testNoMangleYodaConditionFollowedByAndOperator() { + let input = "5 <= foo && foo <= 7" + let output = "foo >= 5 && foo <= 7" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionFollowedByOrOperator() { + let input = "5 <= foo || foo <= 7" + let output = "foo >= 5 || foo <= 7" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionFollowedByParentheses() { + let input = "0 <= (foo + bar)" + let output = "(foo + bar) >= 0" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionInTernary() { + let input = "let z = 0 < y ? 3 : 4" + let output = "let z = y > 0 ? 3 : 4" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionInTernary2() { + let input = "let z = y > 0 ? 0 < x : 4" + let output = "let z = y > 0 ? x > 0 : 4" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testNoMangleYodaConditionInTernary3() { + let input = "let z = y > 0 ? 3 : 0 < x" + let output = "let z = y > 0 ? 3 : x > 0" + testFormatting(for: input, output, rule: .yodaConditions) + } + + func testKeyPathNotMangledAndNotTreatedAsYodaCondition() { + let input = "\\.foo == bar" + testFormatting(for: input, rule: .yodaConditions) + } + + func testEnumCaseLessThanEnumCase() { + let input = "XCTAssertFalse(.never < .never)" + testFormatting(for: input, rule: .yodaConditions) + } + + // yodaSwap = literalsOnly + + func testNoSwapYodaDotMember() { + let input = "foo(where: .bar == baz)" + let options = FormatOptions(yodaSwap: .literalsOnly) + testFormatting(for: input, rule: .yodaConditions, options: options) + } +} diff --git a/Tests/RulesTests+General.swift b/Tests/RulesTests+General.swift deleted file mode 100644 index 76805a9d6..000000000 --- a/Tests/RulesTests+General.swift +++ /dev/null @@ -1,1355 +0,0 @@ -// -// RulesTests+General.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 02/10/2021. -// Copyright © 2021 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -private enum TestDateFormat: String { - case basic = "yyyy-MM-dd" - case time = "HH:mmZZZZZ" - case timestamp = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ" -} - -private func createTestDate( - _ input: String, - _ format: TestDateFormat = .basic -) -> Date { - let formatter = DateFormatter() - formatter.dateFormat = format.rawValue - formatter.timeZone = .current - - return formatter.date(from: input)! -} - -class GeneralTests: RulesTests { - // MARK: - initCoderUnavailable - - func testInitCoderUnavailableEmptyFunction() { - let input = """ - struct A: UIView { - required init?(coder aDecoder: NSCoder) {} - } - """ - let output = """ - struct A: UIView { - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) {} - } - """ - testFormatting(for: input, output, rule: .initCoderUnavailable, - exclude: [.unusedArguments]) - } - - func testInitCoderUnavailableFatalErrorNilDisabled() { - let input = """ - extension Module { - final class A: UIView { - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - } - """ - let output = """ - extension Module { - final class A: UIView { - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - } - """ - let options = FormatOptions(initCoderNil: false) - testFormatting(for: input, output, rule: .initCoderUnavailable, options: options) - } - - func testInitCoderUnavailableFatalErrorNilEnabled() { - let input = """ - extension Module { - final class A: UIView { - required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - } - """ - let output = """ - extension Module { - final class A: UIView { - @available(*, unavailable) - required init?(coder _: NSCoder) { - nil - } - } - } - """ - let options = FormatOptions(initCoderNil: true) - testFormatting(for: input, output, rule: .initCoderUnavailable, options: options) - } - - func testInitCoderUnavailableAlreadyPresent() { - let input = """ - extension Module { - final class A: UIView { - @available(*, unavailable) - required init?(coder _: NSCoder) { - fatalError() - } - } - } - """ - testFormatting(for: input, rule: .initCoderUnavailable) - } - - func testInitCoderUnavailableImplemented() { - let input = """ - extension Module { - final class A: UIView { - required init?(coder aCoder: NSCoder) { - aCoder.doSomething() - } - } - } - """ - testFormatting(for: input, rule: .initCoderUnavailable) - } - - func testPublicInitCoderUnavailable() { - let input = """ - class Foo: UIView { - public required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - """ - let output = """ - class Foo: UIView { - @available(*, unavailable) - public required init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - """ - testFormatting(for: input, output, rule: .initCoderUnavailable) - } - - func testPublicInitCoderUnavailable2() { - let input = """ - class Foo: UIView { - required public init?(coder _: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - } - """ - let output = """ - class Foo: UIView { - @available(*, unavailable) - required public init?(coder _: NSCoder) { - nil - } - } - """ - let options = FormatOptions(initCoderNil: true) - testFormatting(for: input, output, rule: .initCoderUnavailable, - options: options, exclude: [.modifierOrder]) - } - - // MARK: - trailingCommas - - func testCommaAddedToSingleItem() { - let input = "[\n foo\n]" - let output = "[\n foo,\n]" - testFormatting(for: input, output, rule: .trailingCommas) - } - - func testCommaAddedToLastItem() { - let input = "[\n foo,\n bar\n]" - let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: .trailingCommas) - } - - func testCommaAddedToDictionary() { - let input = "[\n foo: bar\n]" - let output = "[\n foo: bar,\n]" - testFormatting(for: input, output, rule: .trailingCommas) - } - - func testCommaNotAddedToInlineArray() { - let input = "[foo, bar]" - testFormatting(for: input, rule: .trailingCommas) - } - - func testCommaNotAddedToInlineDictionary() { - let input = "[foo: bar]" - testFormatting(for: input, rule: .trailingCommas) - } - - func testCommaNotAddedToSubscript() { - let input = "foo[bar]" - testFormatting(for: input, rule: .trailingCommas) - } - - func testCommaAddedBeforeComment() { - let input = "[\n foo // comment\n]" - let output = "[\n foo, // comment\n]" - testFormatting(for: input, output, rule: .trailingCommas) - } - - func testCommaNotAddedAfterComment() { - let input = "[\n foo, // comment\n]" - testFormatting(for: input, rule: .trailingCommas) - } - - func testCommaNotAddedInsideEmptyArrayLiteral() { - let input = "foo = [\n]" - testFormatting(for: input, rule: .trailingCommas) - } - - func testCommaNotAddedInsideEmptyDictionaryLiteral() { - let input = "foo = [:\n]" - let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, rule: .trailingCommas, options: options) - } - - func testTrailingCommaRemovedInInlineArray() { - let input = "[foo,]" - let output = "[foo]" - testFormatting(for: input, output, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToSubscript() { - let input = "foo[\n bar\n]" - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToSubscript2() { - let input = "foo?[\n bar\n]" - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToSubscript3() { - let input = "foo()[\n bar\n]" - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToSubscriptInsideArrayLiteral() { - let input = """ - let array = [ - foo - .bar[ - 0 - ] - .baz, - ] - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaAddedToArrayLiteralInsideTuple() { - let input = """ - let arrays = ([ - foo - ], [ - bar - ]) - """ - let output = """ - let arrays = ([ - foo, - ], [ - bar, - ]) - """ - testFormatting(for: input, output, rule: .trailingCommas) - } - - func testNoTrailingCommaAddedToArrayLiteralInsideTuple() { - let input = """ - let arrays = ([ - Int - ], [ - Int - ]).self - """ - testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) - } - - func testTrailingCommaNotAddedToTypeDeclaration() { - let input = """ - var foo: [ - Int: - String - ] - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration2() { - let input = """ - func foo(bar: [ - Int: - String - ]) - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration3() { - let input = """ - func foo() -> [ - String: String - ] - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration4() { - let input = """ - func foo() -> [String: [ - String: Int - ]] - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration5() { - let input = """ - let foo = [String: [ - String: Int - ]]() - """ - testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) - } - - func testTrailingCommaNotAddedToTypeDeclaration6() { - let input = """ - let foo = [String: [ - (Foo<[ - String - ]>, [ - Int - ]) - ]]() - """ - testFormatting(for: input, rule: .trailingCommas, exclude: [.propertyType]) - } - - func testTrailingCommaNotAddedToTypeDeclaration7() { - let input = """ - func foo() -> Foo<[String: [ - String: Int - ]]> - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToTypeDeclaration8() { - let input = """ - extension Foo { - var bar: [ - Int - ] { - fatalError() - } - } - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToTypealias() { - let input = """ - typealias Foo = [ - Int - ] - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToCaptureList() { - let input = """ - let foo = { [ - self - ] in } - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToCaptureListWithComment() { - let input = """ - let foo = { [ - self // captures self - ] in } - """ - testFormatting(for: input, rule: .trailingCommas) - } - - func testTrailingCommaNotAddedToCaptureListWithMainActor() { - let input = """ - let closure = { @MainActor [ - foo = state.foo, - baz = state.baz - ] _ in } - """ - testFormatting(for: input, rule: .trailingCommas) - } - - // trailingCommas = false - - func testCommaNotAddedToLastItem() { - let input = "[\n foo,\n bar\n]" - let options = FormatOptions(trailingCommas: false) - testFormatting(for: input, rule: .trailingCommas, options: options) - } - - func testCommaRemovedFromLastItem() { - let input = "[\n foo,\n bar,\n]" - let output = "[\n foo,\n bar\n]" - let options = FormatOptions(trailingCommas: false) - testFormatting(for: input, output, rule: .trailingCommas, options: options) - } - - // MARK: - fileHeader - - func testStripHeader() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let output = "/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testStripHeaderWithWhenHeaderContainsUrl() { - let input = """ - // - // RulesTests+General.swift - // SwiftFormatTests - // - // Created by Nick Lockwood on 02/10/2021. - // Copyright © 2021 Nick Lockwood. All rights reserved. - // https://some.example.com - // - - /// func - func foo() {} - """ - let output = "/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testReplaceHeaderWhenFileContainsNoCode() { - let input = "// foobar" - let options = FormatOptions(fileHeader: "// foobar") - testFormatting(for: input, rule: .fileHeader, options: options, - exclude: [.linebreakAtEndOfFile]) - } - - func testReplaceHeaderWhenFileContainsNoCode2() { - let input = "// foobar\n" - let options = FormatOptions(fileHeader: "// foobar") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testMultilineCommentHeader() { - let input = "/****************************/\n/* Created by Nick Lockwood */\n/****************************/\n\n\n/// func\nfunc foo() {}" - let output = "/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testNoStripHeaderWhenDisabled() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: .ignore) - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testNoStripComment() { - let input = "\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testNoStripPackageHeader() { - let input = "// swift-tools-version:4.2\n\nimport PackageDescription" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testNoStripFormatDirective() { - let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testNoStripFormatDirectiveAfterHeader() { - let input = "// header\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testNoReplaceFormatDirective() { - let input = "// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" - let output = "// Hello World\n\n// swiftformat:options --swiftversion 5.2\n\nimport PackageDescription" - let options = FormatOptions(fileHeader: "// Hello World") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testSetSingleLineHeader() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let output = "// Hello World\n\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "// Hello World") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testSetMultilineHeader() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let output = "// Hello\n// World\n\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "// Hello\n// World") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testSetMultilineHeaderWithMarkup() { - let input = "//\n// test.swift\n// SwiftFormat\n//\n// Created by Nick Lockwood on 08/11/2016.\n// Copyright © 2016 Nick Lockwood. All rights reserved.\n//\n\n/// func\nfunc foo() {}" - let output = "/*--- Hello ---*/\n/*--- World ---*/\n\n/// func\nfunc foo() {}" - let options = FormatOptions(fileHeader: "/*--- Hello ---*/\n/*--- World ---*/") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testNoStripHeaderIfRuleDisabled() { - let input = "// swiftformat:disable fileHeader\n// test\n// swiftformat:enable fileHeader\n\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testNoStripHeaderIfNextRuleDisabled() { - let input = "// swiftformat:disable:next fileHeader\n// test\n\nfunc foo() {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testNoStripHeaderDocWithNewlineBeforeCode() { - let input = "/// Header doc\n\nclass Foo {}" - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options, exclude: [.docComments]) - } - - func testNoDuplicateHeaderIfMissingTrailingBlankLine() { - let input = "// Header comment\nclass Foo {}" - let output = "// Header comment\n\nclass Foo {}" - let options = FormatOptions(fileHeader: "Header comment") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testNoDuplicateHeaderContainingPossibleCommentDirective() { - let input = """ - // Copyright (c) 2010-2023 Foobar - // - // SPDX-License-Identifier: EPL-2.0 - - class Foo {} - """ - let output = """ - // Copyright (c) 2010-2024 Foobar - // - // SPDX-License-Identifier: EPL-2.0 - - class Foo {} - """ - let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// SPDX-License-Identifier: EPL-2.0") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testNoDuplicateHeaderContainingCommentDirective() { - let input = """ - // Copyright (c) 2010-2023 Foobar - // - // swiftformat:disable all - - class Foo {} - """ - let output = """ - // Copyright (c) 2010-2024 Foobar - // - // swiftformat:disable all - - class Foo {} - """ - let options = FormatOptions(fileHeader: "// Copyright (c) 2010-2024 Foobar\n//\n// swiftformat:disable all") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderYearReplacement() { - let input = "let foo = bar" - let output: String = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy" - return "// Copyright © \(formatter.string(from: Date()))\n\nlet foo = bar" - }() - let options = FormatOptions(fileHeader: "// Copyright © {year}") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderCreationYearReplacement() { - let input = "let foo = bar" - let date = Date(timeIntervalSince1970: 0) - let output: String = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy" - return "// Copyright © \(formatter.string(from: date))\n\nlet foo = bar" - }() - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// Copyright © {created.year}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderAuthorReplacement() { - let name = "Test User" - let email = "test@email.com" - let input = "let foo = bar" - let output = "// Created by \(name) \(email)\n\nlet foo = bar" - let fileInfo = FileInfo(replacements: [.authorName: .constant(name), .authorEmail: .constant(email)]) - let options = FormatOptions(fileHeader: "// Created by {author.name} {author.email}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderAuthorReplacement2() { - let author = "Test User " - let input = "let foo = bar" - let output = "// Created by \(author)\n\nlet foo = bar" - let fileInfo = FileInfo(replacements: [.author: .constant(author)]) - let options = FormatOptions(fileHeader: "// Created by {author}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderMultipleReplacement() { - let name = "Test User" - let input = "let foo = bar" - let output = "// Copyright © \(name)\n// Created by \(name)\n\nlet foo = bar" - let fileInfo = FileInfo(replacements: [.authorName: .constant(name)]) - let options = FormatOptions(fileHeader: "// Copyright © {author.name}\n// Created by {author.name}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderCreationDateReplacement() { - let input = "let foo = bar" - let date = Date(timeIntervalSince1970: 0) - let output: String = { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .none - return "// Created by Nick Lockwood on \(formatter.string(from: date)).\n\nlet foo = bar" - }() - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderDateFormattingIso() { - let date = createTestDate("2023-08-09") - - let input = "let foo = bar" - let output = "// 2023-08-09\n\nlet foo = bar" - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// {created}", dateFormat: .iso, fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderDateFormattingDayMonthYear() { - let date = createTestDate("2023-08-09") - - let input = "let foo = bar" - let output = "// 09/08/2023\n\nlet foo = bar" - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// {created}", dateFormat: .dayMonthYear, fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderDateFormattingMonthDayYear() { - let date = createTestDate("2023-08-09") - - let input = "let foo = bar" - let output = "// 08/09/2023\n\nlet foo = bar" - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// {created}", - dateFormat: .monthDayYear, - fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderDateFormattingCustom() { - let date = createTestDate("2023-08-09T12:59:30.345Z", .timestamp) - - let input = "let foo = bar" - let output = "// 23.08.09-12.59.30.345\n\nlet foo = bar" - let fileInfo = FileInfo(creationDate: date) - let options = FormatOptions(fileHeader: "// {created}", - dateFormat: .custom("yy.MM.dd-HH.mm.ss.SSS"), - timeZone: .identifier("UTC"), - fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - private func testTimeZone( - timeZone: FormatTimeZone, - tests: [String: String] - ) { - for (input, expected) in tests { - let date = createTestDate(input, .time) - let input = "let foo = bar" - let output = "// \(expected)\n\nlet foo = bar" - - let fileInfo = FileInfo(creationDate: date) - - let options = FormatOptions( - fileHeader: "// {created}", - dateFormat: .custom("HH:mm"), - timeZone: timeZone, - fileInfo: fileInfo - ) - - testFormatting(for: input, output, - rule: .fileHeader, - options: options) - } - } - - func testFileHeaderDateTimeZoneSystem() { - let baseDate = createTestDate("15:00Z", .time) - let offset = TimeZone.current.secondsFromGMT(for: baseDate) - - let date = baseDate.addingTimeInterval(Double(offset)) - - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm" - formatter.timeZone = TimeZone(secondsFromGMT: 0) - - let expected = formatter.string(from: date) - - testTimeZone(timeZone: .system, tests: [ - "15:00Z": expected, - "16:00+1": expected, - "01:00+10": expected, - "16:30+0130": expected, - ]) - } - - func testFileHeaderDateTimeZoneAbbreviations() { - // GMT+0530 - testTimeZone(timeZone: FormatTimeZone(rawValue: "IST")!, tests: [ - "15:00Z": "20:30", - "16:00+1": "20:30", - "01:00+10": "20:30", - "16:30+0130": "20:30", - ]) - } - - func testFileHeaderDateTimeZoneIdentifiers() { - // GMT+0845 - testTimeZone(timeZone: FormatTimeZone(rawValue: "Australia/Eucla")!, tests: [ - "15:00Z": "23:45", - "16:00+1": "23:45", - "01:00+10": "23:45", - "16:30+0130": "23:45", - ]) - } - - func testGitHelpersReturnsInfo() { - let info = GitFileInfo(url: URL(fileURLWithPath: #file)) - XCTAssertNotNil(info?.authorName) - XCTAssertNotNil(info?.authorEmail) - XCTAssertNotNil(info?.creationDate) - } - - func testFileHeaderRuleThrowsIfCreationDateUnavailable() { - let input = "let foo = bar" - let options = FormatOptions(fileHeader: "// Created by Nick Lockwood on {created}.", fileInfo: FileInfo()) - XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) - } - - func testFileHeaderFileReplacement() { - let input = "let foo = bar" - let output = "// MyFile.swift\n\nlet foo = bar" - let fileInfo = FileInfo(filePath: "~/MyFile.swift") - let options = FormatOptions(fileHeader: "// {file}", fileInfo: fileInfo) - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderRuleThrowsIfFileNameUnavailable() { - let input = "let foo = bar" - let options = FormatOptions(fileHeader: "// {file}.", fileInfo: FileInfo()) - XCTAssertThrowsError(try format(input, rules: [.fileHeader], options: options)) - } - - func testEdgeCaseHeaderEndIndexPlusNewHeaderTokensCountEqualsFileTokensEndIndex() { - let input = "// Header comment\n\nclass Foo {}" - let output = "// Header line1\n// Header line2\n\nclass Foo {}" - let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderBlankLineNotRemovedBeforeFollowingComment() { - let input = """ - // - // Header - // - - // Something else... - """ - let options = FormatOptions(fileHeader: "//\n// Header\n//") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testFileHeaderBlankLineNotRemovedBeforeFollowingComment2() { - let input = """ - // - // Header - // - - // - // Something else... - // - """ - let options = FormatOptions(fileHeader: "//\n// Header\n//") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testFileHeaderRemovedAfterHashbang() { - let input = """ - #!/usr/bin/swift - - // Header line1 - // Header line2 - - let foo = 5 - """ - let output = """ - #!/usr/bin/swift - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testFileHeaderPlacedAfterHashbang() { - let input = """ - #!/usr/bin/swift - - let foo = 5 - """ - let output = """ - #!/usr/bin/swift - - // Header line1 - // Header line2 - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "// Header line1\n// Header line2") - testFormatting(for: input, output, rule: .fileHeader, options: options) - } - - func testBlankLineAfterHashbangNotRemovedByFileHeader() { - let input = """ - #!/usr/bin/swift - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testLineAfterHashbangNotAffectedByFileHeaderRemoval() { - let input = """ - #!/usr/bin/swift - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testDisableFileHeaderCommentRespectedAfterHashbang() { - let input = """ - #!/usr/bin/swift - // swiftformat:disable fileHeader - - // Header line1 - // Header line2 - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - func testDisableFileHeaderCommentRespectedAfterHashbang2() { - let input = """ - #!/usr/bin/swift - - // swiftformat:disable fileHeader - // Header line1 - // Header line2 - - let foo = 5 - """ - let options = FormatOptions(fileHeader: "") - testFormatting(for: input, rule: .fileHeader, options: options) - } - - // MARK: - headerFileName - - func testHeaderFileNameReplaced() { - let input = """ - // MyFile.swift - - let foo = bar - """ - let output = """ - // YourFile.swift - - let foo = bar - """ - let options = FormatOptions(fileInfo: FileInfo(filePath: "~/YourFile.swift")) - testFormatting(for: input, output, rule: .headerFileName, options: options) - } - - // MARK: - strongOutlets - - func testRemoveWeakFromOutlet() { - let input = "@IBOutlet weak var label: UILabel!" - let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: .strongOutlets) - } - - func testRemoveWeakFromPrivateOutlet() { - let input = "@IBOutlet private weak var label: UILabel!" - let output = "@IBOutlet private var label: UILabel!" - testFormatting(for: input, output, rule: .strongOutlets) - } - - func testRemoveWeakFromOutletOnSplitLine() { - let input = "@IBOutlet\nweak var label: UILabel!" - let output = "@IBOutlet\nvar label: UILabel!" - testFormatting(for: input, output, rule: .strongOutlets) - } - - func testNoRemoveWeakFromNonOutlet() { - let input = "weak var label: UILabel!" - testFormatting(for: input, rule: .strongOutlets) - } - - func testNoRemoveWeakFromNonOutletAfterOutlet() { - let input = "@IBOutlet weak var label1: UILabel!\nweak var label2: UILabel!" - let output = "@IBOutlet var label1: UILabel!\nweak var label2: UILabel!" - testFormatting(for: input, output, rule: .strongOutlets) - } - - func testNoRemoveWeakFromDelegateOutlet() { - let input = "@IBOutlet weak var delegate: UITableViewDelegate?" - testFormatting(for: input, rule: .strongOutlets) - } - - func testNoRemoveWeakFromDataSourceOutlet() { - let input = "@IBOutlet weak var dataSource: UITableViewDataSource?" - testFormatting(for: input, rule: .strongOutlets) - } - - func testRemoveWeakFromOutletAfterDelegateOutlet() { - let input = "@IBOutlet weak var delegate: UITableViewDelegate?\n@IBOutlet weak var label1: UILabel!" - let output = "@IBOutlet weak var delegate: UITableViewDelegate?\n@IBOutlet var label1: UILabel!" - testFormatting(for: input, output, rule: .strongOutlets) - } - - func testRemoveWeakFromOutletAfterDataSourceOutlet() { - let input = "@IBOutlet weak var dataSource: UITableViewDataSource?\n@IBOutlet weak var label1: UILabel!" - let output = "@IBOutlet weak var dataSource: UITableViewDataSource?\n@IBOutlet var label1: UILabel!" - testFormatting(for: input, output, rule: .strongOutlets) - } - - // MARK: - strongifiedSelf - - func testBacktickedSelfConvertedToSelfInGuard() { - let input = """ - { [weak self] in - guard let `self` = self else { return } - } - """ - let output = """ - { [weak self] in - guard let self = self else { return } - } - """ - let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, output, rule: .strongifiedSelf, options: options, - exclude: [.wrapConditionalBodies]) - } - - func testBacktickedSelfConvertedToSelfInIf() { - let input = """ - { [weak self] in - if let `self` = self else { print(self) } - } - """ - let output = """ - { [weak self] in - if let self = self else { print(self) } - } - """ - let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, output, rule: .strongifiedSelf, options: options, - exclude: [.wrapConditionalBodies]) - } - - func testBacktickedSelfNotConvertedIfVersionLessThan4_2() { - let input = """ - { [weak self] in - guard let `self` = self else { return } - } - """ - let options = FormatOptions(swiftVersion: "4.1.5") - testFormatting(for: input, rule: .strongifiedSelf, options: options, - exclude: [.wrapConditionalBodies]) - } - - func testBacktickedSelfNotConvertedIfVersionUnspecified() { - let input = """ - { [weak self] in - guard let `self` = self else { return } - } - """ - testFormatting(for: input, rule: .strongifiedSelf, - exclude: [.wrapConditionalBodies]) - } - - func testBacktickedSelfNotConvertedIfNotConditional() { - let input = "nonisolated(unsafe) let `self` = self" - let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, rule: .strongifiedSelf, options: options) - } - - // MARK: - yodaConditions - - func testNumericLiteralEqualYodaCondition() { - let input = "5 == foo" - let output = "foo == 5" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNumericLiteralGreaterYodaCondition() { - let input = "5.1 > foo" - let output = "foo < 5.1" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testStringLiteralNotEqualYodaCondition() { - let input = "\"foo\" != foo" - let output = "foo != \"foo\"" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNilNotEqualYodaCondition() { - let input = "nil != foo" - let output = "foo != nil" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testTrueNotEqualYodaCondition() { - let input = "true != foo" - let output = "foo != true" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testEnumCaseNotEqualYodaCondition() { - let input = ".foo != foo" - let output = "foo != .foo" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testArrayLiteralNotEqualYodaCondition() { - let input = "[5, 6] != foo" - let output = "foo != [5, 6]" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNestedArrayLiteralNotEqualYodaCondition() { - let input = "[5, [6, 7]] != foo" - let output = "foo != [5, [6, 7]]" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testDictionaryLiteralNotEqualYodaCondition() { - let input = "[foo: 5, bar: 6] != foo" - let output = "foo != [foo: 5, bar: 6]" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testSubscriptNotTreatedAsYodaCondition() { - let input = "foo[5] != bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testSubscriptOfParenthesizedExpressionNotTreatedAsYodaCondition() { - let input = "(foo + bar)[5] != baz" - testFormatting(for: input, rule: .yodaConditions) - } - - func testSubscriptOfUnwrappedValueNotTreatedAsYodaCondition() { - let input = "foo![5] != bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testSubscriptOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { - let input = "foo /* foo */ [5] != bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testSubscriptOfCollectionNotTreatedAsYodaCondition() { - let input = "[foo][5] != bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testSubscriptOfTrailingClosureNotTreatedAsYodaCondition() { - let input = "foo { [5] }[0] != bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testSubscriptOfRhsNotMangledInYodaCondition() { - let input = "[1] == foo[0]" - let output = "foo[0] == [1]" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testTupleYodaCondition() { - let input = "(5, 6) != bar" - let output = "bar != (5, 6)" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testLabeledTupleYodaCondition() { - let input = "(foo: 5, bar: 6) != baz" - let output = "baz != (foo: 5, bar: 6)" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNestedTupleYodaCondition() { - let input = "(5, (6, 7)) != baz" - let output = "baz != (5, (6, 7))" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testFunctionCallNotTreatedAsYodaCondition() { - let input = "foo(5) != bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testCallOfParenthesizedExpressionNotTreatedAsYodaCondition() { - let input = "(foo + bar)(5) != baz" - testFormatting(for: input, rule: .yodaConditions) - } - - func testCallOfUnwrappedValueNotTreatedAsYodaCondition() { - let input = "foo!(5) != bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testCallOfExpressionWithInlineCommentNotTreatedAsYodaCondition() { - let input = "foo /* foo */ (5) != bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testCallOfRhsNotMangledInYodaCondition() { - let input = "(1, 2) == foo(0)" - let output = "foo(0) == (1, 2)" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testTrailingClosureOnRhsNotMangledInYodaCondition() { - let input = "(1, 2) == foo { $0 }" - let output = "foo { $0 } == (1, 2)" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testYodaConditionInIfStatement() { - let input = "if 5 != foo {}" - let output = "if foo != 5 {}" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testSubscriptYodaConditionInIfStatementWithBraceOnNextLine() { - let input = "if [0] == foo.bar[0]\n{ baz() }" - let output = "if foo.bar[0] == [0]\n{ baz() }" - testFormatting(for: input, output, rule: .yodaConditions, - exclude: [.wrapConditionalBodies]) - } - - func testYodaConditionInSecondClauseOfIfStatement() { - let input = "if foo, 5 != bar {}" - let output = "if foo, bar != 5 {}" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testYodaConditionInExpression() { - let input = "let foo = 5 < bar\nbaz()" - let output = "let foo = bar > 5\nbaz()" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testYodaConditionInExpressionWithTrailingClosure() { - let input = "let foo = 5 < bar { baz() }" - let output = "let foo = bar { baz() } > 5" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testYodaConditionInFunctionCall() { - let input = "foo(5 < bar)" - let output = "foo(bar > 5)" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testYodaConditionFollowedByExpression() { - let input = "5 == foo + 6" - let output = "foo + 6 == 5" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testPrefixExpressionYodaCondition() { - let input = "!false == foo" - let output = "foo == !false" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testPrefixExpressionYodaCondition2() { - let input = "true == !foo" - let output = "!foo == true" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testPostfixExpressionYodaCondition() { - let input = "5<*> == foo" - let output = "foo == 5<*>" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testDoublePostfixExpressionYodaCondition() { - let input = "5!! == foo" - let output = "foo == 5!!" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testPostfixExpressionNonYodaCondition() { - let input = "5 == 5<*>" - testFormatting(for: input, rule: .yodaConditions) - } - - func testPostfixExpressionNonYodaCondition2() { - let input = "5<*> == 5" - testFormatting(for: input, rule: .yodaConditions) - } - - func testStringEqualsStringNonYodaCondition() { - let input = "\"foo\" == \"bar\"" - testFormatting(for: input, rule: .yodaConditions) - } - - func testConstantAfterNullCoalescingNonYodaCondition() { - let input = "foo.last ?? -1 < bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testNoMangleYodaConditionFollowedByAndOperator() { - let input = "5 <= foo && foo <= 7" - let output = "foo >= 5 && foo <= 7" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNoMangleYodaConditionFollowedByOrOperator() { - let input = "5 <= foo || foo <= 7" - let output = "foo >= 5 || foo <= 7" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNoMangleYodaConditionFollowedByParentheses() { - let input = "0 <= (foo + bar)" - let output = "(foo + bar) >= 0" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNoMangleYodaConditionInTernary() { - let input = "let z = 0 < y ? 3 : 4" - let output = "let z = y > 0 ? 3 : 4" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNoMangleYodaConditionInTernary2() { - let input = "let z = y > 0 ? 0 < x : 4" - let output = "let z = y > 0 ? x > 0 : 4" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testNoMangleYodaConditionInTernary3() { - let input = "let z = y > 0 ? 3 : 0 < x" - let output = "let z = y > 0 ? 3 : x > 0" - testFormatting(for: input, output, rule: .yodaConditions) - } - - func testKeyPathNotMangledAndNotTreatedAsYodaCondition() { - let input = "\\.foo == bar" - testFormatting(for: input, rule: .yodaConditions) - } - - func testEnumCaseLessThanEnumCase() { - let input = "XCTAssertFalse(.never < .never)" - testFormatting(for: input, rule: .yodaConditions) - } - - // yodaSwap = literalsOnly - - func testNoSwapYodaDotMember() { - let input = "foo(where: .bar == baz)" - let options = FormatOptions(yodaSwap: .literalsOnly) - testFormatting(for: input, rule: .yodaConditions, options: options) - } - - // MARK: - leadingDelimiters - - func testLeadingCommaMovedToPreviousLine() { - let input = """ - let foo = 5 - , bar = 6 - """ - let output = """ - let foo = 5, - bar = 6 - """ - testFormatting(for: input, output, rule: .leadingDelimiters) - } - - func testLeadingColonFollowedByCommentMovedToPreviousLine() { - let input = """ - let foo - : /* string */ String - """ - let output = """ - let foo: - /* string */ String - """ - testFormatting(for: input, output, rule: .leadingDelimiters) - } - - func testCommaMovedBeforeCommentIfLineEndsInComment() { - let input = """ - let foo = 5 // first - , bar = 6 - """ - let output = """ - let foo = 5, // first - bar = 6 - """ - testFormatting(for: input, output, rule: .leadingDelimiters) - } -} diff --git a/Tests/RulesTests+Hoisting.swift b/Tests/RulesTests+Hoisting.swift deleted file mode 100644 index bb7c8ed63..000000000 --- a/Tests/RulesTests+Hoisting.swift +++ /dev/null @@ -1,1079 +0,0 @@ -// -// RulesTests+Hoisting.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 25/03/2023. -// Copyright © 2023 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class HoistingTests: RulesTests { - // MARK: - hoistTry - - func testHoistTry() { - let input = "greet(try name(), try surname())" - let output = "try greet(name(), surname())" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryWithOptionalTry() { - let input = "greet(try name(), try? surname())" - let output = "try greet(name(), try? surname())" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideStringInterpolation() { - let input = "\"\\(replace(regex: try something()))\"" - let output = "try \"\\(replace(regex: something()))\"" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideStringInterpolation2() { - let input = """ - "Hello \\(try await someValue())" - """ - let output = """ - try "Hello \\(await someValue())" - """ - testFormatting(for: input, output, rule: .hoistTry, - options: FormatOptions(swiftVersion: "5.5"), - exclude: [.hoistAwait]) - } - - func testHoistTryInsideStringInterpolation3() { - let input = """ - let text = "\"" - abc - \\(try bar()) - xyz - "\"" - """ - let output = """ - let text = try "\"" - abc - \\(bar()) - xyz - "\"" - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideStringInterpolation4() { - let input = """ - let str = "&enrolments[\\(index)][userid]=\\(try Foo.tryMe())" - """ - let output = """ - let str = try "&enrolments[\\(index)][userid]=\\(Foo.tryMe())" - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideStringInterpolation5() { - let input = """ - return str + - "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + - "&enrolments[\\(index)][userid]=\\(try user.requireMoodleID())" - """ - let output = """ - return try str + - "&enrolments[\\(index)][roleid]=\\(MoodleRoles.studentRole.rawValue)" + - "&enrolments[\\(index)][userid]=\\(user.requireMoodleID())" - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideStringInterpolation6() { - let input = #""" - """ - let \(object.varName) = - \(tripleQuote) - \(try encode(object.object)) - \(tripleQuote) - """ - """# - let output = #""" - try """ - let \(object.varName) = - \(tripleQuote) - \(encode(object.object)) - \(tripleQuote) - """ - """# - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideArgument() { - let input = """ - array.append(contentsOf: try await asyncFunction(param1: param1)) - """ - let output = """ - try array.append(contentsOf: await asyncFunction(param1: param1)) - """ - testFormatting(for: input, output, rule: .hoistTry, exclude: [.hoistAwait]) - } - - func testNoHoistTryInsideXCTAssert() { - let input = "XCTAssertFalse(try foo())" - testFormatting(for: input, rule: .hoistTry) - } - - func testNoMergeTrysInsideXCTAssert() { - let input = "XCTAssertEqual(try foo(), try bar())" - testFormatting(for: input, rule: .hoistTry) - } - - func testNoHoistTryInsideDo() { - let input = "do { rg.box.seal(.fulfilled(try body(error))) }" - let output = "do { try rg.box.seal(.fulfilled(body(error))) }" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testNoHoistTryInsideDoThrows() { - let input = "do throws(Foo) { rg.box.seal(.fulfilled(try body(error))) }" - let output = "do throws(Foo) { try rg.box.seal(.fulfilled(body(error))) }" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testNoHoistTryInsideMultilineDo() { - let input = """ - do { - rg.box.seal(.fulfilled(try body(error))) - } - """ - let output = """ - do { - try rg.box.seal(.fulfilled(body(error))) - } - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistedTryPlacedBeforeAwait() { - let input = "let foo = await bar(contentsOf: try baz())" - let output = "let foo = try await bar(contentsOf: baz())" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInExpressionWithNoSpaces() { - let input = "let foo=bar(contentsOf:try baz())" - let output = "let foo=try bar(contentsOf:baz())" - testFormatting(for: input, output, rule: .hoistTry, - exclude: [.spaceAroundOperators]) - } - - func testHoistTryInExpressionWithExcessSpaces() { - let input = "let foo = bar ( contentsOf: try baz() )" - let output = "let foo = try bar ( contentsOf: baz() )" - testFormatting(for: input, output, rule: .hoistTry, - exclude: [.spaceAroundParens, .spaceInsideParens]) - } - - func testHoistTryWithReturn() { - let input = "return .enumCase(try await service.greet())" - let output = "return try .enumCase(await service.greet())" - testFormatting(for: input, output, rule: .hoistTry, - exclude: [.hoistAwait]) - } - - func testHoistDeeplyNestedTrys() { - let input = "let foo = (bar: (5, (try quux(), 6)), baz: (7, quux: try quux()))" - let output = "let foo = try (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testTryNotHoistedOutOfClosure() { - let input = "let foo = { (try bar(), 5) }" - let output = "let foo = { try (bar(), 5) }" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testTryNotHoistedOutOfClosureWithArguments() { - let input = "let foo = { bar in (try baz(bar), 5) }" - let output = "let foo = { bar in try (baz(bar), 5) }" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testTryNotHoistedOutOfForCondition() { - let input = "for foo in bar(try baz()) {}" - let output = "for foo in try bar(baz()) {}" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryWithInitAssignment() { - let input = "let variable = String(try await asyncFunction())" - let output = "let variable = try String(await asyncFunction())" - testFormatting(for: input, output, rule: .hoistTry, - exclude: [.hoistAwait]) - } - - func testHoistTryWithAssignment() { - let input = "let variable = (try await asyncFunction())" - let output = "let variable = try (await asyncFunction())" - testFormatting(for: input, output, rule: .hoistTry, - exclude: [.hoistAwait]) - } - - func testHoistTryOnlyOne() { - let input = "greet(name, try surname())" - let output = "try greet(name, surname())" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryRedundantTry() { - let input = "try greet(try name(), try surname())" - let output = "try greet(name(), surname())" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryWithAwaitOnDifferentStatement() { - let input = """ - let asyncVariable = try await performSomething() - return Foo(param1: try param1()) - """ - let output = """ - let asyncVariable = try await performSomething() - return try Foo(param1: param1()) - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryDoubleParens() { - let input = """ - array.append((value: try compute())) - """ - let output = """ - try array.append((value: compute())) - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryDoesNothing() { - let input = "try greet(name, surname)" - testFormatting(for: input, rule: .hoistTry) - } - - func testHoistOptionalTryDoesNothing() { - let input = "try? greet(name, surname)" - testFormatting(for: input, rule: .hoistTry) - } - - func testHoistedTryOnLineBeginningWithInfixDot() { - let input = """ - let foo = bar() - .baz(try quux()) - """ - let output = """ - let foo = try bar() - .baz(quux()) - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistedTryOnLineBeginningWithInfixPlus() { - let input = """ - let foo = bar() - + baz(try quux()) - """ - let output = """ - let foo = try bar() - + baz(quux()) - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistedTryOnLineBeginningWithPrefixOperator() { - let input = """ - foo() - !bar(try quux()) - """ - let output = """ - foo() - try !bar(quux()) - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testNoHoistTryIntoPreviousLineEndingWithPostfixOperator() { - let input = """ - let foo = bar! - (try baz(), quux()).foo() - """ - let output = """ - let foo = bar! - try (baz(), quux()).foo() - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testNoHoistTryInCapturingFunction() { - let input = "foo(try bar)" - testFormatting(for: input, rule: .hoistTry, - options: FormatOptions(throwCapturing: ["foo"])) - } - - func testNoHoistSecondArgumentTryInCapturingFunction() { - let input = "foo(bar, try baz)" - testFormatting(for: input, rule: .hoistTry, - options: FormatOptions(throwCapturing: ["foo"])) - } - - func testNoHoistFailToTerminate() { - let input = """ - return ManyInitExample( - a: try Example(string: try throwingExample()), - b: try throwingExample(), - c: try throwingExample(), - d: try throwingExample(), - e: try throwingExample(), - f: try throwingExample(), - g: try throwingExample(), - h: try throwingExample(), - i: try throwingExample() - ) - """ - let output = """ - return try ManyInitExample( - a: Example(string: throwingExample()), - b: throwingExample(), - c: throwingExample(), - d: throwingExample(), - e: throwingExample(), - f: throwingExample(), - g: throwingExample(), - h: throwingExample(), - i: throwingExample() - ) - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideOptionalFunction() { - let input = "foo?(try bar())" - let output = "try foo?(bar())" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testNoHoistTryAfterOptionalTry() { - let input = "let foo = try? bar(try baz())" - testFormatting(for: input, rule: .hoistTry) - } - - func testHoistTryInsideOptionalSubscript() { - let input = "foo?[try bar()]" - let output = "try foo?[bar()]" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryAfterGenericType() { - let input = "let foo = Tree.Foo(bar: try baz())" - let output = "let foo = try Tree.Foo(bar: baz())" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryAfterArrayLiteral() { - let input = "if [.first, .second].contains(try foo()) {}" - let output = "if try [.first, .second].contains(foo()) {}" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryAfterSubscript() { - let input = "if foo[5].bar(try baz()) {}" - let output = "if try foo[5].bar(baz()) {}" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideGenericInit() { - let input = """ - return Target( - file: try parseFile(path: $0) - ) - """ - let output = """ - return try Target( - file: parseFile(path: $0) - ) - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryInsideArrayClosure() { - let input = "foo[bar](try parseFile(path: $0))" - let output = "try foo[bar](parseFile(path: $0))" - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryAfterString() { - let input = """ - let json = "{}" - - someFunction(try parse(json), "someKey") - """ - let output = """ - let json = "{}" - - try someFunction(parse(json), "someKey") - """ - testFormatting(for: input, output, rule: .hoistTry) - } - - func testHoistTryAfterMultilineString() { - let input = #""" - let json = """ - { - "foo": "bar" - } - """ - - someFunction(try parse(json), "someKey") - """# - let output = #""" - let json = """ - { - "foo": "bar" - } - """ - - try someFunction(parse(json), "someKey") - """# - testFormatting(for: input, output, rule: .hoistTry) - } - - // MARK: - hoistAwait - - func testHoistAwait() { - let input = "greet(await name, await surname)" - let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitInsideIf() { - let input = "if !(await isSomething()) {}" - let output = "if await !(isSomething()) {}" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), - exclude: [.redundantParens]) - } - - func testHoistAwaitInsideArgument() { - let input = """ - array.append(contentsOf: try await asyncFunction(param1: param1)) - """ - let output = """ - await array.append(contentsOf: try asyncFunction(param1: param1)) - """ - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) - } - - func testHoistAwaitInsideStringInterpolation() { - let input = "\"\\(replace(regex: await something()))\"" - let output = "await \"\\(replace(regex: something()))\"" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitInsideStringInterpolation2() { - let input = """ - "Hello \\(try await someValue())" - """ - let output = """ - await "Hello \\(try someValue())" - """ - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) - } - - func testNoHoistAwaitInsideDo() { - let input = """ - do { - rg.box.seal(.fulfilled(await body(error))) - } - """ - let output = """ - do { - await rg.box.seal(.fulfilled(body(error))) - } - """ - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testNoHoistAwaitInsideDoThrows() { - let input = """ - do throws(Foo) { - rg.box.seal(.fulfilled(await body(error))) - } - """ - let output = """ - do throws(Foo) { - await rg.box.seal(.fulfilled(body(error))) - } - """ - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitInExpressionWithNoSpaces() { - let input = "let foo=bar(contentsOf:await baz())" - let output = "let foo=await bar(contentsOf:baz())" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.spaceAroundOperators]) - } - - func testHoistAwaitInExpressionWithExcessSpaces() { - let input = "let foo = bar ( contentsOf: await baz() )" - let output = "let foo = await bar ( contentsOf: baz() )" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), - exclude: [.spaceAroundParens, .spaceInsideParens]) - } - - func testHoistAwaitWithReturn() { - let input = "return .enumCase(try await service.greet())" - let output = "return await .enumCase(try service.greet())" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) - } - - func testHoistDeeplyNestedAwaits() { - let input = "let foo = (bar: (5, (await quux(), 6)), baz: (7, quux: await quux()))" - let output = "let foo = await (bar: (5, (quux(), 6)), baz: (7, quux: quux()))" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testAwaitNotHoistedOutOfClosure() { - let input = "let foo = { (await bar(), 5) }" - let output = "let foo = { await (bar(), 5) }" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testAwaitNotHoistedOutOfClosureWithArguments() { - let input = "let foo = { bar in (await baz(bar), 5) }" - let output = "let foo = { bar in await (baz(bar), 5) }" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testAwaitNotHoistedOutOfForCondition() { - let input = "for foo in bar(await baz()) {}" - let output = "for foo in await bar(baz()) {}" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testAwaitNotHoistedOutOfForIndex() { - let input = "for await foo in asyncSequence() {}" - testFormatting(for: input, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitWithInitAssignment() { - let input = "let variable = String(try await asyncFunction())" - let output = "let variable = await String(try asyncFunction())" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) - } - - func testHoistAwaitWithAssignment() { - let input = "let variable = (try await asyncFunction())" - let output = "let variable = await (try asyncFunction())" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.hoistTry]) - } - - func testHoistAwaitInRedundantScopePriorToNumber() { - let input = """ - let identifiersTypes = 1 - (try? await asyncFunction(param1: param1)) - """ - let output = """ - let identifiersTypes = 1 - await (try? asyncFunction(param1: param1)) - """ - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitOnlyOne() { - let input = "greet(name, await surname)" - let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitRedundantAwait() { - let input = "await greet(await name, await surname)" - let output = "await greet(name, surname)" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testHoistAwaitDoesNothing() { - let input = "await greet(name, surname)" - testFormatting(for: input, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testNoHoistAwaitBeforeTry() { - let input = "try foo(await bar())" - let output = "try await foo(bar())" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testNoHoistAwaitInCapturingFunction() { - let input = "foo(await bar)" - testFormatting(for: input, rule: .hoistAwait, - options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) - } - - func testNoHoistSecondArgumentAwaitInCapturingFunction() { - let input = "foo(bar, await baz)" - testFormatting(for: input, rule: .hoistAwait, - options: FormatOptions(asyncCapturing: ["foo"], swiftVersion: "5.5")) - } - - func testHoistAwaitAfterOrdinaryOperator() { - let input = "let foo = bar + (await baz)" - let output = "let foo = await bar + (baz)" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.redundantParens]) - } - - func testHoistAwaitAfterUnknownOperator() { - let input = "let foo = bar ??? (await baz)" - let output = "let foo = await bar ??? (baz)" - testFormatting(for: input, output, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.redundantParens]) - } - - func testNoHoistAwaitAfterCapturingOperator() { - let input = "let foo = await bar ??? (await baz)" - testFormatting(for: input, rule: .hoistAwait, - options: FormatOptions(asyncCapturing: ["???"], swiftVersion: "5.5")) - } - - func testNoHoistAwaitInMacroArgument() { - let input = "#expect (await monitor.isAvailable == false)" - testFormatting(for: input, rule: .hoistAwait, - options: FormatOptions(swiftVersion: "5.5"), exclude: [.spaceAroundParens]) - } - - // MARK: - hoistPatternLet - - // hoist = true - - func testHoistCaseLet() { - let input = "if case .foo(let bar, let baz) = quux {}" - let output = "if case let .foo(bar, baz) = quux {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testHoistLabelledCaseLet() { - let input = "if case .foo(bar: let bar, baz: let baz) = quux {}" - let output = "if case let .foo(bar: bar, baz: baz) = quux {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testHoistCaseVar() { - let input = "if case .foo(var bar, var baz) = quux {}" - let output = "if case var .foo(bar, baz) = quux {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testNoHoistMixedCaseLetVar() { - let input = "if case .foo(let bar, var baz) = quux {}" - testFormatting(for: input, rule: .hoistPatternLet) - } - - func testNoHoistIfFirstArgSpecified() { - let input = "if case .foo(bar, let baz) = quux {}" - testFormatting(for: input, rule: .hoistPatternLet) - } - - func testNoHoistIfLastArgSpecified() { - let input = "if case .foo(let bar, baz) = quux {}" - testFormatting(for: input, rule: .hoistPatternLet) - } - - func testHoistIfArgIsNumericLiteral() { - let input = "if case .foo(5, let baz) = quux {}" - let output = "if case let .foo(5, baz) = quux {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testHoistIfArgIsEnumCaseLiteral() { - let input = "if case .foo(.bar, let baz) = quux {}" - let output = "if case let .foo(.bar, baz) = quux {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testHoistIfArgIsNamespacedEnumCaseLiteralInParens() { - let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" - let output = "switch foo {\ncase let (Foo.bar(baz)):\n}" - testFormatting(for: input, output, rule: .hoistPatternLet, exclude: [.redundantParens]) - } - - func testHoistIfFirstArgIsUnderscore() { - let input = "if case .foo(_, let baz) = quux {}" - let output = "if case let .foo(_, baz) = quux {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testHoistIfSecondArgIsUnderscore() { - let input = "if case .foo(let baz, _) = quux {}" - let output = "if case let .foo(baz, _) = quux {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testNestedHoistLet() { - let input = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" - let output = "if case let (.foo(a, b), .bar(c, d)) = quux {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testHoistCommaSeparatedSwitchCaseLets() { - let input = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" - let output = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" - testFormatting(for: input, output, rule: .hoistPatternLet, - exclude: [.wrapSwitchCases, .sortSwitchCases]) - } - - func testHoistNewlineSeparatedSwitchCaseLets() { - let input = """ - switch foo { - case .foo(let bar), - .bar(let bar): - } - """ - - let output = """ - switch foo { - case let .foo(bar), - let .bar(bar): - } - """ - - testFormatting(for: input, output, rule: .hoistPatternLet, - exclude: [.wrapSwitchCases, .sortSwitchCases]) - } - - func testHoistCatchLet() { - let input = "do {} catch Foo.foo(bar: let bar) {}" - let output = "do {} catch let Foo.foo(bar: bar) {}" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testNoNestedHoistLetWithSpecifiedArgs() { - let input = "if case (.foo(let a, b), .bar(let c, d)) = quux {}" - testFormatting(for: input, rule: .hoistPatternLet) - } - - func testNoHoistClosureVariables() { - let input = "foo({ let bar = 5 })" - testFormatting(for: input, rule: .hoistPatternLet, exclude: [.trailingClosures]) - } - - // TODO: this should actually hoist the let, but that's tricky to implement without - // breaking the `testNoOverHoistSwitchCaseWithNestedParens` case - func testHoistSwitchCaseWithNestedParens() { - let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), Foo.bar): break\n}" - testFormatting(for: input, rule: .hoistPatternLet, - exclude: [.blankLineAfterImports]) - } - - // TODO: this could actually hoist the let by one level, but that's tricky to implement - func testNoOverHoistSwitchCaseWithNestedParens() { - let input = "import Foo\nswitch (foo, bar) {\ncase (.baz(let quux), bar): break\n}" - testFormatting(for: input, rule: .hoistPatternLet, - exclude: [.blankLineAfterImports]) - } - - func testNoHoistLetWithEmptArg() { - let input = "if .foo(let _) = bar {}" - testFormatting(for: input, rule: .hoistPatternLet, - exclude: [.redundantLet, .redundantPattern]) - } - - func testHoistLetWithNoSpaceAfterCase() { - let input = "switch x { case.some(let y): return y }" - let output = "switch x { case let .some(y): return y }" - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testHoistWrappedGuardCaseLet() { - let input = """ - guard case Foo - .bar(let baz) - else { - return - } - """ - let output = """ - guard case let Foo - .bar(baz) - else { - return - } - """ - testFormatting(for: input, output, rule: .hoistPatternLet) - } - - func testNoHoistCaseLetContainingGenerics() { - // Hoisting in this case causes a compilation error as-of Swift 5.3 - // See: https://github.com/nicklockwood/SwiftFormat/issues/768 - let input = "if case .some(Optional.some(let foo)) = bar else {}" - testFormatting(for: input, rule: .hoistPatternLet, exclude: [.typeSugar]) - } - - // hoist = false - - func testUnhoistCaseLet() { - let input = "if case let .foo(bar, baz) = quux {}" - let output = "if case .foo(let bar, let baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testUnhoistCaseLetDictionaryTuple() { - let input = """ - switch (a, b) { - case let (c as [String: Any], d as [String: Any]): - break - } - """ - let output = """ - switch (a, b) { - case (let c as [String: Any], let d as [String: Any]): - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testUnhoistLabelledCaseLet() { - let input = "if case let .foo(bar: bar, baz: baz) = quux {}" - let output = "if case .foo(bar: let bar, baz: let baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testUnhoistCaseVar() { - let input = "if case var .foo(bar, baz) = quux {}" - let output = "if case .foo(var bar, var baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testNoUnhoistGuardCaseLetFollowedByFunction() { - let input = """ - guard case let foo as Foo = bar else { return } - foo.bar(foo: bar) - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .hoistPatternLet, options: options, - exclude: [.wrapConditionalBodies]) - } - - func testNoUnhoistSwitchCaseLetFollowedByWhere() { - let input = """ - switch foo { - case let bar? where bar >= baz(quux): - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .hoistPatternLet, options: options) - } - - func testNoUnhoistSwitchCaseLetFollowedByAs() { - let input = """ - switch foo { - case let bar as (String, String): - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .hoistPatternLet, options: options) - } - - func testUnhoistSingleCaseLet() { - let input = "if case let .foo(bar) = quux {}" - let output = "if case .foo(let bar) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testUnhoistIfArgIsEnumCaseLiteral() { - let input = "if case let .foo(.bar, baz) = quux {}" - let output = "if case .foo(.bar, let baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testUnhoistIfArgIsEnumCaseLiteralInParens() { - let input = "switch foo {\ncase let (.bar(baz)):\n}" - let output = "switch foo {\ncase (.bar(let baz)):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options, - exclude: [.redundantParens]) - } - - func testUnhoistIfArgIsNamespacedEnumCaseLiteral() { - let input = "switch foo {\ncase let Foo.bar(baz):\n}" - let output = "switch foo {\ncase Foo.bar(let baz):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { - let input = "switch foo {\ncase let (Foo.bar(baz)):\n}" - let output = "switch foo {\ncase (Foo.bar(let baz)):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options, - exclude: [.redundantParens]) - } - - func testUnhoistIfArgIsUnderscore() { - let input = "if case let .foo(_, baz) = quux {}" - let output = "if case .foo(_, let baz) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testNestedUnhoistLet() { - let input = "if case let (.foo(a, b), .bar(c, d)) = quux {}" - let output = "if case (.foo(let a, let b), .bar(let c, let d)) = quux {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testUnhoistCommaSeparatedSwitchCaseLets() { - let input = "switch foo {\ncase let .foo(bar), let .bar(bar):\n}" - let output = "switch foo {\ncase .foo(let bar), .bar(let bar):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options, - exclude: [.wrapSwitchCases, .sortSwitchCases]) - } - - func testUnhoistCommaSeparatedSwitchCaseLets2() { - let input = "switch foo {\ncase let Foo.foo(bar), let Foo.bar(bar):\n}" - let output = "switch foo {\ncase Foo.foo(let bar), Foo.bar(let bar):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options, - exclude: [.wrapSwitchCases, .sortSwitchCases]) - } - - func testUnhoistCatchLet() { - let input = "do {} catch let Foo.foo(bar: bar) {}" - let output = "do {} catch Foo.foo(bar: let bar) {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testNoUnhoistTupleLet() { - let input = "let (bar, baz) = quux()" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .hoistPatternLet, options: options) - } - - func testNoUnhoistIfLetTuple() { - let input = "if let x = y, let (_, a) = z {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .hoistPatternLet, options: options) - } - - func testNoUnhoistIfCaseFollowedByLetTuple() { - let input = "if case .foo = bar, let (foo, bar) = baz {}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .hoistPatternLet, options: options) - } - - func testNoUnhoistIfArgIsNamespacedEnumCaseLiteralInParens() { - let input = "switch foo {\ncase (Foo.bar(let baz)):\n}" - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .hoistPatternLet, options: options, - exclude: [.redundantParens]) - } - - func testNoDeleteCommentWhenUnhoistingWrappedLet() { - let input = """ - switch foo { - case /* next */ let .bar(bar): - } - """ - - let output = """ - switch foo { - case /* next */ .bar(let bar): - } - """ - - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, - options: options, exclude: [.wrapSwitchCases, .sortSwitchCases]) - } - - func testMultilineGuardLet() { - let input = """ - guard - let first = response?.first, - let last = response?.last, - case .foo(token: let foo, provider: let bar) = first, - case .foo(token: let baz, provider: let quux) = last - else { - return - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .hoistPatternLet, options: options) - } - - func testUnhoistCaseWithNilValue() { - let input = """ - switch (foo, bar) { - case let (.some(unwrappedFoo), nil): - print(unwrappedFoo) - default: - break - } - """ - let output = """ - switch (foo, bar) { - case (.some(let unwrappedFoo), nil): - print(unwrappedFoo) - default: - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } - - func testUnhoistCaseWithBoolValue() { - let input = """ - switch (foo, bar) { - case let (.some(unwrappedFoo), false): - print(unwrappedFoo) - default: - break - } - """ - let output = """ - switch (foo, bar) { - case (.some(let unwrappedFoo), false): - print(unwrappedFoo) - default: - break - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, output, rule: .hoistPatternLet, options: options) - } -} diff --git a/Tests/RulesTests+Linebreaks.swift b/Tests/RulesTests+Linebreaks.swift deleted file mode 100644 index 8afdd8234..000000000 --- a/Tests/RulesTests+Linebreaks.swift +++ /dev/null @@ -1,861 +0,0 @@ -// -// RulesTests+Linebreaks.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class LinebreakTests: RulesTests { - // MARK: - linebreaks - - func testCarriageReturn() { - let input = "foo\rbar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: .linebreaks) - } - - func testCarriageReturnLinefeed() { - let input = "foo\r\nbar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: .linebreaks) - } - - func testVerticalTab() { - let input = "foo\u{000B}bar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: .linebreaks) - } - - func testFormfeed() { - let input = "foo\u{000C}bar" - let output = "foo\nbar" - testFormatting(for: input, output, rule: .linebreaks) - } - - // MARK: - consecutiveBlankLines - - func testConsecutiveBlankLines() { - let input = "foo\n\n \nbar" - let output = "foo\n\nbar" - testFormatting(for: input, output, rule: .consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAtEndOfFile() { - let input = "foo\n\n" - let output = "foo\n" - testFormatting(for: input, output, rule: .consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAtStartOfFile() { - let input = "\n\n\nfoo" - let output = "\n\nfoo" - testFormatting(for: input, output, rule: .consecutiveBlankLines) - } - - func testConsecutiveBlankLinesInsideStringLiteral() { - let input = "\"\"\"\nhello\n\n\nworld\n\"\"\"" - testFormatting(for: input, rule: .consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAtStartOfStringLiteral() { - let input = "\"\"\"\n\n\nhello world\n\"\"\"" - testFormatting(for: input, rule: .consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAfterStringLiteral() { - let input = "\"\"\"\nhello world\n\"\"\"\n\n\nfoo()" - let output = "\"\"\"\nhello world\n\"\"\"\n\nfoo()" - testFormatting(for: input, output, rule: .consecutiveBlankLines) - } - - func testFragmentWithTrailingLinebreaks() { - let input = "func foo() {}\n\n\n" - let output = "func foo() {}\n\n" - let options = FormatOptions(fragment: true) - testFormatting(for: input, output, rule: .consecutiveBlankLines, options: options) - } - - func testConsecutiveBlankLinesNoInterpolation() { - let input = """ - \"\"\" - AAA - ZZZ - - - - \"\"\" - """ - testFormatting(for: input, rule: .consecutiveBlankLines) - } - - func testConsecutiveBlankLinesAfterInterpolation() { - let input = """ - \"\"\" - AAA - \\(interpolated) - - - - \"\"\" - """ - testFormatting(for: input, rule: .consecutiveBlankLines) - } - - func testLintingConsecutiveBlankLinesReportsCorrectLine() { - let input = "foo\n \n\nbar" - XCTAssertEqual(try lint(input, rules: [.consecutiveBlankLines]), [ - .init(line: 3, rule: .consecutiveBlankLines, filePath: nil), - ]) - } - - // MARK: - blankLinesAtStartOfScope - - func testBlankLinesRemovedAtStartOfFunction() { - let input = "func foo() {\n\n // code\n}" - let output = "func foo() {\n // code\n}" - testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) - } - - func testBlankLinesRemovedAtStartOfParens() { - let input = "(\n\n foo: Int\n)" - let output = "(\n foo: Int\n)" - testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) - } - - func testBlankLinesRemovedAtStartOfBrackets() { - let input = "[\n\n foo,\n bar,\n]" - let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) - } - - func testBlankLinesNotRemovedBetweenElementsInsideBrackets() { - let input = "[foo,\n\n bar]" - testFormatting(for: input, rule: .blankLinesAtStartOfScope, exclude: [.wrapArguments]) - } - - func testBlankLineRemovedFromStartOfTypeByDefault() { - let input = """ - class FooTests { - - func testFoo() {} - } - """ - - let output = """ - class FooTests { - func testFoo() {} - } - """ - testFormatting(for: input, output, rule: .blankLinesAtStartOfScope) - } - - func testBlankLinesNotRemovedFromStartOfTypeWithOptionEnabled() { - let input = """ - class FooClass { - - func fooMethod() {} - } - - struct FooStruct { - - func fooMethod() {} - } - - enum FooEnum { - - func fooMethod() {} - } - - actor FooActor { - - func fooMethod() {} - } - - protocol FooProtocol { - - func fooMethod() - } - - extension Array where Element == Foo { - - func fooMethod() {} - } - """ - testFormatting(for: input, rule: .blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) - } - - func testBlankLineAtStartOfScopeRemovedFromMethodInType() { - let input = """ - class Foo { - func bar() { - - print("hello world") - } - } - """ - - let output = """ - class Foo { - func bar() { - print("hello world") - } - } - """ - testFormatting(for: input, output, rule: .blankLinesAtStartOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) - } - - // MARK: - blankLinesAtEndOfScope - - func testBlankLinesRemovedAtEndOfFunction() { - let input = "func foo() {\n // code\n\n}" - let output = "func foo() {\n // code\n}" - testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) - } - - func testBlankLinesRemovedAtEndOfParens() { - let input = "(\n foo: Int\n\n)" - let output = "(\n foo: Int\n)" - testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) - } - - func testBlankLinesRemovedAtEndOfBrackets() { - let input = "[\n foo,\n bar,\n\n]" - let output = "[\n foo,\n bar,\n]" - testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) - } - - func testBlankLineNotRemovedBeforeElse() { - let input = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n\n}" - let output = "if x {\n\n // do something\n\n} else if y {\n\n // do something else\n}" - testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, - exclude: [.blankLinesAtStartOfScope]) - } - - func testBlankLineRemovedFromEndOfTypeByDefault() { - let input = """ - class FooTests { - func testFoo() {} - - } - """ - - let output = """ - class FooTests { - func testFoo() {} - } - """ - testFormatting(for: input, output, rule: .blankLinesAtEndOfScope) - } - - func testBlankLinesNotRemovedFromEndOfTypeWithOptionEnabled() { - let input = """ - class FooClass { - func fooMethod() {} - - } - - struct FooStruct { - func fooMethod() {} - - } - - enum FooEnum { - func fooMethod() {} - - } - - actor FooActor { - func fooMethod() {} - - } - - protocol FooProtocol { - func fooMethod() - } - - extension Array where Element == Foo { - func fooMethod() {} - - } - """ - testFormatting(for: input, rule: .blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) - } - - func testBlankLineAtEndOfScopeRemovedFromMethodInType() { - let input = """ - class Foo { - func bar() { - print("hello world") - - } - } - """ - - let output = """ - class Foo { - func bar() { - print("hello world") - } - } - """ - testFormatting(for: input, output, rule: .blankLinesAtEndOfScope, options: .init(removeStartOrEndBlankLinesFromTypes: false)) - } - - // MARK: - blankLinesBetweenImports - - func testBlankLinesBetweenImportsShort() { - let input = """ - import ModuleA - - import ModuleB - """ - let output = """ - import ModuleA - import ModuleB - """ - testFormatting(for: input, output, rule: .blankLinesBetweenImports) - } - - func testBlankLinesBetweenImportsLong() { - let input = """ - import ModuleA - import ModuleB - - import ModuleC - import ModuleD - import ModuleE - - import ModuleF - - import ModuleG - import ModuleH - """ - let output = """ - import ModuleA - import ModuleB - import ModuleC - import ModuleD - import ModuleE - import ModuleF - import ModuleG - import ModuleH - """ - testFormatting(for: input, output, rule: .blankLinesBetweenImports) - } - - func testBlankLinesBetweenImportsWithTestable() { - let input = """ - import ModuleA - - @testable import ModuleB - import ModuleC - - @testable import ModuleD - @testable import ModuleE - - @testable import ModuleF - """ - let output = """ - import ModuleA - @testable import ModuleB - import ModuleC - @testable import ModuleD - @testable import ModuleE - @testable import ModuleF - """ - testFormatting(for: input, output, rule: .blankLinesBetweenImports) - } - - // MARK: - blankLinesBetweenChainedFunctions - - func testBlankLinesBetweenChainedFunctions() { - let input = """ - [0, 1, 2] - .map { $0 * 2 } - - - - .map { $0 * 3 } - """ - let output1 = """ - [0, 1, 2] - .map { $0 * 2 } - .map { $0 * 3 } - """ - let output2 = """ - [0, 1, 2] - .map { $0 * 2 } - .map { $0 * 3 } - """ - testFormatting(for: input, [output1, output2], rules: [.blankLinesBetweenChainedFunctions]) - } - - func testBlankLinesWithCommentsBetweenChainedFunctions() { - let input = """ - [0, 1, 2] - .map { $0 * 2 } - - // Multiplies by 3 - - .map { $0 * 3 } - """ - let output = """ - [0, 1, 2] - .map { $0 * 2 } - // Multiplies by 3 - .map { $0 * 3 } - """ - testFormatting(for: input, output, rule: .blankLinesBetweenChainedFunctions) - } - - func testBlankLinesWithMarkCommentBetweenChainedFunctions() { - let input = """ - [0, 1, 2] - .map { $0 * 2 } - - // MARK: hello - - .map { $0 * 3 } - """ - testFormatting(for: input, rules: [.blankLinesBetweenChainedFunctions, .blankLinesAroundMark]) - } - - // MARK: - blankLineAfterImports - - func testBlankLineAfterImport() { - let input = """ - import ModuleA - @testable import ModuleB - import ModuleC - @testable import ModuleD - @_exported import ModuleE - @_implementationOnly import ModuleF - @_spi(SPI) import ModuleG - @_spiOnly import ModuleH - @preconcurrency import ModuleI - class foo {} - """ - let output = """ - import ModuleA - @testable import ModuleB - import ModuleC - @testable import ModuleD - @_exported import ModuleE - @_implementationOnly import ModuleF - @_spi(SPI) import ModuleG - @_spiOnly import ModuleH - @preconcurrency import ModuleI - - class foo {} - """ - testFormatting(for: input, output, rule: .blankLineAfterImports) - } - - func testBlankLinesBetweenConditionalImports() { - let input = """ - #if foo - import ModuleA - #else - import ModuleB - #endif - import ModuleC - func foo() {} - """ - let output = """ - #if foo - import ModuleA - #else - import ModuleB - #endif - import ModuleC - - func foo() {} - """ - testFormatting(for: input, output, rule: .blankLineAfterImports) - } - - func testBlankLinesBetweenNestedConditionalImports() { - let input = """ - #if foo - import ModuleA - #if bar - import ModuleB - #endif - #else - import ModuleC - #endif - import ModuleD - func foo() {} - """ - let output = """ - #if foo - import ModuleA - #if bar - import ModuleB - #endif - #else - import ModuleC - #endif - import ModuleD - - func foo() {} - """ - testFormatting(for: input, output, rule: .blankLineAfterImports) - } - - func testBlankLineAfterScopedImports() { - let input = """ - internal import UIKit - internal import Foundation - private import Time - public class Foo {} - """ - let output = """ - internal import UIKit - internal import Foundation - private import Time - - public class Foo {} - """ - testFormatting(for: input, output, rule: .blankLineAfterImports) - } - - // MARK: - blankLinesBetweenScopes - - func testBlankLineBetweenFunctions() { - let input = "func foo() {\n}\nfunc bar() {\n}" - let output = "func foo() {\n}\n\nfunc bar() {\n}" - testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: [.emptyBraces]) - } - - func testNoBlankLineBetweenPropertyAndFunction() { - let input = "var foo: Int\nfunc bar() {\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) - } - - func testBlankLineBetweenFunctionsIsBeforeComment() { - let input = "func foo() {\n}\n/// headerdoc\nfunc bar() {\n}" - let output = "func foo() {\n}\n\n/// headerdoc\nfunc bar() {\n}" - testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: [.emptyBraces]) - } - - func testBlankLineBeforeAtObjcOnLineBeforeProtocol() { - let input = "@objc\nprotocol Foo {\n}\n@objc\nprotocol Bar {\n}" - let output = "@objc\nprotocol Foo {\n}\n\n@objc\nprotocol Bar {\n}" - testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: [.emptyBraces]) - } - - func testBlankLineBeforeAtAvailabilityOnLineBeforeClass() { - let input = "protocol Foo {\n}\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" - let output = "protocol Foo {\n}\n\n@available(iOS 8.0, OSX 10.10, *)\nclass Bar {\n}" - testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: [.emptyBraces]) - } - - func testNoExtraBlankLineBetweenFunctions() { - let input = "func foo() {\n}\n\nfunc bar() {\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) - } - - func testNoBlankLineBetweenFunctionsInProtocol() { - let input = "protocol Foo {\n func bar() -> Void\n func baz() -> Int\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testNoBlankLineInsideInitFunction() { - let input = "init() {\n super.init()\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testBlankLineAfterProtocolBeforeProperty() { - let input = "protocol Foo {\n}\nvar bar: String" - let output = "protocol Foo {\n}\n\nvar bar: String" - testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: [.emptyBraces]) - } - - func testNoExtraBlankLineAfterSingleLineComment() { - let input = "var foo: Bar? // comment\n\nfunc bar() {}" - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testNoExtraBlankLineAfterMultilineComment() { - let input = "var foo: Bar? /* comment */\n\nfunc bar() {}" - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testNoBlankLineBeforeFuncAsIdentifier() { - let input = "var foo: Bar?\nfoo.func(x) {}" - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testNoBlankLineBetweenFunctionsWithInlineBody() { - let input = "class Foo {\n func foo() { print(\"foo\") }\n func bar() { print(\"bar\") }\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testNoBlankLineBetweenIfStatements() { - let input = "func foo() {\n if x {\n }\n if y {\n }\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) - } - - func testNoBlanksInsideClassFunc() { - let input = "class func foo {\n if x {\n }\n if y {\n }\n}" - let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, - exclude: [.emptyBraces]) - } - - func testNoBlanksInsideClassVar() { - let input = "class var foo: Int {\n if x {\n }\n if y {\n }\n}" - let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, - exclude: [.emptyBraces]) - } - - func testBlankLineBetweenCalledClosures() { - let input = "class Foo {\n var foo = {\n }()\n func bar {\n }\n}" - let output = "class Foo {\n var foo = {\n }()\n\n func bar {\n }\n}" - testFormatting(for: input, output, rule: .blankLinesBetweenScopes, - exclude: [.emptyBraces]) - } - - func testNoBlankLineAfterCalledClosureAtEndOfScope() { - let input = "class Foo {\n var foo = {\n }()\n}" - testFormatting(for: input, rule: .blankLinesBetweenScopes, exclude: [.emptyBraces]) - } - - func testNoBlankLineBeforeWhileInRepeatWhile() { - let input = """ - repeat - { print("foo") } - while false - { print("bar") }() - """ - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: .blankLinesBetweenScopes, options: options, exclude: [.redundantClosure, .wrapLoopBodies]) - } - - func testBlankLineBeforeWhileIfNotRepeatWhile() { - let input = "func foo(x)\n{\n}\nwhile true\n{\n}" - let output = "func foo(x)\n{\n}\n\nwhile true\n{\n}" - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: .blankLinesBetweenScopes, options: options, - exclude: [.emptyBraces]) - } - - func testNoInsertBlankLinesInConditionalCompilation() { - let input = """ - struct Foo { - #if BAR - func something() { - } - #else - func something() { - } - #endif - } - """ - testFormatting(for: input, rule: .blankLinesBetweenScopes, - exclude: [.emptyBraces]) - } - - func testNoInsertBlankLineAfterBraceBeforeSourceryComment() { - let input = """ - struct Foo { - var bar: String - - // sourcery:inline:Foo.init - public init(bar: String) { - self.bar = bar - } - // sourcery:end - } - """ - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testNoBlankLineBetweenChainedClosures() { - let input = """ - foo { - doFoo() - } - // bar - .bar { - doBar() - } - // baz - .baz { - doBaz($0) - } - """ - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testNoBlankLineBetweenTrailingClosures() { - let input = """ - UIView.animate(withDuration: 0) { - fromView.transform = .identity - } - completion: { finished in - context.completeTransition(finished) - } - """ - testFormatting(for: input, rule: .blankLinesBetweenScopes) - } - - func testBlankLineBetweenTrailingClosureAndLabelledLoop() { - let input = """ - UIView.animate(withDuration: 0) { - fromView.transform = .identity - } - completion: for foo in bar { - print(foo) - } - """ - let output = """ - UIView.animate(withDuration: 0) { - fromView.transform = .identity - } - - completion: for foo in bar { - print(foo) - } - """ - testFormatting(for: input, output, rule: .blankLinesBetweenScopes) - } - - // MARK: - blankLinesAroundMark - - func testInsertBlankLinesAroundMark() { - let input = """ - let foo = "foo" - // MARK: bar - let bar = "bar" - """ - let output = """ - let foo = "foo" - - // MARK: bar - - let bar = "bar" - """ - testFormatting(for: input, output, rule: .blankLinesAroundMark) - } - - func testNoInsertExtraBlankLinesAroundMark() { - let input = """ - let foo = "foo" - - // MARK: bar - - let bar = "bar" - """ - testFormatting(for: input, rule: .blankLinesAroundMark) - } - - func testInsertBlankLineAfterMarkAtStartOfFile() { - let input = """ - // MARK: bar - let bar = "bar" - """ - let output = """ - // MARK: bar - - let bar = "bar" - """ - testFormatting(for: input, output, rule: .blankLinesAroundMark) - } - - func testInsertBlankLineBeforeMarkAtEndOfFile() { - let input = """ - let foo = "foo" - // MARK: bar - """ - let output = """ - let foo = "foo" - - // MARK: bar - """ - testFormatting(for: input, output, rule: .blankLinesAroundMark) - } - - func testNoInsertBlankLineBeforeMarkAtStartOfScope() { - let input = """ - do { - // MARK: foo - - let foo = "foo" - } - """ - testFormatting(for: input, rule: .blankLinesAroundMark) - } - - func testNoInsertBlankLineAfterMarkAtEndOfScope() { - let input = """ - do { - let foo = "foo" - - // MARK: foo - } - """ - testFormatting(for: input, rule: .blankLinesAroundMark) - } - - func testInsertBlankLinesJustBeforeMarkNotAfter() { - let input = """ - let foo = "foo" - // MARK: bar - let bar = "bar" - """ - let output = """ - let foo = "foo" - - // MARK: bar - let bar = "bar" - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, output, rule: .blankLinesAroundMark, options: options) - } - - func testNoInsertExtraBlankLinesAroundMarkWithNoBlankLineAfterMark() { - let input = """ - let foo = "foo" - - // MARK: bar - let bar = "bar" - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, rule: .blankLinesAroundMark, options: options) - } - - func testNoInsertBlankLineAfterMarkAtStartOfFile() { - let input = """ - // MARK: bar - let bar = "bar" - """ - let options = FormatOptions(lineAfterMarks: false) - testFormatting(for: input, rule: .blankLinesAroundMark, options: options) - } - - // MARK: - linebreakAtEndOfFile - - func testLinebreakAtEndOfFile() { - let input = "foo\nbar" - let output = "foo\nbar\n" - testFormatting(for: input, output, rule: .linebreakAtEndOfFile) - } - - func testNoLinebreakAtEndOfFragment() { - let input = "foo\nbar" - let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: .linebreakAtEndOfFile, options: options) - } -} diff --git a/Tests/RulesTests+Redundancy.swift b/Tests/RulesTests+Redundancy.swift deleted file mode 100644 index d0827f319..000000000 --- a/Tests/RulesTests+Redundancy.swift +++ /dev/null @@ -1,10766 +0,0 @@ -// -// RulesTests+Redundancy.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class RedundancyTests: RulesTests { - // MARK: - redundantBreak - - func testRedundantBreaksRemoved() { - let input = """ - switch x { - case foo: - print("hello") - break - case bar: - print("world") - break - default: - print("goodbye") - break - } - """ - let output = """ - switch x { - case foo: - print("hello") - case bar: - print("world") - default: - print("goodbye") - } - """ - testFormatting(for: input, output, rule: .redundantBreak) - } - - func testBreakInEmptyCaseNotRemoved() { - let input = """ - switch x { - case foo: - break - case bar: - break - default: - break - } - """ - testFormatting(for: input, rule: .redundantBreak) - } - - func testConditionalBreakNotRemoved() { - let input = """ - switch x { - case foo: - if bar { - break - } - } - """ - testFormatting(for: input, rule: .redundantBreak) - } - - func testBreakAfterSemicolonNotMangled() { - let input = """ - switch foo { - case 1: print(1); break - } - """ - let output = """ - switch foo { - case 1: print(1); - } - """ - testFormatting(for: input, output, rule: .redundantBreak, exclude: [.semicolons]) - } - - // MARK: - redundantExtensionACL - - func testPublicExtensionMemberACLStripped() { - let input = """ - public extension Foo { - public var bar: Int { 5 } - private static let baz = "baz" - public func quux() {} - } - """ - let output = """ - public extension Foo { - var bar: Int { 5 } - private static let baz = "baz" - func quux() {} - } - """ - testFormatting(for: input, output, rule: .redundantExtensionACL) - } - - func testPrivateExtensionMemberACLNotStrippedUnlessFileprivate() { - let input = """ - private extension Foo { - fileprivate var bar: Int { 5 } - private static let baz = "baz" - fileprivate func quux() {} - } - """ - let output = """ - private extension Foo { - var bar: Int { 5 } - private static let baz = "baz" - func quux() {} - } - """ - testFormatting(for: input, output, rule: .redundantExtensionACL) - } - - // MARK: - redundantFileprivate - - func testFileScopeFileprivateVarChangedToPrivate() { - let input = """ - fileprivate var foo = "foo" - """ - let output = """ - private var foo = "foo" - """ - testFormatting(for: input, output, rule: .redundantFileprivate) - } - - func testFileScopeFileprivateVarNotChangedToPrivateIfFragment() { - let input = """ - fileprivate var foo = "foo" - """ - let options = FormatOptions(fragment: true) - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherType() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - } - """ - let output = """ - struct Foo { - private var foo = "foo" - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarChangedToPrivateIfNotAccessedFromAnotherTypeAndFileIncludesImports() { - let input = """ - import Foundation - - struct Foo { - fileprivate var foo = "foo" - } - """ - let output = """ - import Foundation - - struct Foo { - private var foo = "foo" - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAnotherType() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - struct Bar { - func bar() { - print(Foo().foo) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromSubclass() { - let input = """ - class Foo { - fileprivate func foo() {} - } - - class Bar: Foo { - func bar() { - return foo() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAFunction() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - func getFoo() -> String { - return Foo().foo - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAConstant() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - let kFoo = Foo().foo - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAVar() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - var kFoo: String { return Foo().foo } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromCode() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - print(Foo().foo) - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAClosure() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - print({ Foo().foo }()) - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.redundantClosure]) - } - - func testFileprivateVarNotChangedToPrivateIfAccessedFromAnExtensionOnAnotherType() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - extension Bar { - func bar() { - print(Foo().foo) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarChangedToPrivateIfAccessedFromAnExtensionOnSameType() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - extension Foo { - func bar() { - print(foo) - } - } - """ - let output = """ - struct Foo { - private let foo = "foo" - } - - extension Foo { - func bar() { - print(foo) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarChangedToPrivateIfAccessedViaSelfFromAnExtensionOnSameType() { - let input = """ - struct Foo { - fileprivate let foo = "foo" - } - - extension Foo { - func bar() { - print(self.foo) - } - } - """ - let output = """ - struct Foo { - private let foo = "foo" - } - - extension Foo { - func bar() { - print(self.foo) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantFileprivate, options: options, - exclude: [.redundantSelf]) - } - - func testFileprivateMultiLetNotChangedToPrivateIfAccessedOutsideType() { - let input = """ - struct Foo { - fileprivate let foo = "foo", bar = "bar" - } - - extension Foo { - func bar() { - print(foo) - } - } - - extension Bar { - func bar() { - print(Foo().bar) - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInitChangedToPrivateIfConstructorNotCalledOutsideType() { - let input = """ - struct Foo { - fileprivate init() {} - } - """ - let output = """ - struct Foo { - private init() {} - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType() { - let input = """ - struct Foo { - fileprivate init() {} - } - - let foo = Foo() - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) - } - - func testFileprivateInitNotChangedToPrivateIfConstructorCalledOutsideType2() { - let input = """ - class Foo { - fileprivate init() {} - } - - struct Bar { - let foo = Foo() - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) - } - - func testFileprivateStructMemberNotChangedToPrivateIfConstructorCalledOutsideType() { - let input = """ - struct Foo { - fileprivate let bar: String - } - - let foo = Foo(bar: "test") - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) - } - - func testFileprivateClassMemberChangedToPrivateEvenIfConstructorCalledOutsideType() { - let input = """ - class Foo { - fileprivate let bar: String - } - - let foo = Foo() - """ - let output = """ - class Foo { - private let bar: String - } - - let foo = Foo() - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) - } - - func testFileprivateExtensionFuncNotChangedToPrivateIfPartOfProtocolConformance() { - let input = """ - private class Foo: Equatable { - fileprivate static func == (_: Foo, _: Foo) -> Bool { - return true - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInnerTypeNotChangedToPrivate() { - let input = """ - struct Foo { - fileprivate enum Bar { - case a, b - } - - fileprivate let bar: Bar - } - - func foo(foo: Foo) { - print(foo.bar) - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, - rule: .redundantFileprivate, - options: options, - exclude: [.wrapEnumCases]) - } - - func testFileprivateClassTypeMemberNotChangedToPrivate() { - let input = """ - class Foo { - fileprivate class var bar = "bar" - } - - func foo() { - print(Foo.bar) - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testOverriddenFileprivateInitNotChangedToPrivate() { - let input = """ - class Foo { - fileprivate init() {} - } - - class Bar: Foo, Equatable { - override public init() { - super.init() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testNonOverriddenFileprivateInitChangedToPrivate() { - let input = """ - class Foo { - fileprivate init() {} - } - - class Bar: Baz { - override public init() { - super.init() - } - } - """ - let output = """ - class Foo { - private init() {} - } - - class Bar: Baz { - override public init() { - super.init() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInitNotChangedToPrivateWhenUsingTypeInferredInits() { - let input = """ - struct Example { - fileprivate init() {} - } - - enum Namespace { - static let example: Example = .init() - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options, exclude: [.propertyType]) - } - - func testFileprivateInitNotChangedToPrivateWhenUsingTrailingClosureInit() { - let input = """ - private struct Foo {} - - public struct Bar { - fileprivate let consumeFoo: (Foo) -> Void - } - - public func makeBar() -> Bar { - Bar { _ in } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnContainingType() { - let input = """ - extension Foo.Bar { - fileprivate init() {} - } - - extension Foo { - func baz() -> Foo.Bar { - return Bar() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateNotChangedToPrivateWhenAccessedFromExtensionOnNestedType() { - let input = """ - extension Foo { - fileprivate init() {} - } - - extension Foo.Bar { - func baz() -> Foo { - return Foo() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromSubclass() { - let input = """ - class Foo: Bar { - func quux() { - baz() - } - } - - extension Bar { - fileprivate func baz() {} - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInitNotChangedToPrivateWhenAccessedFromSubclass() { - let input = """ - public class Foo { - fileprivate init() {} - } - - private class Bar: Foo { - init(something: String) { - print(something) - super.init() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInExtensionNotChangedToPrivateWhenAccessedFromExtensionOnSubclass() { - let input = """ - class Foo: Bar {} - - extension Foo { - func quux() { - baz() - } - } - - extension Bar { - fileprivate func baz() {} - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateVarWithPropertWrapperNotChangedToPrivateIfAccessedFromSubclass() { - let input = """ - class Foo { - @Foo fileprivate var foo = 5 - } - - class Bar: Foo { - func bar() { - return $foo - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile() { - let input = """ - extension [String] { - fileprivate func fileprivateMember() {} - } - - extension Namespace { - func testCanAccessFileprivateMember() { - ["string", "array"].fileprivateMember() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, options: options) - } - - func testFileprivateInArrayExtensionNotChangedToPrivateWhenAccessedInFile2() { - let input = """ - extension Array { - fileprivate func fileprivateMember() {} - } - - extension Namespace { - func testCanAccessFileprivateMember() { - ["string", "array"].fileprivateMember() - } - } - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, rule: .redundantFileprivate, - options: options, exclude: [.typeSugar]) - } - - // MARK: - redundantGet - - func testRemoveSingleLineIsolatedGet() { - let input = "var foo: Int { get { return 5 } }" - let output = "var foo: Int { return 5 }" - testFormatting(for: input, output, rule: .redundantGet) - } - - func testRemoveMultilineIsolatedGet() { - let input = "var foo: Int {\n get {\n return 5\n }\n}" - let output = "var foo: Int {\n return 5\n}" - testFormatting(for: input, [output], rules: [.redundantGet, .indent]) - } - - func testNoRemoveMultilineGetSet() { - let input = "var foo: Int {\n get { return 5 }\n set { foo = newValue }\n}" - testFormatting(for: input, rule: .redundantGet) - } - - func testNoRemoveAttributedGet() { - let input = "var enabled: Bool { @objc(isEnabled) get { true } }" - testFormatting(for: input, rule: .redundantGet) - } - - func testRemoveSubscriptGet() { - let input = "subscript(_ index: Int) {\n get {\n return lookup(index)\n }\n}" - let output = "subscript(_ index: Int) {\n return lookup(index)\n}" - testFormatting(for: input, [output], rules: [.redundantGet, .indent]) - } - - func testGetNotRemovedInFunction() { - let input = "func foo() {\n get {\n self.lookup(index)\n }\n}" - testFormatting(for: input, rule: .redundantGet) - } - - func testEffectfulGetNotRemoved() { - let input = """ - var foo: Int { - get async throws { - try await getFoo() - } - } - """ - testFormatting(for: input, rule: .redundantGet) - } - - // MARK: - redundantInit - - func testRemoveRedundantInit() { - let input = "[1].flatMap { String.init($0) }" - let output = "[1].flatMap { String($0) }" - testFormatting(for: input, output, rule: .redundantInit) - } - - func testRemoveRedundantInit2() { - let input = "[String.self].map { Type in Type.init(foo: 1) }" - let output = "[String.self].map { Type in Type(foo: 1) }" - testFormatting(for: input, output, rule: .redundantInit) - } - - func testRemoveRedundantInit3() { - let input = "String.init(\"text\")" - let output = "String(\"text\")" - testFormatting(for: input, output, rule: .redundantInit) - } - - func testDontRemoveInitInSuperCall() { - let input = "class C: NSObject { override init() { super.init() } }" - testFormatting(for: input, rule: .redundantInit) - } - - func testDontRemoveInitInSelfCall() { - let input = "struct S { let n: Int }; extension S { init() { self.init(n: 1) } }" - testFormatting(for: input, rule: .redundantInit) - } - - func testDontRemoveInitWhenPassedAsFunction() { - let input = "[1].flatMap(String.init)" - testFormatting(for: input, rule: .redundantInit) - } - - func testDontRemoveInitWhenUsedOnMetatype() { - let input = "[String.self].map { type in type.init(1) }" - testFormatting(for: input, rule: .redundantInit) - } - - func testDontRemoveInitWhenUsedOnImplicitClosureMetatype() { - let input = "[String.self].map { $0.init(1) }" - testFormatting(for: input, rule: .redundantInit) - } - - func testDontRemoveInitWhenUsedOnPossibleMetatype() { - let input = "let something = Foo.bar.init()" - testFormatting(for: input, rule: .redundantInit) - } - - func testDontRemoveInitWithExplicitSignature() { - let input = "[String.self].map(Foo.init(bar:))" - testFormatting(for: input, rule: .redundantInit) - } - - func testRemoveInitWithOpenParenOnFollowingLine() { - let input = """ - var foo: Foo { - Foo.init - ( - bar: bar, - baaz: baaz - ) - } - """ - let output = """ - var foo: Foo { - Foo( - bar: bar, - baaz: baaz - ) - } - """ - testFormatting(for: input, output, rule: .redundantInit) - } - - func testNoRemoveInitWithOpenParenOnFollowingLineAfterComment() { - let input = """ - var foo: Foo { - Foo.init // foo - ( - bar: bar, - baaz: baaz - ) - } - """ - testFormatting(for: input, rule: .redundantInit) - } - - func testNoRemoveInitForLowercaseType() { - let input = """ - let foo = bar.init() - """ - testFormatting(for: input, rule: .redundantInit) - } - - func testNoRemoveInitForLocalLetType() { - let input = """ - let Foo = Foo.self - let foo = Foo.init() - """ - testFormatting(for: input, rule: .redundantInit, exclude: [.propertyType]) - } - - func testNoRemoveInitForLocalLetType2() { - let input = """ - let Foo = Foo.self - if x { - return Foo.init(x) - } else { - return Foo.init(y) - } - """ - testFormatting(for: input, rule: .redundantInit) - } - - func testNoRemoveInitInsideIfdef() { - let input = """ - func myFunc() async throws -> String { - #if DEBUG - .init("foo") - #else - "" - #endif - } - """ - testFormatting(for: input, rule: .redundantInit, exclude: [.indent]) - } - - func testNoRemoveInitInsideIfdef2() { - let input = """ - func myFunc() async throws(Foo) -> String { - #if DEBUG - .init("foo") - #else - "" - #endif - } - """ - testFormatting(for: input, rule: .redundantInit, exclude: [.indent]) - } - - func testRemoveInitAfterCollectionLiterals() { - let input = """ - let array = [String].init() - let arrayElement = [String].Element.init() - let nestedArray = [[String]].init() - let tupleArray = [(key: String, value: Int)].init() - let dictionary = [String: Int].init() - """ - let output = """ - let array = [String]() - let arrayElement = [String].Element() - let nestedArray = [[String]]() - let tupleArray = [(key: String, value: Int)]() - let dictionary = [String: Int]() - """ - testFormatting(for: input, output, rule: .redundantInit, exclude: [.propertyType]) - } - - func testPreservesInitAfterTypeOfCall() { - let input = """ - type(of: oldViewController).init() - """ - - testFormatting(for: input, rule: .redundantInit) - } - - func testRemoveInitAfterOptionalType() { - let input = """ - let someOptional = String?.init("Foo") - // (String!.init("Foo") isn't valid Swift code, so we don't test for it) - """ - let output = """ - let someOptional = String?("Foo") - // (String!.init("Foo") isn't valid Swift code, so we don't test for it) - """ - - testFormatting(for: input, output, rule: .redundantInit, exclude: [.propertyType]) - } - - func testPreservesTryBeforeInit() { - let input = """ - let throwing: Foo = try .init() - let throwingOptional1: Foo = try? .init() - let throwingOptional2: Foo = try! .init() - """ - - testFormatting(for: input, rule: .redundantInit) - } - - func testRemoveInitAfterGenericType() { - let input = """ - let array = Array.init() - let dictionary = Dictionary.init() - let atomicDictionary = Atomic<[String: Int]>.init() - """ - let output = """ - let array = Array() - let dictionary = Dictionary() - let atomicDictionary = Atomic<[String: Int]>() - """ - - testFormatting(for: input, output, rule: .redundantInit, exclude: [.typeSugar, .propertyType]) - } - - func testPreserveNonRedundantInitInTernaryOperator() { - let input = """ - let bar: Bar = (foo.isBar && bar.isBaaz) ? .init() : nil - """ - testFormatting(for: input, rule: .redundantInit) - } - - // MARK: - redundantLetError - - func testCatchLetError() { - let input = "do {} catch let error {}" - let output = "do {} catch {}" - testFormatting(for: input, output, rule: .redundantLetError) - } - - func testCatchLetErrorWithTypedThrows() { - let input = "do throws(Foo) {} catch let error {}" - let output = "do throws(Foo) {} catch {}" - testFormatting(for: input, output, rule: .redundantLetError) - } - - // MARK: - redundantObjc - - func testRedundantObjcRemovedFromBeforeOutlet() { - let input = "@objc @IBOutlet var label: UILabel!" - let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: .redundantObjc) - } - - func testRedundantObjcRemovedFromAfterOutlet() { - let input = "@IBOutlet @objc var label: UILabel!" - let output = "@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: .redundantObjc) - } - - func testRedundantObjcRemovedFromLineBeforeOutlet() { - let input = "@objc\n@IBOutlet var label: UILabel!" - let output = "\n@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: .redundantObjc) - } - - func testRedundantObjcCommentNotRemoved() { - let input = "@objc /// an outlet\n@IBOutlet var label: UILabel!" - let output = "/// an outlet\n@IBOutlet var label: UILabel!" - testFormatting(for: input, output, rule: .redundantObjc) - } - - func testObjcNotRemovedFromNSCopying() { - let input = "@objc @NSCopying var foo: String!" - testFormatting(for: input, rule: .redundantObjc) - } - - func testRenamedObjcNotRemoved() { - let input = "@IBOutlet @objc(uiLabel) var label: UILabel!" - testFormatting(for: input, rule: .redundantObjc) - } - - func testObjcRemovedOnObjcMembersClass() { - let input = """ - @objcMembers class Foo: NSObject { - @objc var foo: String - } - """ - let output = """ - @objcMembers class Foo: NSObject { - var foo: String - } - """ - testFormatting(for: input, output, rule: .redundantObjc) - } - - func testObjcRemovedOnRenamedObjcMembersClass() { - let input = """ - @objcMembers @objc(OCFoo) class Foo: NSObject { - @objc var foo: String - } - """ - let output = """ - @objcMembers @objc(OCFoo) class Foo: NSObject { - var foo: String - } - """ - testFormatting(for: input, output, rule: .redundantObjc) - } - - func testObjcNotRemovedOnNestedClass() { - let input = """ - @objcMembers class Foo: NSObject { - @objc class Bar: NSObject {} - } - """ - testFormatting(for: input, rule: .redundantObjc) - } - - func testObjcNotRemovedOnRenamedPrivateNestedClass() { - let input = """ - @objcMembers class Foo: NSObject { - @objc private class Bar: NSObject {} - } - """ - testFormatting(for: input, rule: .redundantObjc) - } - - func testObjcNotRemovedOnNestedEnum() { - let input = """ - @objcMembers class Foo: NSObject { - @objc enum Bar: Int {} - } - """ - testFormatting(for: input, rule: .redundantObjc) - } - - func testObjcRemovedOnObjcExtensionVar() { - let input = """ - @objc extension Foo { - @objc var foo: String {} - } - """ - let output = """ - @objc extension Foo { - var foo: String {} - } - """ - testFormatting(for: input, output, rule: .redundantObjc) - } - - func testObjcRemovedOnObjcExtensionFunc() { - let input = """ - @objc extension Foo { - @objc func foo() -> String {} - } - """ - let output = """ - @objc extension Foo { - func foo() -> String {} - } - """ - testFormatting(for: input, output, rule: .redundantObjc) - } - - func testObjcNotRemovedOnPrivateFunc() { - let input = """ - @objcMembers class Foo: NSObject { - @objc private func bar() {} - } - """ - testFormatting(for: input, rule: .redundantObjc) - } - - func testObjcNotRemovedOnFileprivateFunc() { - let input = """ - @objcMembers class Foo: NSObject { - @objc fileprivate func bar() {} - } - """ - testFormatting(for: input, rule: .redundantObjc) - } - - func testObjcRemovedOnPrivateSetFunc() { - let input = """ - @objcMembers class Foo: NSObject { - @objc private(set) func bar() {} - } - """ - let output = """ - @objcMembers class Foo: NSObject { - private(set) func bar() {} - } - """ - testFormatting(for: input, output, rule: .redundantObjc) - } - - // MARK: - redundantType - - func testVarRedundantTypeRemoval() { - let input = "var view: UIView = UIView()" - let output = "var view = UIView()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testVarRedundantArrayTypeRemoval() { - let input = "var foo: [String] = [String]()" - let output = "var foo = [String]()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testVarRedundantDictionaryTypeRemoval() { - let input = "var foo: [String: Int] = [String: Int]()" - let output = "var foo = [String: Int]()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testLetRedundantGenericTypeRemoval() { - let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" - let output = "let relay = BehaviourRelay(value: nil)" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testVarNonRedundantTypeDoesNothing() { - let input = "var view: UIView = UINavigationBar()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testLetRedundantTypeRemoval() { - let input = "let view: UIView = UIView()" - let output = "let view = UIView()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testLetNonRedundantTypeDoesNothing() { - let input = "let view: UIView = UINavigationBar()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testTypeNoRedundancyDoesNothing() { - let input = "let foo: Bar = 5" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testClassTwoVariablesNoRedundantTypeDoesNothing() { - let input = """ - final class LGWebSocketClient: WebSocketClient, WebSocketLibraryDelegate { - var webSocket: WebSocketLibraryProtocol - var timeoutIntervalForRequest: TimeInterval = LGCoreKitConstants.websocketTimeOutTimeInterval - } - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testRedundantTypeRemovedIfValueOnNextLine() { - let input = """ - let view: UIView - = UIView() - """ - let output = """ - let view - = UIView() - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testRedundantTypeRemovedIfValueOnNextLine2() { - let input = """ - let view: UIView = - UIView() - """ - let output = """ - let view = - UIView() - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testAllRedundantTypesRemovedInCommaDelimitedDeclaration() { - let input = "var foo: Int = 0, bar: Int = 0" - let output = "var foo = 0, bar = 0" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testRedundantTypeRemovalWithComment() { - let input = "var view: UIView /* view */ = UIView()" - let output = "var view /* view */ = UIView()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testRedundantTypeRemovalWithComment2() { - let input = "var view: UIView = /* view */ UIView()" - let output = "var view = /* view */ UIView()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testNonRedundantTernaryConditionTypeNotRemoved() { - let input = "let foo: Bar = Bar.baz() ? .bar1 : .bar2" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testTernaryConditionAfterLetNotTreatedAsPartOfExpression() { - let input = """ - let foo: Bar = Bar.baz() - baz ? bar2() : bar2() - """ - let output = """ - let foo = Bar.baz() - baz ? bar2() : bar2() - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testNoRemoveRedundantTypeIfVoid() { - let input = "let foo: Void = Void()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, - options: options, exclude: [.void]) - } - - func testNoRemoveRedundantTypeIfVoid2() { - let input = "let foo: () = ()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, - options: options, exclude: [.void]) - } - - func testNoRemoveRedundantTypeIfVoid3() { - let input = "let foo: [Void] = [Void]()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testNoRemoveRedundantTypeIfVoid4() { - let input = "let foo: Array = Array()" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, - options: options, exclude: [.typeSugar]) - } - - func testNoRemoveRedundantTypeIfVoid5() { - let input = "let foo: Void? = Void?.none" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testNoRemoveRedundantTypeIfVoid6() { - let input = "let foo: Optional = Optional.none" - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, - options: options, exclude: [.typeSugar]) - } - - func testRedundantTypeWithLiterals() { - let input = """ - let a1: Bool = true - let a2: Bool = false - - let b1: String = "foo" - let b2: String = "\\(b1)" - - let c1: Int = 1 - let c2: Int = 1.0 - - let d1: Double = 3.14 - let d2: Double = 3 - - let e1: [Double] = [3.14] - let e2: [Double] = [3] - - let f1: [String: Int] = ["foo": 5] - let f2: [String: Int?] = ["foo": nil] - """ - let output = """ - let a1 = true - let a2 = false - - let b1 = "foo" - let b2 = "\\(b1)" - - let c1 = 1 - let c2: Int = 1.0 - - let d1 = 3.14 - let d2: Double = 3 - - let e1 = [3.14] - let e2: [Double] = [3] - - let f1 = ["foo": 5] - let f2: [String: Int?] = ["foo": nil] - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .redundantType, - options: options) - } - - func testRedundantTypePreservesLiteralRepresentableTypes() { - let input = """ - let a: MyBoolRepresentable = true - let b: MyStringRepresentable = "foo" - let c: MyIntRepresentable = 1 - let d: MyDoubleRepresentable = 3.14 - let e: MyArrayRepresentable = ["bar"] - let f: MyDictionaryRepresentable = ["baz": 1] - """ - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testPreservesTypeWithIfExpressionInSwift5_8() { - let input = """ - let foo: Foo - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.8") - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testPreservesNonRedundantTypeWithIfExpression() { - let input = """ - let foo: Foo = if condition { - Foo("foo") - } else { - FooSubclass("bar") - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) - } - - func testRedundantTypeWithIfExpression_inferred() { - let input = """ - let foo: Foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let output = """ - let foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) - } - - func testRedundantTypeWithIfExpression_explicit() { - let input = """ - let foo: Foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let output = """ - let foo: Foo = if condition { - .init("foo") - } else { - .init("bar") - } - """ - let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment, .propertyType]) - } - - func testRedundantTypeWithNestedIfExpression_inferred() { - let input = """ - let foo: Foo = if condition { - switch condition { - case true: - if condition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - Foo("baaz") - } - } else { - Foo("quux") - } - """ - let output = """ - let foo = if condition { - switch condition { - case true: - if condition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - Foo("baaz") - } - } else { - Foo("quux") - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) - } - - func testRedundantTypeWithNestedIfExpression_explicit() { - let input = """ - let foo: Foo = if condition { - switch condition { - case true: - if condition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - Foo("baaz") - } - } else { - Foo("quux") - } - """ - let output = """ - let foo: Foo = if condition { - switch condition { - case true: - if condition { - .init("foo") - } else { - .init("bar") - } - - case false: - .init("baaz") - } - } else { - .init("quux") - } - """ - let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment, .propertyType]) - } - - func testRedundantTypeWithLiteralsInIfExpression() { - let input = """ - let foo: String = if condition { - "foo" - } else { - "bar" - } - """ - let output = """ - let foo = if condition { - "foo" - } else { - "bar" - } - """ - let options = FormatOptions(redundantType: .inferred, swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.wrapMultilineConditionalAssignment]) - } - - // --redundanttype explicit - - func testVarRedundantTypeRemovalExplicitType() { - let input = "var view: UIView = UIView()" - let output = "var view: UIView = .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testVarRedundantTypeRemovalExplicitType2() { - let input = "var view: UIView = UIView /* foo */()" - let output = "var view: UIView = .init /* foo */()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.spaceAroundComments, .propertyType]) - } - - func testLetRedundantGenericTypeRemovalExplicitType() { - let input = "let relay: BehaviourRelay = BehaviourRelay(value: nil)" - let output = "let relay: BehaviourRelay = .init(value: nil)" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testLetRedundantGenericTypeRemovalExplicitTypeIfValueOnNextLine() { - let input = "let relay: Foo = Foo\n .default" - let output = "let relay: Foo = \n .default" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.trailingSpace, .propertyType]) - } - - func testVarNonRedundantTypeDoesNothingExplicitType() { - let input = "var view: UIView = UINavigationBar()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options) - } - - func testLetRedundantTypeRemovalExplicitType() { - let input = "let view: UIView = UIView()" - let output = "let view: UIView = .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeRemovedIfValueOnNextLineExplicitType() { - let input = """ - let view: UIView - = UIView() - """ - let output = """ - let view: UIView - = .init() - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeRemovedIfValueOnNextLine2ExplicitType() { - let input = """ - let view: UIView = - UIView() - """ - let output = """ - let view: UIView = - .init() - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeRemovalWithCommentExplicitType() { - let input = "var view: UIView /* view */ = UIView()" - let output = "var view: UIView /* view */ = .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeRemovalWithComment2ExplicitType() { - let input = "var view: UIView = /* view */ UIView()" - let output = "var view: UIView = /* view */ .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeRemovalWithStaticMember() { - let input = """ - let session: URLSession = URLSession.default - - init(foo: Foo, bar: Bar) { - self.foo = foo - self.bar = bar - } - """ - let output = """ - let session: URLSession = .default - - init(foo: Foo, bar: Bar) { - self.foo = foo - self.bar = bar - } - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeRemovalWithStaticFunc() { - let input = """ - let session: URLSession = URLSession.default() - - init(foo: Foo, bar: Bar) { - self.foo = foo - self.bar = bar - } - """ - let output = """ - let session: URLSession = .default() - - init(foo: Foo, bar: Bar) { - self.foo = foo - self.bar = bar - } - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeDoesNothingWithChainedMember() { - let input = "let session: URLSession = URLSession.default.makeCopy()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) - } - - func testRedundantRedundantChainedMemberTypeRemovedOnSwift5_4() { - let input = "let session: URLSession = URLSession.default.makeCopy()" - let output = "let session: URLSession = .default.makeCopy()" - let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeDoesNothingWithChainedMember2() { - let input = "let color: UIColor = UIColor.red.withAlphaComponent(0.5)" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) - } - - func testRedundantTypeDoesNothingWithChainedMember3() { - let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) - } - - func testRedundantTypeRemovedWithChainedMemberOnSwift5_4() { - let input = "let url: URL = URL(fileURLWithPath: #file).deletingLastPathComponent()" - let output = "let url: URL = .init(fileURLWithPath: #file).deletingLastPathComponent()" - let options = FormatOptions(redundantType: .explicit, swiftVersion: "5.4") - testFormatting(for: input, output, rule: .redundantType, options: options, exclude: [.propertyType]) - } - - func testRedundantTypeDoesNothingIfLet() { - let input = "if let foo: Foo = Foo() {}" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) - } - - func testRedundantTypeDoesNothingGuardLet() { - let input = "guard let foo: Foo = Foo() else {}" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) - } - - func testRedundantTypeDoesNothingIfLetAfterComma() { - let input = "if check == true, let foo: Foo = Foo() {}" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options, exclude: [.propertyType]) - } - - func testRedundantTypeWorksAfterIf() { - let input = """ - if foo {} - let foo: Foo = Foo() - """ - let output = """ - if foo {} - let foo: Foo = .init() - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeIfVoid() { - let input = "let foo: [Void] = [Void]()" - let output = "let foo: [Void] = .init()" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - func testRedundantTypeWithIntegerLiteralNotMangled() { - let input = "let foo: Int = 1.toFoo" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, - options: options) - } - - func testRedundantTypeWithFloatLiteralNotMangled() { - let input = "let foo: Double = 1.0.toFoo" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, - options: options) - } - - func testRedundantTypeWithArrayLiteralNotMangled() { - let input = "let foo: [Int] = [1].toFoo" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, - options: options) - } - - func testRedundantTypeWithBoolLiteralNotMangled() { - let input = "let foo: Bool = false.toFoo" - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, - options: options) - } - - func testRedundantTypeInModelClassNotStripped() { - // See: https://github.com/nicklockwood/SwiftFormat/issues/1649 - let input = """ - @Model - class FooBar { - var created: Date = Date.now - } - """ - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .redundantType, options: options) - } - - // --redundanttype infer-locals-only - - func testRedundantTypeinferLocalsOnly() { - let input = """ - let globalFoo: Foo = Foo() - - struct SomeType { - let instanceFoo: Foo = Foo() - - func method() { - let localFoo: Foo = Foo() - let localString: String = "foo" - } - - let instanceString: String = "foo" - } - - let globalString: String = "foo" - """ - - let output = """ - let globalFoo: Foo = .init() - - struct SomeType { - let instanceFoo: Foo = .init() - - func method() { - let localFoo = Foo() - let localString = "foo" - } - - let instanceString: String = "foo" - } - - let globalString: String = "foo" - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, output, rule: .redundantType, - options: options, exclude: [.propertyType]) - } - - // MARK: - redundantNilInit - - func testRemoveRedundantNilInit() { - let input = "var foo: Int? = nil\nlet bar: Int? = nil" - let output = "var foo: Int?\nlet bar: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveLetNilInitAfterVar() { - let input = "var foo: Int; let bar: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNonNilInit() { - let input = "var foo: Int? = 0" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testRemoveRedundantImplicitUnwrapInit() { - let input = "var foo: Int! = nil" - let output = "var foo: Int!" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testRemoveMultipleRedundantNilInitsInSameLine() { - let input = "var foo: Int? = nil, bar: Int? = nil" - let output = "var foo: Int?, bar: Int?" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveLazyVarNilInit() { - let input = "lazy var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveLazyPublicPrivateSetVarNilInit() { - let input = "lazy private(set) public var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, options: options, - exclude: [.modifierOrder]) - } - - func testNoRemoveCodableNilInit() { - let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNilInitWithPropertyWrapper() { - let input = "@Foo var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNilInitWithLowercasePropertyWrapper() { - let input = "@foo var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNilInitWithPropertyWrapperWithArgument() { - let input = "@Foo(bar: baz) var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNilInitWithLowercasePropertyWrapperWithArgument() { - let input = "@foo(bar: baz) var foo: Int? = nil" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testRemoveNilInitWithObjcAttributes() { - let input = "@objc var foo: Int? = nil" - let output = "@objc var foo: Int?" - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNilInitInStructWithDefaultInit() { - let input = """ - struct Foo { - var bar: String? = nil - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testRemoveNilInitInStructWithDefaultInitInSwiftVersion5_2() { - let input = """ - struct Foo { - var bar: String? = nil - } - """ - let output = """ - struct Foo { - var bar: String? - } - """ - let options = FormatOptions(nilInit: .remove, swiftVersion: "5.2") - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testRemoveNilInitInStructWithCustomInit() { - let input = """ - struct Foo { - var bar: String? = nil - init() { - bar = "bar" - } - } - """ - let output = """ - struct Foo { - var bar: String? - init() { - bar = "bar" - } - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNilInitInViewBuilder() { - let input = """ - struct TestView: View { - var body: some View { - var foo: String? = nil - Text(foo ?? "") - } - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNilInitInIfStatementInViewBuilder() { - let input = """ - struct TestView: View { - var body: some View { - if true { - var foo: String? = nil - Text(foo ?? "") - } else { - EmptyView() - } - } - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoRemoveNilInitInSwitchStatementInViewBuilder() { - let input = """ - struct TestView: View { - var body: some View { - switch foo { - case .bar: - var foo: String? = nil - Text(foo ?? "") - - default: - EmptyView() - } - } - } - """ - let options = FormatOptions(nilInit: .remove) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - // --nilInit insert - - func testInsertNilInit() { - let input = "var foo: Int?\nlet bar: Int? = nil" - let output = "var foo: Int? = nil\nlet bar: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testInsertNilInitBeforeLet() { - let input = "var foo: Int?; let bar: Int? = nil" - let output = "var foo: Int? = nil; let bar: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testInsertNilInitAfterLet() { - let input = "let bar: Int? = nil; var foo: Int?" - let output = "let bar: Int? = nil; var foo: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNonNilInit() { - let input = "var foo: Int? = 0" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testInsertRedundantImplicitUnwrapInit() { - let input = "var foo: Int!" - let output = "var foo: Int! = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testInsertMultipleRedundantNilInitsInSameLine() { - let input = "var foo: Int?, bar: Int?" - let output = "var foo: Int? = nil, bar: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoInsertLazyVarNilInit() { - let input = "lazy var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertLazyPublicPrivateSetVarNilInit() { - let input = "lazy private(set) public var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, options: options, - exclude: [.modifierOrder]) - } - - func testNoInsertCodableNilInit() { - let input = "struct Foo: Codable, Bar {\n enum CodingKeys: String, CodingKey {\n case bar = \"_bar\"\n }\n\n var bar: Int?\n var baz: String? = nil\n}" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitWithPropertyWrapper() { - let input = "@Foo var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitWithLowercasePropertyWrapper() { - let input = "@foo var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitWithPropertyWrapperWithArgument() { - let input = "@Foo(bar: baz) var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitWithLowercasePropertyWrapperWithArgument() { - let input = "@foo(bar: baz) var foo: Int?" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testInsertNilInitWithObjcAttributes() { - let input = "@objc var foo: Int?" - let output = "@objc var foo: Int? = nil" - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitInStructWithDefaultInit() { - let input = """ - struct Foo { - var bar: String? - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testInsertNilInitInStructWithDefaultInitInSwiftVersion5_2() { - let input = """ - struct Foo { - var bar: String? - var foo: String? = nil - } - """ - let output = """ - struct Foo { - var bar: String? = nil - var foo: String? = nil - } - """ - let options = FormatOptions(nilInit: .insert, swiftVersion: "5.2") - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testInsertNilInitInStructWithCustomInit() { - let input = """ - struct Foo { - var bar: String? - var foo: String? = nil - init() { - bar = "bar" - foo = "foo" - } - } - """ - let output = """ - struct Foo { - var bar: String? = nil - var foo: String? = nil - init() { - bar = "bar" - foo = "foo" - } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitInViewBuilder() { - // Not insert `nil` in result builder - let input = """ - struct TestView: View { - var body: some View { - var foo: String? - Text(foo ?? "") - } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitInIfStatementInViewBuilder() { - // Not insert `nil` in result builder - let input = """ - struct TestView: View { - var body: some View { - if true { - var foo: String? - Text(foo ?? "") - } else { - EmptyView() - } - } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitInSwitchStatementInViewBuilder() { - // Not insert `nil` in result builder - let input = """ - struct TestView: View { - var body: some View { - switch foo { - case .bar: - var foo: String? - Text(foo ?? "") - - default: - EmptyView() - } - } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitInSingleLineComputedProperty() { - let input = """ - var bar: String? { "some string" } - var foo: String? { nil } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitInMultilineComputedProperty() { - let input = """ - var foo: String? { - print("some") - } - - var bar: String? { - nil - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitInCustomGetterAndSetterProperty() { - let input = """ - var _foo: String? = nil - var foo: String? { - set { _foo = newValue } - get { newValue } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - func testInsertNilInitInInstancePropertyWithBody() { - let input = """ - var foo: String? { - didSet { print(foo) } - } - """ - - let output = """ - var foo: String? = nil { - didSet { print(foo) } - } - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, output, rule: .redundantNilInit, - options: options) - } - - func testNoInsertNilInitInAs() { - let input = """ - let json: Any = ["key": 1] - var jsonObject = json as? [String: Int] - """ - let options = FormatOptions(nilInit: .insert) - testFormatting(for: input, rule: .redundantNilInit, - options: options) - } - - // MARK: - redundantLet - - func testRemoveRedundantLet() { - let input = "let _ = bar {}" - let output = "_ = bar {}" - testFormatting(for: input, output, rule: .redundantLet) - } - - func testNoRemoveLetWithType() { - let input = "let _: String = bar {}" - testFormatting(for: input, rule: .redundantLet) - } - - func testRemoveRedundantLetInCase() { - let input = "if case .foo(let _) = bar {}" - let output = "if case .foo(_) = bar {}" - testFormatting(for: input, output, rule: .redundantLet, exclude: [.redundantPattern]) - } - - func testRemoveRedundantVarsInCase() { - let input = "if case .foo(var _, var /* unused */ _) = bar {}" - let output = "if case .foo(_, /* unused */ _) = bar {}" - testFormatting(for: input, output, rule: .redundantLet) - } - - func testNoRemoveLetInIf() { - let input = "if let _ = foo {}" - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveLetInMultiIf() { - let input = "if foo == bar, /* comment! */ let _ = baz {}" - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveLetInGuard() { - let input = "guard let _ = foo else {}" - testFormatting(for: input, rule: .redundantLet, - exclude: [.wrapConditionalBodies]) - } - - func testNoRemoveLetInWhile() { - let input = "while let _ = foo {}" - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveLetInViewBuilder() { - let input = """ - HStack { - let _ = print("Hi") - Text("Some text") - } - """ - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveLetInViewBuilderModifier() { - let input = """ - VStack { - Text("Some text") - } - .overlay( - HStack { - let _ = print("") - } - ) - """ - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveLetInIfStatementInViewBuilder() { - let input = """ - VStack { - if visible == "YES" { - let _ = print("") - } - } - """ - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveLetInSwitchStatementInViewBuilder() { - let input = """ - struct TestView: View { - var body: some View { - var foo = "" - switch (self.min, self.max) { - case let (nil, max as Int): - let _ = { - foo = "\\(max)" - }() - - default: - EmptyView() - } - - Text(foo) - } - } - """ - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveAsyncLet() { - let input = "async let _ = foo()" - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveLetImmediatelyAfterMainActorAttribute() { - let input = """ - let foo = bar { @MainActor - let _ = try await baz() - } - """ - testFormatting(for: input, rule: .redundantLet) - } - - func testNoRemoveLetImmediatelyAfterSendableAttribute() { - let input = """ - let foo = bar { @Sendable - let _ = try await baz() - } - """ - testFormatting(for: input, rule: .redundantLet) - } - - // MARK: - redundantPattern - - func testRemoveRedundantPatternInIfCase() { - let input = "if case let .foo(_, _) = bar {}" - let output = "if case .foo = bar {}" - testFormatting(for: input, output, rule: .redundantPattern) - } - - func testNoRemoveRequiredPatternInIfCase() { - let input = "if case (_, _) = bar {}" - testFormatting(for: input, rule: .redundantPattern) - } - - func testRemoveRedundantPatternInSwitchCase() { - let input = "switch foo {\ncase let .bar(_, _): break\ndefault: break\n}" - let output = "switch foo {\ncase .bar: break\ndefault: break\n}" - testFormatting(for: input, output, rule: .redundantPattern) - } - - func testNoRemoveRequiredPatternLetInSwitchCase() { - let input = "switch foo {\ncase let .bar(_, a): break\ndefault: break\n}" - testFormatting(for: input, rule: .redundantPattern) - } - - func testNoRemoveRequiredPatternInSwitchCase() { - let input = "switch foo {\ncase (_, _): break\ndefault: break\n}" - testFormatting(for: input, rule: .redundantPattern) - } - - func testSimplifyLetPattern() { - let input = "let(_, _) = bar" - let output = "let _ = bar" - testFormatting(for: input, output, rule: .redundantPattern, exclude: [.redundantLet]) - } - - func testNoRemoveVoidFunctionCall() { - let input = "if case .foo() = bar {}" - testFormatting(for: input, rule: .redundantPattern) - } - - func testNoRemoveMethodSignature() { - let input = "func foo(_, _) {}" - testFormatting(for: input, rule: .redundantPattern) - } - - // MARK: - redundantRawValues - - func testRemoveRedundantRawString() { - let input = "enum Foo: String {\n case bar = \"bar\"\n case baz = \"baz\"\n}" - let output = "enum Foo: String {\n case bar\n case baz\n}" - testFormatting(for: input, output, rule: .redundantRawValues) - } - - func testRemoveCommaDelimitedCaseRawStringCases() { - let input = "enum Foo: String { case bar = \"bar\", baz = \"baz\" }" - let output = "enum Foo: String { case bar, baz }" - testFormatting(for: input, output, rule: .redundantRawValues, - exclude: [.wrapEnumCases]) - } - - func testRemoveBacktickCaseRawStringCases() { - let input = "enum Foo: String { case `as` = \"as\", `let` = \"let\" }" - let output = "enum Foo: String { case `as`, `let` }" - testFormatting(for: input, output, rule: .redundantRawValues, - exclude: [.wrapEnumCases]) - } - - func testNoRemoveRawStringIfNameDoesntMatch() { - let input = "enum Foo: String {\n case bar = \"foo\"\n}" - testFormatting(for: input, rule: .redundantRawValues) - } - - // MARK: - redundantVoidReturnType - - func testRemoveRedundantVoidReturnType() { - let input = "func foo() -> Void {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantVoidReturnType2() { - let input = "func foo() ->\n Void {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantSwiftDotVoidReturnType() { - let input = "func foo() -> Swift.Void {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantSwiftDotVoidReturnType2() { - let input = "func foo() -> Swift\n .Void {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantEmptyReturnType() { - let input = "func foo() -> () {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantVoidTupleReturnType() { - let input = "func foo() -> (Void) {}" - let output = "func foo() {}" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testNoRemoveCommentFollowingRedundantVoidReturnType() { - let input = "func foo() -> Void /* void */ {}" - let output = "func foo() /* void */ {}" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testNoRemoveRequiredVoidReturnType() { - let input = "typealias Foo = () -> Void" - testFormatting(for: input, rule: .redundantVoidReturnType) - } - - func testNoRemoveChainedVoidReturnType() { - let input = "func foo() -> () -> Void {}" - testFormatting(for: input, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantVoidInClosureArguments() { - let input = "{ (foo: Bar) -> Void in foo() }" - let output = "{ (foo: Bar) in foo() }" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantEmptyReturnTypeInClosureArguments() { - let input = "{ (foo: Bar) -> () in foo() }" - let output = "{ (foo: Bar) in foo() }" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantVoidInClosureArguments2() { - let input = "methodWithTrailingClosure { foo -> Void in foo() }" - let output = "methodWithTrailingClosure { foo in foo() }" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testRemoveRedundantSwiftDotVoidInClosureArguments2() { - let input = "methodWithTrailingClosure { foo -> Swift.Void in foo() }" - let output = "methodWithTrailingClosure { foo in foo() }" - testFormatting(for: input, output, rule: .redundantVoidReturnType) - } - - func testNoRemoveRedundantVoidInClosureArgument() { - let input = "{ (foo: Bar) -> Void in foo() }" - let options = FormatOptions(closureVoidReturn: .preserve) - testFormatting(for: input, rule: .redundantVoidReturnType, options: options) - } - - // MARK: - redundantReturn - - func testRemoveRedundantReturnInClosure() { - let input = "foo(with: { return 5 })" - let output = "foo(with: { 5 })" - testFormatting(for: input, output, rule: .redundantReturn, exclude: [.trailingClosures]) - } - - func testRemoveRedundantReturnInClosureWithArgs() { - let input = "foo(with: { foo in return foo })" - let output = "foo(with: { foo in foo })" - testFormatting(for: input, output, rule: .redundantReturn, exclude: [.trailingClosures]) - } - - func testRemoveRedundantReturnInMap() { - let input = "let foo = bar.map { return 1 }" - let output = "let foo = bar.map { 1 }" - testFormatting(for: input, output, rule: .redundantReturn) - } - - func testNoRemoveReturnInComputedVar() { - let input = "var foo: Int { return 5 }" - testFormatting(for: input, rule: .redundantReturn) - } - - func testRemoveReturnInComputedVar() { - let input = "var foo: Int { return 5 }" - let output = "var foo: Int { 5 }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInGet() { - let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" - testFormatting(for: input, rule: .redundantReturn) - } - - func testRemoveReturnInGet() { - let input = "var foo: Int {\n get { return 5 }\n set { _foo = newValue }\n}" - let output = "var foo: Int {\n get { 5 }\n set { _foo = newValue }\n}" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInGetClosure() { - let input = "let foo = get { return 5 }" - let output = "let foo = get { 5 }" - testFormatting(for: input, output, rule: .redundantReturn) - } - - func testRemoveReturnInVarClosure() { - let input = "var foo = { return 5 }()" - let output = "var foo = { 5 }()" - testFormatting(for: input, output, rule: .redundantReturn, exclude: [.redundantClosure]) - } - - func testRemoveReturnInParenthesizedClosure() { - let input = "var foo = ({ return 5 }())" - let output = "var foo = ({ 5 }())" - testFormatting(for: input, output, rule: .redundantReturn, exclude: [.redundantParens, .redundantClosure]) - } - - func testNoRemoveReturnInFunction() { - let input = "func foo() -> Int { return 5 }" - testFormatting(for: input, rule: .redundantReturn) - } - - func testRemoveReturnInFunction() { - let input = "func foo() -> Int { return 5 }" - let output = "func foo() -> Int { 5 }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInOperatorFunction() { - let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" - testFormatting(for: input, rule: .redundantReturn, exclude: [.unusedArguments]) - } - - func testRemoveReturnInOperatorFunction() { - let input = "func + (lhs: Int, rhs: Int) -> Int { return 5 }" - let output = "func + (lhs: Int, rhs: Int) -> Int { 5 }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, options: options, - exclude: [.unusedArguments]) - } - - func testNoRemoveReturnInFailableInit() { - let input = "init?() { return nil }" - testFormatting(for: input, rule: .redundantReturn) - } - - func testNoRemoveReturnInFailableInitWithConditional() { - let input = """ - init?(optionalHex: String?) { - if let optionalHex { - self.init(hex: optionalHex) - } else { - return nil - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInFailableInitWithNestedConditional() { - let input = """ - init?(optionalHex: String?) { - if let optionalHex { - self.init(hex: optionalHex) - } else { - switch foo { - case .foo: - self.init() - case .bar: - return nil - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRemoveReturnInFailableInit() { - let input = "init?() { return nil }" - let output = "init?() { nil }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInSubscript() { - let input = "subscript(index: Int) -> String { return nil }" - testFormatting(for: input, rule: .redundantReturn, exclude: [.unusedArguments]) - } - - func testRemoveReturnInSubscript() { - let input = "subscript(index: Int) -> String { return nil }" - let output = "subscript(index: Int) -> String { nil }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, options: options, - exclude: [.unusedArguments]) - } - - func testNoRemoveReturnInDoCatch() { - let input = """ - func foo() -> Int { - do { - return try Bar() - } catch { - return -1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInDoThrowsCatch() { - let input = """ - func foo() -> Int { - do throws(Foo) { - return try Bar() - } catch { - return -1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInDoCatchLet() { - let input = """ - func foo() -> Int { - do { - return try Bar() - } catch let e as Error { - return -1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInDoThrowsCatchLet() { - let input = """ - func foo() -> Int { - do throws(Foo) { - return try Bar() - } catch let e as Error { - return -1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInForIn() { - let input = "for foo in bar { return 5 }" - testFormatting(for: input, rule: .redundantReturn, exclude: [.wrapLoopBodies]) - } - - func testNoRemoveReturnInForWhere() { - let input = "for foo in bar where baz { return 5 }" - testFormatting(for: input, rule: .redundantReturn, exclude: [.wrapLoopBodies]) - } - - func testNoRemoveReturnInIfLetTry() { - let input = "if let foo = try? bar() { return 5 }" - testFormatting(for: input, rule: .redundantReturn, - exclude: [.wrapConditionalBodies]) - } - - func testNoRemoveReturnInMultiIfLetTry() { - let input = "if let foo = bar, let bar = baz { return 5 }" - testFormatting(for: input, rule: .redundantReturn, - exclude: [.wrapConditionalBodies]) - } - - func testNoRemoveReturnAfterMultipleAs() { - let input = "if foo as? bar as? baz { return 5 }" - testFormatting(for: input, rule: .redundantReturn, - exclude: [.wrapConditionalBodies]) - } - - func testRemoveVoidReturn() { - let input = "{ _ in return }" - let output = "{ _ in }" - testFormatting(for: input, output, rule: .redundantReturn) - } - - func testNoRemoveReturnAfterKeyPath() { - let input = "func foo() { if bar == #keyPath(baz) { return 5 } }" - testFormatting(for: input, rule: .redundantReturn, - exclude: [.wrapConditionalBodies]) - } - - func testNoRemoveReturnAfterParentheses() { - let input = "if let foo = (bar as? String) { return foo }" - testFormatting(for: input, rule: .redundantReturn, - exclude: [.redundantParens, .wrapConditionalBodies]) - } - - func testRemoveReturnInTupleVarGetter() { - let input = "var foo: (Int, Int) { return (1, 2) }" - let output = "var foo: (Int, Int) { (1, 2) }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, options: options) - } - - func testNoRemoveReturnInIfLetWithNoSpaceAfterParen() { - let input = """ - var foo: String? { - if let bar = baz(){ - return bar - } else { - return nil - } - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: [.spaceAroundBraces, .spaceAroundParens]) - } - - func testNoRemoveReturnInIfWithUnParenthesizedClosure() { - let input = """ - if foo { $0.bar } { - return true - } - """ - testFormatting(for: input, rule: .redundantReturn) - } - - func testRemoveBlankLineWithReturn() { - let input = """ - foo { - return - bar - } - """ - let output = """ - foo { - bar - } - """ - testFormatting(for: input, output, rule: .redundantReturn, - exclude: [.indent]) - } - - func testRemoveRedundantReturnInFunctionWithWhereClause() { - let input = """ - func foo(_ name: String) -> T where T: Equatable { - return name - } - """ - let output = """ - func foo(_ name: String) -> T where T: Equatable { - name - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, - options: options) - } - - func testRemoveRedundantReturnInSubscriptWithWhereClause() { - let input = """ - subscript(_ name: String) -> T where T: Equatable { - return name - } - """ - let output = """ - subscript(_ name: String) -> T where T: Equatable { - name - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, - options: options) - } - - func testNoRemoveReturnFollowedByMoreCode() { - let input = """ - var foo: Bar = { - return foo - let bar = baz - return bar - }() - """ - testFormatting(for: input, rule: .redundantReturn, exclude: [.redundantProperty]) - } - - func testNoRemoveReturnInForWhereLoop() { - let input = """ - func foo() -> Bool { - for bar in baz where !bar { - return false - } - return true - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRedundantReturnInVoidFunction() { - let input = """ - func foo() { - return - } - """ - let output = """ - func foo() { - } - """ - testFormatting(for: input, output, rule: .redundantReturn, - exclude: [.emptyBraces]) - } - - func testRedundantReturnInVoidFunction2() { - let input = """ - func foo() { - print("") - return - } - """ - let output = """ - func foo() { - print("") - } - """ - testFormatting(for: input, output, rule: .redundantReturn) - } - - func testRedundantReturnInVoidFunction3() { - let input = """ - func foo() { - // empty - return - } - """ - let output = """ - func foo() { - // empty - } - """ - testFormatting(for: input, output, rule: .redundantReturn) - } - - func testRedundantReturnInVoidFunction4() { - let input = """ - func foo() { - return // empty - } - """ - let output = """ - func foo() { - // empty - } - """ - testFormatting(for: input, output, rule: .redundantReturn) - } - - func testNoRemoveVoidReturnInCatch() { - let input = """ - func foo() { - do { - try Foo() - } catch Feature.error { - print("feature error") - return - } - print("foo") - } - """ - testFormatting(for: input, rule: .redundantReturn) - } - - func testNoRemoveReturnInIfCase() { - let input = """ - var isSessionDeinitializedError: Bool { - if case .sessionDeinitialized = self { return true } - return false - } - """ - testFormatting(for: input, rule: .redundantReturn, - options: FormatOptions(swiftVersion: "5.1"), - exclude: [.wrapConditionalBodies]) - } - - func testNoRemoveReturnInForCasewhere() { - let input = """ - for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] - where names.contains(name) - { - return true - } - """ - testFormatting(for: input, rule: .redundantReturn, - options: FormatOptions(swiftVersion: "5.1")) - } - - func testNoRemoveRequiredReturnInFunctionInsideClosure() { - let input = """ - foo { - func bar() -> Bar { - let bar = Bar() - return bar - } - } - """ - testFormatting(for: input, rule: .redundantReturn, - options: FormatOptions(swiftVersion: "5.1"), exclude: [.redundantProperty]) - } - - func testNoRemoveRequiredReturnInIfClosure() { - let input = """ - func findButton() -> Button? { - let btns = [top, content, bottom] - if let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { - return btn - } - return btns.first - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testNoRemoveRequiredReturnInIfClosure2() { - let input = """ - func findButton() -> Button? { - let btns = [top, content, bottom] - if let foo, let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { - return btn - } - return btns.first - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRemoveRedundantReturnInIfClosure() { - let input = """ - func findButton() -> Button? { - let btns = [top, content, bottom] - if let btn = btns.first { return !$0.isHidden && $0.alpha > 0.01 } { - print("hello") - } - return btns.first - } - """ - let output = """ - func findButton() -> Button? { - let btns = [top, content, bottom] - if let btn = btns.first { !$0.isHidden && $0.alpha > 0.01 } { - print("hello") - } - return btns.first - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, output, rule: .redundantReturn, options: options) - } - - func testDisableNextRedundantReturn() { - let input = """ - func foo() -> Foo { - // swiftformat:disable:next redundantReturn - return Foo() - } - """ - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRedundantIfStatementReturnSwift5_8() { - let input = """ - func foo(condition: Bool) -> String { - if condition { - return "foo" - } else { - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: .redundantReturn, - options: options) - } - - func testNonRedundantIfStatementReturnSwift5_9() { - let input = """ - func foo(condition: Bool) -> String { - if condition { - return "foo" - } else if !condition { - return "bar" - } - return "baaz" - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRedundantIfStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - if condition { - return "foo" - } else if otherCondition { - if anotherCondition { - return "bar" - } else { - return "baaz" - } - } else { - return "quux" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - if condition { - "foo" - } else if otherCondition { - if anotherCondition { - "bar" - } else { - "baaz" - } - } else { - "quux" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testNoRemoveRedundantIfStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - if condition { - return "foo" - } else if otherCondition { - if anotherCondition { - return "bar" - } else { - return "baaz" - } - } else { - return "quux" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: [.conditionalAssignment]) - } - - func testRedundantIfStatementReturnInClosure() { - let input = """ - let closure: (Bool) -> String = { condition in - if condition { - return "foo" - } else { - return "bar" - } - } - """ - let output = """ - let closure: (Bool) -> String = { condition in - if condition { - "foo" - } else { - "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testNoRemoveRedundantIfStatementReturnInClosure() { - let input = """ - let closure: (Bool) -> String = { condition in - if condition { - return "foo" - } else { - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: [.conditionalAssignment]) - } - - func testNoRemoveReturnInConsecutiveIfStatements() { - let input = """ - func foo() -> String? { - if bar { - return nil - } - if baz { - return "baz" - } else { - return "quux" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRedundantIfStatementReturnInRedundantClosure() { - let input = """ - let value = { - if condition { - return "foo" - } else { - return "bar" - } - }() - """ - let output = """ - let value = if condition { - "foo" - } else { - "bar" - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment, - .redundantClosure, .indent], - options: options, exclude: [.wrapMultilineConditionalAssignment]) - } - - func testRedundantSwitchStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - case false: - return "bar" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - "foo" - case false: - "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testNoRemoveRedundantSwitchStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - case false: - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: [.conditionalAssignment]) - } - - func testClosureAroundConditionalAssignmentNotRedundantForExplicitReturn() { - let input = """ - let myEnum = MyEnum.a - let test: Int = { - switch myEnum { - case .a: - return 0 - case .b: - return 1 - } - }() - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options, - exclude: [.redundantReturn, .propertyType]) - } - - func testNonRedundantSwitchStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - case false: - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRedundantSwitchStatementReturnInFunctionWithDefault() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - default: - return "bar" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - "foo" - default: - "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testRedundantSwitchStatementReturnInFunctionWithComment() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - // foo - return "foo" - - default: - /* bar */ - return "bar" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - // foo - "foo" - - default: - /* bar */ - "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testNonRedundantSwitchStatementReturnInFunctionWithDefault() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - return "foo" - default: - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testNonRedundantSwitchStatementReturnInFunctionWithFallthrough() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - fallthrough - case false: - return "bar" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testVoidReturnNotStrippedFromSwitch() { - let input = """ - func foo(condition: Bool) { - switch condition { - case true: - print("foo") - case false: - return - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRedundantNestedSwitchStatementReturnInFunction() { - let input = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - switch condition { - case true: - return "foo" - case false: - if condition { - return "bar" - } else { - return "baaz" - } - } - - case false: - return "quux" - } - } - """ - let output = """ - func foo(condition: Bool) -> String { - switch condition { - case true: - switch condition { - case true: - "foo" - case false: - if condition { - "bar" - } else { - "baaz" - } - } - - case false: - "quux" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testRedundantSwitchStatementReturnWithAssociatedValueMatchingInFunction() { - let input = """ - func test(_ value: SomeEnum) -> String { - switch value { - case let .first(str): - return "first \\(str)" - case .second("str"): - return "second" - default: - return "default" - } - } - """ - let output = """ - func test(_ value: SomeEnum) -> String { - switch value { - case let .first(str): - "first \\(str)" - case .second("str"): - "second" - default: - "default" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testRedundantReturnDoesntFailToTerminateOnLongSwitch() { - let input = """ - func test(_ value: SomeEnum) -> String { - switch value { - case .one: - return "" - case .two: - return "" - case .three: - return "" - case .four: - return "" - case .five: - return "" - case .six: - return "" - case .seven: - return "" - case .eight: - return "" - case .nine: - return "" - case .ten: - return "" - case .eleven: - return "" - case .twelve: - return "" - case .thirteen: - return "" - case .fourteen: - return "" - case .fifteen: - return "" - case .sixteen: - return "" - case .seventeen: - return "" - case .eighteen: - return "" - case .nineteen: - return "" - } - } - """ - let output = """ - func test(_ value: SomeEnum) -> String { - switch value { - case .one: - "" - case .two: - "" - case .three: - "" - case .four: - "" - case .five: - "" - case .six: - "" - case .seven: - "" - case .eight: - "" - case .nine: - "" - case .ten: - "" - case .eleven: - "" - case .twelve: - "" - case .thirteen: - "" - case .fourteen: - "" - case .fifteen: - "" - case .sixteen: - "" - case .seventeen: - "" - case .eighteen: - "" - case .nineteen: - "" - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testNoRemoveDebugReturnFollowedBySwitch() { - let input = """ - func swiftFormatBug() -> Foo { - return .foo - - switch state { - case .foo, .bar: - return state - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options, - exclude: [.wrapSwitchCases, .sortSwitchCases]) - } - - func testDoesntRemoveReturnFromIfExpressionConditionalCastInSwift5_9() { - // The following code doesn't compile in Swift 5.9 due to this issue: - // https://github.com/apple/swift/issues/68764 - // - // var result: String { - // if condition { - // foo as? String - // } else { - // "bar" - // } - // } - // - let input = """ - var result1: String { - if condition { - return foo as? String - } else { - return "bar" - } - } - - var result2: String { - switch condition { - case true: - return foo as? String - case false: - return "bar" - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantReturn, options: options) - } - - func testRemovseReturnFromIfExpressionNestedConditionalCastInSwift5_9() { - let input = """ - var result1: String { - if condition { - return method(foo as? String) - } else { - return "bar" - } - } - """ - - let output = """ - var result1: String { - if condition { - method(foo as? String) - } else { - "bar" - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testRemovesReturnFromIfExpressionConditionalCastInSwift5_10() { - let input = """ - var result: String { - if condition { - return foo as? String - } else { - return "bar" - } - } - """ - - let output = """ - var result: String { - if condition { - foo as? String - } else { - "bar" - } - } - """ - - let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testRemovesRedundantReturnBeforeIfExpression() { - let input = """ - func foo() -> Foo { - return if condition { - Foo.foo() - } else { - Foo.bar() - } - } - """ - - let output = """ - func foo() -> Foo { - if condition { - Foo.foo() - } else { - Foo.bar() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantReturn, options: options) - } - - func testRemovesRedundantReturnBeforeSwitchExpression() { - let input = """ - func foo() -> Foo { - return switch condition { - case true: - Foo.foo() - case false: - Foo.bar() - } - } - """ - - let output = """ - func foo() -> Foo { - switch condition { - case true: - Foo.foo() - case false: - Foo.bar() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantReturn, options: options) - } - - // MARK: - redundantBackticks - - func testRemoveRedundantBackticksInLet() { - let input = "let `foo` = bar" - let output = "let foo = bar" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundKeyword() { - let input = "let `let` = foo" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundSelf() { - let input = "let `self` = foo" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundClassSelfInTypealias() { - let input = "typealias `Self` = Foo" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundClassSelfAsReturnType() { - let input = "func foo(bar: `Self`) { print(bar) }" - let output = "func foo(bar: Self) { print(bar) }" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundClassSelfAsParameterType() { - let input = "func foo() -> `Self` {}" - let output = "func foo() -> Self {}" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundClassSelfArgument() { - let input = "func foo(`Self`: Foo) { print(Self) }" - let output = "func foo(Self: Foo) { print(Self) }" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundKeywordFollowedByType() { - let input = "let `default`: Int = foo" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundContextualGet() { - let input = "var foo: Int {\n `get`()\n return 5\n}" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundGetArgument() { - let input = "func foo(`get` value: Int) { print(value) }" - let output = "func foo(get value: Int) { print(value) }" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundTypeAtRootLevel() { - let input = "enum `Type` {}" - let output = "enum Type {}" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundTypeInsideType() { - let input = "struct Foo {\n enum `Type` {}\n}" - testFormatting(for: input, rule: .redundantBackticks, exclude: [.enumNamespaces]) - } - - func testNoRemoveBackticksAroundLetArgument() { - let input = "func foo(`let`: Foo) { print(`let`) }" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundTrueArgument() { - let input = "func foo(`true`: Foo) { print(`true`) }" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundTrueArgument() { - let input = "func foo(`true`: Foo) { print(`true`) }" - let output = "func foo(true: Foo) { print(`true`) }" - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantBackticks, options: options) - } - - func testNoRemoveBackticksAroundTypeProperty() { - let input = "var type: Foo.`Type`" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundTypePropertyInsideType() { - let input = "struct Foo {\n enum `Type` {}\n}" - testFormatting(for: input, rule: .redundantBackticks, exclude: [.enumNamespaces]) - } - - func testNoRemoveBackticksAroundTrueProperty() { - let input = "var type = Foo.`true`" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundTrueProperty() { - let input = "var type = Foo.`true`" - let output = "var type = Foo.true" - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantBackticks, options: options, exclude: [.propertyType]) - } - - func testRemoveBackticksAroundProperty() { - let input = "var type = Foo.`bar`" - let output = "var type = Foo.bar" - testFormatting(for: input, output, rule: .redundantBackticks, exclude: [.propertyType]) - } - - func testRemoveBackticksAroundKeywordProperty() { - let input = "var type = Foo.`default`" - let output = "var type = Foo.default" - testFormatting(for: input, output, rule: .redundantBackticks, exclude: [.propertyType]) - } - - func testRemoveBackticksAroundKeypathProperty() { - let input = "var type = \\.`bar`" - let output = "var type = \\.bar" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundKeypathKeywordProperty() { - let input = "var type = \\.`default`" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundKeypathKeywordPropertyInSwift5() { - let input = "var type = \\.`default`" - let output = "var type = \\.default" - let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, output, rule: .redundantBackticks, options: options) - } - - func testNoRemoveBackticksAroundInitPropertyInSwift5() { - let input = "let foo: Foo = .`init`" - let options = FormatOptions(swiftVersion: "5") - testFormatting(for: input, rule: .redundantBackticks, options: options, exclude: [.propertyType]) - } - - func testNoRemoveBackticksAroundAnyProperty() { - let input = "enum Foo {\n case `Any`\n}" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundGetInSubscript() { - let input = """ - subscript(_ name: String) -> T where T: Equatable { - `get`(name) - } - """ - testFormatting(for: input, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundActorProperty() { - let input = "let `actor`: Foo" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundActorRvalue() { - let input = "let foo = `actor`" - let output = "let foo = actor" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundActorLabel() { - let input = "init(`actor`: Foo)" - let output = "init(actor: Foo)" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testRemoveBackticksAroundActorLabel2() { - let input = "init(`actor` foo: Foo)" - let output = "init(actor foo: Foo)" - testFormatting(for: input, output, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundUnderscore() { - let input = "func `_`(_ foo: T) -> T { foo }" - testFormatting(for: input, rule: .redundantBackticks) - } - - func testNoRemoveBackticksAroundShadowedSelf() { - let input = """ - struct Foo { - let `self`: URL - - func printURL() { - print("My URL is \\(self.`self`)") - } - } - """ - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: .redundantBackticks, options: options) - } - - func testNoRemoveBackticksAroundDollar() { - let input = "@attached(peer, names: prefixed(`$`))" - testFormatting(for: input, rule: .redundantBackticks) - } - - // MARK: - redundantSelf - - // explicitSelf = .remove - - func testSimpleRemoveRedundantSelf() { - let input = "func foo() { self.bar() }" - let output = "func foo() { bar() }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfInsideStringInterpolation() { - let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" - let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNoRemoveSelfForArgument() { - let input = "func foo(bar: Int) { self.bar = bar }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForLocalVariable() { - let input = "func foo() { var bar = self.bar }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testRemoveSelfForLocalVariableOn5_4() { - let input = "func foo() { var bar = self.bar }" - let output = "func foo() { var bar = bar }" - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: .redundantSelf, - options: options) - } - - func testNoRemoveSelfForCommaDelimitedLocalVariables() { - let input = "func foo() { let foo = self.foo, bar = self.bar }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testRemoveSelfForCommaDelimitedLocalVariablesOn5_4() { - let input = "func foo() { let foo = self.foo, bar = self.bar }" - let output = "func foo() { let foo = self.foo, bar = bar }" - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: .redundantSelf, - options: options) - } - - func testNoRemoveSelfForCommaDelimitedLocalVariables2() { - let input = "func foo() {\n let foo: Foo, bar: Bar\n foo = self.foo\n bar = self.bar\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForTupleAssignedVariables() { - let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" - testFormatting(for: input, rule: .redundantSelf) - } - - // TODO: make this work -// func testRemoveSelfForTupleAssignedVariablesOn5_4() { -// let input = "func foo() { let (bar, baz) = (self.bar, self.baz) }" -// let output = "func foo() { let (bar, baz) = (bar, baz) }" -// let options = FormatOptions(swiftVersion: "5.4") -// testFormatting(for: input, output, rule: .redundantSelf, -// options: options) -// } - - func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularVariable() { - let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar), baz = self.baz\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForTupleAssignedVariablesFollowedByRegularLet() { - let input = "func foo() {\n let (foo, bar) = (self.foo, self.bar)\n let baz = self.baz\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveNonRedundantNestedFunctionSelf() { - let input = "func foo() { func bar() { self.bar() } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveNonRedundantNestedFunctionSelf2() { - let input = "func foo() {\n func bar() {}\n self.bar()\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveNonRedundantNestedFunctionSelf3() { - let input = "func foo() { let bar = 5; func bar() { self.bar = bar } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveClosureSelf() { - let input = "func foo() { bar { self.bar = 5 } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfAfterOptionalReturn() { - let input = "func foo() -> String? {\n var index = startIndex\n if !matching(self[index]) {\n break\n }\n index = self.index(after: index)\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveRequiredSelfInExtensions() { - let input = "extension Foo {\n func foo() {\n var index = 5\n if true {\n break\n }\n index = self.index(after: index)\n }\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfBeforeInit() { - let input = "convenience init() { self.init(5) }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testRemoveSelfInsideSwitch() { - let input = "func foo() {\n switch self.bar {\n case .foo:\n self.baz()\n }\n}" - let output = "func foo() {\n switch bar {\n case .foo:\n baz()\n }\n}" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfInsideSwitchWhere() { - let input = "func foo() {\n switch self.bar {\n case .foo where a == b:\n self.baz()\n }\n}" - let output = "func foo() {\n switch bar {\n case .foo where a == b:\n baz()\n }\n}" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfInsideSwitchWhereAs() { - let input = "func foo() {\n switch self.bar {\n case .foo where a == b as C:\n self.baz()\n }\n}" - let output = "func foo() {\n switch bar {\n case .foo where a == b as C:\n baz()\n }\n}" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfInsideClassInit() { - let input = "class Foo {\n var bar = 5\n init() { self.bar = 6 }\n}" - let output = "class Foo {\n var bar = 5\n init() { bar = 6 }\n}" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNoRemoveSelfInClosureInsideIf() { - let input = "if foo { bar { self.baz() } }" - testFormatting(for: input, rule: .redundantSelf, - exclude: [.wrapConditionalBodies]) - } - - func testNoRemoveSelfForErrorInCatch() { - let input = "do {} catch { self.error = error }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForErrorInDoThrowsCatch() { - let input = "do throws(Foo) {} catch { self.error = error }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForNewValueInSet() { - let input = "var foo: Int { set { self.newValue = newValue } get { return 0 } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForCustomNewValueInSet() { - let input = "var foo: Int { set(n00b) { self.n00b = n00b } get { return 0 } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForNewValueInWillSet() { - let input = "var foo: Int { willSet { self.newValue = newValue } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForCustomNewValueInWillSet() { - let input = "var foo: Int { willSet(n00b) { self.n00b = n00b } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForOldValueInDidSet() { - let input = "var foo: Int { didSet { self.oldValue = oldValue } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForCustomOldValueInDidSet() { - let input = "var foo: Int { didSet(oldz) { self.oldz = oldz } }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForIndexVarInFor() { - let input = "for foo in bar { self.foo = foo }" - testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) - } - - func testNoRemoveSelfForKeyValueTupleInFor() { - let input = "for (foo, bar) in baz { self.foo = foo; self.bar = bar }" - testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) - } - - func testRemoveSelfFromComputedVar() { - let input = "var foo: Int { return self.bar }" - let output = "var foo: Int { return bar }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfFromOptionalComputedVar() { - let input = "var foo: Int? { return self.bar }" - let output = "var foo: Int? { return bar }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfFromNamespacedComputedVar() { - let input = "var foo: Swift.String { return self.bar }" - let output = "var foo: Swift.String { return bar }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfFromGenericComputedVar() { - let input = "var foo: Foo { return self.bar }" - let output = "var foo: Foo { return bar }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfFromComputedArrayVar() { - let input = "var foo: [Int] { return self.bar }" - let output = "var foo: [Int] { return bar }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfFromVarSetter() { - let input = "var foo: Int { didSet { self.bar() } }" - let output = "var foo: Int { didSet { bar() } }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNoRemoveSelfFromVarClosure() { - let input = "var foo = { self.bar }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfFromLazyVar() { - let input = "lazy var foo = self.bar" - testFormatting(for: input, rule: .redundantSelf) - } - - func testRemoveSelfFromLazyVar() { - let input = "lazy var foo = self.bar" - let output = "lazy var foo = bar" - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testNoRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { - let input = """ - var baz = bar - lazy var foo = self.bar - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testRemoveSelfFromLazyVarImmediatelyAfterOtherVar() { - let input = """ - var baz = bar - lazy var foo = self.bar - """ - let output = """ - var baz = bar - lazy var foo = bar - """ - let options = FormatOptions(swiftVersion: "4") - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testNoRemoveSelfFromLazyVarClosure() { - let input = "lazy var foo = { self.bar }()" - testFormatting(for: input, rule: .redundantSelf, exclude: [.redundantClosure]) - } - - func testNoRemoveSelfFromLazyVarClosure2() { - let input = "lazy var foo = { let bar = self.baz }()" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfFromLazyVarClosure3() { - let input = "lazy var foo = { [unowned self] in let bar = self.baz }()" - testFormatting(for: input, rule: .redundantSelf) - } - - func testRemoveSelfFromVarInFuncWithUnusedArgument() { - let input = "func foo(bar _: Int) { self.baz = 5 }" - let output = "func foo(bar _: Int) { baz = 5 }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfFromVarMatchingUnusedArgument() { - let input = "func foo(bar _: Int) { self.bar = 5 }" - let output = "func foo(bar _: Int) { bar = 5 }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNoRemoveSelfFromVarMatchingRenamedArgument() { - let input = "func foo(bar baz: Int) { self.baz = baz }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfFromVarRedeclaredInSubscope() { - let input = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = self.bar\n}" - let output = "func foo() {\n if quux {\n let bar = 5\n }\n let baz = bar\n}" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNoRemoveSelfFromVarDeclaredLaterInScope() { - let input = "func foo() {\n let bar = self.baz\n let baz = quux\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfFromVarDeclaredLaterInOuterScope() { - let input = "func foo() {\n if quux {\n let bar = self.baz\n }\n let baz = 6\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInWhilePreceededByVarDeclaration() { - let input = "var index = start\nwhile index < end {\n index = self.index(after: index)\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInLocalVarPrecededByLocalVarFollowedByIfComma() { - let input = "func foo() {\n let bar = Bar()\n let baz = Baz()\n self.baz = baz\n if let bar = bar, bar > 0 {}\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInLocalVarPrecededByIfLetContainingClosure() { - let input = "func foo() {\n if let bar = 5 { baz { _ in } }\n let quux = self.quux\n}" - testFormatting(for: input, rule: .redundantSelf, - exclude: [.wrapConditionalBodies]) - } - - func testNoRemoveSelfForVarCreatedInGuardScope() { - let input = "func foo() {\n guard let bar = 5 else {}\n let baz = self.bar\n}" - testFormatting(for: input, rule: .redundantSelf, - exclude: [.wrapConditionalBodies]) - } - - func testRemoveSelfForVarCreatedInIfScope() { - let input = "func foo() {\n if let bar = bar {}\n let baz = self.bar\n}" - let output = "func foo() {\n if let bar = bar {}\n let baz = bar\n}" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNoRemoveSelfForVarDeclaredInWhileCondition() { - let input = "while let foo = bar { self.foo = foo }" - testFormatting(for: input, rule: .redundantSelf, exclude: [.wrapLoopBodies]) - } - - func testRemoveSelfForVarNotDeclaredInWhileCondition() { - let input = "while let foo == bar { self.baz = 5 }" - let output = "while let foo == bar { baz = 5 }" - testFormatting(for: input, output, rule: .redundantSelf, exclude: [.wrapLoopBodies]) - } - - func testNoRemoveSelfForVarDeclaredInSwitchCase() { - let input = "switch foo {\ncase bar: let baz = self.baz\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfAfterGenericInit() { - let input = "init(bar: Int) {\n self = Foo()\n self.bar(bar)\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testRemoveSelfInClassFunction() { - let input = "class Foo {\n class func foo() {\n func bar() { self.foo() }\n }\n}" - let output = "class Foo {\n class func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfInStaticFunction() { - let input = "struct Foo {\n static func foo() {\n func bar() { self.foo() }\n }\n}" - let output = "struct Foo {\n static func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: .redundantSelf, exclude: [.enumNamespaces]) - } - - func testRemoveSelfInClassFunctionWithModifiers() { - let input = "class Foo {\n class private func foo() {\n func bar() { self.foo() }\n }\n}" - let output = "class Foo {\n class private func foo() {\n func bar() { foo() }\n }\n}" - testFormatting(for: input, output, rule: .redundantSelf, - exclude: [.modifierOrder]) - } - - func testNoRemoveSelfInClassFunction() { - let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { self.foo() }\n }\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForVarDeclaredAfterRepeatWhile() { - let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n let foo = 6\n self.foo()\n }\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfForVarInClosureAfterRepeatWhile() { - let input = "class Foo {\n let foo = 5\n func bar() {\n repeat {} while foo\n ({ self.foo() })()\n }\n}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInClosureAfterVar() { - let input = "var foo: String\nbar { self.baz() }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInClosureAfterNamespacedVar() { - let input = "var foo: Swift.String\nbar { self.baz() }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInClosureAfterOptionalVar() { - let input = "var foo: String?\nbar { self.baz() }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInClosureAfterGenericVar() { - let input = "var foo: Foo\nbar { self.baz() }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInClosureAfterArray() { - let input = "var foo: [Int]\nbar { self.baz() }" - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInExpectFunction() { // Special case to support the Nimble framework - let input = """ - class FooTests: XCTestCase { - let foo = 1 - func testFoo() { - expect(self.foo) == 1 - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoRemoveNestedSelfInExpectFunction() { - let input = """ - func testFoo() { - expect(Foo.validate(bar: self.bar)).to(equal(1)) - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoRemoveNestedSelfInArrayInExpectFunction() { - let input = """ - func testFoo() { - expect(Foo.validate(bar: [self.bar])).to(equal(1)) - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoRemoveNestedSelfInSubscriptInExpectFunction() { - let input = """ - func testFoo() { - expect(Foo.validations[self.bar]).to(equal(1)) - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoRemoveSelfInOSLogFunction() { - let input = """ - func testFoo() { - os_log("error: \\(self.bar) is nil") - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoRemoveSelfInExcludedFunction() { - let input = """ - class Foo { - let foo = 1 - func testFoo() { - log(self.foo) - } - } - """ - let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoRemoveSelfForExcludedFunction() { - let input = """ - class Foo { - let foo = 1 - func testFoo() { - self.log(foo) - } - } - """ - let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoRemoveSelfInInterpolatedStringInExcludedFunction() { - let input = """ - class Foo { - let foo = 1 - func testFoo() { - log("\\(self.foo)") - } - } - """ - let options = FormatOptions(selfRequired: ["log"]) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoRemoveSelfInExcludedInitializer() { - let input = """ - let vc = UIHostingController(rootView: InspectionView(inspection: self.inspection)) - """ - let options = FormatOptions(selfRequired: ["InspectionView"]) - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.propertyType]) - } - - func testNoMistakeProtocolClassModifierForClassFunction() { - let input = "protocol Foo: class {}\nfunc bar() {}" - XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) - XCTAssertNoThrow(try format(input, rules: FormatRules.all)) - } - - func testSelfRemovedFromSwitchCaseWhere() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo where self.bar.baz: - return self.bar - default: - return nil - } - } - } - """ - let output = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo where bar.baz: - return bar - default: - return nil - } - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testSwitchCaseLetVarRecognized() { - let input = """ - switch foo { - case .bar: - baz = nil - case let baz: - self.baz = baz - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSwitchCaseHoistedLetVarRecognized() { - let input = """ - switch foo { - case .bar: - baz = nil - case let .foo(baz): - self.baz = baz - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSwitchCaseWhereMemberNotTreatedAsVar() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let bar where self.bar.baz: - return self.bar - default: - return nil - } - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfNotRemovedInClosureAfterSwitch() { - let input = """ - switch x { - default: - break - } - let foo = { y in - switch y { - default: - self.bar() - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfNotRemovedInClosureInCaseWithWhereClause() { - let input = """ - switch foo { - case bar where baz: - quux = { self.foo } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfRemovedInDidSet() { - let input = """ - class Foo { - var bar = false { - didSet { - self.bar = !self.bar - } - } - } - """ - let output = """ - class Foo { - var bar = false { - didSet { - bar = !bar - } - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testSelfNotRemovedInGetter() { - let input = """ - class Foo { - var bar: Int { - return self.bar - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfNotRemovedInIfdef() { - let input = """ - func foo() { - #if os(macOS) - let bar = self.bar - #endif - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testRedundantSelfRemovedWhenFollowedBySwitchContainingIfdef() { - let input = """ - struct Foo { - func bar() { - self.method(self.value) - switch x { - #if BAZ - case .baz: - break - #endif - default: - break - } - } - } - """ - let output = """ - struct Foo { - func bar() { - method(value) - switch x { - #if BAZ - case .baz: - break - #endif - default: - break - } - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRedundantSelfRemovedInsideConditionalCase() { - let input = """ - struct Foo { - func bar() { - let method2 = () -> Void - switch x { - #if BAZ - case .baz: - self.method1(self.value) - #else - case .quux: - self.method2(self.value) - #endif - default: - break - } - } - } - """ - let output = """ - struct Foo { - func bar() { - let method2 = () -> Void - switch x { - #if BAZ - case .baz: - method1(value) - #else - case .quux: - self.method2(value) - #endif - default: - break - } - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRedundantSelfRemovedAfterConditionalLet() { - let input = """ - class Foo { - var bar: Int? - var baz: Bool - - func foo() { - if let bar = bar, self.baz { - // ... - } - } - } - """ - let output = """ - class Foo { - var bar: Int? - var baz: Bool - - func foo() { - if let bar = bar, baz { - // ... - } - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNestedClosureInNotMistakenForForLoop() { - let input = """ - func f() { - let str = "hello" - try! str.withCString(encodedAs: UTF8.self) { _ throws in - try! str.withCString(encodedAs: UTF8.self) { _ throws in } - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testTypedThrowingNestedClosureInNotMistakenForForLoop() { - let input = """ - func f() { - let str = "hello" - try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in - try! str.withCString(encodedAs: UTF8.self) { _ throws(Foo) in } - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testRedundantSelfPreservesSelfInClosureWithExplicitStrongCaptureBefore5_3() { - let input = """ - class Foo { - let bar: Int - - func baaz() { - closure { [self] in - print(self.bar) - } - } - } - """ - - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testRedundantSelfRemovesSelfInClosureWithExplicitStrongCapture() { - let input = """ - class Foo { - let foo: Int - - func baaz() { - closure { [self, bar] baaz, quux in - print(self.foo) - } - } - } - """ - - let output = """ - class Foo { - let foo: Int - - func baaz() { - closure { [self, bar] baaz, quux in - print(foo) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: .redundantSelf, options: options, exclude: [.unusedArguments]) - } - - func testRedundantSelfRemovesSelfInClosureWithNestedExplicitStrongCapture() { - let input = """ - class Foo { - let bar: Int - - func baaz() { - closure { - print(self.bar) - closure { [self] in - print(self.bar) - } - print(self.bar) - } - } - } - """ - - let output = """ - class Foo { - let bar: Int - - func baaz() { - closure { - print(self.bar) - closure { [self] in - print(bar) - } - print(self.bar) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testRedundantSelfKeepsSelfInNestedClosureWithNoExplicitStrongCapture() { - let input = """ - class Foo { - let bar: Int - let baaz: Int? - - func baaz() { - closure { [self] in - print(self.bar) - closure { - print(self.bar) - if let baaz = self.baaz { - print(baaz) - } - } - print(self.bar) - if let baaz = self.baaz { - print(baaz) - } - } - } - } - """ - - let output = """ - class Foo { - let bar: Int - let baaz: Int? - - func baaz() { - closure { [self] in - print(bar) - closure { - print(self.bar) - if let baaz = self.baaz { - print(baaz) - } - } - print(bar) - if let baaz = baaz { - print(baaz) - } - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testRedundantSelfRemovesSelfInClosureCapturingStruct() { - let input = """ - struct Foo { - let bar: Int - - func baaz() { - closure { - print(self.bar) - } - } - } - """ - - let output = """ - struct Foo { - let bar: Int - - func baaz() { - closure { - print(bar) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testRedundantSelfRemovesSelfInClosureCapturingSelfWeakly() { - let input = """ - class Foo { - let bar: Int - - func baaz() { - closure { [weak self] in - print(self?.bar) - guard let self else { - return - } - print(self.bar) - closure { - print(self.bar) - } - closure { [self] in - print(self.bar) - } - print(self.bar) - } - - closure { [weak self] in - guard let self = self else { - return - } - - print(self.bar) - } - - closure { [weak self] in - guard let self = self ?? somethingElse else { - return - } - - print(self.bar) - } - } - } - """ - - let output = """ - class Foo { - let bar: Int - - func baaz() { - closure { [weak self] in - print(self?.bar) - guard let self else { - return - } - print(bar) - closure { - print(self.bar) - } - closure { [self] in - print(bar) - } - print(bar) - } - - closure { [weak self] in - guard let self = self else { - return - } - - print(bar) - } - - closure { [weak self] in - guard let self = self ?? somethingElse else { - return - } - - print(self.bar) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, output, rule: .redundantSelf, - options: options, exclude: [.redundantOptionalBinding]) - } - - func testWeakSelfNotRemovedIfNotUnwrapped() { - let input = """ - class A { - weak var delegate: ADelegate? - - func testFunction() { - DispatchQueue.main.async { [weak self] in - self.flatMap { $0.delegate?.aDidSomething($0) } - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testClosureParameterListShadowingPropertyOnSelf() { - let input = """ - class Foo { - var bar = "bar" - - func method() { - closure { [self] bar in - self.bar = bar - } - } - } - """ - - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testClosureParameterListShadowingPropertyOnSelfInStruct() { - let input = """ - struct Foo { - var bar = "bar" - - func method() { - closure { bar in - self.bar = bar - } - } - } - """ - - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testClosureCaptureListShadowingPropertyOnSelf() { - let input = """ - class Foo { - var bar = "bar" - var baaz = "baaz" - - func method() { - closure { [self, bar, baaz = bar] in - self.bar = bar - self.baaz = baaz - } - } - } - """ - - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testRedundantSelfKeepsSelfInClosureCapturingSelfWeaklyBefore5_8() { - let input = """ - class Foo { - let bar: Int - - func baaz() { - closure { [weak self] in - print(self?.bar) - guard let self else { - return - } - print(self.bar) - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNonRedundantSelfNotRemovedAfterConditionalLet() { - let input = """ - class Foo { - var bar: Int? - var baz: Bool - - func foo() { - let baz = 5 - if let bar = bar, self.baz { - // ... - } - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testRedundantSelfDoesntGetStuckIfNoParensFound() { - let input = "init_ foo: T {}" - testFormatting(for: input, rule: .redundantSelf, - exclude: [.spaceAroundOperators]) - } - - func testNoRemoveSelfInIfLetSelf() { - let input = """ - func foo() { - if let self = self as? Foo { - self.bar() - } - self.bar() - } - """ - let output = """ - func foo() { - if let self = self as? Foo { - self.bar() - } - bar() - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNoRemoveSelfInIfLetEscapedSelf() { - let input = """ - func foo() { - if let `self` = self as? Foo { - self.bar() - } - self.bar() - } - """ - let output = """ - func foo() { - if let `self` = self as? Foo { - self.bar() - } - bar() - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testNoRemoveSelfAfterGuardLetSelf() { - let input = """ - func foo() { - guard let self = self as? Foo else { - return - } - self.bar() - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInClosureInIfCondition() { - let input = """ - class Foo { - func foo() { - if bar({ self.baz() }) {} - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInTrailingClosureInVarAssignment() { - let input = """ - func broken() { - var bad = abc { - self.foo() - self.bar - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfNotRemovedWhenPropertyIsKeyword() { - let input = """ - class Foo { - let `default` = 5 - func foo() { - print(self.default) - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfNotRemovedWhenPropertyIsContextualKeyword() { - let input = """ - class Foo { - let `self` = 5 - func foo() { - print(self.self) - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfRemovedForContextualKeywordThatRequiresNoEscaping() { - let input = """ - class Foo { - let get = 5 - func foo() { - print(self.get) - } - } - """ - let output = """ - class Foo { - let get = 5 - func foo() { - print(get) - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveSelfForMemberNamedLazy() { - let input = "func foo() { self.lazy() }" - let output = "func foo() { lazy() }" - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveRedundantSelfInArrayLiteral() { - let input = """ - class Foo { - func foo() { - print([self.bar.x, self.bar.y]) - } - } - """ - let output = """ - class Foo { - func foo() { - print([bar.x, bar.y]) - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveRedundantSelfInArrayLiteralVar() { - let input = """ - class Foo { - func foo() { - var bars = [self.bar.x, self.bar.y] - print(bars) - } - } - """ - let output = """ - class Foo { - func foo() { - var bars = [bar.x, bar.y] - print(bars) - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemoveRedundantSelfInGuardLet() { - let input = """ - class Foo { - func foo() { - guard let bar = self.baz else { - return - } - } - } - """ - let output = """ - class Foo { - func foo() { - guard let bar = baz else { - return - } - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testSelfNotRemovedInClosureInIf() { - let input = """ - if let foo = bar(baz: { [weak self] in - guard let self = self else { return } - _ = self.myVar - }) {} - """ - testFormatting(for: input, rule: .redundantSelf, - exclude: [.wrapConditionalBodies]) - } - - func testStructSelfRemovedInTrailingClosureInIfCase() { - let input = """ - struct A { - func doSomething() { - B.method { mode in - if case .edit = mode { - self.doA() - } else { - self.doB() - } - } - } - - func doA() {} - func doB() {} - } - """ - let output = """ - struct A { - func doSomething() { - B.method { mode in - if case .edit = mode { - doA() - } else { - doB() - } - } - } - - func doA() {} - func doB() {} - } - """ - testFormatting(for: input, output, rule: .redundantSelf, - options: FormatOptions(swiftVersion: "5.8")) - } - - func testSelfNotRemovedInDynamicMemberLookup() { - let input = """ - @dynamicMemberLookup - struct Foo { - subscript(dynamicMember foo: String) -> String { - foo + "bar" - } - - func bar() { - if self.foo == "foobar" { - return - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfNotRemovedInDeclarationWithDynamicMemberLookup() { - let input = """ - @dynamicMemberLookup - struct Foo { - subscript(dynamicMember foo: String) -> String { - foo + "bar" - } - - func bar() { - let foo = self.foo - print(foo) - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfNotRemovedInExtensionOfTypeWithDynamicMemberLookup() { - let input = """ - @dynamicMemberLookup - struct Foo {} - - extension Foo { - subscript(dynamicMember foo: String) -> String { - foo + "bar" - } - - func bar() { - if self.foo == "foobar" { - return - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfRemovedInNestedExtensionOfTypeWithDynamicMemberLookup() { - let input = """ - @dynamicMemberLookup - struct Foo { - var foo: Int - struct Foo {} - extension Foo { - func bar() { - if self.foo == "foobar" { - return - } - } - } - } - """ - let output = """ - @dynamicMemberLookup - struct Foo { - var foo: Int - struct Foo {} - extension Foo { - func bar() { - if foo == "foobar" { - return - } - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: .redundantSelf, - options: options) - } - - func testNoRemoveSelfAfterGuardCaseLetWithExplicitNamespace() { - let input = """ - class Foo { - var name: String? - - func bug(element: Something) { - guard case let Something.a(name) = element - else { return } - self.name = name - } - } - """ - testFormatting(for: input, rule: .redundantSelf, - exclude: [.wrapConditionalBodies]) - } - - func testNoRemoveSelfInAssignmentInsideIfAsStatement() { - let input = """ - if let foo = foo as? Foo, let bar = baz { - self.bar = bar - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testNoRemoveSelfInAssignmentInsideIfLetWithPostfixOperator() { - let input = """ - if let foo = baz?.foo, let bar = baz?.bar { - self.foo = foo - self.bar = bar - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testRedundantSelfParsingBug() { - let input = """ - private class Foo { - mutating func bar() -> Statement? { - let start = self - guard case Token.identifier(let name)? = self.popFirst() else { - self = start - return nil - } - return Statement.declaration(name: name) - } - } - """ - let output = """ - private class Foo { - mutating func bar() -> Statement? { - let start = self - guard case Token.identifier(let name)? = popFirst() else { - self = start - return nil - } - return Statement.declaration(name: name) - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf, - exclude: [.hoistPatternLet]) - } - - func testRedundantSelfParsingBug2() { - let input = """ - extension Foo { - private enum NonHashableEnum: RawRepresentable { - case foo - case bar - - var rawValue: RuntimeTypeTests.TestStruct { - return TestStruct(foo: 0) - } - - init?(rawValue: RuntimeTypeTests.TestStruct) { - switch rawValue.foo { - case 0: - self = .foo - case 1: - self = .bar - default: - return nil - } - } - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testRedundantSelfParsingBug3() { - let input = """ - final class ViewController { - private func bottomBarModels() -> [BarModeling] { - if let url = URL(string: "..."){ - // ... - } - - models.append( - Footer.barModel( - content: FooterContent( - primaryTitleText: "..."), - style: style) - .setBehaviors { context in - context.view.primaryButtonState = self.isLoading ? .waiting : .normal - context.view.primaryActionHandler = { [weak self] _ in - self?.acceptButtonWasTapped() - } - }) - } - - } - """ - XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) - } - - func testRedundantSelfParsingBug4() { - let input = """ - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - guard let row: Row = promotionSections[indexPath.section][indexPath.row] else { return UITableViewCell() } - let cell = tableView.dequeueReusable(RowTableViewCell.self, forIndexPath: indexPath) - cell.update(row: row) - return cell - } - """ - XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) - } - - func testRedundantSelfParsingBug5() { - let input = """ - Button.primary( - title: "Title", - tapHandler: { [weak self] in - self?.dismissBlock? { - // something - } - } - ) - """ - XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) - } - - func testRedundantSelfParsingBug6() { - let input = """ - if let foo = bar, foo.tracking[jsonDict: "something"] != nil {} - """ - XCTAssertNoThrow(try format(input, rules: [.redundantSelf])) - } - - func testRedundantSelfWithStaticMethodAfterForLoop() { - let input = """ - struct Foo { - init() { - for foo in self.bar {} - } - - static func foo() {} - } - - """ - let output = """ - struct Foo { - init() { - for foo in bar {} - } - - static func foo() {} - } - - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRedundantSelfWithStaticMethodAfterForWhereLoop() { - let input = """ - struct Foo { - init() { - for foo in self.bar where !bar.isEmpty {} - } - - static func foo() {} - } - - """ - let output = """ - struct Foo { - init() { - for foo in bar where !bar.isEmpty {} - } - - static func foo() {} - } - - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRedundantSelfRuleDoesntErrorInForInTryLoop() { - let input = "for foo in try bar() {}" - testFormatting(for: input, rule: .redundantSelf) - } - - func testRedundantSelfInInitWithActorLabel() { - let input = """ - class Foo { - init(actor: Actor, bar: Bar) { - self.actor = actor - self.bar = bar - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testRedundantSelfRuleFailsInGuardWithParenthesizedClosureAfterComma() { - let input = """ - guard let foo = bar, foo.bar(baz: { $0 }) else { - return nil - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testMinSelfNotRemoved() { - let input = """ - extension Array where Element: Comparable { - func foo() -> Int { - self.min() - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testMinSelfNotRemovedOnSwift5_4() { - let input = """ - extension Array where Element == Foo { - func smallest() -> Foo? { - let bar = self.min(by: { rect1, rect2 -> Bool in - rect1.perimeter < rect2.perimeter - }) - return bar - } - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) - } - - func testDisableRedundantSelfDirective() { - let input = """ - func smallest() -> Foo? { - // swiftformat:disable:next redundantSelf - let bar = self.foo { rect1, rect2 -> Bool in - rect1.perimeter < rect2.perimeter - } - return bar - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) - } - - func testDisableRedundantSelfDirective2() { - let input = """ - func smallest() -> Foo? { - let bar = - // swiftformat:disable:next redundantSelf - self.foo { rect1, rect2 -> Bool in - rect1.perimeter < rect2.perimeter - } - return bar - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) - } - - func testSelfInsertDirective() { - let input = """ - func smallest() -> Foo? { - // swiftformat:options:next --self insert - let bar = self.foo { rect1, rect2 -> Bool in - rect1.perimeter < rect2.perimeter - } - return bar - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantProperty]) - } - - func testNoRemoveVariableShadowedLaterInScopeInOlderSwiftVersions() { - let input = """ - func foo() -> Bar? { - guard let baz = self.bar else { - return nil - } - - let bar = Foo() - return Bar(baz) - } - """ - let options = FormatOptions(swiftVersion: "4.2") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testStillRemoveVariableShadowedInSameDecalarationInOlderSwiftVersions() { - let input = """ - func foo() -> Bar? { - guard let bar = self.bar else { - return nil - } - return bar - } - """ - let output = """ - func foo() -> Bar? { - guard let bar = bar else { - return nil - } - return bar - } - """ - let options = FormatOptions(swiftVersion: "5.0") - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testShadowedSelfRemovedInGuardLet() { - let input = """ - func foo() { - guard let optional = self.optional else { - return - } - print(optional) - } - """ - let output = """ - func foo() { - guard let optional = optional else { - return - } - print(optional) - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testShadowedStringValueNotRemovedInInit() { - let input = """ - init() { - let value = "something" - self.value = value - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testShadowedIntValueNotRemovedInInit() { - let input = """ - init() { - let value = 5 - self.value = value - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testShadowedPropertyValueNotRemovedInInit() { - let input = """ - init() { - let value = foo - self.value = value - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testShadowedFuncCallValueNotRemovedInInit() { - let input = """ - init() { - let value = foo() - self.value = value - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testShadowedFuncParamRemovedInInit() { - let input = """ - init() { - let value = foo(self.value) - } - """ - let output = """ - init() { - let value = foo(value) - } - """ - let options = FormatOptions(swiftVersion: "5.4") - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testNoRemoveSelfInMacro() { - let input = """ - struct MyStruct { - private var __myVar: String - var myVar: String { - @storageRestrictions(initializes: self.__myVar) - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - // explicitSelf = .insert - - func testInsertSelf() { - let input = "class Foo {\n let foo: Int\n init() { foo = 5 }\n}" - let output = "class Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testInsertSelfInActor() { - let input = "actor Foo {\n let foo: Int\n init() { foo = 5 }\n}" - let output = "actor Foo {\n let foo: Int\n init() { self.foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testInsertSelfAfterReturn() { - let input = "class Foo {\n let foo: Int\n func bar() -> Int { return foo }\n}" - let output = "class Foo {\n let foo: Int\n func bar() -> Int { return self.foo }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testInsertSelfInsideStringInterpolation() { - let input = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(bar)\")\n }\n}" - let output = "class Foo {\n var bar: String?\n func baz() {\n print(\"\\(self.bar)\")\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testNoInterpretGenericTypesAsMembers() { - let input = "class Foo {\n let foo: Bar\n init() { self.foo = Int(5) }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testInsertSelfForStaticMemberInClassFunction() { - let input = "class Foo {\n static var foo: Int\n class func bar() { foo = 5 }\n}" - let output = "class Foo {\n static var foo: Int\n class func bar() { self.foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForInstanceMemberInClassFunction() { - let input = "class Foo {\n var foo: Int\n class func bar() { foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForStaticMemberInInstanceFunction() { - let input = "class Foo {\n static var foo: Int\n func bar() { foo = 5 }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForShadowedClassMemberInClassFunction() { - let input = "class Foo {\n class func foo() {\n var foo: Int\n func bar() { foo = 5 }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfInForLoopTuple() { - let input = "class Foo {\n var bar: Int\n func foo() { for (bar, baz) in quux {} }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForTupleTypeMembers() { - let input = "class Foo {\n var foo: (Int, UIColor) {\n let bar = UIColor.red\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForArrayElements() { - let input = "class Foo {\n var foo = [1, 2, nil]\n func bar() { baz(nil) }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForNestedVarReference() { - let input = "class Foo {\n func bar() {\n var bar = 5\n repeat { bar = 6 } while true\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.wrapLoopBodies]) - } - - func testNoInsertSelfInSwitchCaseLet() { - let input = "class Foo {\n var foo: Bar? {\n switch bar {\n case let .baz(foo, _):\n return nil\n }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfInFuncAfterImportedClass() { - let input = "import class Foo.Bar\nfunc foo() {\n var bar = 5\n if true {\n bar = 6\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options, - exclude: [.blankLineAfterImports]) - } - - func testNoInsertSelfForSubscriptGetSet() { - let input = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return get(key) }\n set { set(key, newValue) }\n }\n}" - let output = "class Foo {\n func get() {}\n func set() {}\n subscript(key: String) -> String {\n get { return self.get(key) }\n set { self.set(key, newValue) }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfInIfCaseLet() { - let input = "enum Foo {\n case bar(Int)\n var value: Int? {\n if case let .bar(value) = self { return value }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options, - exclude: [.wrapConditionalBodies]) - } - - func testNoInsertSelfForPatternLet() { - let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForPatternLet2() { - let input = "class Foo {\n func foo() {}\n func bar() {\n switch x {\n case let .foo(baz): print(baz)\n case .bar(let foo, var bar): print(foo + bar)\n }\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForTypeOf() { - let input = "class Foo {\n var type: String?\n func bar() {\n print(\"\\(type(of: self))\")\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForConditionalLocal() { - let input = "class Foo {\n func foo() {\n #if os(watchOS)\n var foo: Int\n #else\n var foo: Float\n #endif\n print(foo)\n }\n}" - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testInsertSelfInExtension() { - let input = """ - struct Foo { - var bar = 5 - } - - extension Foo { - func baz() { - bar = 6 - } - } - """ - let output = """ - struct Foo { - var bar = 5 - } - - extension Foo { - func baz() { - self.bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testGlobalAfterTypeNotTreatedAsMember() { - let input = """ - struct Foo { - var foo = 1 - } - - var bar = 5 - - extension Foo { - func baz() { - bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testForWhereVarNotTreatedAsMember() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - for bar in self where bar.baz { - return bar - } - return nil - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSwitchCaseWhereVarNotTreatedAsMember() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let bar where bar.baz: - return bar - default: - return nil - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSwitchCaseVarDoesntLeak() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let bar: - return bar - default: - return bar - } - } - } - """ - let output = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let bar: - return bar - default: - return self.bar - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfInsertedInSwitchCaseLet() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo: - return bar - default: - return bar - } - } - } - """ - let output = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo: - return self.bar - default: - return self.bar - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfInsertedInSwitchCaseWhere() { - let input = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo where bar.baz: - return bar - default: - return bar - } - } - } - """ - let output = """ - class Foo { - var bar: Bar - var bazziestBar: Bar? { - switch x { - case let foo where self.bar.baz: - return self.bar - default: - return self.bar - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfInsertedInDidSet() { - let input = """ - class Foo { - var bar = false { - didSet { - bar = !bar - } - } - } - """ - let output = """ - class Foo { - var bar = false { - didSet { - self.bar = !self.bar - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfInsertedAfterLet() { - let input = """ - struct Foo { - let foo = "foo" - func bar() { - let x = foo - baz(x) - } - - func baz(_: String) {} - } - """ - let output = """ - struct Foo { - let foo = "foo" - func bar() { - let x = self.foo - self.baz(x) - } - - func baz(_: String) {} - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfNotInsertedInParameterNames() { - let input = """ - class Foo { - let a: String - - func bar() { - foo(a: a) - } - } - """ - let output = """ - class Foo { - let a: String - - func bar() { - foo(a: self.a) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfNotInsertedInCaseLet() { - let input = """ - class Foo { - let a: String? - let b: String - - func bar() { - if case let .some(a) = self.a, case var .some(b) = self.b {} - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfNotInsertedInCaseLet2() { - let input = """ - class Foo { - let a: String? - let b: String - - func baz() { - if case let .foos(a, b) = foo, case let .bars(a, b) = bar {} - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfInsertedInTupleAssignment() { - let input = """ - class Foo { - let a: String? - let b: String - - func bar() { - (a, b) = ("foo", "bar") - } - } - """ - let output = """ - class Foo { - let a: String? - let b: String - - func bar() { - (self.a, self.b) = ("foo", "bar") - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfNotInsertedInTupleAssignment() { - let input = """ - class Foo { - let a: String? - let b: String - - func bar() { - let (a, b) = (self.a, self.b) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testInsertSelfForMemberNamedLazy() { - let input = """ - class Foo { - var lazy = "foo" - func foo() { - print(lazy) - } - } - """ - let output = """ - class Foo { - var lazy = "foo" - func foo() { - print(self.lazy) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForVarDefinedInIfCaseLet() { - let input = """ - struct A { - var localVar = "" - - var B: String { - if case let .c(localVar) = self.d, localVar == .e { - print(localVar) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForVarDefinedInUnhoistedIfCaseLet() { - let input = """ - struct A { - var localVar = "" - - var B: String { - if case .c(let localVar) = self.d, localVar == .e { - print(localVar) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options, - exclude: [.hoistPatternLet]) - } - - func testNoInsertSelfForVarDefinedInFor() { - let input = """ - struct A { - var localVar = "" - - var B: String { - for localVar in 0 ..< 6 where localVar < 5 { - print(localVar) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfForVarDefinedInWhileLet() { - let input = """ - struct A { - var localVar = "" - - var B: String { - while let localVar = self.localVar, localVar < 5 { - print(localVar) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfInCaptureList() { - let input = """ - class Thing { - var a: String? { nil } - - func foo() { - let b = "" - { [weak a = b] _ in } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfInCaptureList2() { - let input = """ - class Thing { - var a: String? { nil } - - func foo() { - { [weak a] _ in } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfInCaptureList3() { - let input = """ - class A { - var thing: B? { fatalError() } - - func foo() { - let thing2 = B() - let _: (Bool) -> Void = { [weak thing = thing2] _ in - thing?.bar() - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testBodilessFunctionDoesntBreakParser() { - let input = """ - @_silgen_name("foo") - func foo(_: CFString, _: CFTypeRef) -> Int? - - enum Bar { - static func baz() { - fatalError() - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfBeforeSet() { - let input = """ - class Foo { - var foo: Bool - - var bar: Bool { - get { self.foo } - set { self.foo = newValue } - } - - required init() {} - - func set() {} - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfInMacro() { - let input = """ - struct MyStruct { - private var __myVar: String - var myVar: String { - @storageRestrictions(initializes: __myVar) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfBeforeBinding() { - let input = """ - struct MyView: View { - @Environment(ViewModel.self) var viewModel - - var body: some View { - @Bindable var viewModel = self.viewModel - ZStack { - MySubview( - navigationPath: $viewModel.navigationPath - ) - } - } - } - """ - let options = FormatOptions(explicitSelf: .insert, swiftVersion: "5.10") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - // explicitSelf = .initOnly - - func testPreserveSelfInsideClassInit() { - let input = """ - class Foo { - var bar = 5 - init() { - self.bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testRemoveSelfIfNotInsideClassInit() { - let input = """ - class Foo { - var bar = 5 - func baz() { - self.bar = 6 - } - } - """ - let output = """ - class Foo { - var bar = 5 - func baz() { - bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testInsertSelfInsideClassInit() { - let input = """ - class Foo { - var bar = 5 - init() { - bar = 6 - } - } - """ - let output = """ - class Foo { - var bar = 5 - init() { - self.bar = 6 - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testNoInsertSelfInsideClassInitIfNotLvalue() { - let input = """ - class Foo { - var bar = 5 - let baz = 6 - init() { - bar = baz - } - } - """ - let output = """ - class Foo { - var bar = 5 - let baz = 6 - init() { - self.bar = baz - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testRemoveSelfInsideClassInitIfNotLvalue() { - let input = """ - class Foo { - var bar = 5 - let baz = 6 - init() { - self.bar = self.baz - } - } - """ - let output = """ - class Foo { - var bar = 5 - let baz = 6 - init() { - self.bar = baz - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfDotTypeInsideClassInitEdgeCase() { - let input = """ - class Foo { - let type: Int - - init() { - self.type = 5 - } - - func baz() { - switch type {} - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfInsertedInTupleInInit() { - let input = """ - class Foo { - let a: String? - let b: String - - init() { - (a, b) = ("foo", "bar") - } - } - """ - let output = """ - class Foo { - let a: String? - let b: String - - init() { - (self.a, self.b) = ("foo", "bar") - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testSelfInsertedAfterLetInInit() { - let input = """ - class Foo { - var foo: String - init(bar: Bar) { - let baz = bar.quux - foo = baz - } - } - """ - let output = """ - class Foo { - var foo: String - init(bar: Bar) { - let baz = bar.quux - self.foo = baz - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: .redundantSelf, options: options) - } - - func testRedundantSelfRuleDoesntErrorForStaticFuncInProtocolWithWhere() { - let input = """ - protocol Foo where Self: Bar { - static func baz() -> Self - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testRedundantSelfRuleDoesntErrorForStaticFuncInStructWithWhere() { - let input = """ - struct Foo where T: Bar { - static func baz() -> Foo {} - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testRedundantSelfRuleDoesntErrorForClassFuncInClassWithWhere() { - let input = """ - class Foo where T: Bar { - class func baz() -> Foo {} - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testRedundantSelfRuleFailsInInitOnlyMode() { - let input = """ - class Foo { - func foo() -> Foo? { - guard let bar = { nil }() else { - return nil - } - } - - static func baz() -> String? {} - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.redundantClosure]) - } - - func testRedundantSelfRuleFailsInInitOnlyMode2() { - let input = """ - struct Mesh { - var storage: Storage - init(vertices: [Vertex]) { - let isConvex = pointsAreConvex(vertices) - storage = Storage(vertices: vertices) - } - } - """ - let output = """ - struct Mesh { - var storage: Storage - init(vertices: [Vertex]) { - let isConvex = pointsAreConvex(vertices) - self.storage = Storage(vertices: vertices) - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, output, rule: .redundantSelf, - options: options) - } - - func testSelfNotRemovedInInitForSwift5_4() { - let input = """ - init() { - let foo = 1234 - self.bar = foo - } - """ - let options = FormatOptions(explicitSelf: .initOnly, swiftVersion: "5.4") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testPropertyInitNotInterpretedAsTypeInit() { - let input = """ - struct MyStruct { - private var __myVar: String - var myVar: String { - @storageRestrictions(initializes: __myVar) - init(initialValue) { - __myVar = initialValue - } - set { - __myVar = newValue - } - get { - __myVar - } - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testPropertyInitNotInterpretedAsTypeInit2() { - let input = """ - struct MyStruct { - private var __myVar: String - var myVar: String { - @storageRestrictions(initializes: __myVar) - init { - __myVar = newValue - } - set { - __myVar = newValue - } - get { - __myVar - } - } - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - // parsing bugs - - func testSelfRemovalParsingBug() { - let input = """ - extension Dictionary where Key == String { - func requiredValue(for keyPath: String) throws -> T { - return keyPath as! T - } - - func optionalValue(for keyPath: String) throws -> T? { - guard let anyValue = self[keyPath] else { - return nil - } - guard let value = anyValue as? T else { - return nil - } - return value - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfRemovalParsingBug2() { - let input = """ - if let test = value()["hi"] { - print("hi") - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfRemovalParsingBug3() { - let input = """ - func handleGenericError(_ error: Error) { - if let requestableError = error as? RequestableError, - case let .underlying(error as NSError) = requestableError, - error.code == NSURLErrorNotConnectedToInternet - {} - } - """ - let options = FormatOptions(explicitSelf: .initOnly) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfRemovalParsingBug4() { - let input = """ - struct Foo { - func bar() { - for flag in [] where [].filter({ true }) {} - } - - static func baz() {} - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfRemovalParsingBug5() { - let input = """ - extension Foo { - func method(foo: Bar) { - self.foo = foo - - switch foo { - case let .foo(bar): - closure { - Foo.draw() - } - } - } - - private static func draw() {} - } - """ - - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfRemovalParsingBug6() { - let input = """ - something.do(onSuccess: { result in - if case .success((let d, _)) = result { - self.relay.onNext(d) - } - }) - """ - testFormatting(for: input, rule: .redundantSelf, - exclude: [.hoistPatternLet]) - } - - func testSelfRemovalParsingBug7() { - let input = """ - extension Dictionary where Key == String { - func requiredValue(for keyPath: String) throws(Foo) -> T { - return keyPath as! T - } - - func optionalValue(for keyPath: String) throws(Foo) -> T? { - guard let anyValue = self[keyPath] else { - return nil - } - guard let value = anyValue as? T else { - return nil - } - return value - } - } - """ - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfNotRemovedInCaseIfElse() { - let input = """ - class Foo { - let bar = true - let someOptionalBar: String? = "bar" - - func test() { - guard let bar: String = someOptionalBar else { - return - } - - let result = Result.success(bar) - switch result { - case let .success(value): - if self.bar { - if self.bar { - print(self.bar) - } - } else { - if self.bar { - print(self.bar) - } - } - - case .failure: - if self.bar { - print(self.bar) - } - } - } - } - """ - - testFormatting(for: input, rule: .redundantSelf) - } - - func testSelfCallAfterIfStatementInSwitchStatement() { - let input = """ - closure { [weak self] in - guard let self else { - return - } - - switch result { - case let .success(value): - if value != nil { - if value != nil { - self.method() - } - } - self.method() - - case .failure: - break - } - } - """ - - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testSelfNotRemovedFollowingNestedSwitchStatements() { - let input = """ - class Foo { - let bar = true - let someOptionalBar: String? = "bar" - - func test() { - guard let bar: String = someOptionalBar else { - return - } - - let result = Result.success(bar) - switch result { - case let .success(value): - switch result { - case .success: - print("success") - case .value: - print("value") - } - - case .failure: - guard self.bar else { - print(self.bar) - return - } - print(self.bar) - } - } - } - """ - - testFormatting(for: input, rule: .redundantSelf) - } - - func testRedundantSelfWithStaticAsyncSendableClosureFunction() { - let input = """ - class Foo: Bar { - static func bar( - _ closure: @escaping @Sendable () async -> Foo - ) -> @Sendable () async -> Foo { - self.foo = closure - return closure - } - - static func bar() {} - } - """ - let output = """ - class Foo: Bar { - static func bar( - _ closure: @escaping @Sendable () async -> Foo - ) -> @Sendable () async -> Foo { - foo = closure - return closure - } - - static func bar() {} - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - // enable/disable - - func testDisableRemoveSelf() { - let input = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable redundantSelf - self.bar = 1 - // swiftformat:enable redundantSelf - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable redundantSelf - self.bar = 1 - // swiftformat:enable redundantSelf - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testDisableRemoveSelfCaseInsensitive() { - let input = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable redundantself - self.bar = 1 - // swiftformat:enable RedundantSelf - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable redundantself - self.bar = 1 - // swiftformat:enable RedundantSelf - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testDisableNextRemoveSelf() { - let input = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable:next redundantSelf - self.bar = 1 - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - // swiftformat:disable:next redundantSelf - self.bar = 1 - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testMultilineDisableRemoveSelf() { - let input = """ - class Foo { - var bar: Int - func baz() { - /* swiftformat:disable redundantSelf */ self.bar = 1 /* swiftformat:enable all */ - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - /* swiftformat:disable redundantSelf */ self.bar = 1 /* swiftformat:enable all */ - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testMultilineDisableNextRemoveSelf() { - let input = """ - class Foo { - var bar: Int - func baz() { - /* swiftformat:disable:next redundantSelf */ - self.bar = 1 - self.bar = 2 - } - } - """ - let output = """ - class Foo { - var bar: Int - func baz() { - /* swiftformat:disable:next redundantSelf */ - self.bar = 1 - bar = 2 - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRemovesSelfInNestedFunctionInStrongSelfClosure() { - let input = """ - class Test { - func doWork(_ escaping: @escaping () -> Void) { - escaping() - } - - func test() { - doWork { [self] in - doWork { - // Not allowed. Warning in Swift 5 and error in Swift 6. - self.test() - } - - func innerFunc() { - // Allowed: https://forums.swift.org/t/why-does-se-0269-have-different-rules-for-inner-closures-vs-inner-functions/64334/2 - self.test() - } - - innerFunc() - } - } - } - """ - - let output = """ - class Test { - func doWork(_ escaping: @escaping () -> Void) { - escaping() - } - - func test() { - doWork { [self] in - doWork { - // Not allowed. Warning in Swift 5 and error in Swift 6. - self.test() - } - - func innerFunc() { - // Allowed: https://forums.swift.org/t/why-does-se-0269-have-different-rules-for-inner-closures-vs-inner-functions/64334/2 - test() - } - - innerFunc() - } - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf, options: FormatOptions(swiftVersion: "5.8")) - } - - func testPreservesSelfInNestedFunctionInWeakSelfClosure() { - let input = """ - class Test { - func doWork(_ escaping: @escaping () -> Void) { - escaping() - } - - func test() { - doWork { [weak self] in - func innerFunc() { - self?.test() - } - - guard let self else { - return - } - - self.test() - - func innerFunc() { - self.test() - } - - self.test() - } - } - } - """ - - let output = """ - class Test { - func doWork(_ escaping: @escaping () -> Void) { - escaping() - } - - func test() { - doWork { [weak self] in - func innerFunc() { - self?.test() - } - - guard let self else { - return - } - - test() - - func innerFunc() { - self.test() - } - - test() - } - } - } - """ - - testFormatting(for: input, output, rule: .redundantSelf, - options: FormatOptions(swiftVersion: "5.8")) - } - - func testRedundantSelfAfterScopedImport() { - let input = """ - import struct Foundation.Date - - struct Foo { - let foo: String - init(bar: String) { - self.foo = bar - } - } - """ - let output = """ - import struct Foundation.Date - - struct Foo { - let foo: String - init(bar: String) { - foo = bar - } - } - """ - testFormatting(for: input, output, rule: .redundantSelf) - } - - func testRedundantSelfNotConfusedByParameterPack() { - let input = """ - func pairUp(firstPeople: repeat each T, secondPeople: repeat each U) -> (repeat (first: each T, second: each U)) { - (repeat (each firstPeople, each secondPeople)) - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - func testRedundantSelfNotConfusedByStaticAfterSwitch() { - let input = """ - public final class MyClass { - private static func privateStaticFunction1() -> Bool { - switch Result(catching: { try someThrowingFunction() }) { - case .success: - return true - case .failure: - return false - } - } - - private static func privateStaticFunction2() -> Bool { - return false - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.enumNamespaces]) - } - - func testRedundantSelfNotConfusedByMainActor() { - let input = """ - class Test { - private var p: Int - - func f() { - self.f2( - closure: { @MainActor [weak self] p in - print(p) - } - ) - } - } - """ - let options = FormatOptions(explicitSelf: .insert) - testFormatting(for: input, rule: .redundantSelf, options: options) - } - - // MARK: - redundantStaticSelf - - func testRedundantStaticSelfInStaticVar() { - let input = "enum E { static var x: Int { Self.y } }" - let output = "enum E { static var x: Int { y } }" - testFormatting(for: input, output, rule: .redundantStaticSelf) - } - - func testRedundantStaticSelfInStaticMethod() { - let input = "enum E { static func foo() { Self.bar() } }" - let output = "enum E { static func foo() { bar() } }" - testFormatting(for: input, output, rule: .redundantStaticSelf) - } - - func testRedundantStaticSelfOnNextLine() { - let input = """ - enum E { - static func foo() { - Self - .bar() - } - } - """ - let output = """ - enum E { - static func foo() { - bar() - } - } - """ - testFormatting(for: input, output, rule: .redundantStaticSelf) - } - - func testRedundantStaticSelfWithReturn() { - let input = "enum E { static func foo() { return Self.bar() } }" - let output = "enum E { static func foo() { return bar() } }" - testFormatting(for: input, output, rule: .redundantStaticSelf) - } - - func testRedundantStaticSelfInConditional() { - let input = """ - enum E { - static func foo() { - if Bool.random() { - Self.bar() - } - } - } - """ - let output = """ - enum E { - static func foo() { - if Bool.random() { - bar() - } - } - } - """ - testFormatting(for: input, output, rule: .redundantStaticSelf) - } - - func testRedundantStaticSelfInNestedFunction() { - let input = """ - enum E { - static func foo() { - func bar() { - Self.foo() - } - } - } - """ - let output = """ - enum E { - static func foo() { - func bar() { - foo() - } - } - } - """ - testFormatting(for: input, output, rule: .redundantStaticSelf) - } - - func testRedundantStaticSelfInNestedType() { - let input = """ - enum Outer { - enum Inner { - static func foo() {} - static func bar() { Self.foo() } - } - } - """ - let output = """ - enum Outer { - enum Inner { - static func foo() {} - static func bar() { foo() } - } - } - """ - testFormatting(for: input, output, rule: .redundantStaticSelf) - } - - func testStaticSelfNotRemovedWhenUsedAsImplicitInitializer() { - let input = "enum E { static func foo() { Self().bar() } }" - testFormatting(for: input, rule: .redundantStaticSelf) - } - - func testStaticSelfNotRemovedWhenUsedAsExplicitInitializer() { - let input = "enum E { static func foo() { Self.init().bar() } }" - testFormatting(for: input, rule: .redundantStaticSelf, exclude: [.redundantInit]) - } - - func testPreservesStaticSelfInFunctionAfterStaticVar() { - let input = """ - enum MyFeatureCacheStrategy { - case networkOnly - case cacheFirst - - static let defaultCacheAge = TimeInterval.minutes(5) - - func requestStrategy() -> SingleRequestStrategy { - switch self { - case .networkOnly: - return .networkOnly(writeResultToCache: true) - case .cacheFirst: - return .cacheFirst(maxCacheAge: Self.defaultCacheAge) - } - } - } - """ - testFormatting(for: input, rule: .redundantStaticSelf, exclude: [.propertyType]) - } - - func testPreserveStaticSelfInInstanceFunction() { - let input = """ - enum Foo { - static var value = 0 - - func f() { - Self.value = value - } - } - """ - testFormatting(for: input, rule: .redundantStaticSelf) - } - - func testPreserveStaticSelfForShadowedProperty() { - let input = """ - enum Foo { - static var value = 0 - - static func f(value: Int) { - Self.value = value - } - } - """ - testFormatting(for: input, rule: .redundantStaticSelf) - } - - func testPreserveStaticSelfInGetter() { - let input = """ - enum Foo { - static let foo: String = "foo" - - var sharedFoo: String { - Self.foo - } - } - """ - testFormatting(for: input, rule: .redundantStaticSelf) - } - - func testRemoveStaticSelfInStaticGetter() { - let input = """ - public enum Foo { - static let foo: String = "foo" - - static var getFoo: String { - Self.foo - } - } - """ - let output = """ - public enum Foo { - static let foo: String = "foo" - - static var getFoo: String { - foo - } - } - """ - testFormatting(for: input, output, rule: .redundantStaticSelf) - } - - func testPreserveStaticSelfInGuardLet() { - let input = """ - class LocationDeeplink: Deeplink { - convenience init?(warnRegion: String) { - guard let value = Self.location(for: warnRegion) else { - return nil - } - self.init(location: value) - } - } - """ - testFormatting(for: input, rule: .redundantStaticSelf) - } - - func testPreserveStaticSelfInSingleLineClassInit() { - let input = """ - class A { static let defaultName = "A"; let name: String; init() { name = Self.defaultName }} - """ - testFormatting(for: input, rule: .redundantStaticSelf) - } - - // MARK: - semicolons - - func testSemicolonRemovedAtEndOfLine() { - let input = "print(\"hello\");\n" - let output = "print(\"hello\")\n" - testFormatting(for: input, output, rule: .semicolons) - } - - func testSemicolonRemovedAtStartOfLine() { - let input = "\n;print(\"hello\")" - let output = "\nprint(\"hello\")" - testFormatting(for: input, output, rule: .semicolons) - } - - func testSemicolonRemovedAtEndOfProgram() { - let input = "print(\"hello\");" - let output = "print(\"hello\")" - testFormatting(for: input, output, rule: .semicolons) - } - - func testSemicolonRemovedAtStartOfProgram() { - let input = ";print(\"hello\")" - let output = "print(\"hello\")" - testFormatting(for: input, output, rule: .semicolons) - } - - func testIgnoreInlineSemicolon() { - let input = "print(\"hello\"); print(\"goodbye\")" - let options = FormatOptions(allowInlineSemicolons: true) - testFormatting(for: input, rule: .semicolons, options: options) - } - - func testReplaceInlineSemicolon() { - let input = "print(\"hello\"); print(\"goodbye\")" - let output = "print(\"hello\")\nprint(\"goodbye\")" - let options = FormatOptions(allowInlineSemicolons: false) - testFormatting(for: input, output, rule: .semicolons, options: options) - } - - func testReplaceSemicolonFollowedByComment() { - let input = "print(\"hello\"); // comment\nprint(\"goodbye\")" - let output = "print(\"hello\") // comment\nprint(\"goodbye\")" - let options = FormatOptions(allowInlineSemicolons: true) - testFormatting(for: input, output, rule: .semicolons, options: options) - } - - func testSemicolonNotReplacedAfterReturn() { - let input = "return;\nfoo()" - testFormatting(for: input, rule: .semicolons) - } - - func testSemicolonReplacedAfterReturnIfEndOfScope() { - let input = "do { return; }" - let output = "do { return }" - testFormatting(for: input, output, rule: .semicolons) - } - - func testRequiredSemicolonNotRemovedAfterInferredVar() { - let input = """ - func foo() { - @Environment(\\.colorScheme) var colorScheme; - print(colorScheme) - } - """ - testFormatting(for: input, rule: .semicolons) - } - - // MARK: - duplicateImports - - func testRemoveDuplicateImport() { - let input = "import Foundation\nimport Foundation" - let output = "import Foundation" - testFormatting(for: input, output, rule: .duplicateImports) - } - - func testRemoveDuplicateConditionalImport() { - let input = "#if os(iOS)\n import Foo\n import Foo\n#else\n import Bar\n import Bar\n#endif" - let output = "#if os(iOS)\n import Foo\n#else\n import Bar\n#endif" - testFormatting(for: input, output, rule: .duplicateImports) - } - - func testNoRemoveOverlappingImports() { - let input = "import MyModule\nimport MyModule.Private" - testFormatting(for: input, rule: .duplicateImports) - } - - func testNoRemoveCaseDifferingImports() { - let input = "import Auth0.Authentication\nimport Auth0.authentication" - testFormatting(for: input, rule: .duplicateImports) - } - - func testRemoveDuplicateImportFunc() { - let input = "import func Foo.bar\nimport func Foo.bar" - let output = "import func Foo.bar" - testFormatting(for: input, output, rule: .duplicateImports) - } - - func testNoRemoveTestableDuplicateImport() { - let input = "import Foo\n@testable import Foo" - let output = "\n@testable import Foo" - testFormatting(for: input, output, rule: .duplicateImports) - } - - func testNoRemoveTestableDuplicateImport2() { - let input = "@testable import Foo\nimport Foo" - let output = "@testable import Foo" - testFormatting(for: input, output, rule: .duplicateImports) - } - - func testNoRemoveExportedDuplicateImport() { - let input = "import Foo\n@_exported import Foo" - let output = "\n@_exported import Foo" - testFormatting(for: input, output, rule: .duplicateImports) - } - - func testNoRemoveExportedDuplicateImport2() { - let input = "@_exported import Foo\nimport Foo" - let output = "@_exported import Foo" - testFormatting(for: input, output, rule: .duplicateImports) - } - - // MARK: - unusedArguments - - // closures - - func testUnusedTypedClosureArguments() { - let input = "let foo = { (bar: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" - let output = "let foo = { (_: Int, baz: String) in\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedUntypedClosureArguments() { - let input = "let foo = { bar, baz in\n print(\"Hello \\(baz)\")\n}" - let output = "let foo = { _, baz in\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testNoRemoveClosureReturnType() { - let input = "let foo = { () -> Foo.Bar in baz() }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testNoRemoveClosureThrows() { - let input = "let foo = { () throws in }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testNoRemoveClosureTypedThrows() { - let input = "let foo = { () throws(Foo) in }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testNoRemoveClosureGenericReturnTypes() { - let input = "let foo = { () -> Promise in bar }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testNoRemoveClosureTupleReturnTypes() { - let input = "let foo = { () -> (Int, Int) in (5, 6) }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testNoRemoveClosureGenericArgumentTypes() { - let input = "let foo = { (_: Foo) in }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testNoRemoveFunctionNameBeforeForLoop() { - let input = "{\n func foo() -> Int {}\n for a in b {}\n}" - testFormatting(for: input, rule: .unusedArguments) - } - - func testClosureTypeInClosureArgumentsIsNotMangled() { - let input = "{ (foo: (Int) -> Void) in }" - let output = "{ (_: (Int) -> Void) in }" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedUnnamedClosureArguments() { - let input = "{ (_ foo: Int, _ bar: Int) in }" - let output = "{ (_: Int, _: Int) in }" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedInoutClosureArgumentsNotMangled() { - let input = "{ (foo: inout Foo, bar: inout Bar) in }" - let output = "{ (_: inout Foo, _: inout Bar) in }" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testMalformedFunctionNotMisidentifiedAsClosure() { - let input = "func foo() { bar(5) {} in }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedUsedArguments() { - let input = """ - forEach { foo, bar in - guard let foo = foo, let bar = bar else { - return - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedPartUsedArguments() { - let input = """ - forEach { foo, bar in - guard let foo = baz, bar == baz else { - return - } - } - """ - let output = """ - forEach { _, bar in - guard let foo = baz, bar == baz else { - return - } - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testShadowedParameterUsedInSameGuard() { - let input = """ - forEach { foo in - guard let foo = bar, baz = foo else { - return - } - } - """ - let output = """ - forEach { _ in - guard let foo = bar, baz = foo else { - return - } - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testParameterUsedInForIn() { - let input = """ - forEach { foos in - for foo in foos { - print(foo) - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testParameterUsedInWhereClause() { - let input = """ - forEach { foo in - if bar where foo { - print(bar) - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testParameterUsedInSwitchCase() { - let input = """ - forEach { foo in - switch bar { - case let baz: - foo = baz - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testParameterUsedInStringInterpolation() { - let input = """ - forEach { foo in - print("\\(foo)") - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedClosureArgument() { - let input = """ - _ = Parser { input in - let parser = Parser.with(input) - return parser - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty, .propertyType]) - } - - func testShadowedClosureArgument2() { - let input = """ - _ = foo { input in - let input = ["foo": "Foo", "bar": "Bar"][input] - return input - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) - } - - func testUnusedPropertyWrapperArgument() { - let input = """ - ForEach($list.notes) { $note in - Text(note.foobar) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testUnusedThrowingClosureArgument() { - let input = "foo = { bar throws in \"\" }" - let output = "foo = { _ throws in \"\" }" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedTypedThrowingClosureArgument() { - let input = "foo = { bar throws(Foo) in \"\" }" - let output = "foo = { _ throws(Foo) in \"\" }" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUsedThrowingClosureArgument() { - let input = "let foo = { bar throws in bar + \"\" }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testUsedTypedThrowingClosureArgument() { - let input = "let foo = { bar throws(Foo) in bar + \"\" }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testUnusedTrailingAsyncClosureArgument() { - let input = """ - app.get { foo async in - print("No foo") - } - """ - let output = """ - app.get { _ async in - print("No foo") - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedTrailingAsyncClosureArgument2() { - let input = """ - app.get { foo async -> String in - "No foo" - } - """ - let output = """ - app.get { _ async -> String in - "No foo" - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedTrailingAsyncClosureArgument3() { - let input = """ - app.get { (foo: String) async -> String in - "No foo" - } - """ - let output = """ - app.get { (_: String) async -> String in - "No foo" - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUsedTrailingAsyncClosureArgument() { - let input = """ - app.get { foo async -> String in - "\\(foo)" - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testTrailingAsyncClosureArgumentAlreadyMarkedUnused() { - let input = "app.get { _ async in 5 }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testUnusedTrailingClosureArgumentCalledAsync() { - let input = """ - app.get { async -> String in - "No async" - } - """ - let output = """ - app.get { _ -> String in - "No async" - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testClosureArgumentUsedInGuardNotRemoved() { - let input = """ - bar(for: quux) { _, _, foo in - guard - let baz = quux.baz, - foo.contains(where: { $0.baz == baz }) - else { - return - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testClosureArgumentUsedInIfNotRemoved() { - let input = """ - foo = { reservations, _ in - if let reservations, eligibleToShow( - reservations, - accountService: accountService - ) { - coordinator.startFlow() - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - // init - - func testParameterUsedInInit() { - let input = """ - init(m: Rotation) { - let x = sqrt(max(0, m)) / 2 - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testUnusedParametersShadowedInTupleAssignment() { - let input = """ - init(x: Int, y: Int, v: Vector) { - let (x, y) = v - } - """ - let output = """ - init(x _: Int, y _: Int, v: Vector) { - let (x, y) = v - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUsedParametersShadowedInAssignmentFromFunctionCall() { - let input = """ - init(r: Double) { - let r = max(abs(r), epsilon) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedUsedArgumentInSwitch() { - let input = """ - init(_ action: Action, hub: Hub) { - switch action { - case let .get(hub, key): - self = .get(key, hub) - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testParameterUsedInSwitchCaseAfterShadowing() { - let input = """ - func issue(name: String) -> String { - switch self { - case .b(let name): return name - case .a: return name - } - } - """ - testFormatting(for: input, rule: .unusedArguments, - exclude: [.hoistPatternLet]) - } - - // functions - - func testMarkUnusedFunctionArgument() { - let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - let output = "func foo(bar _: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testMarkUnusedArgumentsInNonVoidFunction() { - let input = "func foo(bar: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" - let output = "func foo(bar _: Int, baz: String) -> (A, D & E, [F: G]) { return baz.quux }" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testMarkUnusedArgumentsInThrowsFunction() { - let input = "func foo(bar: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" - let output = "func foo(bar _: Int, baz: String) throws {\n print(\"Hello \\(baz)\")\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testMarkUnusedArgumentsInOptionalReturningFunction() { - let input = "func foo(bar: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" - let output = "func foo(bar _: Int, baz: String) -> String? {\n return \"Hello \\(baz)\"\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testNoMarkUnusedArgumentsInProtocolFunction() { - let input = "protocol Foo {\n func foo(bar: Int) -> Int\n var bar: Int { get }\n}" - testFormatting(for: input, rule: .unusedArguments) - } - - func testUnusedUnnamedFunctionArgument() { - let input = "func foo(_ foo: Int) {}" - let output = "func foo(_: Int) {}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedInoutFunctionArgumentIsNotMangled() { - let input = "func foo(_ foo: inout Foo) {}" - let output = "func foo(_: inout Foo) {}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedInternallyRenamedFunctionArgument() { - let input = "func foo(foo bar: Int) {}" - let output = "func foo(foo _: Int) {}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testNoMarkProtocolFunctionArgument() { - let input = "func foo(foo bar: Int)\nvar bar: Bool { get }" - testFormatting(for: input, rule: .unusedArguments) - } - - func testMembersAreNotArguments() { - let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(bar.baz)\")\n}" - let output = "func foo(bar: Int, baz _: String) {\n print(\"Hello \\(bar.baz)\")\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testLabelsAreNotArguments() { - let input = "func foo(bar: Int, baz: String) {\n bar: while true { print(baz) }\n}" - let output = "func foo(bar _: Int, baz: String) {\n bar: while true { print(baz) }\n}" - testFormatting(for: input, output, rule: .unusedArguments, exclude: [.wrapLoopBodies]) - } - - func testDictionaryLiteralsRuinEverything() { - let input = "func foo(bar: Int, baz: Int) {\n let quux = [bar: 1, baz: 2]\n}" - testFormatting(for: input, rule: .unusedArguments) - } - - func testOperatorArgumentsAreUnnamed() { - let input = "func == (lhs: Int, rhs: Int) { false }" - let output = "func == (_: Int, _: Int) { false }" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedtFailableInitArgumentsAreNotMangled() { - let input = "init?(foo: Bar) {}" - let output = "init?(foo _: Bar) {}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testTreatEscapedArgumentsAsUsed() { - let input = "func foo(default: Int) -> Int {\n return `default`\n}" - testFormatting(for: input, rule: .unusedArguments) - } - - func testPartiallyMarkedUnusedArguments() { - let input = "func foo(bar: Bar, baz _: Baz) {}" - let output = "func foo(bar _: Bar, baz _: Baz) {}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testPartiallyMarkedUnusedArguments2() { - let input = "func foo(bar _: Bar, baz: Baz) {}" - let output = "func foo(bar _: Bar, baz _: Baz) {}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnownedUnsafeNotStripped() { - let input = """ - func foo() { - var num = 0 - Just(1) - .sink { [unowned(unsafe) self] in - num += $0 - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedUnusedArguments() { - let input = """ - func foo(bar: String, baz: Int) { - let bar = "bar", baz = 5 - print(bar, baz) - } - """ - let output = """ - func foo(bar _: String, baz _: Int) { - let bar = "bar", baz = 5 - print(bar, baz) - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testShadowedUsedArguments2() { - let input = """ - func foo(things: [String], form: Form) { - let form = FormRequest( - things: things, - form: form - ) - print(form) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedUsedArguments3() { - let input = """ - func zoomTo(locations: [Foo], count: Int) { - let num = count - guard num > 0, locations.count >= count else { - return - } - print(locations) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedUsedArguments4() { - let input = """ - func foo(bar: Int) { - if let bar = baz { - return - } - print(bar) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedUsedArguments5() { - let input = """ - func doSomething(with number: Int) { - if let number = Int?(123), - number == 456 - { - print("Not likely") - } - - if number == 180 { - print("Bullseye!") - } - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedUsedArgumentInSwitchCase() { - let input = """ - func foo(bar baz: Foo) -> Foo? { - switch (a, b) { - case (0, _), - (_, nil): - return .none - case let (1, baz?): - return .bar(baz) - default: - return baz - } - } - """ - testFormatting(for: input, rule: .unusedArguments, - exclude: [.sortSwitchCases]) - } - - func testTryArgumentNotMarkedUnused() { - let input = """ - func foo(bar: String) throws -> String? { - let bar = - try parse(bar) - return bar - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) - } - - func testTryAwaitArgumentNotMarkedUnused() { - let input = """ - func foo(bar: String) async throws -> String? { - let bar = try - await parse(bar) - return bar - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) - } - - func testTypedTryAwaitArgumentNotMarkedUnused() { - let input = """ - func foo(bar: String) async throws(Foo) -> String? { - let bar = try - await parse(bar) - return bar - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) - } - - func testConditionalIfLetMarkedAsUnused() { - let input = """ - func foo(bar: UIViewController) { - if let bar = baz { - bar.loadViewIfNeeded() - } - } - """ - let output = """ - func foo(bar _: UIViewController) { - if let bar = baz { - bar.loadViewIfNeeded() - } - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testConditionAfterIfCaseHoistedLetNotMarkedUnused() { - let input = """ - func isLoadingFirst(for tabID: String) -> Bool { - if case let .loading(.first(loadingTabID, _)) = requestState.status, loadingTabID == tabID { - return true - } else { - return false - } - - print(tabID) - } - """ - let options = FormatOptions(hoistPatternLet: true) - testFormatting(for: input, rule: .unusedArguments, options: options) - } - - func testConditionAfterIfCaseInlineLetNotMarkedUnused2() { - let input = """ - func isLoadingFirst(for tabID: String) -> Bool { - if case .loading(.first(let loadingTabID, _)) = requestState.status, loadingTabID == tabID { - return true - } else { - return false - } - - print(tabID) - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .unusedArguments, options: options) - } - - func testConditionAfterIfCaseInlineLetNotMarkedUnused3() { - let input = """ - private func isFocusedView(formDataID: FormDataID) -> Bool { - guard - case .selected(let selectedFormDataID) = currentState.selectedFormItemAction, - selectedFormDataID == formDataID - else { - return false - } - - return true - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .unusedArguments, options: options) - } - - func testConditionAfterIfCaseInlineLetNotMarkedUnused4() { - let input = """ - private func totalRowContent(priceItemsCount: Int, priceBreakdownStyle: PriceBreakdownStyle) { - if - case .all(let shouldCollapseByDefault, _) = priceBreakdownStyle, - priceItemsCount > 0 - { - // .. - } - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .unusedArguments, options: options) - } - - func testConditionAfterIfCaseInlineLetNotMarkedUnused5() { - let input = """ - private mutating func clearPendingRemovals(itemIDs: Set) { - for change in changes { - if case .removal(itemID: let itemID) = change, !itemIDs.contains(itemID) { - // .. - } - } - } - """ - let options = FormatOptions(hoistPatternLet: false) - testFormatting(for: input, rule: .unusedArguments, options: options) - } - - func testSecondConditionAfterTupleMarkedUnused() { - let input = """ - func foobar(bar: Int) { - let (foo, baz) = (1, 2), bar = 3 - print(foo, bar, baz) - } - """ - let output = """ - func foobar(bar _: Int) { - let (foo, baz) = (1, 2), bar = 3 - print(foo, bar, baz) - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedParamsInTupleAssignment() { - let input = """ - func foobar(_ foo: Int, _ bar: Int, _ baz: Int, _ quux: Int) { - let ((foo, bar), baz) = ((foo, quux), bar) - print(foo, bar, baz, quux) - } - """ - let output = """ - func foobar(_ foo: Int, _ bar: Int, _: Int, _ quux: Int) { - let ((foo, bar), baz) = ((foo, quux), bar) - print(foo, bar, baz, quux) - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testShadowedIfLetNotMarkedAsUnused() { - let input = """ - func method(_ foo: Int?, _ bar: String?) { - if let foo = foo, let bar = bar {} - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShorthandIfLetNotMarkedAsUnused() { - let input = """ - func method(_ foo: Int?, _ bar: String?) { - if let foo, let bar {} - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShorthandLetMarkedAsUnused() { - let input = """ - func method(_ foo: Int?, _ bar: Int?) { - var foo, bar: Int? - } - """ - let output = """ - func method(_: Int?, _: Int?) { - var foo, bar: Int? - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testShadowedClosureNotMarkedUnused() { - let input = """ - func foo(bar: () -> Void) { - let bar = { - print("log") - bar() - } - bar() - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testShadowedClosureMarkedUnused() { - let input = """ - func foo(bar: () -> Void) { - let bar = { - print("log") - } - bar() - } - """ - let output = """ - func foo(bar _: () -> Void) { - let bar = { - print("log") - } - bar() - } - """ - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testViewBuilderAnnotationDoesntBreakUnusedArgDetection() { - let input = """ - struct Foo { - let content: View - - public init( - responsibleFileID: StaticString = #fileID, - @ViewBuilder content: () -> View) - { - self.content = content() - } - } - """ - let output = """ - struct Foo { - let content: View - - public init( - responsibleFileID _: StaticString = #fileID, - @ViewBuilder content: () -> View) - { - self.content = content() - } - } - """ - testFormatting(for: input, output, rule: .unusedArguments, - exclude: [.braces, .wrapArguments]) - } - - func testArgumentUsedInDictionaryLiteral() { - let input = """ - class MyClass { - func testMe(value: String) { - let value = [ - "key": value - ] - print(value) - } - } - """ - testFormatting(for: input, rule: .unusedArguments, - exclude: [.trailingCommas]) - } - - func testArgumentUsedAfterIfDefInsideSwitchBlock() { - let input = """ - func test(string: String) { - let number = 5 - switch number { - #if DEBUG - case 1: - print("ONE") - #endif - default: - print("NOT ONE") - } - print(string) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testUsedConsumingArgument() { - let input = """ - func close(file: consuming FileHandle) { - file.close() - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.noExplicitOwnership]) - } - - func testUsedConsumingBorrowingArguments() { - let input = """ - func foo(a: consuming Foo, b: borrowing Bar) { - consume(a) - borrow(b) - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.noExplicitOwnership]) - } - - func testUnusedConsumingArgument() { - let input = """ - func close(file: consuming FileHandle) { - print("no-op") - } - """ - let output = """ - func close(file _: consuming FileHandle) { - print("no-op") - } - """ - testFormatting(for: input, output, rule: .unusedArguments, exclude: [.noExplicitOwnership]) - } - - func testUnusedConsumingBorrowingArguments() { - let input = """ - func foo(a: consuming Foo, b: borrowing Bar) { - print("no-op") - } - """ - let output = """ - func foo(a _: consuming Foo, b _: borrowing Bar) { - print("no-op") - } - """ - testFormatting(for: input, output, rule: .unusedArguments, exclude: [.noExplicitOwnership]) - } - - func testFunctionArgumentUsedInGuardNotRemoved() { - let input = """ - func scrollViewDidEndDecelerating(_ visibleDayRange: DayRange) { - guard - store.state.request.isIdle, - let nextDayToLoad = store.state.request.nextCursor?.lowerBound, - visibleDayRange.upperBound.distance(to: nextDayToLoad) < 30 - else { - return - } - - store.handle(.loadNext) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testFunctionArgumentUsedInGuardNotRemoved2() { - let input = """ - func convert( - filter: Filter, - accounts: [Account], - outgoingTotal: MulticurrencyTotal? - ) -> History? { - guard - let firstParameter = incomingTotal?.currency, - let secondParameter = outgoingTotal?.currency, - isFilter(filter, accounts: accounts) - else { - return nil - } - return History(firstParameter, secondParameter) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testFunctionArgumentUsedInGuardNotRemoved3() { - let input = """ - public func flagMessage(_ message: Message) { - model.withState { state in - guard - let flagMessageFeature, - shouldAllowFlaggingMessage( - message, - thread: state.thread) - else { return } - } - } - """ - testFormatting(for: input, rule: .unusedArguments, - exclude: [.wrapArguments, .wrapConditionalBodies, .indent]) - } - - // functions (closure-only) - - func testNoMarkFunctionArgument() { - let input = "func foo(_ bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - let options = FormatOptions(stripUnusedArguments: .closureOnly) - testFormatting(for: input, rule: .unusedArguments, options: options) - } - - // functions (unnamed-only) - - func testNoMarkNamedFunctionArgument() { - let input = "func foo(bar: Int, baz: String) {\n print(\"Hello \\(baz)\")\n}" - let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, rule: .unusedArguments, options: options) - } - - func testRemoveUnnamedFunctionArgument() { - let input = "func foo(_ foo: Int) {}" - let output = "func foo(_: Int) {}" - let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, output, rule: .unusedArguments, options: options) - } - - func testNoRemoveInternalFunctionArgumentName() { - let input = "func foo(foo bar: Int) {}" - let options = FormatOptions(stripUnusedArguments: .unnamedOnly) - testFormatting(for: input, rule: .unusedArguments, options: options) - } - - // init - - func testMarkUnusedInitArgument() { - let input = "init(bar: Int, baz: String) {\n self.baz = baz\n}" - let output = "init(bar _: Int, baz: String) {\n self.baz = baz\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - // subscript - - func testMarkUnusedSubscriptArgument() { - let input = "subscript(foo: Int, baz: String) -> String {\n return get(baz)\n}" - let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testMarkUnusedUnnamedSubscriptArgument() { - let input = "subscript(_ foo: Int, baz: String) -> String {\n return get(baz)\n}" - let output = "subscript(_: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testMarkUnusedNamedSubscriptArgument() { - let input = "subscript(foo foo: Int, baz: String) -> String {\n return get(baz)\n}" - let output = "subscript(foo _: Int, baz: String) -> String {\n return get(baz)\n}" - testFormatting(for: input, output, rule: .unusedArguments) - } - - func testUnusedArgumentWithClosureShadowingParamName() { - let input = """ - func test(foo: Foo) { - let foo = { - if foo.bar { - baaz - } else { - bar - } - }() - print(foo) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testUnusedArgumentWithConditionalAssignmentShadowingParamName() { - let input = """ - func test(foo: Foo) { - let foo = - if foo.bar { - baaz - } else { - bar - } - print(foo) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testUnusedArgumentWithSwitchAssignmentShadowingParamName() { - let input = """ - func test(foo: Foo) { - let foo = - switch foo.bar { - case true: - baaz - case false: - bar - } - print(foo) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testUnusedArgumentWithConditionalAssignmentNotShadowingParamName() { - let input = """ - func test(bar: Bar) { - let quux = - if foo { - bar - } else { - baaz - } - print(quux) - } - """ - testFormatting(for: input, rule: .unusedArguments) - } - - func testIssue1694() { - let input = """ - listenForUpdates() { [weak self] update, error in - guard let update, error == nil else { - return - } - self?.configure(update) - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantParens]) - } - - func testIssue1696() { - let input = """ - func someFunction(with parameter: Int) -> Int { - let parameter = max( - 200, - parameter - ) - return parameter - } - """ - testFormatting(for: input, rule: .unusedArguments, exclude: [.redundantProperty]) - } - - // MARK: redundantClosure - - func testRemoveRedundantClosureInSingleLinePropertyDeclaration() { - let input = """ - let foo = { "Foo" }() - let bar = { "Bar" }() - - let baaz = { "baaz" }() - - let quux = { "quux" }() - """ - - let output = """ - let foo = "Foo" - let bar = "Bar" - - let baaz = "baaz" - - let quux = "quux" - """ - - testFormatting(for: input, output, rule: .redundantClosure) - } - - func testRedundantClosureWithExplicitReturn() { - let input = """ - let foo = { return "Foo" }() - - let bar = { - return if Bool.random() { - "Bar" - } else { - "Baaz" - } - }() - """ - - let output = """ - let foo = "Foo" - - let bar = if Bool.random() { - "Bar" - } else { - "Baaz" - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure], - options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) - } - - func testRedundantClosureWithExplicitReturn2() { - let input = """ - func foo() -> String { - methodCall() - return { return "Foo" }() - } - - func bar() -> String { - methodCall() - return { "Bar" }() - } - - func baaz() -> String { - { return "Baaz" }() - } - """ - - let output = """ - func foo() -> String { - methodCall() - return "Foo" - } - - func bar() -> String { - methodCall() - return "Bar" - } - - func baaz() -> String { - "Baaz" - } - """ - - testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure]) - } - - func testKeepsClosureThatIsNotCalled() { - let input = """ - let foo = { "Foo" } - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testKeepsEmptyClosures() { - let input = """ - let foo = {}() - let bar = { /* comment */ }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testRemoveRedundantClosureInMultiLinePropertyDeclaration() { - let input = """ - lazy var bar = { - Bar() - }() - """ - - let output = """ - lazy var bar = Bar() - """ - - testFormatting(for: input, output, rule: .redundantClosure, exclude: [.propertyType]) - } - - func testRemoveRedundantClosureInMultiLinePropertyDeclarationWithString() { - let input = #""" - lazy var bar = { - """ - Multiline string literal - """ - }() - """# - - let output = #""" - lazy var bar = """ - Multiline string literal - """ - """# - - testFormatting(for: input, [output], rules: [.redundantClosure, .indent]) - } - - func testRemoveRedundantClosureInMultiLinePropertyDeclarationInClass() { - let input = """ - class Foo { - lazy var bar = { - return Bar(); - }() - } - """ - - let output = """ - class Foo { - lazy var bar = Bar() - } - """ - - testFormatting(for: input, [output], rules: [.redundantReturn, .redundantClosure, - .semicolons], exclude: [.propertyType]) - } - - func testRemoveRedundantClosureInWrappedPropertyDeclaration_beforeFirst() { - let input = """ - lazy var baaz = { - Baaz( - foo: foo, - bar: bar) - }() - """ - - let output = """ - lazy var baaz = Baaz( - foo: foo, - bar: bar) - """ - - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, [output], - rules: [.redundantClosure, .wrapArguments], - options: options, exclude: [.propertyType]) - } - - func testRemoveRedundantClosureInWrappedPropertyDeclaration_afterFirst() { - let input = """ - lazy var baaz = { - Baaz(foo: foo, - bar: bar) - }() - """ - - let output = """ - lazy var baaz = Baaz(foo: foo, - bar: bar) - """ - - let options = FormatOptions(wrapArguments: .afterFirst, closingParenPosition: .sameLine) - testFormatting(for: input, [output], - rules: [.redundantClosure, .wrapArguments], - options: options, exclude: [.propertyType]) - } - - func testRedundantClosureKeepsMultiStatementClosureThatSetsProperty() { - let input = """ - lazy var baaz = { - let baaz = Baaz(foo: foo, bar: bar) - baaz.foo = foo2 - return baaz - }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testRedundantClosureKeepsMultiStatementClosureWithMultipleStatements() { - let input = """ - lazy var quux = { - print("hello world") - return "quux" - }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testRedundantClosureKeepsClosureWithInToken() { - let input = """ - lazy var double = { () -> Double in - 100 - }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testRedundantClosureKeepsMultiStatementClosureOnSameLine() { - let input = """ - lazy var baaz = { - print("Foo"); return baaz - }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testRedundantClosureRemovesComplexMultilineClosure() { - let input = """ - lazy var closureInClosure = { - { - print("Foo") - print("Bar"); return baaz - } - }() - """ - - let output = """ - lazy var closureInClosure = { - print("Foo") - print("Bar"); return baaz - } - """ - - testFormatting(for: input, [output], rules: [.redundantClosure, .indent]) - } - - func testKeepsClosureWithIfStatement() { - let input = """ - lazy var baaz = { - if let foo == foo { - return foo - } else { - return Foo() - } - }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testKeepsClosureWithIfStatementOnSingleLine() { - let input = """ - lazy var baaz = { - if let foo == foo { return foo } else { return Foo() } - }() - """ - - testFormatting(for: input, rule: .redundantClosure, - exclude: [.wrapConditionalBodies]) - } - - func testRemovesClosureWithIfStatementInsideOtherClosure() { - let input = """ - lazy var baaz = { - { - if let foo == foo { - return foo - } else { - return Foo() - } - } - }() - """ - - let output = """ - lazy var baaz = { - if let foo == foo { - return foo - } else { - return Foo() - } - } - """ - - testFormatting(for: input, [output], - rules: [.redundantClosure, .indent]) - } - - func testKeepsClosureWithSwitchStatement() { - let input = """ - lazy var baaz = { - switch foo { - case let .some(foo): - return foo: - case .none: - return Foo() - } - }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testKeepsClosureWithIfDirective() { - let input = """ - lazy var baaz = { - #if DEBUG - return DebugFoo() - #else - return Foo() - #endif - }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testKeepsClosureThatCallsMethodThatReturnsNever() { - let input = """ - lazy var foo: String = { fatalError("no default value has been set") }() - lazy var bar: String = { return preconditionFailure("no default value has been set") }() - """ - - testFormatting(for: input, rule: .redundantClosure, - exclude: [.redundantReturn]) - } - - func testRemovesClosureThatHasNestedFatalError() { - let input = """ - lazy var foo = { - Foo(handle: { fatalError() }) - }() - """ - - let output = """ - lazy var foo = Foo(handle: { fatalError() }) - """ - - testFormatting(for: input, output, rule: .redundantClosure, exclude: [.propertyType]) - } - - func testPreservesClosureWithMultipleVoidMethodCalls() { - let input = """ - lazy var triggerSomething: Void = { - logger.trace("log some stuff before Triggering") - TriggerClass.triggerTheThing() - logger.trace("Finished triggering the thing") - }() - """ - - testFormatting(for: input, rule: .redundantClosure) - } - - func testRemovesClosureWithMultipleNestedVoidMethodCalls() { - let input = """ - lazy var foo: Foo = { - Foo(handle: { - logger.trace("log some stuff before Triggering") - TriggerClass.triggerTheThing() - logger.trace("Finished triggering the thing") - }) - }() - """ - - let output = """ - lazy var foo: Foo = Foo(handle: { - logger.trace("log some stuff before Triggering") - TriggerClass.triggerTheThing() - logger.trace("Finished triggering the thing") - }) - """ - - testFormatting(for: input, [output], rules: [.redundantClosure, .indent], exclude: [.redundantType]) - } - - func testKeepsClosureThatThrowsError() { - let input = "let foo = try bar ?? { throw NSError() }()" - testFormatting(for: input, rule: .redundantClosure) - } - - func testKeepsDiscardableResultClosure() { - let input = """ - @discardableResult - func discardableResult() -> String { "hello world" } - - /// We can't remove this closure, since the method called inline - /// would return a String instead. - let void: Void = { discardableResult() }() - """ - testFormatting(for: input, rule: .redundantClosure) - } - - func testKeepsDiscardableResultClosure2() { - let input = """ - @discardableResult - func discardableResult() -> String { "hello world" } - - /// We can't remove this closure, since the method called inline - /// would return a String instead. - let void: () = { discardableResult() }() - """ - testFormatting(for: input, rule: .redundantClosure) - } - - func testRedundantClosureDoesntLeaveStrayTry() { - let input = """ - let user2: User? = try { - if let data2 = defaults.data(forKey: defaultsKey) { - return try PropertyListDecoder().decode(User.self, from: data2) - } else { - return nil - } - }() - """ - let output = """ - let user2: User? = if let data2 = defaults.data(forKey: defaultsKey) { - try PropertyListDecoder().decode(User.self, from: data2) - } else { - nil - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment, - .redundantClosure], - options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) - } - - func testRedundantClosureDoesntLeaveStrayTryAwait() { - let input = """ - let user2: User? = try await { - if let data2 = defaults.data(forKey: defaultsKey) { - return try await PropertyListDecoder().decode(User.self, from: data2) - } else { - return nil - } - }() - """ - let output = """ - let user2: User? = if let data2 = defaults.data(forKey: defaultsKey) { - try await PropertyListDecoder().decode(User.self, from: data2) - } else { - nil - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment, - .redundantClosure], - options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) - } - - func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInOperatorChain() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes: Int { - { - switch self { - case .uint8: UInt8.bitWidth - case .uint16: UInt16.bitWidth - } - }() / 8 - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes: Int { - { - if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - }() / 8 - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain2() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes: Int { - 8 / { - if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - }() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testRedundantClosureDoesntLeaveInvalidIfExpressionInOperatorChain3() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes = 8 / { - if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - }() - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testRedundantClosureDoesRemoveRedundantIfStatementClosureInAssignmentPosition() { - let input = """ - private enum Format { - case uint8 - case uint16 - - var bytes = { - if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - }() - } - """ - - let output = """ - private enum Format { - case uint8 - case uint16 - - var bytes = if self == .uint8 { - UInt8.bitWidth - } else { - UInt16.bitWidth - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) - } - - func testRedundantClosureDoesntLeaveInvalidSwitchExpressionInArray() { - let input = """ - private func constraint() -> [Int] { - [ - 1, - 2, - { - if Bool.random() { - 3 - } else { - 4 - } - }(), - ] - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testRedundantClosureRemovesClosureAsReturnTryStatement() { - let input = """ - func method() -> Int { - return { - return try! if Bool.random() { - randomThrows() - } else { - randomThrows() - } - }() - } - """ - - let output = """ - func method() -> Int { - return try! if Bool.random() { - randomThrows() - } else { - randomThrows() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) - } - - func testRedundantClosureRemovesClosureAsReturnTryStatement2() { - let input = """ - func method() throws -> Int { - return try { - return try if Bool.random() { - randomThrows() - } else { - randomThrows() - } - }() - } - """ - - let output = """ - func method() throws -> Int { - return try if Bool.random() { - randomThrows() - } else { - randomThrows() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) - } - - func testRedundantClosureRemovesClosureAsReturnTryStatement3() { - let input = """ - func method() async throws -> Int { - return try await { - return try await if Bool.random() { - randomAsyncThrows() - } else { - randomAsyncThrows() - } - }() - } - """ - - let output = """ - func method() async throws -> Int { - return try await if Bool.random() { - randomAsyncThrows() - } else { - randomAsyncThrows() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) - } - - func testRedundantClosureRemovesClosureAsReturnTryStatement4() { - let input = """ - func method() -> Int { - return { - return try! if Bool.random() { - randomThrows() - } else { - randomThrows() - } - }() - } - """ - - let output = """ - func method() -> Int { - return try! if Bool.random() { - randomThrows() - } else { - randomThrows() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.redundantReturn, .indent]) - } - - func testRedundantClosureRemovesClosureAsReturnStatement() { - let input = """ - func method() -> Int { - return { - return if Bool.random() { - 42 - } else { - 43 - } - }() - } - """ - - let output = """ - func method() -> Int { - return if Bool.random() { - 42 - } else { - 43 - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.redundantClosure], - options: options, exclude: [.redundantReturn, .indent]) - } - - func testRedundantClosureRemovesClosureAsImplicitReturnStatement() { - let input = """ - func method() -> Int { - { - if Bool.random() { - 42 - } else { - 43 - } - }() - } - """ - - let output = """ - func method() -> Int { - if Bool.random() { - 42 - } else { - 43 - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent]) - } - - func testClosureNotRemovedAroundIfExpressionInGuard() { - let input = """ - guard let foo = { - if condition { - bar() - } - }() else { - return - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testClosureNotRemovedInMethodCall() { - let input = """ - XCTAssert({ - if foo { - bar - } else { - baaz - } - }()) - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testClosureNotRemovedInMethodCall2() { - let input = """ - method("foo", { - if foo { - bar - } else { - baaz - } - }()) - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testClosureNotRemovedInMethodCall3() { - let input = """ - XCTAssert({ - if foo { - bar - } else { - baaz - } - }(), "message") - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testClosureNotRemovedInMethodCall4() { - let input = """ - method( - "foo", - { - if foo { - bar - } else { - baaz - } - }(), - "bar" - ) - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testDoesntRemoveClosureWithIfExpressionConditionalCastInSwift5_9() { - // The following code doesn't compile in Swift 5.9 due to this issue: - // https://github.com/apple/swift/issues/68764 - // - // let result = if condition { - // foo as? String - // } else { - // "bar" - // } - // - let input = """ - let result1: String? = { - if condition { - return foo as? String - } else { - return "bar" - } - }() - - let result1: String? = { - switch condition { - case true: - return foo as! String - case false: - return "bar" - } - }() - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .redundantClosure, options: options) - } - - func testDoesRemoveClosureWithIfExpressionConditionalCastInSwift5_10() { - let input = """ - let result1: String? = { - if condition { - foo as? String - } else { - "bar" - } - }() - - let result2: String? = { - switch condition { - case true: - foo as? String - case false: - "bar" - } - }() - """ - - let output = """ - let result1: String? = if condition { - foo as? String - } else { - "bar" - } - - let result2: String? = switch condition { - case true: - foo as? String - case false: - "bar" - } - """ - - let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, output, rule: .redundantClosure, options: options, exclude: [.indent, .wrapMultilineConditionalAssignment]) - } - - func testRedundantClosureDoesntBreakBuildWithRedundantReturnRuleDisabled() { - let input = """ - enum MyEnum { - case a - case b - } - let myEnum = MyEnum.a - let test: Int = { - return 0 - }() - """ - - let output = """ - enum MyEnum { - case a - case b - } - let myEnum = MyEnum.a - let test: Int = 0 - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .redundantClosure, options: options, - exclude: [.redundantReturn, .blankLinesBetweenScopes, .propertyType]) - } - - func testRedundantClosureWithSwitchExpressionDoesntBreakBuildWithRedundantReturnRuleDisabled() { - // From https://github.com/nicklockwood/SwiftFormat/issues/1565 - let input = """ - enum MyEnum { - case a - case b - } - let myEnum = MyEnum.a - let test: Int = { - switch myEnum { - case .a: - return 0 - case .b: - return 1 - } - }() - """ - - let output = """ - enum MyEnum { - case a - case b - } - let myEnum = MyEnum.a - let test: Int = switch myEnum { - case .a: - 0 - case .b: - 1 - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment, - .redundantClosure], - options: options, - exclude: [.indent, .blankLinesBetweenScopes, .wrapMultilineConditionalAssignment, - .propertyType]) - } - - func testRemovesRedundantClosureWithGenericExistentialTypes() { - let input = """ - let foo: Foo = { DefaultFoo() }() - let foo: any Foo = { DefaultFoo() }() - let foo: any Foo = { DefaultFoo() }() - """ - - let output = """ - let foo: Foo = DefaultFoo() - let foo: any Foo = DefaultFoo() - let foo: any Foo = DefaultFoo() - """ - - testFormatting(for: input, output, rule: .redundantClosure) - } - - func testRedundantSwitchStatementReturnInFunctionWithMultipleWhereClauses() { - // https://github.com/nicklockwood/SwiftFormat/issues/1554 - let input = """ - func foo(cases: FooCases, count: Int) -> String? { - switch cases { - case .fooCase1 where count == 0: - return "foo" - case .fooCase2 where count < 100, - .fooCase3 where count < 100, - .fooCase4: - return "bar" - default: - return nil - } - } - """ - let output = """ - func foo(cases: FooCases, count: Int) -> String? { - switch cases { - case .fooCase1 where count == 0: - "foo" - case .fooCase2 where count < 100, - .fooCase3 where count < 100, - .fooCase4: - "bar" - default: - nil - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - func testRedundantSwitchStatementReturnInFunctionWithSingleWhereClause() { - // https://github.com/nicklockwood/SwiftFormat/issues/1554 - let input = """ - func anotherFoo(cases: FooCases, count: Int) -> String? { - switch cases { - case .fooCase1 where count == 0: - return "foo" - case .fooCase2 where count < 100, - .fooCase4: - return "bar" - default: - return nil - } - } - """ - let output = """ - func anotherFoo(cases: FooCases, count: Int) -> String? { - switch cases { - case .fooCase1 where count == 0: - "foo" - case .fooCase2 where count < 100, - .fooCase4: - "bar" - default: - nil - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], - rules: [.redundantReturn, .conditionalAssignment], - options: options) - } - - // MARK: - redundantOptionalBinding - - func testRemovesRedundantOptionalBindingsInSwift5_7() { - let input = """ - if let foo = foo { - print(foo) - } - - else if var bar = bar { - print(bar) - } - - guard let self = self else { - return - } - - while var quux = quux { - break - } - """ - - let output = """ - if let foo { - print(foo) - } - - else if var bar { - print(bar) - } - - guard let self else { - return - } - - while var quux { - break - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options, exclude: [.elseOnSameLine]) - } - - func testRemovesMultipleOptionalBindings() { - let input = """ - if let foo = foo, let bar = bar, let baaz = baaz { - print(foo, bar, baaz) - } - - guard let foo = foo, let bar = bar, let baaz = baaz else { - return - } - """ - - let output = """ - if let foo, let bar, let baaz { - print(foo, bar, baaz) - } - - guard let foo, let bar, let baaz else { - return - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options) - } - - func testRemovesMultipleOptionalBindingsOnSeparateLines() { - let input = """ - if - let foo = foo, - let bar = bar, - let baaz = baaz - { - print(foo, bar, baaz) - } - - guard - let foo = foo, - let bar = bar, - let baaz = baaz - else { - return - } - """ - - let output = """ - if - let foo, - let bar, - let baaz - { - print(foo, bar, baaz) - } - - guard - let foo, - let bar, - let baaz - else { - return - } - """ - - let options = FormatOptions(indent: " ", swiftVersion: "5.7") - testFormatting(for: input, output, rule: .redundantOptionalBinding, options: options) - } - - func testKeepsRedundantOptionalBeforeSwift5_7() { - let input = """ - if let foo = foo { - print(foo) - } - """ - - let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: .redundantOptionalBinding, options: options) - } - - func testKeepsNonRedundantOptional() { - let input = """ - if let foo = bar { - print(foo) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .redundantOptionalBinding, options: options) - } - - func testKeepsOptionalNotEligibleForShorthand() { - let input = """ - if let foo = self.foo, let bar = bar(), let baaz = baaz[0] { - print(foo, bar, baaz) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .redundantOptionalBinding, options: options, exclude: [.redundantSelf]) - } - - func testRedundantSelfAndRedundantOptionalTogether() { - let input = """ - if let foo = self.foo { - print(foo) - } - """ - - let output = """ - if let foo { - print(foo) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, [output], rules: [.redundantOptionalBinding, .redundantSelf], options: options) - } - - func testDoesntRemoveShadowingOutsideOfOptionalBinding() { - let input = """ - let foo = foo - - if let bar = baaz({ - let foo = foo - print(foo) - }) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .redundantOptionalBinding, options: options) - } - - // MARK: - redundantInternal - - func testRemoveRedundantInternalACL() { - let input = """ - internal class Foo { - internal let bar: String - - internal func baaz() {} - - internal init() { - bar = "bar" - } - } - """ - - let output = """ - class Foo { - let bar: String - - func baaz() {} - - init() { - bar = "bar" - } - } - """ - - testFormatting(for: input, output, rule: .redundantInternal) - } - - func testPreserveInternalInNonInternalExtensionExtension() { - let input = """ - extension Foo { - /// internal is redundant here since the extension is internal - internal func bar() {} - - public func baaz() {} - - /// internal is redundant here since the extension is internal - internal func bar() {} - } - - public extension Foo { - /// internal is not redundant here since the extension is public - internal func bar() {} - - public func baaz() {} - - /// internal is not redundant here since the extension is public - internal func bar() {} - } - """ - - let output = """ - extension Foo { - /// internal is redundant here since the extension is internal - func bar() {} - - public func baaz() {} - - /// internal is redundant here since the extension is internal - func bar() {} - } - - public extension Foo { - /// internal is not redundant here since the extension is public - internal func bar() {} - - public func baaz() {} - - /// internal is not redundant here since the extension is public - internal func bar() {} - } - """ - - testFormatting(for: input, output, rule: .redundantInternal, exclude: [.redundantExtensionACL]) - } - - func testPreserveInternalImport() { - let input = "internal import MyPackage" - testFormatting(for: input, rule: .redundantInternal) - } - - func testPreservesInternalInPublicExtensionWithWhereClause() { - let input = """ - public extension SomeProtocol where SomeAssociatedType == SomeOtherType { - internal func fun1() {} - func fun2() {} - } - - public extension OtherProtocol { - internal func fun1() {} - func fun2() {} - } - """ - testFormatting(for: input, rule: .redundantInternal) - } - - // MARK: - noExplicitOwnership - - func testRemovesOwnershipKeywordsFromFunc() { - let input = """ - consuming func myMethod(consuming foo: consuming Foo, borrowing bars: borrowing [Bar]) {} - borrowing func myMethod(consuming foo: consuming Foo, borrowing bars: borrowing [Bar]) {} - """ - - let output = """ - func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} - func myMethod(consuming foo: Foo, borrowing bars: [Bar]) {} - """ - - testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: [.unusedArguments]) - } - - func testRemovesOwnershipKeywordsFromClosure() { - let input = """ - foos.map { (foo: consuming Foo) in - foo.bar - } - - foos.map { (foo: borrowing Foo) in - foo.bar - } - """ - - let output = """ - foos.map { (foo: Foo) in - foo.bar - } - - foos.map { (foo: Foo) in - foo.bar - } - """ - - testFormatting(for: input, output, rule: .noExplicitOwnership, exclude: [.unusedArguments]) - } - - func testRemovesOwnershipKeywordsFromType() { - let input = """ - let consuming: (consuming Foo) -> Bar - let borrowing: (borrowing Foo) -> Bar - """ - - let output = """ - let consuming: (Foo) -> Bar - let borrowing: (Foo) -> Bar - """ - - testFormatting(for: input, output, rule: .noExplicitOwnership) - } - - // MARK: - redundantProperty - - func testRemovesRedundantProperty() { - let input = """ - func foo() -> Foo { - let foo = Foo(bar: bar, baaz: baaz) - return foo - } - """ - - let output = """ - func foo() -> Foo { - return Foo(bar: bar, baaz: baaz) - } - """ - - testFormatting(for: input, output, rule: .redundantProperty, exclude: [.redundantReturn]) - } - - func testRemovesRedundantPropertyWithIfExpression() { - let input = """ - func foo() -> Foo { - let foo = - if condition { - Foo.foo() - } else { - Foo.bar() - } - - return foo - } - """ - - let output = """ - func foo() -> Foo { - if condition { - Foo.foo() - } else { - Foo.bar() - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.redundantProperty, .redundantReturn, .indent], options: options) - } - - func testRemovesRedundantPropertyWithSwitchExpression() { - let input = """ - func foo() -> Foo { - let foo: Foo - switch condition { - case true: - foo = Foo(bar) - case false: - foo = Foo(baaz) - } - - return foo - } - """ - - let output = """ - func foo() -> Foo { - switch condition { - case true: - Foo(bar) - case false: - Foo(baaz) - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.conditionalAssignment, .redundantProperty, .redundantReturn, .indent], options: options) - } - - func testRemovesRedundantPropertyWithPreferInferredType() { - let input = """ - func bar() -> Bar { - let bar: Bar = .init(baaz: baaz, quux: quux) - return bar - } - """ - - let output = """ - func bar() -> Bar { - return Bar(baaz: baaz, quux: quux) - } - """ - - testFormatting(for: input, [output], rules: [.propertyType, .redundantProperty, .redundantInit], exclude: [.redundantReturn]) - } - - func testRemovesRedundantPropertyWithComments() { - let input = """ - func foo() -> Foo { - // There's a comment before this property - let foo = Foo(bar: bar, baaz: baaz) - // And there's a comment after the property - return foo - } - """ - - let output = """ - func foo() -> Foo { - // There's a comment before this property - return Foo(bar: bar, baaz: baaz) - // And there's a comment after the property - } - """ - - testFormatting(for: input, output, rule: .redundantProperty, exclude: [.redundantReturn]) - } - - func testRemovesRedundantPropertyFollowingOtherProperty() { - let input = """ - func foo() -> Foo { - let bar = Bar(baaz: baaz) - let foo = Foo(bar: bar) - return foo - } - """ - - let output = """ - func foo() -> Foo { - let bar = Bar(baaz: baaz) - return Foo(bar: bar) - } - """ - - testFormatting(for: input, output, rule: .redundantProperty) - } - - func testPreservesPropertyWhereReturnIsNotRedundant() { - let input = """ - func foo() -> Foo { - let foo = Foo(bar: bar, baaz: baaz) - return foo.with(quux: quux) - } - - func bar() -> Foo { - let bar = Bar(baaz: baaz) - return bar.baaz - } - - func baaz() -> Foo { - let bar = Bar(baaz: baaz) - print(bar) - return bar - } - """ - - testFormatting(for: input, rule: .redundantProperty) - } - - func testPreservesUnwrapConditionInIfStatement() { - let input = """ - func foo() -> Foo { - let foo = Foo(bar: bar, baaz: baaz) - - if let foo = foo.nestedFoo { - print(foo) - } - - return foo - } - """ - - testFormatting(for: input, rule: .redundantProperty) - } - - // MARK: - redundantTypedThrows - - func testRemovesRedundantNeverTypeThrows() { - let input = """ - func foo() throws(Never) -> Int { - 0 - } - """ - - let output = """ - func foo() -> Int { - 0 - } - """ - - let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, output, rule: .redundantTypedThrows, options: options) - } - - func testRemovesRedundantAnyErrorTypeThrows() { - let input = """ - func foo() throws(any Error) -> Int { - throw MyError.foo - } - """ - - let output = """ - func foo() throws -> Int { - throw MyError.foo - } - """ - - let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, output, rule: .redundantTypedThrows, options: options) - } - - func testDontRemovesNonRedundantErrorTypeThrows() { - let input = """ - func bar() throws(BarError) -> Foo { - throw .foo - } - - func foo() throws(Error) -> Int { - throw MyError.foo - } - """ - - let options = FormatOptions(swiftVersion: "6.0") - testFormatting(for: input, rule: .redundantTypedThrows, options: options) - } - - // MARK: - unusedPrivateDeclaration - - func testRemoveUnusedPrivate() { - let input = """ - struct Foo { - private var foo = "foo" - var bar = "bar" - } - """ - let output = """ - struct Foo { - var bar = "bar" - } - """ - testFormatting(for: input, output, rule: .unusedPrivateDeclaration) - } - - func testRemoveUnusedFilePrivate() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - } - """ - let output = """ - struct Foo { - var bar = "bar" - } - """ - testFormatting(for: input, output, rule: .unusedPrivateDeclaration) - } - - func testDoNotRemoveUsedFilePrivate() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - } - - struct Hello { - let localFoo = Foo().foo - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testRemoveMultipleUnusedFilePrivate() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - fileprivate var baz = "baz" - var bar = "bar" - } - """ - let output = """ - struct Foo { - var bar = "bar" - } - """ - testFormatting(for: input, output, rule: .unusedPrivateDeclaration) - } - - func testRemoveMixedUsedAndUnusedFilePrivate() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - fileprivate var baz = "baz" - } - - struct Hello { - let localFoo = Foo().foo - } - """ - let output = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - } - - struct Hello { - let localFoo = Foo().foo - } - """ - testFormatting(for: input, output, rule: .unusedPrivateDeclaration) - } - - func testDoNotRemoveFilePrivateUsedInSameStruct() { - let input = """ - struct Foo { - fileprivate var foo = "foo" - var bar = "bar" - - func useFoo() { - print(foo) - } - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testRemoveUnusedFilePrivateInNestedStruct() { - let input = """ - struct Foo { - var bar = "bar" - - struct Inner { - fileprivate var foo = "foo" - } - } - """ - let output = """ - struct Foo { - var bar = "bar" - - struct Inner { - } - } - """ - testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: [.emptyBraces]) - } - - func testDoNotRemoveFilePrivateUsedInNestedStruct() { - let input = """ - struct Foo { - var bar = "bar" - - struct Inner { - fileprivate var foo = "foo" - func useFoo() { - print(foo) - } - } - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testRemoveUnusedFileprivateFunction() { - let input = """ - struct Foo { - var bar = "bar" - - fileprivate func sayHi() { - print("hi") - } - } - """ - let output = """ - struct Foo { - var bar = "bar" - } - """ - testFormatting(for: input, [output], rules: [.unusedPrivateDeclaration, .blankLinesAtEndOfScope]) - } - - func testDoNotRemoveUnusedFileprivateOperatorDefinition() { - let input = """ - private class Foo: Equatable { - fileprivate static func == (_: Foo, _: Foo) -> Bool { - return true - } - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testRemovePrivateDeclarationButDoNotRemoveUnusedPrivateType() { - let input = """ - private struct Foo { - private func bar() { - print("test") - } - } - """ - let output = """ - private struct Foo { - } - """ - - testFormatting(for: input, output, rule: .unusedPrivateDeclaration, exclude: [.emptyBraces]) - } - - func testRemovePrivateDeclarationButDoNotRemovePrivateExtension() { - let input = """ - private extension Foo { - private func doSomething() {} - func anotherFunction() {} - } - """ - let output = """ - private extension Foo { - func anotherFunction() {} - } - """ - - testFormatting(for: input, output, rule: .unusedPrivateDeclaration) - } - - func testRemovesPrivateTypealias() { - let input = """ - enum Foo { - struct Bar {} - private typealias Baz = Bar - } - """ - let output = """ - enum Foo { - struct Bar {} - } - """ - testFormatting(for: input, output, rule: .unusedPrivateDeclaration) - } - - func testDoesntRemoveFileprivateInit() { - let input = """ - struct Foo { - fileprivate init() {} - static let foo = Foo() - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: [.propertyType]) - } - - func testCanDisableUnusedPrivateDeclarationRule() { - let input = """ - private enum Foo { - // swiftformat:disable:next unusedPrivateDeclaration - fileprivate static func bar() {} - } - """ - - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testDoesNotRemovePropertyWrapperPrefixesIfUsed() { - let input = """ - struct ContentView: View { - public init() { - _showButton = .init(initialValue: false) - } - - @State private var showButton: Bool - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testDoesNotRemoveUnderscoredDeclarationIfUsed() { - let input = """ - struct Foo { - private var _showButton: Bool = true - print(_showButton) - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testDoesNotRemoveBacktickDeclarationIfUsed() { - let input = """ - struct Foo { - fileprivate static var `default`: Bool = true - func printDefault() { - print(Foo.default) - } - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testDoesNotRemoveBacktickUsage() { - let input = """ - struct Foo { - fileprivate static var foo = true - func printDefault() { - print(Foo.`foo`) - } - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration, exclude: [.redundantBackticks]) - } - - func testDoNotRemovePreservedPrivateDeclarations() { - let input = """ - enum Foo { - private static let registryAssociation = false - } - """ - let options = FormatOptions(preservedPrivateDeclarations: ["registryAssociation", "hello"]) - testFormatting(for: input, rule: .unusedPrivateDeclaration, options: options) - } - - func testDoNotRemoveOverridePrivateMethodDeclarations() { - let input = """ - class Poodle: Dog { - override private func makeNoise() { - print("Yip!") - } - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testDoNotRemoveOverridePrivatePropertyDeclarations() { - let input = """ - class Poodle: Dog { - override private var age: Int { - 7 - } - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testDoNotRemoveObjcPrivatePropertyDeclaration() { - let input = """ - struct Foo { - @objc - private var bar = "bar" - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testDoNotRemoveObjcPrivateFunctionDeclaration() { - let input = """ - struct Foo { - @objc - private func doSomething() {} - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } - - func testDoNotRemoveIBActionPrivateFunctionDeclaration() { - let input = """ - class FooViewController: UIViewController { - @IBAction private func buttonPressed(_: UIButton) { - print("Button pressed!") - } - } - """ - testFormatting(for: input, rule: .unusedPrivateDeclaration) - } -} diff --git a/Tests/RulesTests+Spacing.swift b/Tests/RulesTests+Spacing.swift deleted file mode 100644 index 8fafc9ca2..000000000 --- a/Tests/RulesTests+Spacing.swift +++ /dev/null @@ -1,2171 +0,0 @@ -// -// RulesTests+Spacing.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class SpacingTests: RulesTests { - // MARK: - spaceAroundParens - - func testSpaceAfterSet() { - let input = "private(set)var foo: Int" - let output = "private(set) var foo: Int" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenParenAndClass() { - let input = "@objc(XYZFoo)class foo" - let output = "@objc(XYZFoo) class foo" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenConventionAndBlock() { - let input = "@convention(block)() -> Void" - let output = "@convention(block) () -> Void" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenConventionAndEscaping() { - let input = "@convention(block)@escaping () -> Void" - let output = "@convention(block) @escaping () -> Void" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenAutoclosureEscapingAndBlock() { // Swift 2.3 only - let input = "@autoclosure(escaping)() -> Void" - let output = "@autoclosure(escaping) () -> Void" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenSendableAndBlock() { - let input = "@Sendable (Action) -> Void" - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenMainActorAndBlock() { - let input = "@MainActor (Action) -> Void" - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenMainActorAndBlock2() { - let input = "@MainActor (@MainActor (Action) -> Void) async -> Void" - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenMainActorAndClosureParams() { - let input = "{ @MainActor (foo: Int) in foo }" - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testSpaceBetweenUncheckedAndSendable() { - let input = """ - enum Foo: @unchecked Sendable { - case bar - } - """ - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testSpaceBetweenParenAndAs() { - let input = "(foo.bar) as? String" - testFormatting(for: input, rule: .spaceAroundParens, exclude: [.redundantParens]) - } - - func testNoSpaceAfterParenAtEndOfFile() { - let input = "(foo.bar)" - testFormatting(for: input, rule: .spaceAroundParens, exclude: [.redundantParens]) - } - - func testSpaceBetweenParenAndFoo() { - let input = "func foo ()" - let output = "func foo()" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testSpaceBetweenParenAndAny() { - let input = "func any ()" - let output = "func any()" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testSpaceBetweenParenAndAnyType() { - let input = "let foo: any(A & B).Type" - let output = "let foo: any (A & B).Type" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testSpaceBetweenParenAndSomeType() { - let input = "func foo() -> some(A & B).Type" - let output = "func foo() -> some (A & B).Type" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testNoSpaceBetweenParenAndInit() { - let input = "init ()" - let output = "init()" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testNoSpaceBetweenObjcAndSelector() { - let input = "@objc (XYZFoo) class foo" - let output = "@objc(XYZFoo) class foo" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testNoSpaceBetweenHashSelectorAndBrace() { - let input = "#selector(foo)" - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testNoSpaceBetweenHashKeyPathAndBrace() { - let input = "#keyPath (foo.bar)" - let output = "#keyPath(foo.bar)" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testNoSpaceBetweenHashAvailableAndBrace() { - let input = "#available (iOS 9.0, *)" - let output = "#available(iOS 9.0, *)" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testNoSpaceBetweenPrivateAndSet() { - let input = "private (set) var foo: Int" - let output = "private(set) var foo: Int" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testSpaceBetweenLetAndTuple() { - let input = "if let (foo, bar) = baz {}" - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testSpaceBetweenIfAndCondition() { - let input = "if(a || b) == true {}" - let output = "if (a || b) == true {}" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testNoSpaceBetweenArrayLiteralAndParen() { - let input = "[String] ()" - let output = "[String]()" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenCaptureListAndArguments() { - let input = "{ [weak self](foo) in print(foo) }" - let output = "{ [weak self] (foo) in print(foo) }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantParens]) - } - - func testAddSpaceBetweenCaptureListAndArguments2() { - let input = "{ [weak self]() -> Void in }" - let output = "{ [weak self] () -> Void in }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantVoidReturnType]) - } - - func testAddSpaceBetweenCaptureListAndArguments3() { - let input = "{ [weak self]() throws -> Void in }" - let output = "{ [weak self] () throws -> Void in }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantVoidReturnType]) - } - - func testAddSpaceBetweenCaptureListAndArguments4() { - let input = "{ [weak self](foo: @escaping(Bar?) -> Void) -> Baz? in foo }" - let output = "{ [weak self] (foo: @escaping (Bar?) -> Void) -> Baz? in foo }" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenCaptureListAndArguments5() { - let input = "{ [weak self](foo: @autoclosure() -> String) -> Baz? in foo() }" - let output = "{ [weak self] (foo: @autoclosure () -> String) -> Baz? in foo() }" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenCaptureListAndArguments6() { - let input = "{ [weak self](foo: @Sendable() -> String) -> Baz? in foo() }" - let output = "{ [weak self] (foo: @Sendable () -> String) -> Baz? in foo() }" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenCaptureListAndArguments7() { - let input = "Foo(0) { [weak self]() -> Void in }" - let output = "Foo(0) { [weak self] () -> Void in }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantVoidReturnType]) - } - - func testAddSpaceBetweenCaptureListAndArguments8() { - let input = "{ [weak self]() throws(Foo) -> Void in }" - let output = "{ [weak self] () throws(Foo) -> Void in }" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantVoidReturnType]) - } - - func testAddSpaceBetweenEscapingAndParenthesizedClosure() { - let input = "@escaping(() -> Void)" - let output = "@escaping (() -> Void)" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenAutoclosureAndParenthesizedClosure() { - let input = "@autoclosure(() -> String)" - let output = "@autoclosure (() -> String)" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testSpaceBetweenClosingParenAndOpenBrace() { - let input = "func foo(){ foo }" - let output = "func foo() { foo }" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testNoSpaceBetweenClosingBraceAndParens() { - let input = "{ block } ()" - let output = "{ block }()" - testFormatting(for: input, output, rule: .spaceAroundParens, exclude: [.redundantClosure]) - } - - func testDontRemoveSpaceBetweenOpeningBraceAndParens() { - let input = "a = (b + c)" - testFormatting(for: input, rule: .spaceAroundParens, - exclude: [.redundantParens]) - } - - func testKeywordAsIdentifierParensSpacing() { - let input = "if foo.let (foo, bar) {}" - let output = "if foo.let(foo, bar) {}" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testSpaceAfterInoutParam() { - let input = "func foo(bar _: inout(Int, String)) {}" - let output = "func foo(bar _: inout (Int, String)) {}" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testSpaceAfterEscapingAttribute() { - let input = "func foo(bar: @escaping() -> Void)" - let output = "func foo(bar: @escaping () -> Void)" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testSpaceAfterAutoclosureAttribute() { - let input = "func foo(bar: @autoclosure () -> Void)" - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testSpaceAfterSendableAttribute() { - let input = "func foo(bar: @Sendable () -> Void)" - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testSpaceBeforeTupleIndexArgument() { - let input = "foo.1 (true)" - let output = "foo.1(true)" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testRemoveSpaceBetweenParenAndBracket() { - let input = "let foo = bar[5] ()" - let output = "let foo = bar[5]()" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testRemoveSpaceBetweenParenAndBracketInsideClosure() { - let input = "let foo = bar { [Int] () }" - let output = "let foo = bar { [Int]() }" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenParenAndCaptureList() { - let input = "let foo = bar { [self](foo: Int) in foo }" - let output = "let foo = bar { [self] (foo: Int) in foo }" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenParenAndAwait() { - let input = "let foo = await(bar: 5)" - let output = "let foo = await (bar: 5)" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenParenAndAwaitForSwift5_5() { - let input = "let foo = await(bar: 5)" - let output = "let foo = await (bar: 5)" - testFormatting(for: input, output, rule: .spaceAroundParens, - options: FormatOptions(swiftVersion: "5.5")) - } - - func testNoAddSpaceBetweenParenAndAwaitForSwiftLessThan5_5() { - let input = "let foo = await(bar: 5)" - testFormatting(for: input, rule: .spaceAroundParens, - options: FormatOptions(swiftVersion: "5.4.9")) - } - - func testRemoveSpaceBetweenParenAndConsume() { - let input = "let foo = consume (bar)" - let output = "let foo = consume(bar)" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testNoAddSpaceBetweenParenAndAvailableAfterFunc() { - let input = """ - func foo() - - @available(macOS 10.13, *) - func bar() - """ - testFormatting(for: input, rule: .spaceAroundParens) - } - - func testNoAddSpaceAroundTypedThrowsFunctionType() { - let input = "func foo() throws (Bar) -> Baz {}" - let output = "func foo() throws(Bar) -> Baz {}" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenParenAndBorrowing() { - let input = "func foo(_: borrowing(any Foo)) {}" - let output = "func foo(_: borrowing (any Foo)) {}" - testFormatting(for: input, output, rule: .spaceAroundParens, - exclude: [.noExplicitOwnership]) - } - - func testAddSpaceBetweenParenAndIsolated() { - let input = "func foo(isolation _: isolated(any Actor)) {}" - let output = "func foo(isolation _: isolated (any Actor)) {}" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - func testAddSpaceBetweenParenAndSending() { - let input = "func foo(_: sending(any Foo)) {}" - let output = "func foo(_: sending (any Foo)) {}" - testFormatting(for: input, output, rule: .spaceAroundParens) - } - - // MARK: - spaceInsideParens - - func testSpaceInsideParens() { - let input = "( 1, ( 2, 3 ) )" - let output = "(1, (2, 3))" - testFormatting(for: input, output, rule: .spaceInsideParens) - } - - func testSpaceBeforeCommentInsideParens() { - let input = "( /* foo */ 1, 2 )" - let output = "( /* foo */ 1, 2)" - testFormatting(for: input, output, rule: .spaceInsideParens) - } - - // MARK: - spaceAroundBrackets - - func testSubscriptNoAddSpacing() { - let input = "foo[bar] = baz" - testFormatting(for: input, rule: .spaceAroundBrackets) - } - - func testSubscriptRemoveSpacing() { - let input = "foo [bar] = baz" - let output = "foo[bar] = baz" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - func testArrayLiteralSpacing() { - let input = "foo = [bar, baz]" - testFormatting(for: input, rule: .spaceAroundBrackets) - } - - func testAsArrayCastingSpacing() { - let input = "foo as[String]" - let output = "foo as [String]" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - func testAsOptionalArrayCastingSpacing() { - let input = "foo as? [String]" - testFormatting(for: input, rule: .spaceAroundBrackets) - } - - func testIsArrayTestingSpacing() { - let input = "if foo is[String] {}" - let output = "if foo is [String] {}" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - func testKeywordAsIdentifierBracketSpacing() { - let input = "if foo.is[String] {}" - testFormatting(for: input, rule: .spaceAroundBrackets) - } - - func testSpaceBeforeTupleIndexSubscript() { - let input = "foo.1 [2]" - let output = "foo.1[2]" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - func testRemoveSpaceBetweenBracketAndParen() { - let input = "let foo = bar[5] ()" - let output = "let foo = bar[5]()" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - func testRemoveSpaceBetweenBracketAndParenInsideClosure() { - let input = "let foo = bar { [Int] () }" - let output = "let foo = bar { [Int]() }" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - func testAddSpaceBetweenCaptureListAndParen() { - let input = "let foo = bar { [self](foo: Int) in foo }" - let output = "let foo = bar { [self] (foo: Int) in foo }" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - func testAddSpaceBetweenInoutAndStringArray() { - let input = "func foo(arg _: inout[String]) {}" - let output = "func foo(arg _: inout [String]) {}" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - func testAddSpaceBetweenConsumingAndStringArray() { - let input = "func foo(arg _: consuming[String]) {}" - let output = "func foo(arg _: consuming [String]) {}" - testFormatting(for: input, output, rule: .spaceAroundBrackets, - exclude: [.noExplicitOwnership]) - } - - func testAddSpaceBetweenBorrowingAndStringArray() { - let input = "func foo(arg _: borrowing[String]) {}" - let output = "func foo(arg _: borrowing [String]) {}" - testFormatting(for: input, output, rule: .spaceAroundBrackets, - exclude: [.noExplicitOwnership]) - } - - func testAddSpaceBetweenSendingAndStringArray() { - let input = "func foo(arg _: sending[String]) {}" - let output = "func foo(arg _: sending [String]) {}" - testFormatting(for: input, output, rule: .spaceAroundBrackets) - } - - // MARK: - spaceInsideBrackets - - func testSpaceInsideBrackets() { - let input = "foo[ 5 ]" - let output = "foo[5]" - testFormatting(for: input, output, rule: .spaceInsideBrackets) - } - - func testSpaceInsideWrappedArray() { - let input = "[ foo,\n bar ]" - let output = "[foo,\n bar]" - let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, output, rule: .spaceInsideBrackets, options: options) - } - - func testSpaceBeforeCommentInsideWrappedArray() { - let input = "[ // foo\n bar,\n]" - let options = FormatOptions(wrapCollections: .disabled) - testFormatting(for: input, rule: .spaceInsideBrackets, options: options) - } - - // MARK: - spaceAroundBraces - - func testSpaceAroundTrailingClosure() { - let input = "if x{ y }else{ z }" - let output = "if x { y } else { z }" - testFormatting(for: input, output, rule: .spaceAroundBraces, - exclude: [.wrapConditionalBodies]) - } - - func testNoSpaceAroundClosureInsiderParens() { - let input = "foo({ $0 == 5 })" - testFormatting(for: input, rule: .spaceAroundBraces, - exclude: [.trailingClosures]) - } - - func testNoExtraSpaceAroundBracesAtStartOrEndOfFile() { - let input = "{ foo }" - testFormatting(for: input, rule: .spaceAroundBraces) - } - - func testNoSpaceAfterPrefixOperator() { - let input = "let foo = ..{ bar }" - testFormatting(for: input, rule: .spaceAroundBraces) - } - - func testNoSpaceBeforePostfixOperator() { - let input = "let foo = { bar }.." - testFormatting(for: input, rule: .spaceAroundBraces) - } - - func testSpaceAroundBracesAfterOptionalProperty() { - let input = "var: Foo?{}" - let output = "var: Foo? {}" - testFormatting(for: input, output, rule: .spaceAroundBraces) - } - - func testSpaceAroundBracesAfterImplicitlyUnwrappedProperty() { - let input = "var: Foo!{}" - let output = "var: Foo! {}" - testFormatting(for: input, output, rule: .spaceAroundBraces) - } - - func testSpaceAroundBracesAfterNumber() { - let input = "if x = 5{}" - let output = "if x = 5 {}" - testFormatting(for: input, output, rule: .spaceAroundBraces) - } - - func testSpaceAroundBracesAfterString() { - let input = "if x = \"\"{}" - let output = "if x = \"\" {}" - testFormatting(for: input, output, rule: .spaceAroundBraces) - } - - // MARK: - spaceInsideBraces - - func testSpaceInsideBraces() { - let input = "foo({bar})" - let output = "foo({ bar })" - testFormatting(for: input, output, rule: .spaceInsideBraces, exclude: [.trailingClosures]) - } - - func testNoExtraSpaceInsidebraces() { - let input = "{ foo }" - testFormatting(for: input, rule: .spaceInsideBraces, exclude: [.trailingClosures]) - } - - func testNoSpaceAddedInsideEmptybraces() { - let input = "foo({})" - testFormatting(for: input, rule: .spaceInsideBraces, exclude: [.trailingClosures]) - } - - func testNoSpaceAddedBetweenDoublebraces() { - let input = "func foo() -> () -> Void {{ bar() }}" - testFormatting(for: input, rule: .spaceInsideBraces) - } - - // MARK: - spaceAroundGenerics - - func testSpaceAroundGenerics() { - let input = "Foo >" - let output = "Foo>" - testFormatting(for: input, output, rule: .spaceAroundGenerics) - } - - func testSpaceAroundGenericsFollowedByAndOperator() { - let input = "if foo is Foo && baz {}" - testFormatting(for: input, rule: .spaceAroundGenerics, exclude: [.andOperator]) - } - - func testSpaceAroundGenericResultBuilder() { - let input = "func foo(@SomeResultBuilder builder: () -> Void) {}" - testFormatting(for: input, rule: .spaceAroundGenerics) - } - - // MARK: - spaceInsideGenerics - - func testSpaceInsideGenerics() { - let input = "Foo< Bar< Baz > >" - let output = "Foo>" - testFormatting(for: input, output, rule: .spaceInsideGenerics) - } - - // MARK: - spaceAroundOperators - - func testSpaceAfterColon() { - let input = "let foo:Bar = 5" - let output = "let foo: Bar = 5" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpaceBetweenOptionalAndDefaultValue() { - let input = "let foo: String?=nil" - let output = "let foo: String? = nil" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpaceBetweenImplictlyUnwrappedOptionalAndDefaultValue() { - let input = "let foo: String!=nil" - let output = "let foo: String! = nil" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpacePreservedBetweenOptionalTryAndDot() { - let input = "let foo: Int = try? .init()" - testFormatting(for: input, rule: .spaceAroundOperators) - } - - func testSpacePreservedBetweenForceTryAndDot() { - let input = "let foo: Int = try! .init()" - testFormatting(for: input, rule: .spaceAroundOperators) - } - - func testSpaceBetweenOptionalAndDefaultValueInFunction() { - let input = "func foo(bar _: String?=nil) {}" - let output = "func foo(bar _: String? = nil) {}" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testNoSpaceAddedAfterColonInSelector() { - let input = "@objc(foo:bar:)" - testFormatting(for: input, rule: .spaceAroundOperators) - } - - func testSpaceAfterColonInSwitchCase() { - let input = "switch x { case .y:break }" - let output = "switch x { case .y: break }" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpaceAfterColonInSwitchDefault() { - let input = "switch x { default:break }" - let output = "switch x { default: break }" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpaceAfterComma() { - let input = "let foo = [1,2,3]" - let output = "let foo = [1, 2, 3]" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpaceBetweenColonAndEnumValue() { - let input = "[.Foo:.Bar]" - let output = "[.Foo: .Bar]" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpaceBetweenCommaAndEnumValue() { - let input = "[.Foo,.Bar]" - let output = "[.Foo, .Bar]" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testNoRemoveSpaceAroundEnumInBrackets() { - let input = "[ .red ]" - testFormatting(for: input, rule: .spaceAroundOperators, - exclude: [.spaceInsideBrackets]) - } - - func testSpaceBetweenSemicolonAndEnumValue() { - let input = "statement;.Bar" - let output = "statement; .Bar" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpacePreservedBetweenEqualsAndEnumValue() { - let input = "foo = .Bar" - testFormatting(for: input, rule: .spaceAroundOperators) - } - - func testNoSpaceBeforeColon() { - let input = "let foo : Bar = 5" - let output = "let foo: Bar = 5" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpacePreservedBeforeColonInTernary() { - let input = "foo ? bar : baz" - testFormatting(for: input, rule: .spaceAroundOperators) - } - - func testSpacePreservedAroundEnumValuesInTernary() { - let input = "foo ? .Bar : .Baz" - testFormatting(for: input, rule: .spaceAroundOperators) - } - - func testSpaceBeforeColonInNestedTernary() { - let input = "foo ? (hello + a ? b: c) : baz" - let output = "foo ? (hello + a ? b : c) : baz" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testNoSpaceBeforeComma() { - let input = "let foo = [1 , 2 , 3]" - let output = "let foo = [1, 2, 3]" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testSpaceAtStartOfLine() { - let input = "print(foo\n ,bar)" - let output = "print(foo\n , bar)" - testFormatting(for: input, output, rule: .spaceAroundOperators, - exclude: [.leadingDelimiters]) - } - - func testSpaceAroundInfixMinus() { - let input = "foo-bar" - let output = "foo - bar" - testFormatting(for: input, output, rule: .spaceAroundOperators) - } - - func testNoSpaceAroundPrefixMinus() { - let input = "foo + -bar" - testFormatting(for: input, rule: .spaceAroundOperators) - } - - func testSpaceAroundLessThan() { - let input = "foo () - print(foo) - """ - testFormatting(for: input, rule: .void) - } - - func testParensRemovedAroundVoid() { - let input = "() -> (Void)" - let output = "() -> Void" - testFormatting(for: input, output, rule: .void) - } - - func testVoidArgumentConvertedToEmptyParens() { - let input = "Void -> Void" - let output = "() -> Void" - testFormatting(for: input, output, rule: .void) - } - - func testVoidArgumentInParensNotConvertedToEmptyParens() { - let input = "(Void) -> Void" - testFormatting(for: input, rule: .void) - } - - func testAnonymousVoidArgumentNotConvertedToEmptyParens() { - let input = "{ (_: Void) -> Void in }" - testFormatting(for: input, rule: .void, exclude: [.redundantVoidReturnType]) - } - - func testFuncWithAnonymousVoidArgumentNotStripped() { - let input = "func foo(_: Void) -> Void" - testFormatting(for: input, rule: .void) - } - - func testFunctionThatReturnsAFunction() { - let input = "(Void) -> Void -> ()" - let output = "(Void) -> () -> Void" - testFormatting(for: input, output, rule: .void) - } - - func testFunctionThatReturnsAFunctionThatThrows() { - let input = "(Void) -> Void throws -> ()" - let output = "(Void) -> () throws -> Void" - testFormatting(for: input, output, rule: .void) - } - - func testFunctionThatReturnsAFunctionThatHasTypedThrows() { - let input = "(Void) -> Void throws(Foo) -> ()" - let output = "(Void) -> () throws(Foo) -> Void" - testFormatting(for: input, output, rule: .void) - } - - func testChainOfFunctionsIsNotChanged() { - let input = "() -> () -> () -> Void" - testFormatting(for: input, rule: .void) - } - - func testChainOfFunctionsWithThrowsIsNotChanged() { - let input = "() -> () throws -> () throws -> Void" - testFormatting(for: input, rule: .void) - } - - func testChainOfFunctionsWithTypedThrowsIsNotChanged() { - let input = "() -> () throws(Foo) -> () throws(Foo) -> Void" - testFormatting(for: input, rule: .void) - } - - func testVoidThrowsIsNotMangled() { - let input = "(Void) throws -> Void" - testFormatting(for: input, rule: .void) - } - - func testVoidTypedThrowsIsNotMangled() { - let input = "(Void) throws(Foo) -> Void" - testFormatting(for: input, rule: .void) - } - - func testEmptyClosureArgsNotMangled() { - let input = "{ () in }" - testFormatting(for: input, rule: .void) - } - - func testEmptyClosureReturnValueConvertedToVoid() { - let input = "{ () -> () in }" - let output = "{ () -> Void in }" - testFormatting(for: input, output, rule: .void, exclude: [.redundantVoidReturnType]) - } - - func testAnonymousVoidClosureNotChanged() { - let input = "{ (_: Void) in }" - testFormatting(for: input, rule: .void, exclude: [.unusedArguments]) - } - - func testVoidLiteralConvertedToParens() { - let input = "foo(Void())" - let output = "foo(())" - testFormatting(for: input, output, rule: .void) - } - - func testVoidLiteralConvertedToParens2() { - let input = "let foo = Void()" - let output = "let foo = ()" - testFormatting(for: input, output, rule: .void) - } - - func testVoidLiteralReturnValueConvertedToParens() { - let input = """ - func foo() { - return Void() - } - """ - let output = """ - func foo() { - return () - } - """ - testFormatting(for: input, output, rule: .void) - } - - func testVoidLiteralReturnValueConvertedToParens2() { - let input = "{ _ in Void() }" - let output = "{ _ in () }" - testFormatting(for: input, output, rule: .void) - } - - func testNamespacedVoidLiteralNotConverted() { - // TODO: it should actually be safe to convert Swift.Void - only unsafe for other namespaces - let input = "let foo = Swift.Void()" - testFormatting(for: input, rule: .void) - } - - func testMalformedFuncDoesNotCauseInvalidOutput() throws { - let input = "func baz(Void) {}" - testFormatting(for: input, rule: .void) - } - - func testEmptyParensInGenericsConvertedToVoid() { - let input = "Foo<(), ()>" - let output = "Foo" - testFormatting(for: input, output, rule: .void) - } - - func testCaseVoidNotUnwrapped() { - let input = "case some(Void)" - testFormatting(for: input, rule: .void) - } - - func testLocalVoidTypeNotConverted() { - let input = """ - struct Void {} - let foo = Void() - print(foo) - """ - testFormatting(for: input, rule: .void) - } - - func testLocalVoidTypeForwardReferenceNotConverted() { - let input = """ - let foo = Void() - print(foo) - struct Void {} - """ - testFormatting(for: input, rule: .void) - } - - func testLocalVoidTypealiasNotConverted() { - let input = """ - typealias Void = MyVoid - let foo = Void() - print(foo) - """ - testFormatting(for: input, rule: .void) - } - - func testLocalVoidTypealiasForwardReferenceNotConverted() { - let input = """ - let foo = Void() - print(foo) - typealias Void = MyVoid - """ - testFormatting(for: input, rule: .void) - } - - // useVoid = false - - func testUseVoidOptionFalse() { - let input = "(Void) -> Void" - let output = "(()) -> ()" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: .void, options: options) - } - - func testNamespacedVoidNotConverted() { - let input = "() -> Swift.Void" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: .void, options: options) - } - - func testTypealiasVoidNotConverted() { - let input = "public typealias Void = ()" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: .void, options: options) - } - - func testVoidClosureReturnValueConvertedToEmptyTuple() { - let input = "{ () -> Void in }" - let output = "{ () -> () in }" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: .void, options: options, exclude: [.redundantVoidReturnType]) - } - - func testNoConvertVoidSelfToTuple() { - let input = "Void.self" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: .void, options: options) - } - - func testNoConvertVoidTypeToTuple() { - let input = "Void.Type" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, rule: .void, options: options) - } - - func testCaseVoidConvertedToTuple() { - let input = "case some(Void)" - let output = "case some(())" - let options = FormatOptions(useVoid: false) - testFormatting(for: input, output, rule: .void, options: options) - } - - // MARK: - trailingClosures - - func testAnonymousClosureArgumentMadeTrailing() { - let input = "foo(foo: 5, { /* some code */ })" - let output = "foo(foo: 5) { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testNamedClosureArgumentNotMadeTrailing() { - let input = "foo(foo: 5, bar: { /* some code */ })" - testFormatting(for: input, rule: .trailingClosures) - } - - func testClosureArgumentPassedToFunctionInArgumentsNotMadeTrailing() { - let input = "foo(bar { /* some code */ })" - testFormatting(for: input, rule: .trailingClosures) - } - - func testClosureArgumentInFunctionWithOtherClosureArgumentsNotMadeTrailing() { - let input = "foo(foo: { /* some code */ }, { /* some code */ })" - testFormatting(for: input, rule: .trailingClosures) - } - - func testClosureArgumentInExpressionNotMadeTrailing() { - let input = "if let foo = foo(foo: 5, { /* some code */ }) {}" - testFormatting(for: input, rule: .trailingClosures) - } - - func testClosureArgumentInCompoundExpressionNotMadeTrailing() { - let input = "if let foo = foo(foo: 5, { /* some code */ }), let bar = bar(bar: 2, { /* some code */ }) {}" - testFormatting(for: input, rule: .trailingClosures) - } - - func testClosureArgumentAfterLinebreakInGuardNotMadeTrailing() { - let input = "guard let foo =\n bar({ /* some code */ })\nelse { return }" - testFormatting(for: input, rule: .trailingClosures, - exclude: [.wrapConditionalBodies]) - } - - func testClosureMadeTrailingForNumericTupleMember() { - let input = "foo.1(5, { bar })" - let output = "foo.1(5) { bar }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testNoRemoveParensAroundClosureFollowedByOpeningBrace() { - let input = "foo({ bar }) { baz }" - testFormatting(for: input, rule: .trailingClosures) - } - - func testRemoveParensAroundClosureWithInnerSpacesFollowedByUnwrapOperator() { - let input = "foo( { bar } )?.baz" - let output = "foo { bar }?.baz" - testFormatting(for: input, output, rule: .trailingClosures) - } - - // solitary argument - - func testParensAroundSolitaryClosureArgumentRemoved() { - let input = "foo({ /* some code */ })" - let output = "foo { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testParensAroundNamedSolitaryClosureArgumentNotRemoved() { - let input = "foo(foo: { /* some code */ })" - testFormatting(for: input, rule: .trailingClosures) - } - - func testParensAroundSolitaryClosureArgumentInExpressionNotRemoved() { - let input = "if let foo = foo({ /* some code */ }) {}" - testFormatting(for: input, rule: .trailingClosures) - } - - func testParensAroundSolitaryClosureArgumentInCompoundExpressionNotRemoved() { - let input = "if let foo = foo({ /* some code */ }), let bar = bar({ /* some code */ }) {}" - testFormatting(for: input, rule: .trailingClosures) - } - - func testParensAroundOptionalTrailingClosureInForLoopNotRemoved() { - let input = "for foo in bar?.map({ $0.baz }) ?? [] {}" - testFormatting(for: input, rule: .trailingClosures) - } - - func testParensAroundTrailingClosureInGuardCaseLetNotRemoved() { - let input = "guard case let .foo(bar) = baz.filter({ $0 == quux }).isEmpty else {}" - testFormatting(for: input, rule: .trailingClosures, - exclude: [.wrapConditionalBodies]) - } - - func testParensAroundTrailingClosureInWhereClauseLetNotRemoved() { - let input = "for foo in bar where baz.filter({ $0 == quux }).isEmpty {}" - testFormatting(for: input, rule: .trailingClosures) - } - - func testParensAroundTrailingClosureInSwitchNotRemoved() { - let input = "switch foo({ $0 == bar }).count {}" - testFormatting(for: input, rule: .trailingClosures) - } - - func testSolitaryClosureMadeTrailingInChain() { - let input = "foo.map({ $0.path }).joined()" - let output = "foo.map { $0.path }.joined()" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testSpaceNotInsertedAfterClosureBeforeUnwrap() { - let input = "let foo = bar.map({ foo($0) })?.baz" - let output = "let foo = bar.map { foo($0) }?.baz" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testSpaceNotInsertedAfterClosureBeforeForceUnwrap() { - let input = "let foo = bar.map({ foo($0) })!.baz" - let output = "let foo = bar.map { foo($0) }!.baz" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testSolitaryClosureMadeTrailingForNumericTupleMember() { - let input = "foo.1({ bar })" - let output = "foo.1 { bar }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - // dispatch methods - - func testDispatchAsyncClosureArgumentMadeTrailing() { - let input = "queue.async(execute: { /* some code */ })" - let output = "queue.async { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testDispatchAsyncGroupClosureArgumentMadeTrailing() { - // TODO: async(group: , qos: , flags: , execute: ) - let input = "queue.async(group: g, execute: { /* some code */ })" - let output = "queue.async(group: g) { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testDispatchAsyncAfterClosureArgumentMadeTrailing() { - let input = "queue.asyncAfter(deadline: t, execute: { /* some code */ })" - let output = "queue.asyncAfter(deadline: t) { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testDispatchAsyncAfterWallClosureArgumentMadeTrailing() { - let input = "queue.asyncAfter(wallDeadline: t, execute: { /* some code */ })" - let output = "queue.asyncAfter(wallDeadline: t) { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testDispatchSyncClosureArgumentMadeTrailing() { - let input = "queue.sync(execute: { /* some code */ })" - let output = "queue.sync { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - func testDispatchSyncFlagsClosureArgumentMadeTrailing() { - let input = "queue.sync(flags: f, execute: { /* some code */ })" - let output = "queue.sync(flags: f) { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - // autoreleasepool - - func testAutoreleasepoolMadeTrailing() { - let input = "autoreleasepool(invoking: { /* some code */ })" - let output = "autoreleasepool { /* some code */ }" - testFormatting(for: input, output, rule: .trailingClosures) - } - - // explicit trailing closure methods - - func testCustomMethodMadeTrailing() { - let input = "foo(bar: 1, baz: { /* some code */ })" - let output = "foo(bar: 1) { /* some code */ }" - let options = FormatOptions(trailingClosures: ["foo"]) - testFormatting(for: input, output, rule: .trailingClosures, options: options) - } - - // explicit non-trailing closure methods - - func testPerformBatchUpdatesNotMadeTrailing() { - let input = "collectionView.performBatchUpdates({ /* some code */ })" - testFormatting(for: input, rule: .trailingClosures) - } - - func testNimbleExpectNotMadeTrailing() { - let input = "expect({ bar }).to(beNil())" - testFormatting(for: input, rule: .trailingClosures) - } - - func testCustomMethodNotMadeTrailing() { - let input = "foo({ /* some code */ })" - let options = FormatOptions(neverTrailing: ["foo"]) - testFormatting(for: input, rule: .trailingClosures, options: options) - } - - // multiple closures - - func testMultipleNestedClosures() throws { - let repeatCount = 10 - let input = """ - override func foo() { - bar { - var baz = 5 - \(String(repeating: """ - fizz { - buzz { - fizzbuzz() - } - } - - """, count: repeatCount)) } - } - """ - testFormatting(for: input, rule: .trailingClosures) - } - - // MARK: - enumNamespaces - - func testEnumNamespacesClassAsProtocolRestriction() { - let input = """ - @objc protocol Foo: class { - @objc static var expressionTypes: [String: RuntimeType] { get } - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesConformingOtherType() { - let input = "private final class CustomUITableViewCell: UITableViewCell {}" - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesImportClass() { - let input = """ - import class MyUIKit.AutoHeightTableView - - enum Foo { - static var bar: String - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesImportStruct() { - let input = """ - import struct Core.CurrencyFormatter - - enum Foo { - static var bar: String - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesClassFunction() { - let input = """ - class Container { - class func bar() {} - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesRemovingExtraKeywords() { - let input = """ - final class MyNamespace { - static let bar = "bar" - } - """ - let output = """ - enum MyNamespace { - static let bar = "bar" - } - """ - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesNestedTypes() { - let input = """ - enum Namespace {} - extension Namespace { - struct Constants { - static let bar = "bar" - } - } - """ - let output = """ - enum Namespace {} - extension Namespace { - enum Constants { - static let bar = "bar" - } - } - """ - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesNestedTypes2() { - let input = """ - struct Namespace { - struct NestedNamespace { - static let foo: Int - static let bar: Int - } - } - """ - let output = """ - enum Namespace { - enum NestedNamespace { - static let foo: Int - static let bar: Int - } - } - """ - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesNestedTypes3() { - let input = """ - struct Namespace { - struct TypeNestedInNamespace { - let foo: Int - let bar: Int - } - } - """ - let output = """ - enum Namespace { - struct TypeNestedInNamespace { - let foo: Int - let bar: Int - } - } - """ - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesNestedTypes4() { - let input = """ - struct Namespace { - static func staticFunction() { - struct NestedType { - init() {} - } - } - } - """ - let output = """ - enum Namespace { - static func staticFunction() { - struct NestedType { - init() {} - } - } - } - """ - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesNestedTypes5() { - let input = """ - struct Namespace { - static func staticFunction() { - func nestedFunction() { /* ... */ } - } - } - """ - let output = """ - enum Namespace { - static func staticFunction() { - func nestedFunction() { /* ... */ } - } - } - """ - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesStaticVariable() { - let input = """ - struct Constants { - static let β = 0, 5 - } - """ - let output = """ - enum Constants { - static let β = 0, 5 - } - """ - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesStaticAndInstanceVariable() { - let input = """ - struct Constants { - static let β = 0, 5 - let Ɣ = 0, 3 - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesStaticFunction() { - let input = """ - struct Constants { - static func remoteConfig() -> Int { - return 10 - } - } - """ - let output = """ - enum Constants { - static func remoteConfig() -> Int { - return 10 - } - } - """ - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesStaticAndInstanceFunction() { - let input = """ - struct Constants { - static func remoteConfig() -> Int { - return 10 - } - - func instanceConfig(offset: Int) -> Int { - return offset + 10 - } - } - """ - - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespaceDoesNothing() { - let input = """ - struct Foo { - #if BAR - func something() {} - #else - func something() {} - #endif - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespaceDoesNothingForEmptyDeclaration() { - let input = """ - struct Foo {} - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfTypeInitializedInternally() { - let input = """ - struct Foo { - static func bar() { - Foo().baz - } - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfInitializedInternally() { - let input = """ - struct Foo { - static func bar() { - Self().baz - } - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfInitializedInternally2() { - let input = """ - struct Foo { - static func bar() -> Foo { - self.init() - } - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfAssignedInternally() { - let input = """ - class Foo { - public static func bar() { - let bundle = Bundle(for: self) - } - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfAssignedInternally2() { - let input = """ - class Foo { - public static func bar() { - let `class` = self - } - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesDoesNothingIfSelfAssignedInternally3() { - let input = """ - class Foo { - public static func bar() { - let `class` = Foo.self - } - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testClassFuncNotReplacedByEnum() { - let input = """ - class Foo { - class override func foo() { - Bar.bar() - } - } - """ - testFormatting(for: input, rule: .enumNamespaces, - exclude: [.modifierOrder]) - } - - func testOpenClassNotReplacedByEnum() { - let input = """ - open class Foo { - public static let bar = "bar" - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testClassNotReplacedByEnum() { - let input = """ - class Foo { - public static let bar = "bar" - } - """ - let options = FormatOptions(enumNamespaces: .structsOnly) - testFormatting(for: input, rule: .enumNamespaces, options: options) - } - - func testEnumNamespacesAfterImport() { - // https://github.com/nicklockwood/SwiftFormat/issues/1569 - let input = """ - import Foundation - - final class MyViewModel2 { - static let = "A" - } - """ - - let output = """ - import Foundation - - enum MyViewModel2 { - static let = "A" - } - """ - - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesAfterImport2() { - // https://github.com/nicklockwood/SwiftFormat/issues/1569 - let input = """ - final class MyViewModel { - static let = "A" - } - - import Foundation - - final class MyViewModel2 { - static let = "A" - } - """ - - let output = """ - enum MyViewModel { - static let = "A" - } - - import Foundation - - enum MyViewModel2 { - static let = "A" - } - """ - - testFormatting(for: input, output, rule: .enumNamespaces) - } - - func testEnumNamespacesNotAppliedToNonFinalClass() { - let input = """ - class Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfObjC() { - let input = """ - @objc(NSFoo) - final class Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfMacro() { - let input = """ - @FooBar - struct Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfParameterizedMacro() { - let input = """ - @FooMacro(arg: "Foo") - struct Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfGenericMacro() { - let input = """ - @FooMacro - struct Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - func testEnumNamespacesNotAppliedIfGenericParameterizedMacro() { - let input = """ - @FooMacro(arg: 5) - struct Foo { - static let = "A" - } - """ - testFormatting(for: input, rule: .enumNamespaces) - } - - // MARK: - numberFormatting - - // hex case - - func testLowercaseLiteralConvertedToUpper() { - let input = "let foo = 0xabcd" - let output = "let foo = 0xABCD" - testFormatting(for: input, output, rule: .numberFormatting) - } - - func testMixedCaseLiteralConvertedToUpper() { - let input = "let foo = 0xaBcD" - let output = "let foo = 0xABCD" - testFormatting(for: input, output, rule: .numberFormatting) - } - - func testUppercaseLiteralConvertedToLower() { - let input = "let foo = 0xABCD" - let output = "let foo = 0xabcd" - let options = FormatOptions(uppercaseHex: false) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testPInExponentialNotConvertedToUpper() { - let input = "let foo = 0xaBcDp5" - let output = "let foo = 0xABCDp5" - testFormatting(for: input, output, rule: .numberFormatting) - } - - func testPInExponentialNotConvertedToLower() { - let input = "let foo = 0xaBcDP5" - let output = "let foo = 0xabcdP5" - let options = FormatOptions(uppercaseHex: false, uppercaseExponent: true) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - // exponent case - - func testLowercaseExponent() { - let input = "let foo = 0.456E-5" - let output = "let foo = 0.456e-5" - testFormatting(for: input, output, rule: .numberFormatting) - } - - func testUppercaseExponent() { - let input = "let foo = 0.456e-5" - let output = "let foo = 0.456E-5" - let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testUppercaseHexExponent() { - let input = "let foo = 0xFF00p54" - let output = "let foo = 0xFF00P54" - let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testUppercaseGroupedHexExponent() { - let input = "let foo = 0xFF00_AABB_CCDDp54" - let output = "let foo = 0xFF00_AABB_CCDDP54" - let options = FormatOptions(uppercaseExponent: true) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - // decimal grouping - - func testDefaultDecimalGrouping() { - let input = "let foo = 1234_56_78" - let output = "let foo = 12_345_678" - testFormatting(for: input, output, rule: .numberFormatting) - } - - func testIgnoreDecimalGrouping() { - let input = "let foo = 1234_5_678" - let options = FormatOptions(decimalGrouping: .ignore) - testFormatting(for: input, rule: .numberFormatting, options: options) - } - - func testNoDecimalGrouping() { - let input = "let foo = 1234_5_678" - let output = "let foo = 12345678" - let options = FormatOptions(decimalGrouping: .none) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testDecimalGroupingThousands() { - let input = "let foo = 1234" - let output = "let foo = 1_234" - let options = FormatOptions(decimalGrouping: .group(3, 3)) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testExponentialGrouping() { - let input = "let foo = 1234e5678" - let output = "let foo = 1_234e5678" - let options = FormatOptions(decimalGrouping: .group(3, 3)) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testZeroGrouping() { - let input = "let foo = 1234" - let options = FormatOptions(decimalGrouping: .group(0, 0)) - testFormatting(for: input, rule: .numberFormatting, options: options) - } - - // binary grouping - - func testDefaultBinaryGrouping() { - let input = "let foo = 0b11101000_00111111" - let output = "let foo = 0b1110_1000_0011_1111" - testFormatting(for: input, output, rule: .numberFormatting) - } - - func testIgnoreBinaryGrouping() { - let input = "let foo = 0b1110_10_00" - let options = FormatOptions(binaryGrouping: .ignore) - testFormatting(for: input, rule: .numberFormatting, options: options) - } - - func testNoBinaryGrouping() { - let input = "let foo = 0b1110_10_00" - let output = "let foo = 0b11101000" - let options = FormatOptions(binaryGrouping: .none) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testBinaryGroupingCustom() { - let input = "let foo = 0b110011" - let output = "let foo = 0b11_00_11" - let options = FormatOptions(binaryGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - // hex grouping - - func testDefaultHexGrouping() { - let input = "let foo = 0xFF01FF01AE45" - let output = "let foo = 0xFF01_FF01_AE45" - testFormatting(for: input, output, rule: .numberFormatting) - } - - func testCustomHexGrouping() { - let input = "let foo = 0xFF00p54" - let output = "let foo = 0xFF_00p54" - let options = FormatOptions(hexGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - // octal grouping - - func testDefaultOctalGrouping() { - let input = "let foo = 0o123456701234" - let output = "let foo = 0o1234_5670_1234" - testFormatting(for: input, output, rule: .numberFormatting) - } - - func testCustomOctalGrouping() { - let input = "let foo = 0o12345670" - let output = "let foo = 0o12_34_56_70" - let options = FormatOptions(octalGrouping: .group(2, 2)) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - // fraction grouping - - func testIgnoreFractionGrouping() { - let input = "let foo = 1.234_5_678" - let options = FormatOptions(decimalGrouping: .ignore, fractionGrouping: true) - testFormatting(for: input, rule: .numberFormatting, options: options) - } - - func testNoFractionGrouping() { - let input = "let foo = 1.234_5_678" - let output = "let foo = 1.2345678" - let options = FormatOptions(decimalGrouping: .none, fractionGrouping: true) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testFractionGroupingThousands() { - let input = "let foo = 12.34_56_78" - let output = "let foo = 12.345_678" - let options = FormatOptions(decimalGrouping: .group(3, 3), fractionGrouping: true) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - func testHexFractionGrouping() { - let input = "let foo = 0x12.34_56_78p56" - let output = "let foo = 0x12.34_5678p56" - let options = FormatOptions(hexGrouping: .group(4, 4), fractionGrouping: true) - testFormatting(for: input, output, rule: .numberFormatting, options: options) - } - - // MARK: - andOperator - - func testIfAndReplaced() { - let input = "if true && true {}" - let output = "if true, true {}" - testFormatting(for: input, output, rule: .andOperator) - } - - func testGuardAndReplaced() { - let input = "guard true && true\nelse { return }" - let output = "guard true, true\nelse { return }" - testFormatting(for: input, output, rule: .andOperator, - exclude: [.wrapConditionalBodies]) - } - - func testWhileAndReplaced() { - let input = "while true && true {}" - let output = "while true, true {}" - testFormatting(for: input, output, rule: .andOperator) - } - - func testIfDoubleAndReplaced() { - let input = "if true && true && true {}" - let output = "if true, true, true {}" - testFormatting(for: input, output, rule: .andOperator) - } - - func testIfAndParensReplaced() { - let input = "if true && (true && true) {}" - let output = "if true, (true && true) {}" - testFormatting(for: input, output, rule: .andOperator, - exclude: [.redundantParens]) - } - - func testIfFunctionAndReplaced() { - let input = "if functionReturnsBool() && true {}" - let output = "if functionReturnsBool(), true {}" - testFormatting(for: input, output, rule: .andOperator) - } - - func testNoReplaceIfOrAnd() { - let input = "if foo || bar && baz {}" - testFormatting(for: input, rule: .andOperator) - } - - func testNoReplaceIfAndOr() { - let input = "if foo && bar || baz {}" - testFormatting(for: input, rule: .andOperator) - } - - func testIfAndReplacedInFunction() { - let input = "func someFunc() { if bar && baz {} }" - let output = "func someFunc() { if bar, baz {} }" - testFormatting(for: input, output, rule: .andOperator) - } - - func testNoReplaceIfCaseLetAnd() { - let input = "if case let a = foo && bar {}" - testFormatting(for: input, rule: .andOperator) - } - - func testNoReplaceWhileCaseLetAnd() { - let input = "while case let a = foo && bar {}" - testFormatting(for: input, rule: .andOperator) - } - - func testNoReplaceRepeatWhileAnd() { - let input = """ - repeat {} while true && !false - foo {} - """ - testFormatting(for: input, rule: .andOperator) - } - - func testNoReplaceIfLetAndLetAnd() { - let input = "if let a = b && c, let d = e && f {}" - testFormatting(for: input, rule: .andOperator) - } - - func testNoReplaceIfTryAnd() { - let input = "if try true && explode() {}" - testFormatting(for: input, rule: .andOperator) - } - - func testHandleAndAtStartOfLine() { - let input = "if a == b\n && b == c {}" - let output = "if a == b,\n b == c {}" - testFormatting(for: input, output, rule: .andOperator, exclude: [.indent]) - } - - func testHandleAndAtStartOfLineAfterComment() { - let input = "if a == b // foo\n && b == c {}" - let output = "if a == b, // foo\n b == c {}" - testFormatting(for: input, output, rule: .andOperator, exclude: [.indent]) - } - - func testNoReplaceAndOperatorWhereGenericsAmbiguous() { - let input = "if x < y && z > (a * b) {}" - testFormatting(for: input, rule: .andOperator) - } - - func testNoReplaceAndOperatorWhereGenericsAmbiguous2() { - let input = "if x < y && z && w > (a * b) {}" - let output = "if x < y, z && w > (a * b) {}" - testFormatting(for: input, output, rule: .andOperator) - } - - func testAndOperatorCrash() { - let input = """ - DragGesture().onChanged { gesture in - if gesture.translation.width < 50 && gesture.translation.height > 50 { - offset = gesture.translation - } - } - """ - let output = """ - DragGesture().onChanged { gesture in - if gesture.translation.width < 50, gesture.translation.height > 50 { - offset = gesture.translation - } - } - """ - testFormatting(for: input, output, rule: .andOperator) - } - - func testNoReplaceAndInViewBuilder() { - let input = """ - SomeView { - if foo == 5 && bar { - Text("5") - } else { - Text("Not 5") - } - } - """ - testFormatting(for: input, rule: .andOperator) - } - - func testNoReplaceAndInViewBuilder2() { - let input = """ - var body: some View { - ZStack { - if self.foo && self.bar { - self.closedPath - } - } - } - """ - testFormatting(for: input, rule: .andOperator) - } - - func testReplaceAndInViewBuilderInSwift5_3() { - let input = """ - SomeView { - if foo == 5 && bar { - Text("5") - } else { - Text("Not 5") - } - } - """ - let output = """ - SomeView { - if foo == 5, bar { - Text("5") - } else { - Text("Not 5") - } - } - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: .andOperator, options: options) - } - - // MARK: - isEmpty - - // count == 0 - - func testCountEqualsZero() { - let input = "if foo.count == 0 {}" - let output = "if foo.isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testFunctionCountEqualsZero() { - let input = "if foo().count == 0 {}" - let output = "if foo().isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testExpressionCountEqualsZero() { - let input = "if foo || bar.count == 0 {}" - let output = "if foo || bar.isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCompoundIfCountEqualsZero() { - let input = "if foo, bar.count == 0 {}" - let output = "if foo, bar.isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testOptionalCountEqualsZero() { - let input = "if foo?.count == 0 {}" - let output = "if foo?.isEmpty == true {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testOptionalChainCountEqualsZero() { - let input = "if foo?.bar.count == 0 {}" - let output = "if foo?.bar.isEmpty == true {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCompoundIfOptionalCountEqualsZero() { - let input = "if foo, bar?.count == 0 {}" - let output = "if foo, bar?.isEmpty == true {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testTernaryCountEqualsZero() { - let input = "foo ? bar.count == 0 : baz.count == 0" - let output = "foo ? bar.isEmpty : baz.isEmpty" - testFormatting(for: input, output, rule: .isEmpty) - } - - // count != 0 - - func testCountNotEqualToZero() { - let input = "if foo.count != 0 {}" - let output = "if !foo.isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testFunctionCountNotEqualToZero() { - let input = "if foo().count != 0 {}" - let output = "if !foo().isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testExpressionCountNotEqualToZero() { - let input = "if foo || bar.count != 0 {}" - let output = "if foo || !bar.isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCompoundIfCountNotEqualToZero() { - let input = "if foo, bar.count != 0 {}" - let output = "if foo, !bar.isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - // count > 0 - - func testCountGreaterThanZero() { - let input = "if foo.count > 0 {}" - let output = "if !foo.isEmpty {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCountExpressionGreaterThanZero() { - let input = "if a.count - b.count > 0 {}" - testFormatting(for: input, rule: .isEmpty) - } - - // optional count - - func testOptionalCountNotEqualToZero() { - let input = "if foo?.count != 0 {}" // nil evaluates to true - let output = "if foo?.isEmpty != true {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testOptionalChainCountNotEqualToZero() { - let input = "if foo?.bar.count != 0 {}" // nil evaluates to true - let output = "if foo?.bar.isEmpty != true {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCompoundIfOptionalCountNotEqualToZero() { - let input = "if foo, bar?.count != 0 {}" - let output = "if foo, bar?.isEmpty != true {}" - testFormatting(for: input, output, rule: .isEmpty) - } - - // edge cases - - func testTernaryCountNotEqualToZero() { - let input = "foo ? bar.count != 0 : baz.count != 0" - let output = "foo ? !bar.isEmpty : !baz.isEmpty" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCountEqualsZeroAfterOptionalOnPreviousLine() { - let input = "_ = foo?.bar\nbar.count == 0 ? baz() : quux()" - let output = "_ = foo?.bar\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCountEqualsZeroAfterOptionalCallOnPreviousLine() { - let input = "foo?.bar()\nbar.count == 0 ? baz() : quux()" - let output = "foo?.bar()\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCountEqualsZeroAfterTrailingCommentOnPreviousLine() { - let input = "foo?.bar() // foobar\nbar.count == 0 ? baz() : quux()" - let output = "foo?.bar() // foobar\nbar.isEmpty ? baz() : quux()" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCountGreaterThanZeroAfterOpenParen() { - let input = "foo(bar.count > 0)" - let output = "foo(!bar.isEmpty)" - testFormatting(for: input, output, rule: .isEmpty) - } - - func testCountGreaterThanZeroAfterArgumentLabel() { - let input = "foo(bar: baz.count > 0)" - let output = "foo(bar: !baz.isEmpty)" - testFormatting(for: input, output, rule: .isEmpty) - } - - // MARK: - anyObjectProtocol - - func testClassReplacedByAnyObject() { - let input = "protocol Foo: class {}" - let output = "protocol Foo: AnyObject {}" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: .anyObjectProtocol, options: options) - } - - func testClassReplacedByAnyObjectWithOtherProtocols() { - let input = "protocol Foo: class, Codable {}" - let output = "protocol Foo: AnyObject, Codable {}" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: .anyObjectProtocol, options: options) - } - - func testClassReplacedByAnyObjectImmediatelyAfterImport() { - let input = "import Foundation\nprotocol Foo: class {}" - let output = "import Foundation\nprotocol Foo: AnyObject {}" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, output, rule: .anyObjectProtocol, options: options, - exclude: [.blankLineAfterImports]) - } - - func testClassDeclarationNotReplacedByAnyObject() { - let input = "class Foo: Codable {}" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: .anyObjectProtocol, options: options) - } - - func testClassImportNotReplacedByAnyObject() { - let input = "import class Foo.Bar" - let options = FormatOptions(swiftVersion: "4.1") - testFormatting(for: input, rule: .anyObjectProtocol, options: options) - } - - func testClassNotReplacedByAnyObjectIfSwiftVersionLessThan4_1() { - let input = "protocol Foo: class {}" - let options = FormatOptions(swiftVersion: "4.0") - testFormatting(for: input, rule: .anyObjectProtocol, options: options) - } - - // MARK: - applicationMain - - func testUIApplicationMainReplacedByMain() { - let input = """ - @UIApplicationMain - class AppDelegate: UIResponder, UIApplicationDelegate {} - """ - let output = """ - @main - class AppDelegate: UIResponder, UIApplicationDelegate {} - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: .applicationMain, options: options) - } - - func testNSApplicationMainReplacedByMain() { - let input = """ - @NSApplicationMain - class AppDelegate: NSObject, NSApplicationDelegate {} - """ - let output = """ - @main - class AppDelegate: NSObject, NSApplicationDelegate {} - """ - let options = FormatOptions(swiftVersion: "5.3") - testFormatting(for: input, output, rule: .applicationMain, options: options) - } - - func testNSApplicationMainNotReplacedInSwift5_2() { - let input = """ - @NSApplicationMain - class AppDelegate: NSObject, NSApplicationDelegate {} - """ - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: .applicationMain, options: options) - } - - // MARK: - typeSugar - - // arrays - - func testArrayTypeConvertedToSugar() { - let input = "var foo: Array" - let output = "var foo: [String]" - testFormatting(for: input, output, rule: .typeSugar) - } - - func testSwiftArrayTypeConvertedToSugar() { - let input = "var foo: Swift.Array" - let output = "var foo: [String]" - testFormatting(for: input, output, rule: .typeSugar) - } - - func testArrayNestedTypeAliasNotConvertedToSugar() { - let input = "typealias Indices = Array.Indices" - testFormatting(for: input, rule: .typeSugar) - } - - func testArrayTypeReferenceConvertedToSugar() { - let input = "let type = Array.Type" - let output = "let type = [Foo].Type" - testFormatting(for: input, output, rule: .typeSugar) - } - - func testSwiftArrayTypeReferenceConvertedToSugar() { - let input = "let type = Swift.Array.Type" - let output = "let type = [Foo].Type" - testFormatting(for: input, output, rule: .typeSugar) - } - - func testArraySelfReferenceConvertedToSugar() { - let input = "let type = Array.self" - let output = "let type = [Foo].self" - testFormatting(for: input, output, rule: .typeSugar) - } - - func testSwiftArraySelfReferenceConvertedToSugar() { - let input = "let type = Swift.Array.self" - let output = "let type = [Foo].self" - testFormatting(for: input, output, rule: .typeSugar) - } - - func testArrayDeclarationNotConvertedToSugar() { - let input = "struct Array {}" - testFormatting(for: input, rule: .typeSugar) - } - - func testExtensionTypeSugar() { - let input = """ - extension Array {} - extension Optional {} - extension Dictionary {} - extension Optional>>> {} - """ - - let output = """ - extension [Foo] {} - extension Foo? {} - extension [Foo: Bar] {} - extension [[Foo: [Bar]]]? {} - """ - testFormatting(for: input, output, rule: .typeSugar) - } - - // dictionaries - - func testDictionaryTypeConvertedToSugar() { - let input = "var foo: Dictionary" - let output = "var foo: [String: Int]" - testFormatting(for: input, output, rule: .typeSugar) - } - - func testSwiftDictionaryTypeConvertedToSugar() { - let input = "var foo: Swift.Dictionary" - let output = "var foo: [String: Int]" - testFormatting(for: input, output, rule: .typeSugar) - } - - // optionals - - func testOptionalPropertyTypeNotConvertedToSugarByDefault() { - let input = "var bar: Optional" - testFormatting(for: input, rule: .typeSugar) - } - - func testOptionalTypeConvertedToSugar() { - let input = "var foo: Optional" - let output = "var foo: String?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testSwiftOptionalTypeConvertedToSugar() { - let input = "var foo: Swift.Optional" - let output = "var foo: String?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testOptionalClosureParenthesizedConvertedToSugar() { - let input = "var foo: Optional<(Int) -> String>" - let output = "var foo: ((Int) -> String)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testOptionalTupleWrappedInParensConvertedToSugar() { - let input = "let foo: Optional<(foo: Int, bar: String)>" - let output = "let foo: (foo: Int, bar: String)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testOptionalComposedProtocolWrappedInParensConvertedToSugar() { - let input = "let foo: Optional" - let output = "let foo: (UIView & Foo)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testSwiftOptionalClosureParenthesizedConvertedToSugar() { - let input = "var foo: Swift.Optional<(Int) -> String>" - let output = "var foo: ((Int) -> String)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testStrippingSwiftNamespaceInOptionalTypeWhenConvertedToSugar() { - let input = "Swift.Optional" - let output = "String?" - testFormatting(for: input, output, rule: .typeSugar) - } - - func testStrippingSwiftNamespaceDoesNotStripPreviousSwiftNamespaceReferences() { - let input = "let a: Swift.String = Optional" - let output = "let a: Swift.String = String?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testOptionalTypeInsideCaseConvertedToSugar() { - let input = "if case .some(Optional.some(let foo)) = bar else {}" - let output = "if case .some(Any?.some(let foo)) = bar else {}" - testFormatting(for: input, output, rule: .typeSugar, exclude: [.hoistPatternLet]) - } - - func testSwitchCaseOptionalNotReplaced() { - let input = """ - switch foo { - case Optional.none: - } - """ - testFormatting(for: input, rule: .typeSugar) - } - - func testCaseOptionalNotReplaced2() { - let input = "if case Optional.none = foo {}" - testFormatting(for: input, rule: .typeSugar) - } - - func testUnwrappedOptionalSomeParenthesized() { - let input = "func foo() -> Optional> {}" - let output = "func foo() -> (some Publisher)? {}" - testFormatting(for: input, output, rule: .typeSugar) - } - - // swift parser bug - - func testAvoidSwiftParserBugWithClosuresInsideArrays() { - let input = "var foo = Array<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) - } - - func testAvoidSwiftParserBugWithClosuresInsideDictionaries() { - let input = "var foo = Dictionary Void>()" - testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) - } - - func testAvoidSwiftParserBugWithClosuresInsideOptionals() { - let input = "var foo = Optional<(_ image: Data?) -> Void>()" - testFormatting(for: input, rule: .typeSugar, options: FormatOptions(shortOptionals: .always), exclude: [.propertyType]) - } - - func testDontOverApplyBugWorkaround() { - let input = "var foo: Array<(_ image: Data?) -> Void>" - let output = "var foo: [(_ image: Data?) -> Void]" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testDontOverApplyBugWorkaround2() { - let input = "var foo: Dictionary Void>" - let output = "var foo: [String: (_ image: Data?) -> Void]" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testDontOverApplyBugWorkaround3() { - let input = "var foo: Optional<(_ image: Data?) -> Void>" - let output = "var foo: ((_ image: Data?) -> Void)?" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options) - } - - func testDontOverApplyBugWorkaround4() { - let input = "var foo = Array<(image: Data?) -> Void>()" - let output = "var foo = [(image: Data?) -> Void]()" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) - } - - func testDontOverApplyBugWorkaround5() { - let input = "var foo = Array<(Data?) -> Void>()" - let output = "var foo = [(Data?) -> Void]()" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) - } - - func testDontOverApplyBugWorkaround6() { - let input = "var foo = Dictionary Void>>()" - let output = "var foo = [Int: Array<(_ image: Data?) -> Void>]()" - let options = FormatOptions(shortOptionals: .always) - testFormatting(for: input, output, rule: .typeSugar, options: options, exclude: [.propertyType]) - } - - // MARK: - preferKeyPath - - func testMapPropertyToKeyPath() { - let input = "let foo = bar.map { $0.foo }" - let output = "let foo = bar.map(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: .preferKeyPath, - options: options) - } - - func testCompactMapPropertyToKeyPath() { - let input = "let foo = bar.compactMap { $0.foo }" - let output = "let foo = bar.compactMap(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: .preferKeyPath, - options: options) - } - - func testFlatMapPropertyToKeyPath() { - let input = "let foo = bar.flatMap { $0.foo }" - let output = "let foo = bar.flatMap(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: .preferKeyPath, - options: options) - } - - func testMapNestedPropertyWithSpacesToKeyPath() { - let input = "let foo = bar.map { $0 . foo . bar }" - let output = "let foo = bar.map(\\ . foo . bar)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: .preferKeyPath, - options: options, exclude: [.spaceAroundOperators]) - } - - func testMultilineMapPropertyToKeyPath() { - let input = """ - let foo = bar.map { - $0.foo - } - """ - let output = "let foo = bar.map(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: .preferKeyPath, - options: options) - } - - func testParenthesizedMapPropertyToKeyPath() { - let input = "let foo = bar.map({ $0.foo })" - let output = "let foo = bar.map(\\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: .preferKeyPath, - options: options) - } - - func testNoMapSelfToKeyPath() { - let input = "let foo = bar.map { $0 }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: .preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForSwiftLessThan5_2() { - let input = "let foo = bar.map { $0.foo }" - let options = FormatOptions(swiftVersion: "5.1") - testFormatting(for: input, rule: .preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForFunctionCalls() { - let input = "let foo = bar.map { $0.foo() }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: .preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForCompoundExpressions() { - let input = "let foo = bar.map { $0.foo || baz }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: .preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForOptionalChaining() { - let input = "let foo = bar.map { $0?.foo }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: .preferKeyPath, options: options) - } - - func testNoMapPropertyToKeyPathForTrailingContains() { - let input = "let foo = bar.contains { $0.foo }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: .preferKeyPath, options: options) - } - - func testMapPropertyToKeyPathForContainsWhere() { - let input = "let foo = bar.contains(where: { $0.foo })" - let output = "let foo = bar.contains(where: \\.foo)" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, output, rule: .preferKeyPath, options: options) - } - - func testMultipleTrailingClosuresNotConvertedToKeyPath() { - let input = "foo.map { $0.bar } reverse: { $0.bar }" - let options = FormatOptions(swiftVersion: "5.2") - testFormatting(for: input, rule: .preferKeyPath, options: options) - } - - // MARK: - assertionFailures - - func testAssertionFailuresForAssertFalse() { - let input = "assert(false)" - let output = "assertionFailure()" - testFormatting(for: input, output, rule: .assertionFailures) - } - - func testAssertionFailuresForAssertFalseWithSpaces() { - let input = "assert ( false )" - let output = "assertionFailure()" - testFormatting(for: input, output, rule: .assertionFailures) - } - - func testAssertionFailuresForAssertFalseWithLinebreaks() { - let input = """ - assert( - false - ) - """ - let output = "assertionFailure()" - testFormatting(for: input, output, rule: .assertionFailures) - } - - func testAssertionFailuresForAssertTrue() { - let input = "assert(true)" - testFormatting(for: input, rule: .assertionFailures) - } - - func testAssertionFailuresForAssertFalseWithArgs() { - let input = "assert(false, msg, 20, 21)" - let output = "assertionFailure(msg, 20, 21)" - testFormatting(for: input, output, rule: .assertionFailures) - } - - func testAssertionFailuresForPreconditionFalse() { - let input = "precondition(false)" - let output = "preconditionFailure()" - testFormatting(for: input, output, rule: .assertionFailures) - } - - func testAssertionFailuresForPreconditionTrue() { - let input = "precondition(true)" - testFormatting(for: input, rule: .assertionFailures) - } - - func testAssertionFailuresForPreconditionFalseWithArgs() { - let input = "precondition(false, msg, 0, 1)" - let output = "preconditionFailure(msg, 0, 1)" - testFormatting(for: input, output, rule: .assertionFailures) - } - - // MARK: - acronyms - - func testUppercaseAcronyms() { - let input = """ - let url: URL - let destinationUrl: URL - let id: ID - let screenId = "screenId" // We intentionally don't change the content of strings - let validUrls: Set - let validUrlschemes: Set - - let uniqueIdentifier = UUID() - - /// Opens Urls based on their scheme - struct UrlRouter {} - - /// The Id of a screen that can be displayed in the app - struct ScreenId {} - """ - - let output = """ - let url: URL - let destinationURL: URL - let id: ID - let screenID = "screenId" // We intentionally don't change the content of strings - let validURLs: Set - let validUrlschemes: Set - - let uniqueIdentifier = UUID() - - /// Opens URLs based on their scheme - struct URLRouter {} - - /// The ID of a screen that can be displayed in the app - struct ScreenID {} - """ - - testFormatting(for: input, output, rule: .acronyms, exclude: [.propertyType]) - } - - func testUppercaseCustomAcronym() { - let input = """ - let url: URL - let destinationUrl: URL - let pngData: Data - let imageInPngFormat: UIImage - """ - - let output = """ - let url: URL - let destinationUrl: URL - let pngData: Data - let imageInPNGFormat: UIImage - """ - - testFormatting(for: input, output, rule: .acronyms, options: FormatOptions(acronyms: ["png"])) - } - - func testDisableUppercaseAcronym() { - let input = """ - // swiftformat:disable:next acronyms - typeNotOwnedByAuthor.destinationUrl = URL() - typeOwnedByAuthor.destinationURL = URL() - """ - - testFormatting(for: input, rule: .acronyms) - } - - // MARK: - blockComments - - func testBlockCommentsOneLine() { - let input = "foo = bar /* comment */" - let output = "foo = bar // comment" - testFormatting(for: input, output, rule: .blockComments) - } - - func testDocBlockCommentsOneLine() { - let input = "foo = bar /** doc comment */" - let output = "foo = bar /// doc comment" - testFormatting(for: input, output, rule: .blockComments) - } - - func testPreservesBlockCommentInSingleLineScope() { - let input = "if foo { /* code */ }" - testFormatting(for: input, rule: .blockComments) - } - - func testBlockCommentsMultiLine() { - let input = """ - /* - * foo - * bar - */ - """ - let output = """ - // foo - // bar - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockCommentsWithoutBlankFirstLine() { - let input = """ - /* foo - * bar - */ - """ - let output = """ - // foo - // bar - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockCommentsWithBlankLine() { - let input = """ - /* - * foo - * - * bar - */ - """ - let output = """ - // foo - // - // bar - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockDocCommentsWithAsterisksOnEachLine() { - let input = """ - /** - * This is a documentation comment, - * not a standard comment. - */ - """ - let output = """ - /// This is a documentation comment, - /// not a standard comment. - """ - testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) - } - - func testBlockDocCommentsWithoutAsterisksOnEachLine() { - let input = """ - /** - This is a documentation comment, - not a standard comment. - */ - """ - let output = """ - /// This is a documentation comment, - /// not a standard comment. - """ - testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) - } - - func testBlockCommentWithBulletPoints() { - let input = """ - /* - This is a list of nice colors: - - * green - * blue - * red - - Yellow is also great. - */ - - /* - * Another comment. - */ - """ - let output = """ - // This is a list of nice colors: - // - // * green - // * blue - // * red - // - // Yellow is also great. - - // Another comment. - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockCommentsNested() { - let input = """ - /* - * comment - * /* inside */ - * a comment - */ - """ - let output = """ - // comment - // inside - // a comment - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockCommentsIndentPreserved() { - let input = """ - func foo() { - /* - foo - bar. - */ - } - """ - let output = """ - func foo() { - // foo - // bar. - } - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockCommentsIndentPreserved2() { - let input = """ - func foo() { - /* - * foo - * bar. - */ - } - """ - let output = """ - func foo() { - // foo - // bar. - } - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockDocCommentsIndentPreserved() { - let input = """ - func foo() { - /** - * foo - * bar. - */ - } - """ - let output = """ - func foo() { - /// foo - /// bar. - } - """ - testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) - } - - func testLongBlockCommentsWithoutPerLineMarkersFullyConverted() { - let input = """ - /* - The beginnings of the lines in this multiline comment body - have only spaces in them. There are no asterisks, only spaces. - - This should not cause the blockComments rule to convert only - part of the comment body and leave the rest hanging. - - The comment must have at least this many lines to trigger the bug. - */ - """ - let output = """ - // The beginnings of the lines in this multiline comment body - // have only spaces in them. There are no asterisks, only spaces. - // - // This should not cause the blockComments rule to convert only - // part of the comment body and leave the rest hanging. - // - // The comment must have at least this many lines to trigger the bug. - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockCommentImmediatelyFollowedByCode() { - let input = """ - /** - foo - - bar - */ - func foo() {} - """ - let output = """ - /// foo - /// - /// bar - func foo() {} - """ - testFormatting(for: input, output, rule: .blockComments) - } - - func testBlockCommentImmediatelyFollowedByCode2() { - let input = """ - /** - Line 1. - - Line 2. - - Line 3. - */ - foo(bar) - """ - let output = """ - /// Line 1. - /// - /// Line 2. - /// - /// Line 3. - foo(bar) - """ - testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) - } - - func testBlockCommentImmediatelyFollowedByCode3() { - let input = """ - /* foo - bar */ - func foo() {} - """ - let output = """ - // foo - // bar - func foo() {} - """ - testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) - } - - func testBlockCommentFollowedByBlankLine() { - let input = """ - /** - foo - - bar - */ - - func foo() {} - """ - let output = """ - /// foo - /// - /// bar - - func foo() {} - """ - testFormatting(for: input, output, rule: .blockComments, exclude: [.docComments]) - } - - // MARK: - opaqueGenericParameters - - func testGenericNotModifiedBelowSwift5_7() { - let input = """ - func foo(_ value: T) { - print(value) - } - """ - - let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithNoConstraint() { - let input = """ - func foo(_ value: T) { - print(value) - } - - init(_ value: T) { - print(value) - } - - subscript(_ value: T) -> Foo { - Foo(value) - } - - subscript(_ value: T) -> Foo { - get { - Foo(value) - } - set { - print(newValue) - } - } - """ - - let output = """ - func foo(_ value: some Any) { - print(value) - } - - init(_ value: some Any) { - print(value) - } - - subscript(_ value: some Any) -> Foo { - Foo(value) - } - - subscript(_ value: some Any) -> Foo { - get { - Foo(value) - } - set { - print(newValue) - } - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testDisableSomeAnyGenericType() { - let input = """ - func foo(_ value: T) { - print(value) - } - """ - - let options = FormatOptions(useSomeAny: false, swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithConstraintInBracket() { - let input = """ - func foo(_ fooable: T, barable: U) -> Baaz { - print(fooable, barable) - } - - init(_ fooable: T, barable: U) { - print(fooable, barable) - } - - subscript(_ fooable: T, barable: U) -> Any { - (fooable, barable) - } - """ - - let output = """ - func foo(_ fooable: some Fooable, barable: some Barable) -> Baaz { - print(fooable, barable) - } - - init(_ fooable: some Fooable, barable: some Barable) { - print(fooable, barable) - } - - subscript(_ fooable: some Fooable, barable: some Barable) -> Any { - (fooable, barable) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithConstraintsInWhereClause() { - let input = """ - func foo(_ t: T, _ u: U) -> Baaz where T: Fooable, T: Barable, U: Baazable { - print(t, u) - } - - init(_ t: T, _ u: U) where T: Fooable, T: Barable, U: Baazable { - print(t, u) - } - """ - - let output = """ - func foo(_ t: some Fooable & Barable, _ u: some Baazable) -> Baaz { - print(t, u) - } - - init(_ t: some Fooable & Barable, _ u: some Baazable) { - print(t, u) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterCanRemoveOneButNotOthers_onOneLine() { - let input = """ - func foo(_ foo: T, bar1: U, bar2: U) where S.AssociatedType == Baaz, T: Quuxable, U: Qaaxable { - print(foo, bar1, bar2) - } - """ - - let output = """ - func foo(_ foo: some Fooable & Quuxable, bar1: U, bar2: U) where S.AssociatedType == Baaz, U: Qaaxable { - print(foo, bar1, bar2) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterCanRemoveOneButNotOthers_onMultipleLines() { - let input = """ - func foo< - S: Baazable, - T: Fooable, - U: Barable - >(_ foo: T, bar1: U, bar2: U) where - S.AssociatedType == Baaz, - T: Quuxable, - U: Qaaxable - { - print(foo, bar1, bar2) - } - """ - - let output = """ - func foo< - S: Baazable, - U: Barable - >(_ foo: some Fooable & Quuxable, bar1: U, bar2: U) where - S.AssociatedType == Baaz, - U: Qaaxable - { - print(foo, bar1, bar2) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithUnknownAssociatedTypeConstraint() { - // If we knew that `T.AssociatedType` was the protocol's primary - // associated type we could update this to `value: some Fooable`, - // but we don't necessarily have that type information available. - // - If primary associated types become very widespread, it may make - // sense to assume (or have an option to assume) that this would work. - let input = """ - func foo(_ value: T) where T.AssociatedType == Bar { - print(value) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithAssociatedTypeConformance() { - // There is no opaque generic parameter syntax that supports this type of constraint - let input = """ - func foo(_ value: T) where T.AssociatedType: Bar { - print(value) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithKnownAssociatedTypeConstraint() { - // For known types (like those in the standard library), - // we are able to know their primary associated types - let input = """ - func foo(_ value: T) where T.Element == Foo { - print(value) - } - """ - - let output = """ - func foo(_ value: some Collection) { - print(value) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParameterWithAssociatedTypeConstraint() { - let input = """ - func foo>(_: T) {} - func bar(_: T) where T: Collection {} - func baaz(_: T) where T == any Collection {} - """ - - let output = """ - func foo(_: some Collection) {} - func bar(_: some Collection) {} - func baaz(_: any Collection) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testGenericTypeUsedInMultipleParameters() { - let input = """ - func foo(_ first: T, second: T) { - print(first, second) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericTypeUsedInClosureMultipleTimes() { - let input = """ - func foo(_ closure: (T) -> T) { - closure(foo) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericTypeUsedAsReturnType() { - // A generic used as a return type is different from an opaque result type (SE-244). - // In `-> T where T: Fooable`, the generic type is caller-specified, but with - // `-> some Fooable` the generic type is specified by the function implementation. - // Because those represent different concepts, we can't convert between them. - let input = """ - func foo() -> T { - // ... - } - - func bar() -> T where T: Barable { - // ... - } - - func baaz() -> Set> { - // ... - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericTypeUsedAsReturnTypeAndParameter() { - // Since we can't change the return value, we can't change any of the use cases of T - let input = """ - func foo(_ value: T) -> T { - value - } - - func bar(_ value: T) -> T where T: Barable { - value - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericTypeWithClosureInWhereClauseDoesntCrash() { - let input = """ - struct Foo { - func bar(_ value: V) where U == @Sendable (V) -> Int {} - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericExtensionSameTypeConstraint() { - let input = """ - func foo(_ u: U) where U == String { - print(u) - } - """ - - let output = """ - func foo(_ u: String) { - print(u) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testGenericExtensionSameTypeGenericConstraint() { - let input = """ - func foo(_ u: U, _ v: V) where U == V { - print(u, v) - } - - func foo(_ u: U, _ v: V) where V == U { - print(u, v) - } - """ - - let output = """ - func foo(_ u: V, _ v: V) { - print(u, v) - } - - func foo(_ u: U, _ v: U) { - print(u, v) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testIssue1269() { - let input = """ - func bar( - _ value: V, - _ work: () -> R - ) -> R - where Value == @Sendable () -> V, - V: Sendable - { - work() - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testVariadicParameterNotConvertedToOpaqueGeneric() { - let input = """ - func variadic(_ t: T...) { - print(t) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testNonGenericVariadicParametersDoesntPreventUsingOpaqueGenerics() { - let input = """ - func variadic(t: Any..., u: U, v: Any...) { - print(t, u, v) - } - """ - - let output = """ - func variadic(t: Any..., u: some Any, v: Any...) { - print(t, u, v) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testIssue1275() { - let input = """ - func loggedKeypath( - by _: KeyPath..., - actionKeyword _: UserActionKeyword, - identifier _: String - ) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testIssue1278() { - let input = """ - public struct Foo { - public func withValue( - _: V, - operation _: () throws -> R - ) rethrows -> R - where Value == @Sendable () -> V, - V: Sendable - {} - - public func withValue( - _: V, - operation _: () async throws -> R - ) async rethrows -> R - where Value == () -> V - {} - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testIssue1392() { - let input = """ - public struct Ref {} - - public extension Ref { - static func weak( - _: Base, - _: ReferenceWritableKeyPath - ) -> Ref where T? == Value {} - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testIssue1684() { - let input = """ - @_specialize(where S == Int) - func foo>(t: S) { - print(t) - } - """ - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericSimplifiedInMethodWithAttributeOrMacro() { - let input = """ - @MyResultBuilder - func foo(foo: T, bar: U) -> MyResult { - foo - bar - } - - @MyFunctionBodyMacro(withArgument: true) - func foo(foo: T, bar: U) { - print(foo, bar) - } - """ - - let output = """ - @MyResultBuilder - func foo(foo: some Foo, bar: some Bar) -> MyResult { - foo - bar - } - - @MyFunctionBodyMacro(withArgument: true) - func foo(foo: some Foo, bar: some Bar) { - print(foo, bar) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - // MARK: - genericExtensions - - func testGenericExtensionNotModifiedBeforeSwift5_7() { - let input = "extension Array where Element == Foo {}" - - let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testUpdatesArrayGenericExtensionToAngleBracketSyntax() { - let input = "extension Array where Element == Foo {}" - let output = "extension Array {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) - } - - func testUpdatesOptionalGenericExtensionToAngleBracketSyntax() { - let input = "extension Optional where Wrapped == Foo {}" - let output = "extension Optional {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) - } - - func testUpdatesArrayGenericExtensionToAngleBracketSyntaxWithSelf() { - let input = "extension Array where Self.Element == Foo {}" - let output = "extension Array {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) - } - - func testUpdatesArrayWithGenericElement() { - let input = "extension Array where Element == Foo {}" - let output = "extension Array> {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) - } - - func testUpdatesDictionaryGenericExtensionToAngleBracketSyntax() { - let input = "extension Dictionary where Key == Foo, Value == Bar {}" - let output = "extension Dictionary {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) - } - - func testRequiresAllGenericTypesToBeProvided() { - // No type provided for `Value`, so we can't use the angle bracket syntax - let input = "extension Dictionary where Key == Foo {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .genericExtensions, options: options) - } - - func testHandlesNestedCollectionTypes() { - let input = "extension Array where Element == [[Foo: Bar]] {}" - let output = "extension Array<[[Foo: Bar]]> {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) - } - - func testDoesntUpdateIneligibleConstraints() { - // This could potentially by `extension Optional` in a future language version - // but that syntax isn't implemented as of Swift 5.7 - let input = "extension Optional where Wrapped: Fooable {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .genericExtensions, options: options) - } - - func testPreservesOtherConstraintsInWhereClause() { - let input = "extension Collection where Element == String, Index == Int {}" - let output = "extension Collection where Index == Int {}" - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options) - } - - func testSupportsUserProvidedGenericTypes() { - let input = """ - extension StateStore where State == FooState, Action == FooAction {} - extension LinkedList where Element == Foo {} - """ - let output = """ - extension StateStore {} - extension LinkedList {} - """ - - let options = FormatOptions( - genericTypes: "LinkedList;StateStore", - swiftVersion: "5.7" - ) - testFormatting(for: input, output, rule: .genericExtensions, options: options) - } - - func testSupportsMultilineUserProvidedGenericTypes() { - let input = """ - extension Reducer where - State == MyFeatureState, - Action == MyFeatureAction, - Environment == ApplicationEnvironment - {} - """ - let output = """ - extension Reducer {} - """ - - let options = FormatOptions( - genericTypes: "Reducer", - swiftVersion: "5.7" - ) - testFormatting(for: input, output, rule: .genericExtensions, options: options) - } - - func testOpaqueGenericParametersRuleSuccessfullyTerminatesInSampleCode() { - let input = """ - class Service { - public func run() {} - private let foo: Foo - private func a() -> Eventual {} - private func b() -> Eventual {} - private func c() -> Eventual {} - private func d() -> Eventual {} - private func e() -> Eventual {} - private func f() -> Eventual {} - private func g() -> Eventual {} - private func h() -> Eventual {} - private func i() {} - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericParameterUsedInConstraintOfOtherTypeNotChanged() { - let input = """ - func combineResults( - _: Potential, - _: Potential - ) -> Potential where - Success == (Result, Result), - Failure == Never - {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericParameterInheritedFromContextNotRemoved() { - let input = """ - func assign( - on _: DispatchQueue, - to _: AssignTarget, - at _: ReferenceWritableKeyPath - ) where Value: Equatable {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericParameterUsedInBodyNotRemoved() { - let input = """ - func foo(_ value: T) { - typealias TTT = T - let casted = value as TTT - print(casted) - } - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testGenericParameterUsedAsClosureParameterNotRemoved() { - let input = """ - func foo(_: (Foo) -> Void) {} - func bar(_: (Foo) throws -> Void) {} - func baz(_: (Foo) throws(Bar) -> Void) {} - func baaz(_: (Foo) async -> Void) {} - func qux(_: (Foo) async throws -> Void) {} - func quux(_: (Foo) async throws(Bar) -> Void) {} - func qaax(_: ([Foo]) -> Void) {} - func qaax(_: ((Foo, Bar)) -> Void) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testFinalGenericParamRemovedProperlyWithoutHangingComma() { - let input = """ - func foo( - bar _: (Bar) -> Void, - baaz _: Baaz - ) {} - """ - - let output = """ - func foo( - bar _: (Bar) -> Void, - baaz _: some Any - ) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testAddsParensAroundTypeIfNecessary() { - let input = """ - func foo(_: Foo.Type) {} - func bar(_: Foo?) {} - """ - - let output = """ - func foo(_: (some Any).Type) {} - func bar(_: (some Any)?) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testHandlesSingleExactTypeGenericConstraint() { - let input = """ - func foo(with _: T) -> Foo where T == Dependencies {} - """ - - let output = """ - func foo(with _: Dependencies) -> Foo {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testGenericConstraintThatIsGeneric() { - let input = """ - class Foo {} - func foo>(_: T) {} - class Bar {} - func bar>(_: T) {} - """ - - let output = """ - class Foo {} - func foo(_: some Foo) {} - class Bar {} - func bar(_: some Bar) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, options: options) - } - - func testDoesntChangeTypeWithConstraintThatReferencesItself() { - // This is a weird one but in the actual code this comes from `ViewModelContext` is both defined - // on the parent type of this declaration (where it has additional important constraints), - // and again in the method itself. Changing this to an opaque parameter breaks the build, because - // it loses the generic constraints applied by the parent type. - let input = """ - func makeSections>(_: ViewModelContext) {} - """ - - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) - } - - func testOpaqueGenericParametersDoesntleaveTrailingComma() { - let input = "func f(x: U) -> T where T: A, U: B {}" - let output = "func f(x: some B) -> T where T: A {}" - let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .opaqueGenericParameters, - options: options, exclude: [.unusedArguments]) - } - - // MARK: docComments - - func testConvertCommentsToDocComments() { - let input = """ - // Multi-line comment before class with - // attribute between comment and declaration - @objc - class Foo { - // Single line comment before property - let foo = Foo() - - // Single line comment before property with property wrapper - @State - let bar = Bar() - - // Single line comment - func foo() {} - - /* Single line block comment before method */ - func baaz() {} - - /* - Multi-line block comment before method with attribute. - - This comment has a blank line in it. - */ - @nonobjc - func baaz() {} - } - - // Enum with a case - enum Quux { - // Documentation on an enum case - case quux - } - - extension Collection where Element: Foo { - // Property in extension with where clause - var foo: Foo { - first! - } - } - """ - - let output = """ - /// Multi-line comment before class with - /// attribute between comment and declaration - @objc - class Foo { - /// Single line comment before property - let foo = Foo() - - /// Single line comment before property with property wrapper - @State - let bar = Bar() - - /// Single line comment - func foo() {} - - /** Single line block comment before method */ - func baaz() {} - - /** - Multi-line block comment before method with attribute. - - This comment has a blank line in it. - */ - @nonobjc - func baaz() {} - } - - /// Enum with a case - enum Quux { - /// Documentation on an enum case - case quux - } - - extension Collection where Element: Foo { - /// Property in extension with where clause - var foo: Foo { - first! - } - } - """ - - testFormatting(for: input, output, rule: .docComments, - exclude: [.spaceInsideComments, .propertyType]) - } - - func testConvertDocCommentsToComments() { - let input = """ - /// Comment not associated with class - - class Foo { - /** Comment not associated with function */ - - func bar() { - /// Comment inside function declaration. - /// This one is multi-line. - - /// This comment is inside a function and precedes a declaration, - /// but we don't want to use doc comments inside property or function - /// scopes since users typically don't think of these as doc comments, - /// and this also breaks a common pattern where comments introduce - /// an entire following block of code (not just the property) - let bar: Bar? = Bar() - print(bar) - } - - var baaz: Baaz { - /// Comment inside property getter - let baazImpl = Baaz() - return baazImpl - } - - var quux: Quux { - didSet { - /// Comment inside didSet - let newQuux = Quux() - print(newQuux) - } - } - } - """ - - let output = """ - // Comment not associated with class - - class Foo { - /* Comment not associated with function */ - - func bar() { - // Comment inside function declaration. - // This one is multi-line. - - // This comment is inside a function and precedes a declaration, - // but we don't want to use doc comments inside property or function - // scopes since users typically don't think of these as doc comments, - // and this also breaks a common pattern where comments introduce - // an entire following block of code (not just the property) - let bar: Bar? = Bar() - print(bar) - } - - var baaz: Baaz { - // Comment inside property getter - let baazImpl = Baaz() - return baazImpl - } - - var quux: Quux { - didSet { - // Comment inside didSet - let newQuux = Quux() - print(newQuux) - } - } - } - """ - - testFormatting(for: input, output, rule: .docComments, - exclude: [.spaceInsideComments, .redundantProperty, .propertyType]) - } - - func testPreservesDocComments() { - let input = """ - /// Comment not associated with class - - class Foo { - /** Comment not associated with function */ - - // Documentation for function - func bar() { - /// Comment inside function declaration. - /// This one is multi-line. - - /// This comment is inside a function and precedes a declaration. - /// Since the option to preserve doc comments is enabled, - /// it should be left as-is. - let bar: Bar? = Bar() - print(bar) - } - - // Documentation for property - var baaz: Baaz { - /// Comment inside property getter - let baazImpl = Baaz() - return baazImpl - } - - // Documentation for function - var quux: Quux { - didSet { - /// Comment inside didSet - let newQuux = Quux() - print(newQuux) - } - } - } - """ - - let output = """ - /// Comment not associated with class - - class Foo { - /** Comment not associated with function */ - - /// Documentation for function - func bar() { - /// Comment inside function declaration. - /// This one is multi-line. - - /// This comment is inside a function and precedes a declaration. - /// Since the option to preserve doc comments is enabled, - /// it should be left as-is. - let bar: Bar? = Bar() - print(bar) - } - - /// Documentation for property - var baaz: Baaz { - /// Comment inside property getter - let baazImpl = Baaz() - return baazImpl - } - - /// Documentation for function - var quux: Quux { - didSet { - /// Comment inside didSet - let newQuux = Quux() - print(newQuux) - } - } - } - """ - - let options = FormatOptions(preserveDocComments: true) - testFormatting(for: input, output, rule: .docComments, options: options, exclude: [.spaceInsideComments, .redundantProperty, .propertyType]) - } - - func testDoesntConvertCommentBeforeConsecutivePropertiesToDocComment() { - let input = """ - // Names of the planets - struct PlanetNames { - // Inner planets - let mercury = "Mercury" - let venus = "Venus" - let earth = "Earth" - let mars = "Mars" - - // Inner planets - let jupiter = "Jupiter" - let saturn = "Saturn" - let uranus = "Uranus" - let neptune = "Neptune" - - /// Dwarf planets - let pluto = "Pluto" - let ceres = "Ceres" - } - """ - - let output = """ - /// Names of the planets - struct PlanetNames { - // Inner planets - let mercury = "Mercury" - let venus = "Venus" - let earth = "Earth" - let mars = "Mars" - - // Inner planets - let jupiter = "Jupiter" - let saturn = "Saturn" - let uranus = "Uranus" - let neptune = "Neptune" - - /// Dwarf planets - let pluto = "Pluto" - let ceres = "Ceres" - } - """ - - testFormatting(for: input, output, rule: .docComments) - } - - func testConvertsCommentsToDocCommentsInConsecutiveDeclarations() { - let input = """ - // Names of the planets - enum PlanetNames { - // Mercuy - case mercury - // Venus - case venus - // Earth - case earth - // Mars - case mars - - // Jupiter - case jupiter - - // Saturn - case saturn - - // Uranus - case uranus - - // Neptune - case neptune - } - """ - - let output = """ - /// Names of the planets - enum PlanetNames { - /// Mercuy - case mercury - /// Venus - case venus - /// Earth - case earth - /// Mars - case mars - - /// Jupiter - case jupiter - - /// Saturn - case saturn - - /// Uranus - case uranus - - /// Neptune - case neptune - } - """ - - testFormatting(for: input, output, rule: .docComments) - } - - func testDoesntConvertCommentBeforeConsecutiveEnumCasesToDocComment() { - let input = """ - // Names of the planets - enum PlanetNames { - // Inner planets - case mercury - case venus - case earth - case mars - - // Inner planets - case jupiter - case saturn - case uranus - case neptune - - // Dwarf planets - case pluto - case ceres - } - """ - - let output = """ - /// Names of the planets - enum PlanetNames { - // Inner planets - case mercury - case venus - case earth - case mars - - // Inner planets - case jupiter - case saturn - case uranus - case neptune - - // Dwarf planets - case pluto - case ceres - } - """ - - testFormatting(for: input, output, rule: .docComments) - } - - func testDoesntConvertAnnotationCommentsToDocComments() { - let input = """ - // swiftformat:disable some_swift_format_rule - let testSwiftLint: Foo - - // swiftlint:disable some_swift_lint_rule - let testSwiftLint: Foo - - // sourcery:directive - let testSourcery: Foo - """ - - testFormatting(for: input, rule: .docComments) - } - - func testDoesntConvertTODOCommentsToDocComments() { - let input = """ - // TODO: Clean up this mess - func doSomething() {} - """ - - testFormatting(for: input, rule: .docComments) - } - - func testDoesntConvertCommentAfterTODOToDocComments() { - let input = """ - // TODO: Clean up this mess - // because it's bothering me - func doSomething() {} - """ - testFormatting(for: input, rule: .docComments) - } - - func testDoesntConvertCommentBeforeTODOToDocComments() { - let input = """ - // Something, something - // TODO: Clean up this mess - func doSomething() {} - """ - testFormatting(for: input, rule: .docComments) - } - - func testConvertNoteCommentsToDocComments() { - let input = """ - // Does something - // Note: not really - func doSomething() {} - """ - let output = """ - /// Does something - /// Note: not really - func doSomething() {} - """ - testFormatting(for: input, output, rule: .docComments) - } - - func testConvertURLCommentsToDocComments() { - let input = """ - // Does something - // http://example.com - func doSomething() {} - """ - let output = """ - /// Does something - /// http://example.com - func doSomething() {} - """ - testFormatting(for: input, output, rule: .docComments) - } - - func testMultilineDocCommentReplaced() { - let input = """ - // A class - // With some other details - class Foo {} - """ - let output = """ - /// A class - /// With some other details - class Foo {} - """ - testFormatting(for: input, output, rule: .docComments) - } - - func testCommentWithBlankLineNotReplaced() { - let input = """ - // A class - // With some other details - - class Foo {} - """ - testFormatting(for: input, rule: .docComments) - } - - func testDocCommentsAssociatedTypeNotReplaced() { - let input = """ - /// An interesting comment about Foo. - associatedtype Foo - """ - testFormatting(for: input, rule: .docComments) - } - - func testNonDocCommentsAssociatedTypeReplaced() { - let input = """ - // An interesting comment about Foo. - associatedtype Foo - """ - let output = """ - /// An interesting comment about Foo. - associatedtype Foo - """ - testFormatting(for: input, output, rule: .docComments) - } - - func testConditionalDeclarationCommentNotReplaced() { - let input = """ - if let foo = bar, - // baz - let baz = bar - {} - """ - testFormatting(for: input, rule: .docComments) - } - - func testCommentInsideSwitchCaseNotReplaced() { - let input = """ - switch foo { - case .bar: - // bar - let bar = baz() - - default: - // baz - let baz = quux() - } - """ - testFormatting(for: input, rule: .docComments) - } - - func testDocCommentInsideIfdef() { - let input = """ - #if DEBUG - // return 3 - func returnNumber() { 3 } - #endif - """ - let output = """ - #if DEBUG - /// return 3 - func returnNumber() { 3 } - #endif - """ - testFormatting(for: input, output, rule: .docComments) - } - - func testDocCommentInsideIfdefElse() { - let input = """ - #if DEBUG - #elseif PROD - /// return 2 - func returnNumber() { 2 } - #else - /// return 3 - func returnNumber() { 3 } - #endif - """ - testFormatting(for: input, rule: .docComments) - } - - func testDocCommentForMacro() { - let input = """ - /// Adds a static `logger` member to the type. - @attached(member, names: named(logger)) public macro StaticLogger( - subsystem: String? = nil, - category: String? = nil - ) = #externalMacro(module: "StaticLoggerMacros", type: "StaticLogger") - """ - testFormatting(for: input, rule: .docComments) - } - - // MARK: - conditionalAssignment - - func testDoesntConvertIfStatementAssignmentSwift5_8() { - let input = """ - let foo: Foo - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.8") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testConvertsIfStatementAssignment() { - let input = """ - let foo: Foo - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - """ - let output = """ - let foo: Foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) - } - - func testConvertsSimpleSwitchStatementAssignment() { - let input = """ - let foo: Foo - switch condition { - case true: - foo = Foo("foo") - case false: - foo = Foo("bar") - } - """ - let output = """ - let foo: Foo = switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) - } - - func testConvertsTrivialSwitchStatementAssignment() { - let input = """ - let foo: Foo - switch enumWithOnceCase(let value) { - case singleCase: - foo = value - } - """ - let output = """ - let foo: Foo = switch enumWithOnceCase(let value) { - case singleCase: - value - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) - } - - func testConvertsNestedIfAndStatementAssignments() { - let input = """ - let foo: Foo - switch condition { - case true: - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - - case false: - switch condition { - case true: - foo = Foo("baaz") - - case false: - if condition { - foo = Foo("quux") - } else { - foo = Foo("quack") - } - } - } - """ - let output = """ - let foo: Foo = switch condition { - case true: - if condition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - switch condition { - case true: - Foo("baaz") - - case false: - if condition { - Foo("quux") - } else { - Foo("quack") - } - } - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.redundantType, .wrapMultilineConditionalAssignment]) - } - - func testConvertsIfStatementAssignmentPreservingComment() { - let input = """ - let foo: Foo - // This is a comment between the property and condition - if condition { - foo = Foo("foo") - } else { - foo = Foo("bar") - } - """ - let output = """ - let foo: Foo - // This is a comment between the property and condition - = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.indent, .redundantType, .wrapMultilineConditionalAssignment]) - } - - func testDoesntConvertsIfStatementAssigningMultipleProperties() { - let input = """ - let foo: Foo - let bar: Bar - if condition { - foo = Foo("foo") - bar = Bar("foo") - } else { - foo = Foo("bar") - bar = Bar("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertsIfStatementAssigningDifferentProperties() { - let input = """ - var foo: Foo? - var bar: Bar? - if condition { - foo = Foo("foo") - } else { - bar = Bar("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertNonExhaustiveIfStatementAssignment1() { - let input = """ - var foo: Foo? - if condition { - foo = Foo("foo") - } else if someOtherCondition { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertNonExhaustiveIfStatementAssignment2() { - let input = """ - var foo: Foo? - if condition { - if condition { - foo = Foo("foo") - } - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementAssignment1() { - let input = """ - let foo: Foo - if condition { - foo = Foo("foo") - print("Multi-statement") - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementAssignment2() { - let input = """ - let foo: Foo - switch condition { - case true: - foo = Foo("foo") - print("Multi-statement") - - case false: - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementAssignment3() { - let input = """ - let foo: Foo - if condition { - if condition { - foo = Foo("bar") - } else { - foo = Foo("baaz") - } - print("Multi-statement") - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementAssignment4() { - let input = """ - let foo: Foo - switch condition { - case true: - if condition { - foo = Foo("bar") - } else { - foo = Foo("baaz") - } - print("Multi-statement") - - case false: - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithStringLiteral() { - let input = """ - let text: String - if conditionOne { - text = "Hello World!" - doSomeStuffHere() - } else { - text = "Goodbye!" - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithCollectionLiteral() { - let input = """ - let text: [String] - if conditionOne { - text = [] - doSomeStuffHere() - } else { - text = ["Goodbye!"] - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithIntLiteral() { - let input = """ - let number: Int? - if conditionOne { - number = 5 - doSomeStuffHere() - } else { - number = 10 - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithNilLiteral() { - let input = """ - let number: Int? - if conditionOne { - number = nil - doSomeStuffHere() - } else { - number = 10 - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertMultiStatementIfStatementWithOtherProperty() { - let input = """ - let number: Int? - if conditionOne { - number = someOtherProperty - doSomeStuffHere() - } else { - number = 10 - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertConditionalCastInSwift5_9() { - // The following code doesn't compile in Swift 5.9 due to this issue: - // https://github.com/apple/swift/issues/68764 - // - // let result = if condition { - // foo as? String - // } else { - // "bar" - // } - // - let input = """ - let result1: String? - if condition { - result1 = foo as? String - } else { - result1 = "bar" - } - - let result2: String? - switch condition { - case true: - result2 = foo as? String - case false: - result2 = "bar" - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testAllowsAsWithinInnerScope() { - let input = """ - let result: String? - switch condition { - case true: - result = method(string: foo as? String) - case false: - result = "bar" - } - """ - - let output = """ - let result: String? = switch condition { - case true: - method(string: foo as? String) - case false: - "bar" - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) - } - - // TODO: update branches parser to handle this case properly - func testIgnoreSwitchWithConditionalCompilation() { - let input = """ - func foo() -> String? { - let result: String? - switch condition { - #if os(macOS) - case .foo: - result = method(string: foo as? String) - #endif - case .bar: - return nil - } - return result - } - """ - - let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - // TODO: update branches parser to handle this scenario properly - func testIgnoreSwitchWithConditionalCompilation2() { - let input = """ - func foo() -> String? { - let result: String? - switch condition { - case .foo: - result = method(string: foo as? String) - #if os(macOS) - case .bar: - return nil - #endif - } - return result - } - """ - - let options = FormatOptions(ifdefIndent: .noIndent, swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testConvertsConditionalCastInSwift5_10() { - let input = """ - let result1: String? - if condition { - result1 = foo as? String - } else { - result1 = "bar" - } - - let result2: String? - switch condition { - case true: - result2 = foo as? String - case false: - result2 = "bar" - } - """ - - let output = """ - let result1: String? = if condition { - foo as? String - } else { - "bar" - } - - let result2: String? = switch condition { - case true: - foo as? String - case false: - "bar" - } - """ - - let options = FormatOptions(swiftVersion: "5.10") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment]) - } - - func testConvertsSwitchWithDefaultCase() { - let input = """ - let foo: Foo - switch condition { - case .foo: - foo = Foo("foo") - case .bar: - foo = Foo("bar") - default: - foo = Foo("default") - } - """ - - let output = """ - let foo: Foo = switch condition { - case .foo: - Foo("foo") - case .bar: - Foo("bar") - default: - Foo("default") - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType]) - } - - func testConvertsSwitchWithUnknownDefaultCase() { - let input = """ - let foo: Foo - switch condition { - case .foo: - foo = Foo("foo") - case .bar: - foo = Foo("bar") - @unknown default: - foo = Foo("default") - } - """ - - let output = """ - let foo: Foo = switch condition { - case .foo: - Foo("foo") - case .bar: - Foo("bar") - @unknown default: - Foo("default") - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, output, rule: .conditionalAssignment, options: options, exclude: [.wrapMultilineConditionalAssignment, .redundantType]) - } - - func testPreservesSwitchWithReturnInDefaultCase() { - let input = """ - let foo: Foo - switch condition { - case .foo: - foo = Foo("foo") - case .bar: - foo = Foo("bar") - default: - return - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testPreservesSwitchWithReturnInUnknownDefaultCase() { - let input = """ - let foo: Foo - switch condition { - case .foo: - foo = Foo("foo") - case .bar: - foo = Foo("bar") - @unknown default: - return - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testDoesntConvertIfStatementWithForLoopInBranch() { - let input = """ - var foo: Foo? - if condition { - foo = Foo("foo") - for foo in foos { - print(foo) - } - } else { - foo = Foo("bar") - } - """ - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rule: .conditionalAssignment, options: options) - } - - func testConvertsIfStatementNotFollowingPropertyDefinition() { - let input = """ - if condition { - property = Foo("foo") - } else { - property = Foo("bar") - } - """ - - let output = """ - property = - if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - func testPreservesIfStatementNotFollowingPropertyDefinitionWithInvalidBranch() { - let input = """ - if condition { - property = Foo("foo") - } else { - property = Foo("bar") - print("A second expression on this branch") - } - - if condition { - property = Foo("foo") - } else { - if otherCondition { - property = Foo("foo") - } - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - func testPreservesNonExhaustiveIfStatementNotFollowingPropertyDefinition() { - let input = """ - if condition { - property = Foo("foo") - } - - if condition { - property = Foo("foo") - } else if otherCondition { - property = Foo("foo") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - func testConvertsSwitchStatementNotFollowingPropertyDefinition() { - let input = """ - switch condition { - case true: - property = Foo("foo") - case false: - property = Foo("bar") - } - """ - - let output = """ - property = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - func testConvertsSwitchStatementWithComplexLValueNotFollowingPropertyDefinition() { - let input = """ - switch condition { - case true: - property?.foo!.bar["baaz"] = Foo("foo") - case false: - property?.foo!.bar["baaz"] = Foo("bar") - } - """ - - let output = """ - property?.foo!.bar["baaz"] = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - func testDoesntMergePropertyWithUnrelatedCondition() { - let input = """ - let differentProperty: Foo - switch condition { - case true: - property = Foo("foo") - case false: - property = Foo("bar") - } - """ - - let output = """ - let differentProperty: Foo - property = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - func testConvertsNestedIfSwitchStatementNotFollowingPropertyDefinition() { - let input = """ - switch firstCondition { - case true: - if secondCondition { - property = Foo("foo") - } else { - property = Foo("bar") - } - - case false: - if thirdCondition { - property = Foo("baaz") - } else { - property = Foo("quux") - } - } - """ - - let output = """ - property = - switch firstCondition { - case true: - if secondCondition { - Foo("foo") - } else { - Foo("bar") - } - - case false: - if thirdCondition { - Foo("baaz") - } else { - Foo("quux") - } - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, [output], rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - func testPreservesSwitchConditionWithIneligibleBranch() { - let input = """ - switch firstCondition { - case true: - // Even though this condition is eligible to be converted, - // we leave it as-is because it's nested in an ineligible condition. - if secondCondition { - property = Foo("foo") - } else { - property = Foo("bar") - } - - case false: - if thirdCondition { - property = Foo("baaz") - } else { - property = Foo("quux") - print("A second expression on this branch") - } - } - """ - - let options = FormatOptions(conditionalAssignmentOnlyAfterNewProperties: false, swiftVersion: "5.9") - testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - func testPreservesIfConditionWithIneligibleBranch() { - let input = """ - if firstCondition { - // Even though this condition is eligible to be converted, - // we leave it as-is because it's nested in an ineligible condition. - if secondCondition { - property = Foo("foo") - } else { - property = Foo("bar") - } - } else { - if thirdCondition { - property = Foo("baaz") - } else { - property = Foo("quux") - print("A second expression on this branch") - } - } - """ - - let options = FormatOptions(swiftVersion: "5.9") - testFormatting(for: input, rules: [.conditionalAssignment, .wrapMultilineConditionalAssignment, .indent], options: options) - } - - // MARK: - preferForLoop - - func testConvertSimpleForEachToForLoop() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { string in - print(string) - } - - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { (string: String) in - print(string) - } - """ - - let output = """ - let placeholderStrings = ["foo", "bar", "baaz"] - for string in placeholderStrings { - print(string) - } - - let placeholderStrings = ["foo", "bar", "baaz"] - for string in placeholderStrings { - print(string) - } - """ - - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testConvertAnonymousForEachToForLoop() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { - print($0) - } - - potatoes.forEach({ $0.bake() }) - """ - - let output = """ - let placeholderStrings = ["foo", "bar", "baaz"] - for placeholderString in placeholderStrings { - print(placeholderString) - } - - potatoes.forEach({ $0.bake() }) - """ - - testFormatting(for: input, output, rule: .preferForLoop, exclude: [.trailingClosures]) - } - - func testNoConvertAnonymousForEachToForLoop() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { - print($0) - } - - potatoes.forEach({ $0.bake() }) - """ - - let options = FormatOptions(preserveAnonymousForEach: true, preserveSingleLineForEach: false) - testFormatting(for: input, rule: .preferForLoop, options: options, exclude: [.trailingClosures]) - } - - func testConvertSingleLineForEachToForLoop() { - let input = "potatoes.forEach({ item in item.bake() })" - let output = "for item in potatoes { item.bake() }" - - let options = FormatOptions(preserveSingleLineForEach: false) - testFormatting(for: input, output, rule: .preferForLoop, options: options, - exclude: [.wrapLoopBodies]) - } - - func testConvertSingleLineAnonymousForEachToForLoop() { - let input = "potatoes.forEach({ $0.bake() })" - let output = "for potato in potatoes { potato.bake() }" - - let options = FormatOptions(preserveSingleLineForEach: false) - testFormatting(for: input, output, rule: .preferForLoop, options: options, - exclude: [.wrapLoopBodies]) - } - - func testConvertNestedForEach() { - let input = """ - let nestedArrays = [[1, 2], [3, 4]] - nestedArrays.forEach { - $0.forEach { - $0.forEach { - print($0) - } - } - } - """ - - let output = """ - let nestedArrays = [[1, 2], [3, 4]] - for nestedArray in nestedArrays { - for item in nestedArray { - for item in item { - print(item) - } - } - } - """ - - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testDefaultNameAlreadyUsedInLoopBody() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { - let placeholderString = $0.uppercased() - print(placeholderString, $0) - } - """ - - let output = """ - let placeholderStrings = ["foo", "bar", "baaz"] - for item in placeholderStrings { - let placeholderString = item.uppercased() - print(placeholderString, item) - } - """ - - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testIgnoreLoopsWithCaptureListForNow() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { [someCapturedValue = fooBar] in - print($0, someCapturedValue) - } - """ - testFormatting(for: input, rule: .preferForLoop) - } - - func testRemoveAllPrefixFromLoopIdentifier() { - let input = """ - allWindows.forEach { - print($0) - } - """ - - let output = """ - for window in allWindows { - print(window) - } - """ - - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testConvertsReturnToContinue() { - let input = """ - let placeholderStrings = ["foo", "bar", "baaz"] - placeholderStrings.forEach { - func capitalize(_ value: String) -> String { - return value.uppercased() - } - - if $0 == "foo" { - return - } else { - print(capitalize($0)) - } - } - """ - - let output = """ - let placeholderStrings = ["foo", "bar", "baaz"] - for placeholderString in placeholderStrings { - func capitalize(_ value: String) -> String { - return value.uppercased() - } - - if placeholderString == "foo" { - continue - } else { - print(capitalize(placeholderString)) - } - } - """ - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testHandlesForEachOnChainedProperties() { - let input = """ - let bar = foo.bar - bar.baaz.quux.strings.forEach { - print($0) - } - """ - - let output = """ - let bar = foo.bar - for string in bar.baaz.quux.strings { - print(string) - } - """ - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testHandlesForEachOnFunctionCallResult() { - let input = """ - let bar = foo.bar - foo.item().bar[2].baazValues(option: true).forEach { - print($0) - } - """ - - let output = """ - let bar = foo.bar - for baazValue in foo.item().bar[2].baazValues(option: true) { - print(baazValue) - } - """ - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testHandlesForEachOnSubscriptResult() { - let input = """ - let bar = foo.bar - foo.item().bar[2].dictionary["myValue"].forEach { - print($0) - } - """ - - let output = """ - let bar = foo.bar - for item in foo.item().bar[2].dictionary["myValue"] { - print(item) - } - """ - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testHandlesForEachOnArrayLiteral() { - let input = """ - let quux = foo.bar.baaz.quux - ["foo", "bar", "baaz", quux].forEach { - print($0) - } - """ - - let output = """ - let quux = foo.bar.baaz.quux - for item in ["foo", "bar", "baaz", quux] { - print(item) - } - """ - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testHandlesForEachOnCurriedFunctionWithSubscript() { - let input = """ - let quux = foo.bar.baaz.quux - foo(bar)(baaz)["item"].forEach { - print($0) - } - """ - - let output = """ - let quux = foo.bar.baaz.quux - for item in foo(bar)(baaz)["item"] { - print(item) - } - """ - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testHandlesForEachOnArrayLiteralInParens() { - let input = """ - let quux = foo.bar.baaz.quux - (["foo", "bar", "baaz", quux]).forEach { - print($0) - } - """ - - let output = """ - let quux = foo.bar.baaz.quux - for item in (["foo", "bar", "baaz", quux]) { - print(item) - } - """ - testFormatting(for: input, output, rule: .preferForLoop, exclude: [.redundantParens]) - } - - func testPreservesForEachAfterMultilineChain() { - let input = """ - placeholderStrings - .filter { $0.style == .fooBar } - .map { $0.uppercased() } - .forEach { print($0) } - - placeholderStrings - .filter({ $0.style == .fooBar }) - .map({ $0.uppercased() }) - .forEach({ print($0) }) - """ - testFormatting(for: input, rule: .preferForLoop, exclude: [.trailingClosures]) - } - - func testPreservesChainWithClosure() { - let input = """ - // Converting this to a for loop would result in unusual looking syntax like - // `for string in strings.map { $0.uppercased() } { print($0) }` - // which causes a warning to be emitted: "trailing closure in this context is - // confusable with the body of the statement; pass as a parenthesized argument - // to silence this warning". - strings.map { $0.uppercased() }.forEach { print($0) } - """ - testFormatting(for: input, rule: .preferForLoop) - } - - func testForLoopVariableNotUsedIfClashesWithKeyword() { - let input = """ - Foo.allCases.forEach { - print($0) - } - """ - let output = """ - for item in Foo.allCases { - print(item) - } - """ - testFormatting(for: input, output, rule: .preferForLoop) - } - - func testTryNotRemovedInThrowingForEach() { - let input = """ - try list().forEach { - print($0) - } - """ - testFormatting(for: input, rule: .preferForLoop) - } - - func testOptionalTryNotRemovedInThrowingForEach() { - let input = """ - try? list().forEach { - print($0) - } - """ - testFormatting(for: input, rule: .preferForLoop) - } - - func testAwaitNotRemovedInAsyncForEach() { - let input = """ - await list().forEach { - print($0) - } - """ - testFormatting(for: input, rule: .preferForLoop) - } - - func testForEachOverDictionary() { - let input = """ - let dict = ["a": "b"] - - dict.forEach { (header: (key: String, value: String)) in - print(header.key) - print(header.value) - } - """ - - let output = """ - let dict = ["a": "b"] - - for header in dict { - print(header.key) - print(header.value) - } - """ - - testFormatting(for: input, output, rule: .preferForLoop) - } - - // MARK: propertyType - - func testConvertsExplicitTypeToInferredType() { - let input = """ - let foo: Foo = .init() - let bar: Bar = .staticBar - let baaz: Baaz = .Example.default - let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) - - let dictionary: [Foo: Bar] = .init() - let array: [Foo] = .init() - let genericType: MyGenericType = .init() - """ - - let output = """ - let foo = Foo() - let bar = Bar.staticBar - let baaz = Baaz.Example.default - let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - - let dictionary = [Foo: Bar]() - let array = [Foo]() - let genericType = MyGenericType() - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) - } - - func testConvertsInferredTypeToExplicitType() { - let input = """ - let foo = Foo() - let bar = Bar.staticBar - let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - - let dictionary = [Foo: Bar]() - let array = [Foo]() - let genericType = MyGenericType() - """ - - let output = """ - let foo: Foo = .init() - let bar: Bar = .staticBar - let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) - - let dictionary: [Foo: Bar] = .init() - let array: [Foo] = .init() - let genericType: MyGenericType = .init() - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, output, rule: .propertyType, options: options) - } - - func testConvertsTypeMembersToExplicitType() { - let input = """ - struct Foo { - let foo = Foo() - let bar = Bar.staticBar - let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - - let dictionary = [Foo: Bar]() - let array = [Foo]() - let genericType = MyGenericType() - } - """ - - let output = """ - struct Foo { - let foo: Foo = .init() - let bar: Bar = .staticBar - let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) - - let dictionary: [Foo: Bar] = .init() - let array: [Foo] = .init() - let genericType: MyGenericType = .init() - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, output, rule: .propertyType, options: options) - } - - func testConvertsLocalsToImplicitType() { - let input = """ - struct Foo { - let foo = Foo() - - func bar() { - let bar: Bar = .staticBar - let quux: Quux = .quuxBulder(foo: .foo, bar: .bar) - - let dictionary: [Foo: Bar] = .init() - let array: [Foo] = .init() - let genericType: MyGenericType = .init() - } - } - """ - - let output = """ - struct Foo { - let foo: Foo = .init() - - func bar() { - let bar = Bar.staticBar - let quux = Quux.quuxBulder(foo: .foo, bar: .bar) - - let dictionary = [Foo: Bar]() - let array = [Foo]() - let genericType = MyGenericType() - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) - } - - func testPreservesInferredTypeFollowingTypeWithDots() { - let input = """ - let baaz = Baaz.Example.default - let color = Color.Theme.default - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPreservesExplicitTypeIfNoRHS() { - let input = """ - let foo: Foo - let bar: Bar - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPreservesImplicitTypeIfNoRHSType() { - let input = """ - let foo = foo() - let bar = bar - let int = 24 - let array = ["string"] - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPreservesImplicitForVoidAndTuples() { - let input = """ - let foo = Void() - let foo = (foo: "foo", bar: "bar").foo - let foo = ["bar", "baz"].quux(quuz) - let foo = [bar].first - let foo = [bar, baaz].first - let foo = ["foo": "bar"].first - let foo = [foo: bar].first - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .propertyType, options: options, exclude: [.void]) - } - - func testPreservesExplicitTypeIfUsingLocalValueOrLiteral() { - let input = """ - let foo: Foo = localFoo - let bar: Bar = localBar - let int: Int64 = 1234 - let number: CGFloat = 12.345 - let array: [String] = [] - let dictionary: [String: Int] = [:] - let tuple: (String, Int) = ("foo", 123) - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .propertyType, options: options, exclude: [.redundantType]) - } - - func testCompatibleWithRedundantTypeInferred() { - let input = """ - let foo: Foo = Foo() - """ - - let output = """ - let foo = Foo() - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, [output], rules: [.redundantType, .propertyType], options: options) - } - - func testCompatibleWithRedundantTypeExplicit() { - let input = """ - let foo: Foo = Foo() - """ - - let output = """ - let foo: Foo = .init() - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, [output], rules: [.redundantType, .propertyType], options: options) - } - - func testCompatibleWithRedundantTypeInferLocalsOnly() { - let input = """ - let foo: Foo = Foo.init() - let foo: Foo = .init() - - func bar() { - let baaz: Baaz = Baaz.init() - let baaz: Baaz = .init() - } - """ - - let output = """ - let foo: Foo = .init() - let foo: Foo = .init() - - func bar() { - let baaz = Baaz() - let baaz = Baaz() - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [.redundantType, .propertyType, .redundantInit], options: options) - } - - func testPropertyTypeWithIfExpressionDisabledByDefault() { - let input = """ - let foo: SomeTypeWithALongGenrericName = - if condition { - .init(bar) - } else { - .init(baaz) - } - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPropertyTypeWithIfExpression() { - let input = """ - let foo: Foo = - if condition { - .init(bar) - } else { - .init(baaz) - } - """ - - let output = """ - let foo = - if condition { - Foo(bar) - } else { - Foo(baaz) - } - """ - - let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) - } - - func testPropertyTypeWithSwitchExpression() { - let input = """ - let foo: Foo = - switch condition { - case true: - .init(bar) - case false: - .init(baaz) - } - """ - - let output = """ - let foo = - switch condition { - case true: - Foo(bar) - case false: - Foo(baaz) - } - """ - - let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) - } - - func testPreservesNonMatchingIfExpression() { - let input = """ - let foo: Foo = - if condition { - .init(bar) - } else { - [] // e.g. using ExpressibleByArrayLiteral - } - """ - - let options = FormatOptions(redundantType: .inferred, inferredTypesInConditionalExpressions: true) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPreservesExplicitOptionalType() { - // `let foo = Foo?.foo` doesn't work if `.foo` is defined on `Foo` but not `Foo?` - let input = """ - let optionalFoo1: Foo? = .foo - let optionalFoo2: Foo? = Foo.foo - let optionalFoo3: Foo! = .foo - let optionalFoo4: Foo! = Foo.foo - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPreservesTypeWithSeparateDeclarationAndProperty() { - let input = """ - var foo: Foo! - foo = Foo(afterDelay: { - print(foo) - }) - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPreservesTypeWithExistentialAny() { - let input = """ - protocol ShapeStyle {} - struct MyShapeStyle: ShapeStyle {} - - extension ShapeStyle where Self == MyShapeStyle { - static var myShape: MyShapeStyle { MyShapeStyle() } - } - - /// This compiles - let myShape1: any ShapeStyle = .myShape - - // This would fail with "error: static member 'myShape' cannot be used on protocol metatype '(any ShapeStyle).Type'" - // let myShape2 = (any ShapeStyle).myShape - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPreservesExplicitRightHandSideWithOperator() { - let input = """ - let value: ClosedRange = .zero ... 10 - let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge - let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() - let dynamicTypeSizeRange: ClosedRange = .convertFromLiteral(.large ... .xxxLarge) - """ - - let output = """ - let value: ClosedRange = .zero ... 10 - let dynamicTypeSizeRange: ClosedRange = .large ... .xxxLarge - let dynamicTypeSizeRange: ClosedRange = .large() ... .xxxLarge() - let dynamicTypeSizeRange = ClosedRange.convertFromLiteral(.large ... .xxxLarge) - """ - - let options = FormatOptions(redundantType: .inferred) - testFormatting(for: input, output, rule: .propertyType, options: options) - } - - func testPreservesInferredRightHandSideWithOperators() { - let input = """ - let foo = Foo().bar - let foo = Foo.bar.baaz.quux - let foo = Foo.bar ... baaz - """ - - let options = FormatOptions(redundantType: .explicit) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPreservesUserProvidedSymbolTypes() { - let input = """ - class Foo { - let foo = Foo() - let bar = Bar() - - func bar() { - let foo: Foo = .foo - let bar: Bar = .bar - let baaz: Baaz = .baaz - let quux: Quux = .quux - } - } - """ - - let output = """ - class Foo { - let foo = Foo() - let bar: Bar = .init() - - func bar() { - let foo: Foo = .foo - let bar = Bar.bar - let baaz: Baaz = .baaz - let quux: Quux = .quux - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["Foo", "Baaz", "quux"]) - testFormatting(for: input, output, rule: .propertyType, options: options) - } - - func testPreserveInitIfExplicitlyExcluded() { - let input = """ - class Foo { - let foo = Foo() - let bar = Bar.init() - let baaz = Baaz.baaz() - - func bar() { - let foo: Foo = .init() - let bar: Bar = .init() - let baaz: Baaz = .baaz() - } - } - """ - - let output = """ - class Foo { - let foo = Foo() - let bar = Bar.init() - let baaz: Baaz = .baaz() - - func bar() { - let foo: Foo = .init() - let bar: Bar = .init() - let baaz = Baaz.baaz() - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly, preserveSymbols: ["init"]) - testFormatting(for: input, output, rule: .propertyType, options: options, exclude: [.redundantInit]) - } - - func testClosureBodyIsConsideredLocal() { - let input = """ - foo { - let bar = Bar() - let baaz: Baaz = .init() - } - - foo(bar: bar, baaz: baaz, quux: { - let bar = Bar() - let baaz: Baaz = .init() - }) - - foo { - let bar = Bar() - let baaz: Baaz = .init() - } bar: { - let bar = Bar() - let baaz: Baaz = .init() - } - - class Foo { - let foo = Foo.bar { - let baaz = Baaz() - let baaz: Baaz = .init() - } - } - """ - - let output = """ - foo { - let bar = Bar() - let baaz = Baaz() - } - - foo(bar: bar, baaz: baaz, quux: { - let bar = Bar() - let baaz = Baaz() - }) - - foo { - let bar = Bar() - let baaz = Baaz() - } bar: { - let bar = Bar() - let baaz = Baaz() - } - - class Foo { - let foo: Foo = .bar { - let baaz = Baaz() - let baaz = Baaz() - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, [output], rules: [.propertyType, .redundantInit], options: options) - } - - func testIfGuardConditionsPreserved() { - let input = """ - if let foo = Foo(bar) { - let foo = Foo(bar) - } else if let foo = Foo(bar) { - let foo = Foo(bar) - } else { - let foo = Foo(bar) - } - - guard let foo = Foo(bar) else { - return - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, rule: .propertyType, options: options) - } - - func testPropertyObserversConsideredLocal() { - let input = """ - class Foo { - var foo: Foo { - get { - let foo = Foo(bar) - } - set { - let foo = Foo(bar) - } - willSet { - let foo = Foo(bar) - } - didSet { - let foo = Foo(bar) - } - } - } - """ - - let options = FormatOptions(redundantType: .inferLocalsOnly) - testFormatting(for: input, rule: .propertyType, options: options) - } - - // MARK: - docCommentsBeforeAttributes - - func testDocCommentsBeforeAttributes() { - let input = """ - @MainActor - /// Doc comment on this type declaration - public struct Baaz { - @available(*, deprecated) - /// Doc comment on this property declaration. - /// This comment spans multiple lines. - private var bar: Bar - - @FooBarMacro(arg1: true, arg2: .baaz) - /** - * Doc comment on this function declaration - */ - func foo() {} - } - """ - - let output = """ - /// Doc comment on this type declaration - @MainActor - public struct Baaz { - /// Doc comment on this property declaration. - /// This comment spans multiple lines. - @available(*, deprecated) - private var bar: Bar - - /** - * Doc comment on this function declaration - */ - @FooBarMacro(arg1: true, arg2: .baaz) - func foo() {} - } - """ - - testFormatting(for: input, output, rule: .docCommentsBeforeAttributes) - } - - func testDocCommentsBeforeMultipleAttributes() { - let input = """ - @MainActor @Macro(argument: true) @available(*, deprecated) - /// Doc comment on this function declaration after several attributes - public func foo() {} - - @MainActor - @Macro(argument: true) - @available(*, deprecated) - /// Doc comment on this function declaration after several attributes - public func bar() {} - """ - - let output = """ - /// Doc comment on this function declaration after several attributes - @MainActor @Macro(argument: true) @available(*, deprecated) - public func foo() {} - - /// Doc comment on this function declaration after several attributes - @MainActor - @Macro(argument: true) - @available(*, deprecated) - public func bar() {} - """ - - testFormatting(for: input, output, rule: .docCommentsBeforeAttributes) - } - - func testUpdatesCommentsAfterMark() { - let input = """ - import FooBarKit - - // MARK: - Foo - - @MainActor - /// Doc comment on this type declaration. - enum Foo { - - // MARK: Public - - @MainActor - /// Doc comment on this function declaration. - public func foo() {} - - // MARK: Private - - // TODO: This function also has a TODO comment. - @MainActor - /// Doc comment on this function declaration. - private func bar() {} - - } - """ - - let output = """ - import FooBarKit - - // MARK: - Foo - - /// Doc comment on this type declaration. - @MainActor - enum Foo { - - // MARK: Public - - /// Doc comment on this function declaration. - @MainActor - public func foo() {} - - // MARK: Private - - // TODO: This function also has a TODO comment. - /// Doc comment on this function declaration. - @MainActor - private func bar() {} - - } - """ - - testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) - } - - func testPreservesCommentsBetweenAttributes() { - let input = """ - @MainActor - /// Doc comment between attributes - @available(*, deprecated) - /// Doc comment before declaration - func bar() {} - - @MainActor /// Doc comment after main actor attribute - @available(*, deprecated) /// Doc comment after deprecation attribute - /// Doc comment before declaration - func bar() {} - """ - - let output = """ - /// Doc comment before declaration - @MainActor - /// Doc comment between attributes - @available(*, deprecated) - func bar() {} - - /// Doc comment before declaration - @MainActor /// Doc comment after main actor attribute - @available(*, deprecated) /// Doc comment after deprecation attribute - func bar() {} - """ - - testFormatting(for: input, output, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) - } - - func testPreservesCommentOnSameLineAsAttribute() { - let input = """ - @MainActor /// Doc comment trailing attributes - func foo() {} - """ - - testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) - } - - func testPreservesRegularComments() { - let input = """ - @MainActor - // Comment after attribute - func foo() {} - """ - - testFormatting(for: input, rule: .docCommentsBeforeAttributes, exclude: [.docComments]) - } - - func testCombinesWithDocCommentsRule() { - let input = """ - @MainActor - // Comment after attribute - func foo() {} - """ - - let output = """ - /// Comment after attribute - @MainActor - func foo() {} - """ - - testFormatting(for: input, [output], rules: [.docComments, .docCommentsBeforeAttributes]) - } -} diff --git a/Tests/RulesTests+Wrapping.swift b/Tests/RulesTests+Wrapping.swift deleted file mode 100644 index ee4a36d1d..000000000 --- a/Tests/RulesTests+Wrapping.swift +++ /dev/null @@ -1,5715 +0,0 @@ -// -// RulesTests+Wrapping.swift -// SwiftFormatTests -// -// Created by Nick Lockwood on 04/09/2020. -// Copyright © 2020 Nick Lockwood. All rights reserved. -// - -import XCTest -@testable import SwiftFormat - -class WrappingTests: RulesTests { - // MARK: - elseOnSameLine - - func testElseOnSameLine() { - let input = "if true {\n 1\n}\nelse { 2 }" - let output = "if true {\n 1\n} else { 2 }" - testFormatting(for: input, output, rule: .elseOnSameLine, - exclude: [.wrapConditionalBodies]) - } - - func testElseOnSameLineOnlyAppliedToDanglingBrace() { - let input = "if true { 1 }\nelse { 2 }" - testFormatting(for: input, rule: .elseOnSameLine, - exclude: [.wrapConditionalBodies]) - } - - func testGuardNotAffectedByElseOnSameLine() { - let input = "guard true\nelse { return }" - testFormatting(for: input, rule: .elseOnSameLine, - exclude: [.wrapConditionalBodies]) - } - - func testElseOnSameLineDoesntEatPreviousStatement() { - let input = "if true {}\nguard true else { return }" - testFormatting(for: input, rule: .elseOnSameLine, - exclude: [.wrapConditionalBodies]) - } - - func testElseNotOnSameLineForAllman() { - let input = "if true\n{\n 1\n} else { 2 }" - let output = "if true\n{\n 1\n}\nelse { 2 }" - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: [.wrapConditionalBodies]) - } - - func testElseOnNextLineOption() { - let input = "if true {\n 1\n} else { 2 }" - let output = "if true {\n 1\n}\nelse { 2 }" - let options = FormatOptions(elseOnNextLine: true) - testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: [.wrapConditionalBodies]) - } - - func testGuardNotAffectedByElseOnSameLineForAllman() { - let input = "guard true else { return }" - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, rule: .elseOnSameLine, - options: options, exclude: [.wrapConditionalBodies]) - } - - func testRepeatWhileNotOnSameLineForAllman() { - let input = "repeat\n{\n foo\n} while x" - let output = "repeat\n{\n foo\n}\nwhile x" - let options = FormatOptions(allmanBraces: true) - testFormatting(for: input, output, rule: .elseOnSameLine, options: options) - } - - func testWhileNotAffectedByElseOnSameLineIfNotRepeatWhile() { - let input = "func foo(x) {}\n\nwhile true {}" - testFormatting(for: input, rule: .elseOnSameLine) - } - - func testCommentsNotDiscardedByElseOnSameLineRule() { - let input = "if true {\n 1\n}\n\n// comment\nelse {}" - testFormatting(for: input, rule: .elseOnSameLine) - } - - func testElseOnSameLineInferenceEdgeCase() { - let input = """ - func foo() { - if let foo == bar { - // ... - } else { - // ... - } - - if let foo == bar, - let baz = quux - { - print() - } - - if let foo == bar, - let baz = quux - { - print() - } - - if let foo == bar, - let baz = quux - { - print() - } - - if let foo == bar, - let baz = quux - { - print() - } - } - """ - let options = FormatOptions(elseOnNextLine: false) - testFormatting(for: input, rule: .elseOnSameLine, options: options, - exclude: [.braces]) - } - - // guardelse = auto - - func testSingleLineGuardElseNotWrappedByDefault() { - let input = "guard foo = bar else {}" - testFormatting(for: input, rule: .elseOnSameLine, - exclude: [.wrapConditionalBodies]) - } - - func testSingleLineGuardElseNotUnwrappedByDefault() { - let input = "guard foo = bar\nelse {}" - testFormatting(for: input, rule: .elseOnSameLine, - exclude: [.wrapConditionalBodies]) - } - - func testSingleLineGuardElseWrappedByDefaultIfBracesOnNextLine() { - let input = "guard foo = bar else\n{}" - let output = "guard foo = bar\nelse {}" - testFormatting(for: input, output, rule: .elseOnSameLine, - exclude: [.wrapConditionalBodies]) - } - - func testMultilineGuardElseNotWrappedByDefault() { - let input = """ - guard let foo = bar, - bar > 5 else { - return - } - """ - testFormatting(for: input, rule: .elseOnSameLine, - exclude: [.wrapMultilineStatementBraces]) - } - - func testMultilineGuardElseWrappedByDefaultIfBracesOnNextLine() { - let input = """ - guard let foo = bar, - bar > 5 else - { - return - } - """ - let output = """ - guard let foo = bar, - bar > 5 - else { - return - } - """ - testFormatting(for: input, output, rule: .elseOnSameLine) - } - - func testWrappedMultilineGuardElseCorrectlyIndented() { - let input = """ - func foo() { - guard let foo = bar, - bar > 5 else - { - return - } - } - """ - let output = """ - func foo() { - guard let foo = bar, - bar > 5 - else { - return - } - } - """ - testFormatting(for: input, output, rule: .elseOnSameLine) - } - - // guardelse = nextLine - - func testSingleLineGuardElseNotWrapped() { - let input = "guard foo = bar else {}" - let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, rule: .elseOnSameLine, - options: options, exclude: [.wrapConditionalBodies]) - } - - func testSingleLineGuardElseNotUnwrapped() { - let input = "guard foo = bar\nelse {}" - let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, rule: .elseOnSameLine, - options: options, exclude: [.wrapConditionalBodies]) - } - - func testSingleLineGuardElseWrappedIfBracesOnNextLine() { - let input = "guard foo = bar else\n{}" - let output = "guard foo = bar\nelse {}" - let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: [.wrapConditionalBodies]) - } - - func testMultilineGuardElseWrapped() { - let input = """ - guard let foo = bar, - bar > 5 else { - return - } - """ - let output = """ - guard let foo = bar, - bar > 5 - else { - return - } - """ - let options = FormatOptions(guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testMultilineGuardElseEndingInParen() { - let input = """ - guard let foo = bar, - let baz = quux() else - { - return - } - """ - let output = """ - guard let foo = bar, - let baz = quux() - else { - return - } - """ - let options = FormatOptions(guardElsePosition: .auto) - testFormatting(for: input, output, rule: .elseOnSameLine, - options: options) - } - - // guardelse = sameLine - - func testMultilineGuardElseUnwrapped() { - let input = """ - guard let foo = bar, - bar > 5 - else { - return - } - """ - let output = """ - guard let foo = bar, - bar > 5 else { - return - } - """ - let options = FormatOptions(guardElsePosition: .sameLine) - testFormatting(for: input, output, rule: .elseOnSameLine, - options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testGuardElseUnwrappedIfBracesOnNextLine() { - let input = "guard foo = bar\nelse {}" - let output = "guard foo = bar else {}" - let options = FormatOptions(guardElsePosition: .sameLine) - testFormatting(for: input, output, rule: .elseOnSameLine, - options: options) - } - - func testPreserveBlankLineBeforeElse() { - let input = """ - if foo { - print("foo") - } - - else if bar { - print("bar") - } - - else { - print("baaz") - } - """ - - testFormatting(for: input, rule: .elseOnSameLine) - } - - func testPreserveBlankLineBeforeElseOnSameLine() { - let input = """ - if foo { - print("foo") - } - - else if bar { - print("bar") - } - - else { - print("baaz") - } - """ - - let options = FormatOptions(elseOnNextLine: false) - testFormatting(for: input, rule: .elseOnSameLine, options: options) - } - - func testPreserveBlankLineBeforeElseWithComments() { - let input = """ - if foo { - print("foo") - } - // Comment before else if - else if bar { - print("bar") - } - - // Comment before else - else { - print("baaz") - } - """ - - testFormatting(for: input, rule: .elseOnSameLine) - } - - func testPreserveBlankLineBeforeElseDoesntAffectOtherCases() { - let input = """ - if foo { - print("foo") - } - else { - print("bar") - } - - guard foo else { - return - } - - guard - let foo, - let bar, - lat baaz else - { - return - } - """ - - let output = """ - if foo { - print("foo") - } else { - print("bar") - } - - guard foo else { - return - } - - guard - let foo, - let bar, - lat baaz - else { - return - } - """ - - let options = FormatOptions(elseOnNextLine: false, guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: .elseOnSameLine, options: options) - } - - // MARK: - wrapConditionalBodies - - func testGuardReturnWraps() { - let input = "guard let foo = bar else { return }" - let output = """ - guard let foo = bar else { - return - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testEmptyGuardReturnWithSpaceDoesNothing() { - let input = "guard let foo = bar else { }" - testFormatting(for: input, rule: .wrapConditionalBodies, - exclude: [.emptyBraces]) - } - - func testEmptyGuardReturnWithoutSpaceDoesNothing() { - let input = "guard let foo = bar else {}" - testFormatting(for: input, rule: .wrapConditionalBodies, - exclude: [.emptyBraces]) - } - - func testGuardReturnWithValueWraps() { - let input = "guard let foo = bar else { return baz }" - let output = """ - guard let foo = bar else { - return baz - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testGuardBodyWithClosingBraceAlreadyOnNewlineWraps() { - let input = """ - guard foo else { return - } - """ - let output = """ - guard foo else { - return - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testGuardContinueWithNoSpacesToCleanupWraps() { - let input = "guard let foo = bar else {continue}" - let output = """ - guard let foo = bar else { - continue - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testGuardReturnWrapsSemicolonDelimitedStatements() { - let input = "guard let foo = bar else { var baz = 0; let boo = 1; fatalError() }" - let output = """ - guard let foo = bar else { - var baz = 0; let boo = 1; fatalError() - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testGuardReturnWrapsSemicolonDelimitedStatementsWithNoSpaces() { - let input = "guard let foo = bar else {var baz=0;let boo=1;fatalError()}" - let output = """ - guard let foo = bar else { - var baz=0;let boo=1;fatalError() - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies, - exclude: [.spaceAroundOperators]) - } - - func testGuardReturnOnNewlineUnchanged() { - let input = """ - guard let foo = bar else { - return - } - """ - testFormatting(for: input, rule: .wrapConditionalBodies) - } - - func testGuardCommentSameLineUnchanged() { - let input = """ - guard let foo = bar else { // Test comment - return - } - """ - testFormatting(for: input, rule: .wrapConditionalBodies) - } - - func testGuardMultilineCommentSameLineUnchanged() { - let input = "guard let foo = bar else { /* Test comment */ return }" - let output = """ - guard let foo = bar else { /* Test comment */ - return - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testGuardTwoMultilineCommentsSameLine() { - let input = "guard let foo = bar else { /* Test comment 1 */ return /* Test comment 2 */ }" - let output = """ - guard let foo = bar else { /* Test comment 1 */ - return /* Test comment 2 */ - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testNestedGuardElseIfStatementsPutOnNewline() { - let input = "guard let foo = bar else { if qux { return quux } else { return quuz } }" - let output = """ - guard let foo = bar else { - if qux { - return quux - } else { - return quuz - } - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testNestedGuardElseGuardStatementPutOnNewline() { - let input = "guard let foo = bar else { guard qux else { return quux } }" - let output = """ - guard let foo = bar else { - guard qux else { - return quux - } - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testGuardWithClosureOnlyWrapsElseBody() { - let input = "guard foo { $0.bar } else { return true }" - let output = """ - guard foo { $0.bar } else { - return true - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testIfElseReturnsWrap() { - let input = "if foo { return bar } else if baz { return qux } else { return quux }" - let output = """ - if foo { - return bar - } else if baz { - return qux - } else { - return quux - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testIfElseBodiesWrap() { - let input = "if foo { bar } else if baz { qux } else { quux }" - let output = """ - if foo { - bar - } else if baz { - qux - } else { - quux - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testIfElsesWithClosuresDontWrapClosures() { - let input = "if foo { $0.bar } { baz } else if qux { $0.quux } { quuz } else { corge }" - let output = """ - if foo { $0.bar } { - baz - } else if qux { $0.quux } { - quuz - } else { - corge - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) - } - - func testEmptyIfElseBodiesWithSpaceDoNothing() { - let input = "if foo { } else if baz { } else { }" - testFormatting(for: input, rule: .wrapConditionalBodies, - exclude: [.emptyBraces]) - } - - func testEmptyIfElseBodiesWithoutSpaceDoNothing() { - let input = "if foo {} else if baz {} else {}" - testFormatting(for: input, rule: .wrapConditionalBodies, - exclude: [.emptyBraces]) - } - - func testGuardElseBraceStartingOnDifferentLine() { - let input = """ - guard foo else - { return bar } - """ - let output = """ - guard foo else - { - return bar - } - """ - - testFormatting(for: input, output, rule: .wrapConditionalBodies, - exclude: [.braces, .indent, .elseOnSameLine]) - } - - func testIfElseBracesStartingOnDifferentLines() { - let input = """ - if foo - { return bar } - else if baz - { return qux } - else - { return quux } - """ - let output = """ - if foo - { - return bar - } - else if baz - { - return qux - } - else - { - return quux - } - """ - testFormatting(for: input, output, rule: .wrapConditionalBodies, - exclude: [.braces, .indent, .elseOnSameLine]) - } - - // MARK: - wrapLoopBodies - - func testWrapForLoop() { - let input = "for foo in bar { print(foo) }" - let output = """ - for foo in bar { - print(foo) - } - """ - testFormatting(for: input, output, rule: .wrapLoopBodies) - } - - func testWrapWhileLoop() { - let input = "while let foo = bar.next() { print(foo) }" - let output = """ - while let foo = bar.next() { - print(foo) - } - """ - testFormatting(for: input, output, rule: .wrapLoopBodies) - } - - func testWrapRepeatWhileLoop() { - let input = "repeat { print(foo) } while condition()" - let output = """ - repeat { - print(foo) - } while condition() - """ - testFormatting(for: input, output, rule: .wrapLoopBodies) - } - - // MARK: - wrap - - func testWrapIfStatement() { - let input = """ - if let foo = foo, let bar = bar, let baz = baz {} - """ - let output = """ - if let foo = foo, - let bar = bar, - let baz = baz {} - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrapIfElseStatement() { - let input = """ - if let foo = foo {} else if let bar = bar {} - """ - let output = """ - if let foo = foo {} - else if let bar = - bar {} - """ - let output2 = """ - if let foo = foo {} - else if let bar = - bar {} - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options) - } - - func testWrapGuardStatement() { - let input = """ - guard let foo = foo, let bar = bar else { - break - } - """ - let output = """ - guard let foo = foo, - let bar = bar - else { - break - } - """ - let output2 = """ - guard let foo = foo, - let bar = bar - else { - break - } - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapClosure() { - let input = """ - let foo = { () -> Bool in true } - """ - let output = """ - let foo = - { () -> Bool in - true } - """ - let output2 = """ - let foo = - { () -> Bool in - true - } - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options) - } - - func testWrapClosure2() { - let input = """ - let foo = { bar, _ in bar } - """ - let output = """ - let foo = - { bar, _ in - bar } - """ - let output2 = """ - let foo = - { bar, _ in - bar - } - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options) - } - - func testWrapClosureWithAllmanBraces() { - let input = """ - let foo = { bar, _ in bar } - """ - let output = """ - let foo = - { bar, _ in - bar } - """ - let output2 = """ - let foo = - { bar, _ in - bar - } - """ - let options = FormatOptions(allmanBraces: true, maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options) - } - - func testWrapClosure3() { - let input = "let foo = bar { $0.baz }" - let output = """ - let foo = bar { - $0.baz } - """ - let output2 = """ - let foo = bar { - $0.baz - } - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth() { - let input = """ - func testFunc() -> ReturnType { - doSomething() - doSomething() - } - """ - let output = """ - func testFunc() - -> ReturnType { - doSomething() - doSomething() - } - """ - let options = FormatOptions(maxWidth: 25) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidthWithXcodeIndentation() { - let input = """ - func testFunc() -> ReturnType { - doSomething() - doSomething() - } - """ - let output = """ - func testFunc() - -> ReturnType { - doSomething() - doSomething() - } - """ - let output2 = """ - func testFunc() - -> ReturnType { - doSomething() - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 25) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth2() { - let input = """ - func testFunc() -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output = """ - func testFunc() - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation() { - let input = """ - func testFunc() throws -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output = """ - func testFunc() throws - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output2 = """ - func testFunc() throws - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth2WithXcodeIndentation2() { - let input = """ - func testFunc() throws(Foo) -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output = """ - func testFunc() throws(Foo) - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let output2 = """ - func testFunc() throws(Foo) - -> (ReturnType, ReturnType2) { - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth3() { - let input = """ - func testFunc() -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc() - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth3WithXcodeIndentation() { - let input = """ - func testFunc() -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc() - -> (Bool, String) -> String? { - doSomething() - } - """ - let output2 = """ - func testFunc() - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth4() { - let input = """ - func testFunc(_: () -> Void) -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc(_: () -> Void) - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 35) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapFunctionIfReturnTypeExceedsMaxWidth4WithXcodeIndentation() { - let input = """ - func testFunc(_: () -> Void) -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc(_: () -> Void) - -> (Bool, String) -> String? { - doSomething() - } - """ - let output2 = """ - func testFunc(_: () -> Void) - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(xcodeIndentation: true, maxWidth: 35) - testFormatting(for: input, [output, output2], rules: [.wrap], options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapChainedFunctionAfterSubscriptCollection() { - let input = """ - let foo = bar["baz"].quuz() - """ - let output = """ - let foo = bar["baz"] - .quuz() - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrapChainedFunctionInSubscriptCollection() { - let input = """ - let foo = bar[baz.quuz()] - """ - let output = """ - let foo = - bar[baz.quuz()] - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrapThrowingFunctionIfReturnTypeExceedsMaxWidth() { - let input = """ - func testFunc(_: () -> Void) throws -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc(_: () -> Void) throws - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 42) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testWrapTypedThrowingFunctionIfReturnTypeExceedsMaxWidth() { - let input = """ - func testFunc(_: () -> Void) throws(Foo) -> (Bool, String) -> String? { - doSomething() - } - """ - let output = """ - func testFunc(_: () -> Void) throws(Foo) - -> (Bool, String) -> String? { - doSomething() - } - """ - let options = FormatOptions(maxWidth: 42) - testFormatting(for: input, output, rule: .wrap, options: options, exclude: [.wrapMultilineStatementBraces]) - } - - func testNoWrapInterpolatedStringLiteral() { - let input = """ - "a very long \\(string) literal" - """ - let options = FormatOptions(maxWidth: 20) - testFormatting(for: input, rule: .wrap, options: options) - } - - func testNoWrapAtUnspacedOperator() { - let input = "let foo = bar+baz+quux" - let output = "let foo =\n bar+baz+quux" - let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, output, rule: .wrap, options: options, - exclude: [.spaceAroundOperators]) - } - - func testNoWrapAtUnspacedEquals() { - let input = "let foo=bar+baz+quux" - let options = FormatOptions(maxWidth: 15) - testFormatting(for: input, rule: .wrap, options: options, - exclude: [.spaceAroundOperators]) - } - - func testNoWrapSingleParameter() { - let input = "let fooBar = try unkeyedContainer.decode(FooBar.self)" - let output = """ - let fooBar = try unkeyedContainer - .decode(FooBar.self) - """ - let options = FormatOptions(maxWidth: 50) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrapSingleParameter() { - let input = "let fooBar = try unkeyedContainer.decode(FooBar.self)" - let output = """ - let fooBar = try unkeyedContainer.decode( - FooBar.self - ) - """ - let options = FormatOptions(maxWidth: 50, noWrapOperators: [".", "="]) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrapFunctionArrow() { - let input = "func foo() -> Int {}" - let output = """ - func foo() - -> Int {} - """ - let options = FormatOptions(maxWidth: 14) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testNoWrapFunctionArrow() { - let input = "func foo() -> Int {}" - let output = """ - func foo( - ) -> Int {} - """ - let options = FormatOptions(maxWidth: 14, noWrapOperators: ["->"]) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testNoCrashWrap() { - let input = """ - struct Foo { - func bar(a: Set, c: D) {} - } - """ - let output = """ - struct Foo { - func bar( - a: Set< - B - >, - c: D - ) {} - } - """ - let options = FormatOptions(maxWidth: 10) - testFormatting(for: input, output, rule: .wrap, options: options, - exclude: [.unusedArguments]) - } - - func testNoCrashWrap2() { - let input = """ - struct Test { - func webView(_: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - authenticationChallengeProcessor.process(challenge: challenge, completionHandler: completionHandler) - } - } - """ - let output = """ - struct Test { - func webView( - _: WKWebView, - didReceive challenge: URLAuthenticationChallenge, - completionHandler: @escaping (URLSession.AuthChallengeDisposition, - URLCredential?) -> Void - ) { - authenticationChallengeProcessor.process( - challenge: challenge, - completionHandler: completionHandler - ) - } - } - """ - let options = FormatOptions(wrapParameters: .preserve, maxWidth: 80) - testFormatting(for: input, output, rule: .wrap, options: options, - exclude: [.indent, .wrapArguments]) - } - - func testNoCrashWrap3() throws { - let input = """ - override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { - let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext - context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size - return context - } - """ - let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 100) - let rules: [FormatRule] = [.wrap, .wrapArguments] - XCTAssertNoThrow(try format(input, rules: rules, options: options)) - } - - func testWrapColorLiteral() throws { - let input = """ - button.setTitleColor(#colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1), for: .normal) - """ - let options = FormatOptions(maxWidth: 80, assetLiteralWidth: .visualWidth) - testFormatting(for: input, rule: .wrap, options: options) - } - - func testWrapImageLiteral() { - let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" - let options = FormatOptions(maxWidth: 40, assetLiteralWidth: .visualWidth) - testFormatting(for: input, rule: .wrap, options: options) - } - - func testNoWrapBeforeFirstArgumentInSingleLineStringInterpolation() { - let input = """ - "a very long string literal with \\(interpolation) inside" - """ - let options = FormatOptions(maxWidth: 40) - testFormatting(for: input, rule: .wrap, options: options) - } - - func testWrapBeforeFirstArgumentInMultineStringInterpolation() { - let input = """ - \""" - a very long string literal with \\(interpolation) inside - \""" - """ - let output = """ - \""" - a very long string literal with \\( - interpolation - ) inside - \""" - """ - let options = FormatOptions(maxWidth: 40) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - // ternary expressions - - func testWrapSimpleTernaryOperator() { - let input = """ - let foo = fooCondition ? longValueThatContainsFoo : longValueThatContainsBar - """ - - let output = """ - let foo = fooCondition - ? longValueThatContainsFoo - : longValueThatContainsBar - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testRewrapsSimpleTernaryOperator() { - let input = """ - let foo = fooCondition ? longValueThatContainsFoo : - longValueThatContainsBar - """ - - let output = """ - let foo = fooCondition - ? longValueThatContainsFoo - : longValueThatContainsBar - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrapComplexTernaryOperator() { - let input = """ - let foo = fooCondition ? Foo(property: value) : barContainer.getBar(using: barProvider) - """ - - let output = """ - let foo = fooCondition - ? Foo(property: value) - : barContainer.getBar(using: barProvider) - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testRewrapsComplexTernaryOperator() { - let input = """ - let foo = fooCondition ? Foo(property: value) : - barContainer.getBar(using: barProvider) - """ - - let output = """ - let foo = fooCondition - ? Foo(property: value) - : barContainer.getBar(using: barProvider) - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrappedTernaryOperatorIndentsChainedCalls() { - let input = """ - let ternary = condition - ? values - .map { $0.bar } - .filter { $0.hasFoo } - .last - : other.values - .compactMap { $0 } - .first? - .with(property: updatedValue) - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, rule: .indent, options: options) - } - - func testWrapsSimpleNestedTernaryOperator() { - let input = """ - let foo = fooCondition ? (barCondition ? a : b) : (baazCondition ? c : d) - """ - - let output = """ - let foo = fooCondition - ? (barCondition ? a : b) - : (baazCondition ? c : d) - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrapsDoubleNestedTernaryOperation() { - let input = """ - let foo = fooCondition ? barCondition ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult - """ - - let output = """ - let foo = fooCondition - ? barCondition - ? longTrueBarResult - : longFalseBarResult - : baazCondition - ? longTrueBaazResult - : longFalseBaazResult - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testWrapsTripleNestedTernaryOperation() { - let input = """ - let foo = fooCondition ? barCondition ? quuxCondition ? longTrueQuuxResult : longFalseQuuxResult : barCondition2 ? longTrueBarResult : longFalseBarResult : baazCondition ? longTrueBaazResult : longFalseBaazResult - """ - - let output = """ - let foo = fooCondition - ? barCondition - ? quuxCondition - ? longTrueQuuxResult - : longFalseQuuxResult - : barCondition2 - ? longTrueBarResult - : longFalseBarResult - : baazCondition - ? longTrueBaazResult - : longFalseBaazResult - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 60) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testNoWrapTernaryWrappedWithinChildExpression() { - let input = """ - func foo() { - return _skipString(string) ? .token( - string, Location(source: input, range: startIndex ..< index) - ) : nil - } - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) - testFormatting(for: input, rule: .wrap, options: options) - } - - func testNoWrapTernaryWrappedWithinChildExpression2() { - let input = """ - let types: [PolygonType] = plane.isEqual(to: plane) ? [] : vertices.map { - let t = plane.normal.dot($0.position) - plane.w - let type: PolygonType = (t < -epsilon) ? .back : (t > epsilon) ? .front : .coplanar - polygonType = PolygonType(rawValue: polygonType.rawValue | type.rawValue)! - return type - } - """ - - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 0) - testFormatting(for: input, rule: .wrap, options: options) - } - - func testNoWrapTernaryInsideStringLiteral() { - let input = """ - "\\(true ? "Some string literal" : "Some other string")" - """ - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) - testFormatting(for: input, rule: .wrap, options: options) - } - - func testWrapTernaryInsideMultilineStringLiteral() { - let input = """ - let foo = \""" - \\(true ? "Some string literal" : "Some other string")" - \""" - """ - let output = """ - let foo = \""" - \\(true - ? "Some string literal" - : "Some other string")" - \""" - """ - let options = FormatOptions(wrapTernaryOperators: .beforeOperators, maxWidth: 50) - testFormatting(for: input, output, rule: .wrap, options: options) - } - - func testErrorNotReportedOnBlankLineAfterWrap() throws { - let input = """ - [ - abagdiasiudbaisndoanosdasdasdasdasdnaosnooanso(), - - bar(), - ] - """ - let options = FormatOptions(truncateBlankLines: false, maxWidth: 40) - let changes = try lint(input, rules: [.wrap, .indent], options: options) - XCTAssertEqual(changes, [.init(line: 2, rule: .wrap, filePath: nil)]) - } - - // MARK: - wrapArguments - - func testIndentFirstElementWhenApplyingWrap() { - let input = """ - let foo = Set([ - Thing(), - Thing(), - ]) - """ - let output = """ - let foo = Set([ - Thing(), - Thing(), - ]) - """ - testFormatting(for: input, output, rule: .wrapArguments, exclude: [.propertyType]) - } - - func testWrapArgumentsDoesntIndentTrailingComment() { - let input = """ - foo( // foo - bar: Int - ) - """ - let output = """ - foo( // foo - bar: Int - ) - """ - testFormatting(for: input, output, rule: .wrapArguments) - } - - func testWrapArgumentsDoesntIndentClosingBracket() { - let input = """ - [ - "foo": [ - ], - ] - """ - testFormatting(for: input, rule: .wrapArguments) - } - - func testWrapParametersDoesNotAffectFunctionDeclaration() { - let input = "foo(\n bar _: Int,\n baz _: String\n)" - let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .afterFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapParametersClosureAfterParameterListDoesNotWrapClosureArguments() { - let input = """ - func foo() {} - bar = (baz: 5, quux: 7, - quuz: 10) - """ - let options = FormatOptions(wrapArguments: .preserve, wrapParameters: .beforeFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapParametersNotSetWrapArgumentsAfterFirstDefaultsToAfterFirst() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersNotSetWrapArgumentsBeforeFirstDefaultsToBeforeFirst() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersNotSetWrapArgumentsPreserveDefaultsToPreserve() { - let input = "func foo(\n bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapArguments: .preserve) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersFunctionDeclarationClosingParenOnSameLine() { - let input = """ - func foo( - bar _: Int, - baz _: String - ) {} - """ - let output = """ - func foo( - bar _: Int, - baz _: String) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersFunctionDeclarationClosingParenOnNextLine() { - let input = """ - func foo( - bar _: Int, - baz _: String) {} - """ - let output = """ - func foo( - bar _: Int, - baz _: String - ) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersFunctionDeclarationClosingParenOnSameLineAndForce() { - let input = """ - func foo( - bar _: Int, - baz _: String - ) {} - """ - let output = """ - func foo( - bar _: Int, - baz _: String) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .sameLine, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersFunctionDeclarationClosingParenOnNextLineAndForce() { - let input = """ - func foo( - bar _: Int, - baz _: String) {} - """ - let output = """ - func foo( - bar _: Int, - baz _: String - ) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersFunctionCallClosingParenOnNextLineAndForce() { - let input = """ - foo( - bar: 42, - baz: "foo" - ) - """ - let output = """ - foo( - bar: 42, - baz: "foo") - """ - let options = FormatOptions(wrapArguments: .beforeFirst, closingParenPosition: .balanced, callSiteClosingParenPosition: .sameLine) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testIndentMultilineStringWhenWrappingArguments() { - let input = """ - foobar(foo: \"\"" - baz - \"\"", - bar: \"\"" - baz - \"\"") - """ - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testHandleXcodeTokenApplyingWrap() { - let input = """ - test(image: \u{003c}#T##UIImage#>, name: "Name") - """ - - let output = """ - test( - image: \u{003c}#T##UIImage#>, - name: "Name" - ) - """ - let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testIssue1530() { - let input = """ - extension DRAutoWeatherReadRequestResponse { - static let mock = DRAutoWeatherReadRequestResponse( - offlineFirstWeather: DRAutoWeatherReadRequestResponse.DROfflineFirstWeather( - daily: .mockWeatherID, hourly: [] - ) - ) - } - """ - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) - } - - // MARK: wrapParameters - - // MARK: preserve - - func testAfterFirstPreserved() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testAfterFirstPreservedIndentFixed() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testAfterFirstPreservedNewlineRemoved() { - let input = "func foo(bar _: Int,\n baz _: String\n) {}" - let output = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testBeforeFirstPreserved() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testBeforeFirstPreservedIndentFixed() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testBeforeFirstPreservedNewlineAdded() { - let input = "func foo(\n bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersAfterMultilineComment() { - let input = """ - /** - Some function comment. - */ - func barFunc( - _ firstParam: FirstParamType, - secondParam: SecondParamType - ) - """ - let options = FormatOptions(wrapParameters: .preserve) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - // MARK: afterFirst - - func testBeforeFirstConvertedToAfterFirst() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testNoWrapInnerArguments() { - let input = "func foo(\n bar _: Int,\n baz _: foo(bar, baz)\n) {}" - let output = "func foo(bar _: Int,\n baz _: foo(bar, baz)) {}" - let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - // MARK: afterFirst, maxWidth - - func testWrapAfterFirstIfMaxLengthExceeded() { - let input = """ - func foo(bar: Int, baz: String) -> Bool {} - """ - let output = """ - func foo(bar: Int, - baz: String) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: [.unusedArguments, .wrap]) - } - - func testWrapAfterFirstIfMaxLengthExceeded2() { - let input = """ - func foo(bar: Int, baz: String, quux: Bool) -> Bool {} - """ - let output = """ - func foo(bar: Int, - baz: String, - quux: Bool) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: [.unusedArguments, .wrap]) - } - - func testWrapAfterFirstIfMaxLengthExceeded3() { - let input = """ - func foo(bar: Int, baz: String, aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} - """ - let output = """ - func foo(bar: Int, baz: String, - aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) - testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: [.unusedArguments, .wrap]) - } - - func testWrapAfterFirstIfMaxLengthExceeded3WithWrap() { - let input = """ - func foo(bar: Int, baz: String, aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) -> Bool {} - """ - let output = """ - func foo(bar: Int, baz: String, - aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) - -> Bool {} - """ - let output2 = """ - func foo(bar: Int, baz: String, - aVeryLongLastArgumentThatExceedsTheMaxWidthByItself: Bool) - -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 32) - testFormatting(for: input, [output, output2], - rules: [.wrapArguments, .wrap], - options: options, exclude: [.unusedArguments]) - } - - func testWrapAfterFirstIfMaxLengthExceeded4WithWrap() { - let input = """ - func foo(bar: String, baz: String, quux: Bool) -> Bool {} - """ - let output = """ - func foo(bar: String, - baz: String, - quux: Bool) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) - testFormatting(for: input, [output], - rules: [.wrapArguments, .wrap], - options: options, exclude: [.unusedArguments]) - } - - func testWrapAfterFirstIfMaxLengthExceededInClassScopeWithWrap() { - let input = """ - class TestClass { - func foo(bar: String, baz: String, quux: Bool) -> Bool {} - } - """ - let output = """ - class TestClass { - func foo(bar: String, - baz: String, - quux: Bool) - -> Bool {} - } - """ - let output2 = """ - class TestClass { - func foo(bar: String, - baz: String, - quux: Bool) - -> Bool {} - } - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 31) - testFormatting(for: input, [output, output2], - rules: [.wrapArguments, .wrap], - options: options, exclude: [.unusedArguments]) - } - - func testWrapParametersListInClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: (Int, - Int, - String) -> Int = { _, _, _ in - 0 - } - """ - let output2 = """ - var mathFunction: (Int, - Int, - String) - -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 30) - testFormatting(for: input, [output, output2], - rules: [.wrapArguments], - options: options) - } - - func testWrapParametersAfterFirstIfMaxLengthExceededInReturnType() { - let input = """ - func foo(bar: Int, baz: String, quux: Bool) -> LongReturnType {} - """ - let output2 = """ - func foo(bar: Int, baz: String, - quux: Bool) -> LongReturnType {} - """ - let options = FormatOptions(wrapParameters: .afterFirst, maxWidth: 50) - testFormatting(for: input, [input, output2], rules: [.wrapArguments], - options: options, exclude: [.unusedArguments]) - } - - func testWrapParametersAfterFirstWithSeparatedArgumentLabels() { - let input = """ - func foo(with - bar: Int, and - baz: String, and - quux: Bool - ) -> LongReturnType {} - """ - let output = """ - func foo(with bar: Int, - and baz: String, - and quux: Bool) -> LongReturnType {} - """ - let options = FormatOptions(wrapParameters: .afterFirst) - testFormatting(for: input, output, rule: .wrapArguments, - options: options, exclude: [.unusedArguments]) - } - - // MARK: beforeFirst - - func testWrapAfterFirstConvertedToWrapBefore() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testLinebreakInsertedAtEndOfWrappedFunction() { - let input = "func foo(\n bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testAfterFirstConvertedToBeforeFirst() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapParametersListBeforeFirstInClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options) - } - - func testWrapParametersListBeforeFirstInThrowingClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) throws -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) throws -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options) - } - - func testWrapParametersListBeforeFirstInTypedThrowingClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) throws(Foo) -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) throws(Foo) -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options) - } - - func testWrapParametersListBeforeFirstInRethrowingClosureType() { - let input = """ - var mathFunction: (Int, - Int, String) rethrows -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) rethrows -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options) - } - - func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameter() { - let input = """ - func foo(bar: (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options, - exclude: [.unusedArguments]) - } - - func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParams() { - let input = """ - func foo(bar: Int, baz: (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: Int, baz: ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options, - exclude: [.unusedArguments]) - } - - func testWrapParametersListBeforeFirstInClosureTypeAsFunctionParameterWithOtherParamsAfterWrappedClosure() { - let input = """ - func foo(bar: Int, baz: (Int, - Bool, String) -> Int, quux: String) -> Int {} - """ - let output = """ - func foo(bar: Int, baz: ( - Int, - Bool, - String - ) -> Int, quux: String) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options, - exclude: [.unusedArguments]) - } - - func testWrapParametersListBeforeFirstInEscapingClosureTypeAsFunctionParameter() { - let input = """ - func foo(bar: @escaping (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: @escaping ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options, - exclude: [.unusedArguments]) - } - - func testWrapParametersListBeforeFirstInNoEscapeClosureTypeAsFunctionParameter() { - let input = """ - func foo(bar: @noescape (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: @noescape ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options, - exclude: [.unusedArguments]) - } - - func testWrapParametersListBeforeFirstInEscapingAutoclosureTypeAsFunctionParameter() { - let input = """ - func foo(bar: @escaping @autoclosure (Int, - Bool, String) -> Int) -> Int {} - """ - let output = """ - func foo(bar: @escaping @autoclosure ( - Int, - Bool, - String - ) -> Int) -> Int {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, [output], - rules: [.wrapArguments], - options: options, - exclude: [.unusedArguments]) - } - - // MARK: beforeFirst, maxWidth - - func testWrapBeforeFirstIfMaxLengthExceeded() { - let input = """ - func foo(bar: Int, baz: String) -> Bool {} - """ - let output = """ - func foo( - bar: Int, - baz: String - ) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: [.unusedArguments]) - } - - func testNoWrapBeforeFirstIfMaxLengthNotExceeded() { - let input = """ - func foo(bar: Int, baz: String) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 42) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.unusedArguments]) - } - - func testNoWrapGenericsIfClosingBracketWithinMaxWidth() { - let input = """ - func foo(bar: Int, baz: String) -> Bool {} - """ - let output = """ - func foo( - bar: Int, - baz: String - ) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 20) - testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: [.unusedArguments]) - } - - func testWrapAlreadyWrappedArgumentsIfMaxLengthExceeded() { - let input = """ - func foo( - bar: Int, baz: String, quux: Bool - ) -> Bool {} - """ - let output = """ - func foo( - bar: Int, baz: String, - quux: Bool - ) -> Bool {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 26) - testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: [.unusedArguments]) - } - - func testWrapParametersBeforeFirstIfMaxLengthExceededInReturnType() { - let input = """ - func foo(bar: Int, baz: String, quux: Bool) -> LongReturnType {} - """ - let output2 = """ - func foo( - bar: Int, - baz: String, - quux: Bool - ) -> LongReturnType {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 50) - testFormatting(for: input, [input, output2], rules: [.wrapArguments], - options: options, exclude: [.unusedArguments]) - } - - func testWrapParametersBeforeFirstWithSeparatedArgumentLabels() { - let input = """ - func foo(with - bar: Int, and - baz: String - ) -> LongReturnType {} - """ - let output = """ - func foo( - with bar: Int, - and baz: String - ) -> LongReturnType {} - """ - let options = FormatOptions(wrapParameters: .beforeFirst) - testFormatting(for: input, output, rule: .wrapArguments, - options: options, exclude: [.unusedArguments]) - } - - func testWrapParametersListBeforeFirstInClosureTypeWithMaxWidth() { - let input = """ - var mathFunction: (Int, Int, String) -> Int = { _, _, _ in - 0 - } - """ - let output = """ - var mathFunction: ( - Int, - Int, - String - ) -> Int = { _, _, _ in - 0 - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, [output], rules: [.wrapArguments], - options: options) - } - - func testNoWrapBeforeFirstMaxWidthNotExceededWithLineBreakSinceLastEndOfArgumentScope() { - let input = """ - class Foo { - func foo() { - bar() - } - - func bar(foo: String, bar: Int) { - quux() - } - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 37) - testFormatting(for: input, rule: .wrapArguments, - options: options, exclude: [.unusedArguments]) - } - - func testNoWrapSubscriptWithSingleElement() { - let input = "guard let foo = bar[0] {}" - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 20) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.wrap]) - } - - func testNoWrapArrayWithSingleElement() { - let input = "let foo = [0]" - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 11) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.wrap]) - } - - func testNoWrapDictionaryWithSingleElement() { - let input = "let foo = [bar: baz]" - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 15) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.wrap]) - } - - func testNoWrapImageLiteral() { - let input = "if let image = #imageLiteral(resourceName: \"abc.png\") {}" - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.wrap]) - } - - func testNoWrapColorLiteral() { - let input = """ - if let color = #colorLiteral(red: 0.2392156863, green: 0.6470588235, blue: 0.3647058824, alpha: 1) {} - """ - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 30) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.wrap]) - } - - func testWrapArgumentsNoIndentBlankLines() { - let input = """ - let foo = [ - - bar, - - ] - """ - let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.wrap, .blankLinesAtStartOfScope, .blankLinesAtEndOfScope]) - } - - // MARK: closingParenPosition = true - - func testParenOnSameLineWhenWrapAfterFirstConvertedToWrapBefore() { - let input = "func foo(bar _: Int,\n baz _: String) {}" - let output = "func foo(\n bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testParenOnSameLineWhenWrapBeforeFirstUnchanged() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(\n bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .beforeFirst, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testParenOnSameLineWhenWrapBeforeFirstPreserved() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let output = "func foo(\n bar _: Int,\n baz _: String) {}" - let options = FormatOptions(wrapParameters: .preserve, closingParenPosition: .sameLine) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - // MARK: indent with tabs - - func testTabIndentWrappedFunctionWithSmartTabs() { - let input = """ - func foo(bar: Int, - baz: Int) {} - """ - let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, tabWidth: 2) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.unusedArguments]) - } - - func testTabIndentWrappedFunctionWithoutSmartTabs() { - let input = """ - func foo(bar: Int, - baz: Int) {} - """ - let output = """ - func foo(bar: Int, - \t\t\t\t baz: Int) {} - """ - let options = FormatOptions(indent: "\t", wrapParameters: .afterFirst, - tabWidth: 2, smartTabs: false) - testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: [.unusedArguments]) - } - - // MARK: - wrapArguments --wrapArguments - - func testWrapArgumentsDoesNotAffectFunctionDeclaration() { - let input = "func foo(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapArgumentsDoesNotAffectInit() { - let input = "init(\n bar _: Int,\n baz _: String\n) {}" - let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapArgumentsDoesNotAffectSubscript() { - let input = "subscript(\n bar _: Int,\n baz _: String\n) -> Int {}" - let options = FormatOptions(wrapArguments: .afterFirst, wrapParameters: .preserve) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - // MARK: afterFirst - - func testWrapArgumentsConvertBeforeFirstToAfterFirst() { - let input = """ - foo( - bar _: Int, - baz _: String - ) - """ - let output = """ - foo(bar _: Int, - baz _: String) - """ - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testCorrectWrapIndentForNestedArguments() { - let input = "foo(\nbar: (\nx: 0,\ny: 0\n),\nbaz: (\nx: 0,\ny: 0\n)\n)" - let output = "foo(bar: (x: 0,\n y: 0),\n baz: (x: 0,\n y: 0))" - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testNoRemoveLinebreakAfterCommentInArguments() { - let input = "a(b // comment\n)" - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testNoRemoveLinebreakAfterCommentInArguments2() { - let input = """ - foo(bar: bar - // , - // baz: baz - ) {} - """ - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.indent]) - } - - func testConsecutiveCodeCommentsNotIndented() { - let input = """ - foo(bar: bar, - // bar, - // baz, - quux) - """ - let options = FormatOptions(wrapArguments: .afterFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - // MARK: afterFirst maxWidth - - func testWrapArgumentsAfterFirst() { - let input = """ - foo(bar: Int, baz: String, quux: Bool) - """ - let output = """ - foo(bar: Int, - baz: String, - quux: Bool) - """ - let options = FormatOptions(wrapArguments: .afterFirst, maxWidth: 20) - testFormatting(for: input, output, rule: .wrapArguments, options: options, - exclude: [.unusedArguments, .wrap]) - } - - // MARK: beforeFirst - - func testClosureInsideParensNotWrappedOntoNextLine() { - let input = "foo({\n bar()\n})" - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.trailingClosures]) - } - - func testNoMangleCommentedLinesWhenWrappingArguments() { - let input = """ - foo(bar: bar - // , - // baz: baz - ) {} - """ - let output = """ - foo( - bar: bar - // , - // baz: baz - ) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testNoMangleCommentedLinesWhenWrappingArgumentsWithNoCommas() { - let input = """ - foo(bar: bar - // baz: baz - ) {} - """ - let output = """ - foo( - bar: bar - // baz: baz - ) {} - """ - let options = FormatOptions(wrapArguments: .beforeFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - // MARK: preserve - - func testWrapArgumentsDoesNotAffectLessThanOperator() { - let input = """ - func foo() { - guard foo < bar.count else { return nil } - } - """ - let options = FormatOptions(wrapArguments: .preserve) - testFormatting(for: input, rule: .wrapArguments, - options: options, exclude: [.wrapConditionalBodies]) - } - - // MARK: - --wrapArguments, --wrapParameter - - // MARK: beforeFirst - - func testNoMistakeTernaryExpressionForArguments() { - let input = """ - (foo ? - bar : - baz) - """ - let options = FormatOptions(wrapArguments: .beforeFirst, wrapParameters: .beforeFirst) - testFormatting(for: input, rule: .wrapArguments, options: options, - exclude: [.redundantParens]) - } - - // MARK: beforeFirst, maxWidth : string interpolation - - func testNoWrapBeforeFirstArgumentInStringInterpolation() { - let input = """ - "a very long string literal with \\(interpolation) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 40) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testNoWrapBeforeFirstArgumentInStringInterpolation2() { - let input = """ - "a very long string literal with \\(interpolation) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 50) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testNoWrapBeforeFirstArgumentInStringInterpolation3() { - let input = """ - "a very long string literal with \\(interpolated, variables) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 40) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testNoWrapBeforeNestedFirstArgumentInStringInterpolation() { - let input = """ - "a very long string literal with \\(foo(interpolated)) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 45) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testNoWrapBeforeNestedFirstArgumentInStringInterpolation2() { - let input = """ - "a very long string literal with \\(foo(interpolated, variables)) inside" - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapParameters: .beforeFirst, - maxWidth: 45) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapProtocolFuncParametersBeforeFirst() { - let input = """ - protocol Foo { - public func stringify(_ value: T, label: String) -> (T, String) - } - """ - let output = """ - protocol Foo { - public func stringify( - _ value: T, - label: String - ) -> (T, String) - } - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, output, rule: .wrapArguments, - options: options) - } - - // MARK: afterFirst maxWidth : string interpolation - - func testNoWrapAfterFirstArgumentInStringInterpolation() { - let input = """ - "a very long string literal with \\(interpolated) inside" - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapParameters: .afterFirst, - maxWidth: 46) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testNoWrapAfterFirstArgumentInStringInterpolation2() { - let input = """ - "a very long string literal with \\(interpolated, variables) inside" - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapParameters: .afterFirst, - maxWidth: 50) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testNoWrapAfterNestedFirstArgumentInStringInterpolation() { - let input = """ - "a very long string literal with \\(foo(interpolated, variables)) inside" - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapParameters: .afterFirst, - maxWidth: 55) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - // macros - - func testWrapMacroParametersBeforeFirst() { - let input = """ - @freestanding(expression) - public macro stringify(_ value: T, label: String) -> (T, String) - """ - let output = """ - @freestanding(expression) - public macro stringify( - _ value: T, - label: String - ) -> (T, String) - """ - let options = FormatOptions(wrapParameters: .beforeFirst, maxWidth: 30) - testFormatting(for: input, output, rule: .wrapArguments, - options: options) - } - - // MARK: - wrapArguments --wrapCollections - - // MARK: beforeFirst - - func testNoDoubleSpaceAddedToWrappedArray() { - let input = "[ foo,\n bar ]" - let output = "[\n foo,\n bar\n]" - let options = FormatOptions(trailingCommas: false, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [.wrapArguments, .spaceInsideBrackets], - options: options) - } - - func testTrailingCommasAddedToWrappedArray() { - let input = "[foo,\n bar]" - let output = "[\n foo,\n bar,\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], - options: options) - } - - func testTrailingCommasAddedToWrappedNestedDictionary() { - let input = "[foo: [bar: baz,\n bar2: baz2]]" - let output = "[foo: [\n bar: baz,\n bar2: baz2,\n]]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], - options: options) - } - - func testTrailingCommasAddedToSingleLineNestedDictionary() { - let input = "[\n foo: [bar: baz, bar2: baz2]]" - let output = "[\n foo: [bar: baz, bar2: baz2],\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], - options: options) - } - - func testTrailingCommasAddedToWrappedNestedDictionaries() { - let input = "[foo: [bar: baz,\n bar2: baz2],\n foo2: [bar: baz,\n bar2: baz2]]" - let output = "[\n foo: [\n bar: baz,\n bar2: baz2,\n ],\n foo2: [\n bar: baz,\n bar2: baz2,\n ],\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .beforeFirst) - testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], - options: options) - } - - func testSpaceAroundEnumValuesInArray() { - let input = "[\n .foo,\n .bar, .baz,\n]" - let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - // MARK: beforeFirst maxWidth - - func testWrapCollectionOnOneLineBeforeFirstWidthExceededInChainedFunctionCallAfterCollection() { - let input = """ - let foo = ["bar", "baz"].quux(quuz) - """ - let output2 = """ - let foo = ["bar", "baz"] - .quux(quuz) - """ - let options = FormatOptions(wrapCollections: .beforeFirst, maxWidth: 26) - testFormatting(for: input, [input, output2], - rules: [.wrapArguments], options: options) - } - - // MARK: afterFirst - - func testTrailingCommaRemovedInWrappedArray() { - let input = "[\n .foo,\n .bar,\n .baz,\n]" - let output = "[.foo,\n .bar,\n .baz]" - let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testNoRemoveLinebreakAfterCommentInElements() { - let input = "[a, // comment\n]" - let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapCollectionsConsecutiveCodeCommentsNotIndented() { - let input = """ - let a = [foo, - // bar, - // baz, - quux] - """ - let options = FormatOptions(wrapCollections: .afterFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapCollectionsConsecutiveCodeCommentsNotIndentedInWrapBeforeFirst() { - let input = """ - let a = [ - foo, - // bar, - // baz, - quux, - ] - """ - let options = FormatOptions(wrapCollections: .beforeFirst) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - // MARK: preserve - - func testNoBeforeFirstPreservedAndTrailingCommaIgnoredInMultilineNestedDictionary() { - let input = "[foo: [bar: baz,\n bar2: baz2]]" - let output = "[foo: [bar: baz,\n bar2: baz2]]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], - options: options) - } - - func testBeforeFirstPreservedAndTrailingCommaAddedInSingleLineNestedDictionary() { - let input = "[\n foo: [bar: baz, bar2: baz2]]" - let output = "[\n foo: [bar: baz, bar2: baz2],\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], - options: options) - } - - func testBeforeFirstPreservedAndTrailingCommaAddedInSingleLineNestedDictionaryWithOneNestedItem() { - let input = "[\n foo: [bar: baz]]" - let output = "[\n foo: [bar: baz],\n]" - let options = FormatOptions(trailingCommas: true, wrapCollections: .preserve) - testFormatting(for: input, [output], rules: [.wrapArguments, .trailingCommas], - options: options) - } - - // MARK: - wrapArguments --wrapCollections & --wrapArguments - - // MARK: beforeFirst maxWidth - - func testWrapArgumentsBeforeFirstWhenArgumentsExceedMaxWidthAndArgumentIsCollection() { - let input = """ - foo(bar: ["baz", "quux"], quuz: corge) - """ - let output = """ - foo( - bar: ["baz", "quux"], - quuz: corge - ) - """ - let options = FormatOptions(wrapArguments: .beforeFirst, - wrapCollections: .beforeFirst, - maxWidth: 26) - testFormatting(for: input, [output], - rules: [.wrapArguments], options: options) - } - - // MARK: afterFirst maxWidth - - func testWrapArgumentsAfterFirstWhenArgumentsExceedMaxWidthAndArgumentIsCollection() { - let input = """ - foo(bar: ["baz", "quux"], quuz: corge) - """ - let output = """ - foo(bar: ["baz", "quux"], - quuz: corge) - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapCollections: .beforeFirst, - maxWidth: 26) - testFormatting(for: input, [output], - rules: [.wrapArguments], options: options) - } - - // MARK: - wrapArguments Multiple Wraps On Same Line - - func testWrapAfterFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { - let input = """ - foo.bar(baz: [qux, quux]).quuz([corge: grault], garply: waldo) - """ - let output = """ - foo.bar(baz: [qux, quux]) - .quuz([corge: grault], - garply: waldo) - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapCollections: .afterFirst, - maxWidth: 28) - testFormatting(for: input, [output], - rules: [.wrapArguments, .wrap], options: options) - } - - func testWrapAfterFirstWrapCollectionsBeforeFirstWhenChainedFunctionAndThenArgumentsExceedMaxWidth() { - let input = """ - foo.bar(baz: [qux, quux]).quuz([corge: grault], garply: waldo) - """ - let output = """ - foo.bar(baz: [qux, quux]) - .quuz([corge: grault], - garply: waldo) - """ - let options = FormatOptions(wrapArguments: .afterFirst, - wrapCollections: .beforeFirst, - maxWidth: 28) - testFormatting(for: input, [output], - rules: [.wrapArguments, .wrap], options: options) - } - - func testNoMangleNestedFunctionCalls() { - let input = """ - points.append(.curve( - quadraticBezier(p0.position.x, Double(p1.x), Double(p2.x), t), - quadraticBezier(p0.position.y, Double(p1.y), Double(p2.y), t) - )) - """ - let output = """ - points.append(.curve( - quadraticBezier( - p0.position.x, - Double(p1.x), - Double(p2.x), - t - ), - quadraticBezier( - p0.position.y, - Double(p1.y), - Double(p2.y), - t - ) - )) - """ - let options = FormatOptions(wrapArguments: .beforeFirst, maxWidth: 40) - testFormatting(for: input, [output], - rules: [.wrapArguments, .wrap], options: options) - } - - func testWrapArguments_typealias_beforeFirst() { - let input = """ - typealias Dependencies = FooProviding & BarProviding & BaazProviding & QuuxProviding - """ - - let output = """ - typealias Dependencies - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 40) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_multipleTypealiases_beforeFirst() { - let input = """ - enum Namespace { - typealias DependenciesA = FooProviding & BarProviding - typealias DependenciesB = BaazProviding & QuuxProviding - } - """ - - let output = """ - enum Namespace { - typealias DependenciesA - = FooProviding - & BarProviding - typealias DependenciesB - = BaazProviding - & QuuxProviding - } - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 45) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_afterFirst() { - let input = """ - typealias Dependencies = FooProviding & BarProviding & BaazProviding & QuuxProviding - """ - - let output = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 40) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_multipleTypealiases_afterFirst() { - let input = """ - enum Namespace { - typealias DependenciesA = FooProviding & BarProviding - typealias DependenciesB = BaazProviding & QuuxProviding - } - """ - - let output = """ - enum Namespace { - typealias DependenciesA = FooProviding - & BarProviding - typealias DependenciesB = BaazProviding - & QuuxProviding - } - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 45) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth() { - let input = """ - typealias Dependencies = FooProviding & BarProviding & BaazProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 100) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently() { - let input = """ - typealias Dependencies = FooProviding & BarProviding & - BaazProviding & QuuxProviding - """ - - let output = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently2() { - let input = """ - enum Namespace { - typealias Dependencies = FooProviding & BarProviding - & BaazProviding & QuuxProviding - } - """ - - let output = """ - enum Namespace { - typealias Dependencies - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - } - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently3() { - let input = """ - typealias Dependencies - = FooProviding & BarProviding & - BaazProviding & QuuxProviding - """ - - let output = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistently4() { - let input = """ - typealias Dependencies - = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let output = """ - typealias Dependencies = FooProviding - & BarProviding - & BaazProviding - & QuuxProviding - """ - - let options = FormatOptions(wrapTypealiases: .afterFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_shorterThanMaxWidth_butWrappedInconsistentlyWithComment() { - let input = """ - typealias Dependencies = FooProviding & BarProviding // trailing comment 1 - // Inline Comment 1 - & BaazProviding & QuuxProviding // trailing comment 2 - """ - - let output = """ - typealias Dependencies - = FooProviding - & BarProviding // trailing comment 1 - // Inline Comment 1 - & BaazProviding - & QuuxProviding // trailing comment 2 - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 200) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_singleTypePreserved() { - let input = """ - typealias Dependencies = FooProviding - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 10) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.wrap]) - } - - func testWrapArguments_typealias_preservesCommentsBetweenTypes() { - let input = """ - typealias Dependencies - // We use `FooProviding` because `FooFeature` depends on `Foo` - = FooProviding - // We use `BarProviding` because `BarFeature` depends on `Bar` - & BarProviding - // We use `BaazProviding` because `BaazFeature` depends on `Baaz` - & BaazProviding - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_preservesCommentsAfterTypes() { - let input = """ - typealias Dependencies - = FooProviding // We use `FooProviding` because `FooFeature` depends on `Foo` - & BarProviding // We use `BarProviding` because `BarFeature` depends on `Bar` - & BaazProviding // We use `BaazProviding` because `BaazFeature` depends on `Baaz` - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 100) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - func testWrapArguments_typealias_withAssociatedType() { - let input = """ - typealias Collections = Collection & Collection & Collection & Collection - """ - - let output = """ - typealias Collections - = Collection - & Collection - & Collection - & Collection - """ - - let options = FormatOptions(wrapTypealiases: .beforeFirst, maxWidth: 50) - testFormatting(for: input, output, rule: .wrapArguments, options: options, exclude: [.sortTypealiases]) - } - - // MARK: - -return wrap-if-multiline - - func testWrapReturnOnMultilineFunctionDeclaration() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) -> String {} - """ - - let output = """ - func multilineFunction( - foo _: String, - bar _: String) - -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapReturnAndEffectOnMultilineFunctionDeclaration() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) async -> String {} - """ - - let output = """ - func multilineFunction( - foo _: String, - bar _: String) - async -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testDoesntWrapReturnAndEffectOnSingleLineFunctionDeclaration() { - let input = """ - func singleLineFunction() async throws -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testDoesntWrapReturnAndTypedEffectOnSingleLineFunctionDeclaration() { - let input = """ - func singleLineFunction() async throws(Foo) -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapEffectOnMultilineFunctionDeclaration() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) async throws - -> String {} - """ - - let output = """ - func multilineFunction( - foo _: String, - bar _: String) - async throws -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testUnwrapEffectOnMultilineFunctionDeclaration() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) - async throws -> String {} - """ - - let output = """ - func multilineFunction( - foo _: String, - bar _: String) async throws - -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .never - ) - - testFormatting(for: input, output, rule: .wrapArguments, options: options) - } - - func testWrapArgumentsDoesntBreakFunctionDeclaration_issue_1776() { - let input = """ - struct OpenAPIController: RouteCollection { - let info = InfoObject(title: "Swagger {{cookiecutter.service_name}} - OpenAPI", - description: "{{cookiecutter.description}}", - contact: .init(email: "{{cookiecutter.email}}"), - version: Version(0, 0, 1)) - func boot(routes: RoutesBuilder) throws { - routes.get("swagger", "swagger.json") { - $0.application.routes.openAPI(info: info) - } - .excludeFromOpenAPI() - } - } - """ - - let options = FormatOptions(wrapEffects: .never) - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) - } - - func testWrapEffectsNeverPreservesComments() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) - // Comment here between the parameters and effects - async throws -> String {} - """ - - let options = FormatOptions(closingParenPosition: .sameLine, wrapEffects: .never) - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testWrapReturnOnMultilineFunctionDeclarationWithAfterFirst() { - let input = """ - func multilineFunction(foo _: String, - bar _: String) -> String {} - """ - - let output = """ - func multilineFunction(foo _: String, - bar _: String) - -> String {} - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting( - for: input, output, rule: .wrapArguments, options: options, - exclude: [.indent] - ) - } - - func testWrapReturnOnMultilineThrowingFunctionDeclarationWithAfterFirst() { - let input = """ - func multilineFunction(foo _: String, - bar _: String) throws -> String {} - """ - - let output = """ - func multilineFunction(foo _: String, - bar _: String) throws - -> String {} - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting( - for: input, output, rule: .wrapArguments, options: options, - exclude: [.indent] - ) - } - - func testWrapReturnAndEffectOnMultilineThrowingFunctionDeclarationWithAfterFirst() { - let input = """ - func multilineFunction(foo _: String, - bar _: String) throws -> String {} - """ - - let output = """ - func multilineFunction(foo _: String, - bar _: String) - throws -> String {} - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - - testFormatting( - for: input, output, rule: .wrapArguments, options: options, - exclude: [.indent] - ) - } - - func testDoesntWrapReturnOnMultilineThrowingFunction() { - let input = """ - func multilineFunction(foo _: String, - bar _: String) - throws -> String {} - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting( - for: input, rule: .wrapArguments, options: options, - exclude: [.indent] - ) - } - - func testDoesntWrapReturnOnSingleLineFunctionDeclaration() { - let input = """ - func multilineFunction(foo _: String, bar _: String) -> String {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineArray() { - let input = """ - final class Foo { - private static let array = [ - "one", - ] - - private func singleLine() -> String {} - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - func testDoesntWrapReturnOnSingleLineFunctionDeclarationAfterMultilineMethodCall() { - let input = """ - public final class Foo { - public var multiLineMethodCall = Foo.multiLineMethodCall( - bar: bar, - baz: baz) - - func singleLine() -> String { - return "method body" - } - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline - ) - - testFormatting(for: input, rule: .wrapArguments, options: options, exclude: [.propertyType]) - } - - func testPreserveReturnOnMultilineFunctionDeclarationByDefault() { - let input = """ - func multilineFunction( - foo _: String, - bar _: String) -> String - {} - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - - testFormatting(for: input, rule: .wrapArguments, options: options) - } - - // MARK: wrapMultilineStatementBraces - - func testMultilineIfBraceOnNextLine() { - let input = """ - if firstConditional, - array.contains(where: { secondConditional }) { - print("statement body") - } - """ - let output = """ - if firstConditional, - array.contains(where: { secondConditional }) - { - print("statement body") - } - """ - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) - } - - func testMultilineFuncBraceOnNextLine() { - let input = """ - func method( - foo: Int, - bar: Int) { - print("function body") - } - """ - let output = """ - func method( - foo: Int, - bar: Int) - { - print("function body") - } - """ - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - exclude: [.wrapArguments, .unusedArguments]) - } - - func testMultilineInitBraceOnNextLine() { - let input = """ - init(foo: Int, - bar: Int) { - print("function body") - } - """ - let output = """ - init(foo: Int, - bar: Int) - { - print("function body") - } - """ - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - exclude: [.wrapArguments, .unusedArguments]) - } - - func testMultilineForLoopBraceOnNextLine() { - let input = """ - for foo in - [1, 2] { - print(foo) - } - """ - let output = """ - for foo in - [1, 2] - { - print(foo) - } - """ - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) - } - - func testMultilineForLoopBraceOnNextLine2() { - let input = """ - for foo in [ - 1, - 2, - ] { - print(foo) - } - """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces) - } - - func testMultilineForWhereLoopBraceOnNextLine() { - let input = """ - for foo in bar - where foo != baz { - print(foo) - } - """ - let output = """ - for foo in bar - where foo != baz - { - print(foo) - } - """ - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) - } - - func testMultilineGuardBraceOnNextLine() { - let input = """ - guard firstConditional, - array.contains(where: { secondConditional }) else { - print("statement body") - } - """ - let output = """ - guard firstConditional, - array.contains(where: { secondConditional }) else - { - print("statement body") - } - """ - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - exclude: [.braces, .elseOnSameLine]) - } - - func testInnerMultilineIfBraceOnNextLine() { - let input = """ - if outerConditional { - if firstConditional, - array.contains(where: { secondConditional }) { - print("statement body") - } - } - """ - let output = """ - if outerConditional { - if firstConditional, - array.contains(where: { secondConditional }) - { - print("statement body") - } - } - """ - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces) - } - - func testMultilineIfBraceOnSameLine() { - let input = """ - if let object = Object([ - foo, - bar, - ]) { - print("statement body") - } - """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces, exclude: [.propertyType]) - } - - func testSingleLineIfBraceOnSameLine() { - let input = """ - if firstConditional { - print("statement body") - } - """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces) - } - - func testSingleLineGuardBrace() { - let input = """ - guard firstConditional else { - print("statement body") - } - """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces) - } - - func testGuardElseOnOwnLineBraceNotWrapped() { - let input = """ - guard let foo = bar, - bar == baz - else { - print("statement body") - } - """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces) - } - - func testMultilineGuardClosingBraceOnSameLine() { - let input = """ - guard let foo = bar, - let baz = quux else { return } - """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces, - exclude: [.wrapConditionalBodies]) - } - - func testMultilineGuardBraceOnSameLineAsElse() { - let input = """ - guard let foo = bar, - let baz = quux - else { - return - } - """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces) - } - - func testMultilineClassBrace() { - let input = """ - class Foo: BarProtocol, - BazProtocol - { - init() {} - } - """ - testFormatting(for: input, rule: .wrapMultilineStatementBraces) - } - - func testMultilineClassBraceNotAppliedForXcodeIndentationMode() { - let input = """ - class Foo: BarProtocol, - BazProtocol { - init() {} - } - """ - let options = FormatOptions(xcodeIndentation: true) - testFormatting(for: input, rule: .wrapMultilineStatementBraces, options: options) - } - - func testMultilineBraceAppliedToTrailingClosure_wrapBeforeFirst() { - let input = """ - UIView.animate( - duration: 10, - options: []) { - print() - } - """ - - let output = """ - UIView.animate( - duration: 10, - options: []) - { - print() - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - options: options, exclude: [.indent]) - } - - func testMultilineBraceAppliedToTrailingClosure2_wrapBeforeFirst() { - let input = """ - moveGradient( - to: defaultPosition, - isTouchDown: false, - animated: animated) { - self.isTouchDown = false - } - """ - - let output = """ - moveGradient( - to: defaultPosition, - isTouchDown: false, - animated: animated) - { - self.isTouchDown = false - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .indent, .braces, - ], options: options) - } - - func testMultilineBraceAppliedToGetterBody_wrapBeforeFirst() { - let input = """ - var items = Adaptive.adaptive( - compact: Sizes.horizontalPaddingTiny_8, - regular: Sizes.horizontalPaddingLarge_64) { - didSet { updateAccessoryViewSpacing() } - } - """ - - let output = """ - var items = Adaptive.adaptive( - compact: Sizes.horizontalPaddingTiny_8, - regular: Sizes.horizontalPaddingLarge_64) - { - didSet { updateAccessoryViewSpacing() } - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .indent, - ], options: options, exclude: [.propertyType]) - } - - func testMultilineBraceAppliedToTrailingClosure_wrapAfterFirst() { - let input = """ - UIView.animate(duration: 10, - options: []) { - print() - } - """ - - let output = """ - UIView.animate(duration: 10, - options: []) - { - print() - } - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, output, rule: .wrapMultilineStatementBraces, - options: options, exclude: [.indent]) - } - - func testMultilineBraceAppliedToGetterBody_wrapAfterFirst() { - let input = """ - var items = Adaptive.adaptive(compact: Sizes.horizontalPaddingTiny_8, - regular: Sizes.horizontalPaddingLarge_64) - { - didSet { updateAccessoryViewSpacing() } - } - """ - - let options = FormatOptions( - wrapArguments: .afterFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, [], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options, exclude: [.propertyType]) - } - - func testMultilineBraceAppliedToSubscriptBody() { - let input = """ - public subscript( - key: Foo) - -> ServerDrivenLayoutContentPresenter? - { - get { foo[key] } - set { foo[key] = newValue } - } - """ - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, rule: .wrapMultilineStatementBraces, - options: options, exclude: [.trailingClosures]) - } - - func testWrapsMultilineStatementConsistently() { - let input = """ - func aFunc( - one _: Int, - two _: Int) -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int) - -> String - { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistentlyWithEffects() { - let input = """ - func aFunc( - one _: Int, - two _: Int) async throws -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int) - async throws -> String - { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistentlyWithArrayReturnType() { - let input = """ - public func aFunc( - one _: Int, - two _: Int) -> [String] { - ["one"] - } - """ - - let output = """ - public func aFunc( - one _: Int, - two _: Int) - -> [String] - { - ["one"] - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistentlyWithComplexGenericReturnType() { - let input = """ - public func aFunc( - one _: Int, - two _: Int) throws -> some Collection { - ["one"] - } - """ - - let output = """ - public func aFunc( - one _: Int, - two _: Int) - throws -> some Collection - { - ["one"] - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistentlyWithTuple() { - let input = """ - public func aFunc( - one: Int, - two: Int) -> (one: String, two: String) { - (one: String(one), two: String(two)) - } - """ - - let output = """ - public func aFunc( - one: Int, - two: Int) - -> (one: String, two: String) - { - (one: String(one), two: String(two)) - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine, - wrapReturnType: .ifMultiline, - wrapEffects: .ifMultiline - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently2() { - let input = """ - func aFunc( - one _: Int, - two _: Int) -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int - ) -> String { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .balanced - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently2_withEffects() { - let input = """ - func aFunc( - one _: Int, - two _: Int) async throws -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int - ) async throws -> String { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .balanced, - wrapEffects: .never - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently2_withTypedEffects() { - let input = """ - func aFunc( - one _: Int, - two _: Int) async throws(Foo) -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int - ) async throws(Foo) -> String { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .balanced, - wrapEffects: .never - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently3() { - let input = """ - func aFunc( - one _: Int, - two _: Int - ) -> String { - "one" - } - """ - - let options = FormatOptions( - // wrapMultilineStatementBraces: true, - wrapArguments: .beforeFirst, - closingParenPosition: .balanced - ) - - testFormatting(for: input, [], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapsMultilineStatementConsistently4() { - let input = """ - func aFunc( - one _: Int, - two _: Int - ) -> String { - "one" - } - """ - - let output = """ - func aFunc( - one _: Int, - two _: Int) -> String - { - "one" - } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, [output], rules: [ - .wrapMultilineStatementBraces, - .wrapArguments, - ], options: options) - } - - func testWrapMultilineStatementConsistently5() { - let input = """ - foo( - one: 1, - two: 2).bar({ _ in - "one" - }) - """ - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, rule: .wrapMultilineStatementBraces, - options: options, exclude: [.trailingClosures]) - } - - func testOpenBraceAfterEqualsInGuardNotWrapped() { - let input = """ - guard - let foo = foo, - let bar: String = { - nil - }() - else { return } - """ - - let options = FormatOptions( - wrapArguments: .beforeFirst, - closingParenPosition: .sameLine - ) - testFormatting(for: input, rules: [.wrapMultilineStatementBraces, .wrap], - options: options, exclude: [.indent, .redundantClosure, .wrapConditionalBodies]) - } - - // MARK: wrapConditions before-first - - func testWrapConditionsBeforeFirstPreservesMultilineStatements() { - let input = """ - if - let unwrappedFoo = Foo( - bar: bar, - baz: baz), - unwrappedFoo.elements - .compactMap({ $0 }) - .filter({ - if $0.matchesCondition { - return true - } else { - return false - } - }).isEmpty, - let bar = unwrappedFoo.bar, - let baz = unwrappedFoo.bar? - .first(where: { $0.isBaz }), - let unwrappedFoo2 = Foo( - bar: bar2, - baz: baz2), - let quux = baz.quux - {} - """ - testFormatting( - for: input, rules: [.wrapArguments, .indent], - options: FormatOptions(closingParenPosition: .sameLine, wrapConditions: .beforeFirst), - exclude: [.propertyType] - ) - } - - func testWrapConditionsBeforeFirst() { - let input = """ - if let foo = foo, - let bar = bar, - foo == bar {} - - else if foo != bar, - let quux = quux {} - - if let baz = baz {} - - guard baz.filter({ $0 == foo }), - let bar = bar else {} - - while let foo = foo, - let bar = bar {} - """ - let output = """ - if - let foo = foo, - let bar = bar, - foo == bar {} - - else if - foo != bar, - let quux = quux {} - - if let baz = baz {} - - guard - baz.filter({ $0 == foo }), - let bar = bar else {} - - while - let foo = foo, - let bar = bar {} - """ - testFormatting( - for: input, output, rule: .wrapArguments, - options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), - exclude: [.wrapConditionalBodies] - ) - } - - func testWrapConditionsBeforeFirstWhereShouldPreserveExisting() { - let input = """ - else {} - - else - {} - - if foo == bar - {} - - guard let foo = bar else - {} - - guard let foo = bar - else {} - """ - testFormatting( - for: input, rule: .wrapArguments, - options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), - exclude: [.elseOnSameLine, .wrapConditionalBodies] - ) - } - - func testWrapConditionsAfterFirst() { - let input = """ - if - let foo = foo, - let bar = bar, - foo == bar {} - - else if - foo != bar, - let quux = quux {} - - else {} - - if let baz = baz {} - - guard - baz.filter({ $0 == foo }), - let bar = bar else {} - - while - let foo = foo, - let bar = bar {} - """ - let output = """ - if let foo = foo, - let bar = bar, - foo == bar {} - - else if foo != bar, - let quux = quux {} - - else {} - - if let baz = baz {} - - guard baz.filter({ $0 == foo }), - let bar = bar else {} - - while let foo = foo, - let bar = bar {} - """ - testFormatting( - for: input, output, rule: .wrapArguments, - options: FormatOptions(indent: " ", wrapConditions: .afterFirst), - exclude: [.wrapConditionalBodies] - ) - } - - func testWrapConditionsAfterFirstWhenFirstLineIsComment() { - let input = """ - guard - // Apply this rule to any function-like declaration - ["func", "init", "subscript"].contains(keyword.string), - // Opaque generic parameter syntax is only supported in Swift 5.7+ - formatter.options.swiftVersion >= "5.7", - // Validate that this is a generic method using angle bracket syntax, - // and find the indices for all of the key tokens - let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), - let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), - let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), - let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), - genericSignatureStartIndex < paramListStartIndex, - genericSignatureEndIndex < paramListStartIndex, - let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), - let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) - else { return } - """ - let output = """ - guard // Apply this rule to any function-like declaration - ["func", "init", "subscript"].contains(keyword.string), - // Opaque generic parameter syntax is only supported in Swift 5.7+ - formatter.options.swiftVersion >= "5.7", - // Validate that this is a generic method using angle bracket syntax, - // and find the indices for all of the key tokens - let paramListStartIndex = formatter.index(of: .startOfScope("("), after: keywordIndex), - let paramListEndIndex = formatter.endOfScope(at: paramListStartIndex), - let genericSignatureStartIndex = formatter.index(of: .startOfScope("<"), after: keywordIndex), - let genericSignatureEndIndex = formatter.endOfScope(at: genericSignatureStartIndex), - genericSignatureStartIndex < paramListStartIndex, - genericSignatureEndIndex < paramListStartIndex, - let openBraceIndex = formatter.index(of: .startOfScope("{"), after: paramListEndIndex), - let closeBraceIndex = formatter.endOfScope(at: openBraceIndex) - else { return } - """ - testFormatting( - for: input, [output], rules: [.wrapArguments, .indent], - options: FormatOptions(wrapConditions: .afterFirst), - exclude: [.wrapConditionalBodies] - ) - } - - // MARK: conditionsWrap auto - - func testConditionsWrapAutoForLongGuard() { - let input = """ - guard let foo = foo, let bar = bar, let third = third else {} - """ - - let output = """ - guard let foo = foo, - let bar = bar, - let third = third - else {} - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) - ) - } - - func testConditionsWrapAutoForLongGuardWithoutChanges() { - let input = """ - guard let foo = foo, let bar = bar, let third = third else {} - """ - testFormatting( - for: input, - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120) - ) - } - - func testConditionsWrapAutoForMultilineGuard() { - let input = """ - guard let foo = foo, - let bar = bar, let third = third else {} - """ - - let output = """ - guard let foo = foo, - let bar = bar, - let third = third - else {} - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments, .indent], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) - ) - } - - func testConditionsWrapAutoOptionForGuardStyledAsBeforeArgument() { - let input = """ - guard - let foo = foo, - let bar = bar, - let third = third - else {} - - guard - let foo = foo, - let bar = bar, - let third = third - else {} - """ - - let output = """ - guard let foo = foo, - let bar = bar, - let third = third - else {} - - guard let foo = foo, - let bar = bar, - let third = third - else {} - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) - ) - } - - func testConditionsWrapAutoOptionForGuardWhenElseOnNewLine() { - let input = """ - guard let foo = foo, let bar = bar, let third = third - else {} - """ - - let output = """ - guard let foo = foo, - let bar = bar, - let third = third - else {} - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) - ) - } - - func testConditionsWrapAutoOptionForGuardWhenElseOnNewLineAndNotAligned() { - let input = """ - guard let foo = foo, let bar = bar, let third = third - else {} - - guard let foo = foo, let bar = bar, let third = third - - else {} - """ - - let output = """ - guard let foo = foo, - let bar = bar, - let third = third - else {} - - guard let foo = foo, - let bar = bar, - let third = third - else {} - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) - ) - } - - func testConditionsWrapAutoOptionForGuardInMethod() { - let input = """ - func doSmth() { - let a = smth as? SmthElse - - guard - let foo = foo, - let bar = bar, - let third = third - else { - return nil - } - - let value = a.doSmth() - } - """ - - let output = """ - func doSmth() { - let a = smth as? SmthElse - - guard let foo = foo, - let bar = bar, - let third = third - else { - return nil - } - - let value = a.doSmth() - } - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120) - ) - } - - func testConditionsWrapAutoOptionForIfInsideMethod() { - let input = """ - func doSmth() { - let a = smth as? SmthElse - - if - let foo = foo, - let bar = bar, - let third = third { - return nil - } - - let value = a.doSmth() - } - """ - - let output = """ - func doSmth() { - let a = smth as? SmthElse - - if let foo = foo, - let bar = bar, - let third = third { - return nil - } - - let value = a.doSmth() - } - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 120), - exclude: [.wrapMultilineStatementBraces] - ) - } - - func testConditionsWrapAutoOptionForLongIf() { - let input = """ - if let foo = foo, let bar = bar, let third = third {} - """ - - let output = """ - if let foo = foo, - let bar = bar, - let third = third {} - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments, .indent], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 25) - ) - } - - func testConditionsWrapAutoOptionForLongMultilineIf() { - let input = """ - if let foo = foo, - let bar = bar, let third = third {} - """ - - let output = """ - if let foo = foo, - let bar = bar, - let third = third {} - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments, .indent], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 25) - ) - } - - // MARK: conditionsWrap always - - func testConditionWrapAlwaysOptionForLongGuard() { - let input = """ - guard let foo = foo, let bar = bar, let third = third else {} - """ - - let output = """ - guard let foo = foo, - let bar = bar, - let third = third - else {} - """ - - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .always, maxWidth: 120) - ) - } - - // MARK: - wrapAttributes - - func testPreserveWrappedFuncAttributeByDefault() { - let input = """ - @objc - func foo() {} - """ - testFormatting(for: input, rule: .wrapAttributes) - } - - func testPreserveUnwrappedFuncAttributeByDefault() { - let input = """ - @objc func foo() {} - """ - testFormatting(for: input, rule: .wrapAttributes) - } - - func testWrapFuncAttribute() { - let input = """ - @available(iOS 14.0, *) func foo() {} - """ - let output = """ - @available(iOS 14.0, *) - func foo() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testWrapInitAttribute() { - let input = """ - @available(iOS 14.0, *) init() {} - """ - let output = """ - @available(iOS 14.0, *) - init() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testMultipleAttributesNotSeparated() { - let input = """ - @objc @IBAction func foo {} - """ - let output = """ - @objc @IBAction - func foo {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, - options: options, exclude: [.redundantObjc]) - } - - func testFuncAttributeStaysWrapped() { - let input = """ - @available(iOS 14.0, *) - func foo() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testUnwrapFuncAttribute() { - let input = """ - @available(iOS 14.0, *) - func foo() {} - """ - let output = """ - @available(iOS 14.0, *) func foo() {} - """ - let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testUnwrapFuncAttribute2() { - let input = """ - class MyClass: NSObject { - @objc - func myFunction() { - print("Testing") - } - } - """ - let output = """ - class MyClass: NSObject { - @objc func myFunction() { - print("Testing") - } - } - """ - let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testFuncAttributeStaysUnwrapped() { - let input = """ - @objc func foo() {} - """ - let options = FormatOptions(funcAttributes: .sameLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testVarAttributeIsNotWrapped() { - let input = """ - @IBOutlet var foo: UIView? - - @available(iOS 14.0, *) - func foo() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testWrapTypeAttribute() { - let input = """ - @available(iOS 14.0, *) class Foo {} - """ - let output = """ - @available(iOS 14.0, *) - class Foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting( - for: input, - output, - rule: .wrapAttributes, - options: options - ) - } - - func testWrapExtensionAttribute() { - let input = """ - @available(iOS 14.0, *) extension Foo {} - """ - let output = """ - @available(iOS 14.0, *) - extension Foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting( - for: input, - output, - rule: .wrapAttributes, - options: options - ) - } - - func testTypeAttributeStaysWrapped() { - let input = """ - @available(iOS 14.0, *) - struct Foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testUnwrapTypeAttribute() { - let input = """ - @available(iOS 14.0, *) - enum Foo {} - """ - let output = """ - @available(iOS 14.0, *) enum Foo {} - """ - let options = FormatOptions(typeAttributes: .sameLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testTypeAttributeStaysUnwrapped() { - let input = """ - @objc class Foo {} - """ - let options = FormatOptions(typeAttributes: .sameLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testTestableImportIsNotWrapped() { - let input = """ - @testable import Framework - - @available(iOS 14.0, *) - class Foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testModifiersDontAffectAttributeWrapping() { - let input = """ - @objc override public func foo {} - """ - let output = """ - @objc - override public func foo {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testClassFuncAttributeTreatedAsFunction() { - let input = """ - @objc class func foo {} - """ - let output = """ - @objc - class func foo {} - """ - let options = FormatOptions(funcAttributes: .prevLine, fragment: true) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testClassFuncAttributeNotTreatedAsType() { - let input = """ - @objc class func foo {} - """ - let options = FormatOptions(typeAttributes: .prevLine, fragment: true) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testClassAttributeNotMistakenForClassLet() { - let input = """ - @objc final class MyClass: NSObject {} - let myClass = MyClass() - """ - let output = """ - @objc - final class MyClass: NSObject {} - let myClass = MyClass() - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) - } - - func testClassImportAttributeNotTreatedAsType() { - let input = """ - @testable import class Framework.Foo - """ - let options = FormatOptions(typeAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testWrapPrivateSetComputedVarAttributes() { - let input = """ - @objc private(set) dynamic var foo = Foo() - """ - let output = """ - @objc - private(set) dynamic var foo = Foo() - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) - } - - func testWrapPrivateSetVarAttributes() { - let input = """ - @objc private(set) dynamic var foo = Foo() - """ - let output = """ - @objc - private(set) dynamic var foo = Foo() - """ - let options = FormatOptions(varAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) - } - - func testDontWrapPrivateSetVarAttributes() { - let input = """ - @objc - private(set) dynamic var foo = Foo() - """ - let output = """ - @objc private(set) dynamic var foo = Foo() - """ - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) - } - - func testWrapConvenienceInitAttribute() { - let input = """ - @objc public convenience init() {} - """ - let output = """ - @objc - public convenience init() {} - """ - let options = FormatOptions(funcAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testWrapPropertyWrapperAttributeVarAttributes() { - let input = """ - @OuterType.Wrapper var foo: Int - """ - let output = """ - @OuterType.Wrapper - var foo: Int - """ - let options = FormatOptions(varAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testWrapPropertyWrapperAttribute() { - let input = """ - @OuterType.Wrapper var foo: Int - """ - let output = """ - @OuterType.Wrapper - var foo: Int - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testDontWrapPropertyWrapperAttribute() { - let input = """ - @OuterType.Wrapper - var foo: Int - """ - let output = """ - @OuterType.Wrapper var foo: Int - """ - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testWrapGenericPropertyWrapperAttribute() { - let input = """ - @OuterType.Generic var foo: WrappedType - """ - let output = """ - @OuterType.Generic - var foo: WrappedType - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testWrapGenericPropertyWrapperAttribute2() { - let input = """ - @OuterType.Generic.Foo var foo: WrappedType - """ - let output = """ - @OuterType.Generic.Foo - var foo: WrappedType - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testAttributeOnComputedProperty() { - let input = """ - extension SectionContainer: ContentProviding where Section: ContentProviding { - @_disfavoredOverload - public var content: Section.Content { - section.content - } - } - """ - - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testWrapAvailableAttributeUnderMaxWidth() { - let input = """ - @available(*, unavailable, message: "This property is deprecated.") - var foo: WrappedType - """ - let output = """ - @available(*, unavailable, message: "This property is deprecated.") var foo: WrappedType - """ - let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testDoesntWrapAvailableAttributeWithLongMessage() { - // Unwrapping this attribute would just cause it to wrap in a different way: - // - // @available( - // *, - // unavailable, - // message: "This property is deprecated. It has a really long message." - // ) var foo: WrappedType - // - // so instead leave it un-wrapped to preserve the existing formatting. - let input = """ - @available(*, unavailable, message: "This property is deprecated. It has a really long message.") - var foo: WrappedType - """ - let options = FormatOptions(maxWidth: 100, varAttributes: .prevLine, storedVarAttributes: .sameLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testDoesntWrapComplexAttribute() { - let input = """ - @Option( - name: ["myArgument"], - help: "Long help text for my example arg from Swift argument parser") - var foo: WrappedType - """ - let options = FormatOptions(closingParenPosition: .sameLine, varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testDoesntWrapComplexMultilineAttribute() { - let input = """ - @available(*, deprecated, message: "Deprecated!") - var foo: WrappedType - """ - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testWrapsComplexAttribute() { - let input = """ - @available(*, deprecated, message: "Deprecated!") var foo: WrappedType - """ - - let output = """ - @available(*, deprecated, message: "Deprecated!") - var foo: WrappedType - """ - let options = FormatOptions(varAttributes: .prevLine, storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testWrapAttributesIndentsLineCorrectly() { - let input = """ - class Foo { - @objc var foo = Foo() - } - """ - let output = """ - class Foo { - @objc - var foo = Foo() - } - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) - } - - func testComplexAttributesException() { - let input = """ - @Environment(\\.myEnvironmentVar) var foo: Foo - - @SomeCustomAttr(argument: true) var foo: Foo - - @available(*, deprecated) var foo: Foo - """ - - let output = """ - @Environment(\\.myEnvironmentVar) var foo: Foo - - @SomeCustomAttr(argument: true) var foo: Foo - - @available(*, deprecated) - var foo: Foo - """ - - let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine, complexAttributesExceptions: ["@SomeCustomAttr"]) - testFormatting(for: input, output, rule: .wrapAttributes, options: options) - } - - func testMixedComplexAndSimpleAttributes() { - let input = """ - /// Simple attributes stay on a single line: - @State private var warpDriveEnabled: Bool - - @ObservedObject private var lifeSupportService: LifeSupportService - - @Environment(\\.controlPanelStyle) private var controlPanelStyle - - @AppStorage("ControlsConfig") private var controlsConfig: ControlConfiguration - - /// Complex attributes are wrapped: - @AppStorage("ControlPanelState", store: myCustomUserDefaults) private var controlPanelState: ControlPanelState - - @Tweak(name: "Aspect ratio") private var aspectRatio = AspectRatio.stretch - - @available(*, unavailable) var saturn5Builder: Saturn5Builder - - @available(*, unavailable, message: "No longer in production") var saturn5Builder: Saturn5Builder - """ - - let output = """ - /// Simple attributes stay on a single line: - @State private var warpDriveEnabled: Bool - - @ObservedObject private var lifeSupportService: LifeSupportService - - @Environment(\\.controlPanelStyle) private var controlPanelStyle - - @AppStorage("ControlsConfig") private var controlsConfig: ControlConfiguration - - /// Complex attributes are wrapped: - @AppStorage("ControlPanelState", store: myCustomUserDefaults) - private var controlPanelState: ControlPanelState - - @Tweak(name: "Aspect ratio") - private var aspectRatio = AspectRatio.stretch - - @available(*, unavailable) - var saturn5Builder: Saturn5Builder - - @available(*, unavailable, message: "No longer in production") - var saturn5Builder: Saturn5Builder - """ - - let options = FormatOptions(storedVarAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) - } - - func testEscapingClosureNotMistakenForComplexAttribute() { - let input = """ - func foo(_ fooClosure: @escaping () throws -> Void) { - try fooClosure() - } - """ - - let options = FormatOptions(complexAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testEscapingTypedThrowClosureNotMistakenForComplexAttribute() { - let input = """ - func foo(_ fooClosure: @escaping () throws(Foo) -> Void) { - try fooClosure() - } - """ - - let options = FormatOptions(complexAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testWrapOrDontWrapMultipleDeclarationsInClass() { - let input = """ - class Foo { - @objc - var foo = Foo() - - @available(*, unavailable) - var bar: Bar - - @available(*, unavailable) - var myComputedFoo: String { - "myComputedFoo" - } - - @Environment(\\.myEnvironmentVar) - var foo - - @State - var myStoredFoo: String = "myStoredFoo" { - didSet { - print(newValue) - } - } - } - """ - let output = """ - class Foo { - @objc var foo = Foo() - - @available(*, unavailable) - var bar: Bar - - @available(*, unavailable) - var myComputedFoo: String { - "myComputedFoo" - } - - @Environment(\\.myEnvironmentVar) var foo - - @State var myStoredFoo: String = "myStoredFoo" { - didSet { - print(newValue) - } - } - } - """ - let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine, complexAttributes: .prevLine) - testFormatting(for: input, output, rule: .wrapAttributes, options: options, exclude: [.propertyType]) - } - - func testWrapOrDontAttributesInSwiftUIView() { - let input = """ - struct MyView: View { - @State var textContent: String - - var body: some View { - childView - } - - @ViewBuilder - var childView: some View { - Text(verbatim: textContent) - } - } - """ - - let options = FormatOptions(varAttributes: .sameLine, storedVarAttributes: .sameLine, computedVarAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testWrapAttributesInSwiftUIView() { - let input = """ - struct MyView: View { - @State var textContent: String - @Environment(\\.myEnvironmentVar) var environmentVar - - var body: some View { - childView - } - - @ViewBuilder var childView: some View { - Text(verbatim: textContent) - } - } - """ - - let options = FormatOptions(varAttributes: .sameLine, complexAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - func testInlineMainActorAttributeNotWrapped() { - let input = """ - var foo: @MainActor (Foo) -> Void - var bar: @MainActor (Bar) -> Void - """ - let options = FormatOptions(storedVarAttributes: .prevLine, computedVarAttributes: .prevLine) - testFormatting(for: input, rule: .wrapAttributes, options: options) - } - - // MARK: wrapEnumCases - - func testMultilineEnumCases() { - let input = """ - enum Enum1: Int { - case a = 0, p = 2, c, d - case e, k - case m(String, String) - } - """ - let output = """ - enum Enum1: Int { - case a = 0 - case p = 2 - case c - case d - case e - case k - case m(String, String) - } - """ - testFormatting(for: input, output, rule: .wrapEnumCases) - } - - func testMultilineEnumCasesWithNestedEnumsDoesNothing() { - let input = """ - public enum SearchTerm: Decodable, Equatable { - case term(name: String) - case category(category: Category) - - enum CodingKeys: String, CodingKey { - case name - case type - case categoryID = "category_id" - case attributes - } - } - """ - testFormatting(for: input, rule: .wrapEnumCases) - } - - func testEnumCaseSplitOverMultipleLines() { - let input = """ - enum Foo { - case bar( - x: String, - y: Int - ), baz - } - """ - let output = """ - enum Foo { - case bar( - x: String, - y: Int - ) - case baz - } - """ - testFormatting(for: input, output, rule: .wrapEnumCases) - } - - func testEnumCasesAlreadyWrappedOntoMultipleLines() { - let input = """ - enum Foo { - case bar, - baz, - quux - } - """ - let output = """ - enum Foo { - case bar - case baz - case quux - } - """ - testFormatting(for: input, output, rule: .wrapEnumCases) - } - - func testEnumCasesIfValuesWithoutValuesDoesNothing() { - let input = """ - enum Foo { - case bar, baz, quux - } - """ - testFormatting(for: input, rule: .wrapEnumCases, - options: FormatOptions(wrapEnumCases: .withValues)) - } - - func testEnumCasesIfValuesWithRawValuesAndNestedEnum() { - let input = """ - enum Foo { - case bar = 1, baz, quux - - enum Foo2 { - case bar, baz, quux - } - } - """ - let output = """ - enum Foo { - case bar = 1 - case baz - case quux - - enum Foo2 { - case bar, baz, quux - } - } - """ - testFormatting( - for: input, - output, - rule: .wrapEnumCases, - options: FormatOptions(wrapEnumCases: .withValues) - ) - } - - func testEnumCasesIfValuesWithAssociatedValues() { - let input = """ - enum Foo { - case bar(a: Int), baz, quux - } - """ - let output = """ - enum Foo { - case bar(a: Int) - case baz - case quux - } - """ - testFormatting( - for: input, - output, - rule: .wrapEnumCases, - options: FormatOptions(wrapEnumCases: .withValues) - ) - } - - func testEnumCasesWithCommentsAlreadyWrappedOntoMultipleLines() { - let input = """ - enum Foo { - case bar, // bar - baz, // baz - quux // quux - } - """ - let output = """ - enum Foo { - case bar // bar - case baz // baz - case quux // quux - } - """ - testFormatting(for: input, output, rule: .wrapEnumCases) - } - - func testNoWrapEnumStatementAllOnOneLine() { - let input = "enum Foo { bar, baz }" - testFormatting(for: input, rule: .wrapEnumCases) - } - - func testNoConfuseIfCaseWithEnum() { - let input = """ - enum Foo { - case foo - case bar(value: [Int]) - } - - func baz() { - if case .foo = foo, - case .bar(let value) = bar, - value.isEmpty - { - print("") - } - } - """ - testFormatting(for: input, rule: .wrapEnumCases, - exclude: [.hoistPatternLet]) - } - - func testNoMangleUnindentedEnumCases() { - let input = """ - enum Foo { - case foo, bar - } - """ - let output = """ - enum Foo { - case foo - case bar - } - """ - testFormatting(for: input, output, rule: .wrapEnumCases, exclude: [.indent]) - } - - func testNoMangleEnumCaseOnOpeningLine() { - let input = """ - enum SortOrder { case - asc(String), desc(String) - } - """ - // TODO: improve formatting here - let output = """ - enum SortOrder { case - asc(String) - case desc(String) - } - """ - testFormatting(for: input, output, rule: .wrapEnumCases, exclude: [.indent]) - } - - func testNoWrapSingleLineEnumCases() { - let input = "enum Foo { case foo, bar }" - testFormatting(for: input, rule: .wrapEnumCases) - } - - // MARK: wrapSwitchCases - - func testMultilineSwitchCases() { - let input = """ - func foo() { - switch bar { - case .a(_), .b, "c": - print("") - case .d: - print("") - } - } - """ - let output = """ - func foo() { - switch bar { - case .a(_), - .b, - "c": - print("") - case .d: - print("") - } - } - """ - testFormatting(for: input, output, rule: .wrapSwitchCases) - } - - func testIfAfterSwitchCaseNotWrapped() { - let input = """ - switch foo { - case "foo": - print("") - default: - print("") - } - if let foo = bar, foo != .baz { - throw error - } - """ - testFormatting(for: input, rule: .wrapSwitchCases) - } - - // MARK: wrapSingleLineComments - - func testWrapSingleLineComment() { - let input = """ - // a b cde fgh - """ - let output = """ - // a b - // cde - // fgh - """ - - testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 6)) - } - - func testWrapSingleLineCommentThatOverflowsByOneCharacter() { - let input = """ - // a b cde fg h - """ - let output = """ - // a b cde fg - // h - """ - - testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 14)) - } - - func testNoWrapSingleLineCommentThatExactlyFits() { - let input = """ - // a b cde fg h - """ - - testFormatting(for: input, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 15)) - } - - func testWrapSingleLineCommentWithNoLeadingSpace() { - let input = """ - //a b cde fgh - """ - let output = """ - //a b - //cde - //fgh - """ - - testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 6), - exclude: [.spaceInsideComments]) - } - - func testWrapDocComment() { - let input = """ - /// a b cde fgh - """ - let output = """ - /// a b - /// cde - /// fgh - """ - - testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 7), exclude: [.docComments]) - } - - func testWrapDocLineCommentWithNoLeadingSpace() { - let input = """ - ///a b cde fgh - """ - let output = """ - ///a b - ///cde - ///fgh - """ - - testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 6), - exclude: [.spaceInsideComments, .docComments]) - } - - func testWrapSingleLineCommentWithIndent() { - let input = """ - func f() { - // a b cde fgh - let x = 1 - } - """ - let output = """ - func f() { - // a b cde - // fgh - let x = 1 - } - """ - - testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 14), exclude: [.docComments]) - } - - func testWrapSingleLineCommentAfterCode() { - let input = """ - func f() { - foo.bar() // this comment is much much much too long - } - """ - let output = """ - func f() { - foo.bar() // this comment - // is much much much too - // long - } - """ - - testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 29), exclude: [.wrap]) - } - - func testWrapDocCommentWithLongURL() { - let input = """ - /// See [Link](https://www.domain.com/pathextension/pathextension/pathextension/pathextension/pathextension/pathextension). - """ - - testFormatting(for: input, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 100), exclude: [.docComments]) - } - - func testWrapDocCommentWithLongURL2() { - let input = """ - /// Link to SDK documentation - https://docs.adyen.com/checkout/3d-secure/native-3ds2/api-integration#collect-the-3d-secure-2-device-fingerprint-from-an-ios-app - """ - - testFormatting(for: input, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 80)) - } - - func testWrapDocCommentWithMultipleLongURLs() { - let input = """ - /// Link to http://a-very-long-url-that-wont-fit-on-one-line, http://another-very-long-url-that-wont-fit-on-one-line - """ - let output = """ - /// Link to http://a-very-long-url-that-wont-fit-on-one-line, - /// http://another-very-long-url-that-wont-fit-on-one-line - """ - - testFormatting(for: input, output, rule: .wrapSingleLineComments, - options: FormatOptions(maxWidth: 40), exclude: [.docComments]) - } - - // MARK: - wrapMultilineConditionalAssignment - - func testWrapIfExpressionAssignment() { - let input = """ - let foo = if let bar { - bar - } else { - baaz - } - """ - - let output = """ - let foo = - if let bar { - bar - } else { - baaz - } - """ - - testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) - } - - func testUnwrapsAssignmentOperatorInIfExpressionAssignment() { - let input = """ - let foo - = if let bar { - bar - } else { - baaz - } - """ - - let output = """ - let foo = - if let bar { - bar - } else { - baaz - } - """ - - testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) - } - - func testUnwrapsAssignmentOperatorInIfExpressionFollowingComment() { - let input = """ - let foo - // In order to unwrap the `=` here it has to move it to - // before the comment, rather than simply unwrapping it. - = if let bar { - bar - } else { - baaz - } - """ - - let output = """ - let foo = - // In order to unwrap the `=` here it has to move it to - // before the comment, rather than simply unwrapping it. - if let bar { - bar - } else { - baaz - } - """ - - testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) - } - - func testWrapIfAssignmentWithoutIntroducer() { - let input = """ - property = if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - - let output = """ - property = - if condition { - Foo("foo") - } else { - Foo("bar") - } - """ - - testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) - } - - func testWrapSwitchAssignmentWithoutIntroducer() { - let input = """ - property = switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let output = """ - property = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) - } - - func testWrapSwitchAssignmentWithComplexLValue() { - let input = """ - property?.foo!.bar["baaz"] = switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - let output = """ - property?.foo!.bar["baaz"] = - switch condition { - case true: - Foo("foo") - case false: - Foo("bar") - } - """ - - testFormatting(for: input, [output], rules: [.wrapMultilineConditionalAssignment, .indent]) - } -} diff --git a/Tests/RulesTests.swift b/Tests/XCTestCase+testFormatting.swift similarity index 98% rename from Tests/RulesTests.swift rename to Tests/XCTestCase+testFormatting.swift index 279382f37..5d3070e91 100644 --- a/Tests/RulesTests.swift +++ b/Tests/XCTestCase+testFormatting.swift @@ -1,5 +1,5 @@ // -// RulesTests.swift +// XCTestCase+testFormatting.swift // SwiftFormat // // Created by Nick Lockwood on 12/08/2016. @@ -32,9 +32,7 @@ import XCTest @testable import SwiftFormat -class RulesTests: XCTestCase { - // MARK: - shared test infra - +extension XCTestCase { func testFormatting(for input: String, _ output: String? = nil, rule: FormatRule, options: FormatOptions = .default, exclude: [FormatRule] = [], file: StaticString = #file, line: UInt = #line) From a23b9165b7fdec269a2cc5791f9dc2f81642e361 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Tue, 30 Jul 2024 08:47:11 -0700 Subject: [PATCH 29/52] Update organizeDeclarations to avoid inserting blank lines inside consecutive groups of properties (#1795) --- Rules.md | 1 + Sources/DeclarationHelpers.swift | 282 +++++++++++++++----- Sources/OptionDescriptor.swift | 8 + Sources/Options.swift | 3 + Sources/Rules/MarkTypes.swift | 6 +- Sources/Rules/OrganizeDeclarations.swift | 1 + Sources/Rules/SortDeclarations.swift | 2 +- Tests/MetadataTests.swift | 1 + Tests/Rules/OrganizeDeclarationsTests.swift | 160 +++++++++++ 9 files changed, 386 insertions(+), 78 deletions(-) diff --git a/Rules.md b/Rules.md index 1164c94dd..b47410c21 100644 --- a/Rules.md +++ b/Rules.md @@ -1392,6 +1392,7 @@ Option | Description `--typeorder` | Order for declaration type groups inside declaration `--visibilitymarks` | Marks for visibility groups (public:Public Fields,..) `--typemarks` | Marks for declaration type groups (classMethod:Baaz,..) +`--groupblanklines` | Require a blank line after each subgroup. Default: true
Examples diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 180c278fe..8b5c49dc8 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -103,6 +103,16 @@ enum Declaration: Hashable { return typeKeywords.contains(keyword) } + /// Whether or not this is a simple `declaration` (not a `type` or `conditionalCompilation`) + var isSimpleDeclaration: Bool { + switch self { + case .declaration: + return true + case .type, .conditionalCompilation: + return false + } + } + /// The name of this type or variable var name: String? { let parser = Formatter(openTokens) @@ -929,14 +939,37 @@ extension Formatter { // Remove all of the existing category separators, so they can be re-added // at the correct location after sorting the declarations. - let typeBodyWithoutCategorySeparators = removeExistingCategorySeparators( + var typeBody = removeExistingCategorySeparators( from: typeDeclaration.body, with: options.organizationMode, using: categoryOrder ) + // Track the consecutive groups of property declarations so we can avoid inserting + // blank lines between elements in the group if possible. + var consecutivePropertyGroups = consecutivePropertyDeclarationGroups(in: typeDeclaration.body) + .filter { group in + // Only track declaration groups where the group as a whole is followed by a + // blank line, since otherwise the declarations can be reordered without issues. + guard let lastDeclarationInGroup = group.last else { return false } + return lastDeclarationInGroup.tokens.numberOfTrailingLinebreaks() > 1 + } + + // Remove the trailing blank line from the last declaration in each consecutive group + for (groupIndex, consecutivePropertyGroup) in consecutivePropertyGroups.enumerated() { + guard let lastDeclarationInGroup = consecutivePropertyGroup.last, + let indexOfDeclaration = typeBody.firstIndex(of: lastDeclarationInGroup) + else { continue } + + let updatedDeclaration = lastDeclarationInGroup.endingWithoutBlankLine() + let indexInGroup = consecutivePropertyGroup.indices.last! + + typeBody[indexOfDeclaration] = updatedDeclaration + consecutivePropertyGroups[groupIndex][indexInGroup] = updatedDeclaration + } + // Categorize each of the declarations into their primary groups - let categorizedDeclarations: [CategorizedDeclaration] = typeBodyWithoutCategorySeparators + let categorizedDeclarations: [CategorizedDeclaration] = typeBody .map { declaration in let declarationCategory = category( of: declaration, @@ -948,16 +981,27 @@ extension Formatter { } // Sort the declarations based on their category and type - guard let sortedDeclarations = sortCategorizedDeclarations( + guard var sortedTypeBody = sortCategorizedDeclarations( categorizedDeclarations, in: typeDeclaration ) else { return typeDeclaration } + // Insert a blank line after the last declaration in each original group + for consecutivePropertyGroup in consecutivePropertyGroups { + let propertiesInGroup = Set(consecutivePropertyGroup) + + guard let lastDeclarationInSortedBody = sortedTypeBody.lastIndex(where: { propertiesInGroup.contains($0.declaration) }) + else { continue } + + sortedTypeBody[lastDeclarationInSortedBody].declaration = + sortedTypeBody[lastDeclarationInSortedBody].declaration.endingWithBlankLine() + } + // Add a mark comment for each top-level category let sortedAndMarkedType = addCategorySeparators( to: typeDeclaration, - sortedDeclarations: sortedDeclarations + sortedDeclarations: sortedTypeBody ) return sortedAndMarkedType @@ -997,9 +1041,7 @@ extension Formatter { private func sortCategorizedDeclarations( _ categorizedDeclarations: [CategorizedDeclaration], in typeDeclaration: TypeDeclaration - ) - -> [CategorizedDeclaration]? - { + ) -> [CategorizedDeclaration]? { let sortAlphabeticallyWithinSubcategories = shouldSortAlphabeticallyWithinSubcategories(in: typeDeclaration) var sortedDeclarations = sortDeclarations( @@ -1140,6 +1182,44 @@ extension Formatter { return lhsPropertiesOrder == rhsPropertiesOrder } + // Finds all of the consecutive groups of property declarations in the type body + private func consecutivePropertyDeclarationGroups(in body: [Declaration]) -> [[Declaration]] { + var declarationGroups: [[Declaration]] = [] + var currentGroup: [Declaration] = [] + + /// Ends the current group, ensuring that groups are only recorded + /// when they contain two or more declarations. + func endCurrentGroup(addingToExistingGroup declarationToAdd: Declaration? = nil) { + if let declarationToAdd = declarationToAdd { + currentGroup.append(declarationToAdd) + } + + if currentGroup.count >= 2 { + declarationGroups.append(currentGroup) + } + + currentGroup = [] + } + + for declaration in body { + guard declaration.keyword == "let" || declaration.keyword == "var" else { + endCurrentGroup() + continue + } + + let hasTrailingBlankLine = declaration.tokens.numberOfTrailingLinebreaks() > 1 + + if hasTrailingBlankLine { + endCurrentGroup(addingToExistingGroup: declaration) + } else { + currentGroup.append(declaration) + } + } + + endCurrentGroup() + return declarationGroups + } + /// Adds MARK category separates to the given type private func addCategorySeparators( to typeDeclaration: TypeDeclaration, @@ -1178,7 +1258,7 @@ extension Formatter { // make sure the type's opening sequence of tokens ends with // at least one blank line so the category separator appears balanced if markedDeclarations.isEmpty { - typeDeclaration.open = endingWithBlankLine(typeDeclaration.open) + typeDeclaration.open = typeDeclaration.open.endingWithBlankLine() } markedDeclarations.append(.declaration( @@ -1188,11 +1268,12 @@ extension Formatter { )) } - if let lastIndexOfSameDeclaration = sortedDeclarations.map(\.category).lastIndex(of: category), + if options.blankLineAfterSubgroups, + let lastIndexOfSameDeclaration = sortedDeclarations.map(\.category).lastIndex(of: category), lastIndexOfSameDeclaration == index, lastIndexOfSameDeclaration != sortedDeclarations.indices.last { - markedDeclarations.append(mapClosingTokens(in: declaration, with: { endingWithBlankLine($0) })) + markedDeclarations.append(declaration.endingWithBlankLine()) } else { markedDeclarations.append(declaration) } @@ -1285,13 +1366,13 @@ extension Formatter { parser.removeTokens(in: rangeBeforeComment) // ... and append them to the end of the previous declaration - typeBody[declarationIndex - 1] = mapClosingTokens(in: typeBody[declarationIndex - 1]) { + typeBody[declarationIndex - 1] = typeBody[declarationIndex - 1].mapClosingTokens { $0 + tokensBeforeCommentLine } } // Apply the updated tokens back to this declaration - typeBody[declarationIndex] = mapOpeningTokens(in: typeBody[declarationIndex]) { _ in + typeBody[declarationIndex] = typeBody[declarationIndex].mapOpeningTokens { _ in parser.tokens } } @@ -1429,14 +1510,62 @@ extension Formatter { } } + /// Removes the given visibility keyword from the given declaration + func remove(_ visibilityKeyword: Visibility, from declaration: Declaration) -> Declaration { + declaration.mapOpeningTokens { openTokens in + guard let visibilityKeywordIndex = openTokens + .firstIndex(of: .keyword(visibilityKeyword.rawValue)) + else { + return openTokens + } + + let openTokensFormatter = Formatter(openTokens) + openTokensFormatter.removeToken(at: visibilityKeywordIndex) + + while openTokensFormatter.token(at: visibilityKeywordIndex)?.isSpace == true { + openTokensFormatter.removeToken(at: visibilityKeywordIndex) + } + + return openTokensFormatter.tokens + } + } + + /// Adds the given visibility keyword to the given declaration, + /// replacing any existing visibility keyword. + func add(_ visibilityKeyword: Visibility, to declaration: Declaration) -> Declaration { + var declaration = declaration + + if let existingVisibilityKeyword = visibility(of: declaration) { + declaration = remove(existingVisibilityKeyword, from: declaration) + } + + return declaration.mapOpeningTokens { openTokens in + guard let indexOfKeyword = openTokens + .firstIndex(of: .keyword(declaration.keyword)) + else { + return openTokens + } + + let openTokensFormatter = Formatter(openTokens) + let startOfModifiers = openTokensFormatter + .startOfModifiers(at: indexOfKeyword, includingAttributes: false) + + openTokensFormatter.insert( + tokenize("\(visibilityKeyword.rawValue) "), + at: startOfModifiers + ) + + return openTokensFormatter.tokens + } + } +} + +extension Declaration { /// Maps the first group of tokens in this declaration /// - For declarations with a body, this maps the `open` tokens /// - For declarations without a body, this maps the entire declaration's tokens - func mapOpeningTokens( - in declaration: Declaration, - with transform: ([Token]) -> [Token] - ) -> Declaration { - switch declaration { + func mapOpeningTokens(with transform: ([Token]) -> [Token]) -> Declaration { + switch self { case let .type(kind, open, body, close, originalRange): return .type( kind: kind, @@ -1463,14 +1592,27 @@ extension Formatter { } } + /// Maps the tokens of this simple `declaration` + func mapDeclarationTokens(with transform: ([Token]) -> [Token]) -> Declaration { + switch self { + case let .declaration(kind, originalTokens, originalRange): + return .declaration( + kind: kind, + tokens: transform(originalTokens), + originalRange: originalRange + ) + + case .type, .conditionalCompilation: + assertionFailure("`mapDeclarationTokens` only supports `declaration`s.") + return self + } + } + /// Maps the last group of tokens in this declaration /// - For declarations with a body, this maps the `close` tokens /// - For declarations without a body, this maps the entire declaration's tokens - func mapClosingTokens( - in declaration: Declaration, - with transform: ([Token]) -> [Token] - ) -> Declaration { - switch declaration { + func mapClosingTokens(with transform: ([Token]) -> [Token]) -> Declaration { + switch self { case let .type(kind, open, body, close, originalRange): return .type( kind: kind, @@ -1499,23 +1641,28 @@ extension Formatter { /// Updates the given declaration tokens so it ends with at least one blank like /// (e.g. so it ends with at least two newlines) - func endingWithBlankLine(_ tokens: [Token]) -> [Token] { - let parser = Formatter(tokens) + func endingWithBlankLine() -> Declaration { + mapClosingTokens { tokens in + tokens.endingWithBlankLine() + } + } - // Determine how many trailing linebreaks there are in this declaration - var numberOfTrailingLinebreaks = 0 - var searchIndex = parser.tokens.count - 1 + /// Updates the given declaration tokens so it ends with no blank lines + /// (e.g. so it ends with one newline) + func endingWithoutBlankLine() -> Declaration { + mapClosingTokens { tokens in + tokens.endingWithoutBlankLine() + } + } +} - while searchIndex > 0, - let token = parser.token(at: searchIndex), - token.isSpaceOrCommentOrLinebreak - { - if token.isLinebreak { - numberOfTrailingLinebreaks += 1 - } +extension Array where Element == Token { + /// Updates the given declaration tokens so it ends with at least one blank like + /// (e.g. so it ends with at least two newlines) + func endingWithBlankLine() -> [Token] { + let parser = Formatter(self) - searchIndex -= 1 - } + var numberOfTrailingLinebreaks = self.numberOfTrailingLinebreaks() // Make sure there are at least two newlines, // so we get a blank line between individual declaration types @@ -1527,52 +1674,41 @@ extension Formatter { return parser.tokens } - /// Removes the given visibility keyword from the given declaration - func remove(_ visibilityKeyword: Visibility, from declaration: Declaration) -> Declaration { - mapOpeningTokens(in: declaration) { openTokens in - guard let visibilityKeywordIndex = openTokens - .firstIndex(of: .keyword(visibilityKeyword.rawValue)) - else { - return openTokens - } + /// Updates the given tokens so it ends with no blank lines + /// (e.g. so it ends with one newline) + func endingWithoutBlankLine() -> [Token] { + let parser = Formatter(self) - let openTokensFormatter = Formatter(openTokens) - openTokensFormatter.removeToken(at: visibilityKeywordIndex) - - while openTokensFormatter.token(at: visibilityKeywordIndex)?.isSpace == true { - openTokensFormatter.removeToken(at: visibilityKeywordIndex) - } + var numberOfTrailingLinebreaks = self.numberOfTrailingLinebreaks() - return openTokensFormatter.tokens + // Make sure there are at least two newlines, + // so we get a blank line between individual declaration types + while numberOfTrailingLinebreaks > 1 { + parser.removeLastToken() + numberOfTrailingLinebreaks -= 1 } + + return parser.tokens } - /// Adds the given visibility keyword to the given declaration, - /// replacing any existing visibility keyword. - func add(_ visibilityKeyword: Visibility, to declaration: Declaration) -> Declaration { - var declaration = declaration + // The number of trailing line breaks in this array of tokens + func numberOfTrailingLinebreaks() -> Int { + let parser = Formatter(self) - if let existingVisibilityKeyword = visibility(of: declaration) { - declaration = remove(existingVisibilityKeyword, from: declaration) - } + var numberOfTrailingLinebreaks = 0 + var searchIndex = parser.tokens.count - 1 - return mapOpeningTokens(in: declaration) { openTokens in - guard let indexOfKeyword = openTokens - .firstIndex(of: .keyword(declaration.keyword)) - else { - return openTokens + while searchIndex > 0, + let token = parser.token(at: searchIndex), + token.isSpaceOrCommentOrLinebreak + { + if token.isLinebreak { + numberOfTrailingLinebreaks += 1 } - let openTokensFormatter = Formatter(openTokens) - let startOfModifiers = openTokensFormatter - .startOfModifiers(at: indexOfKeyword, includingAttributes: false) - - openTokensFormatter.insert( - tokenize("\(visibilityKeyword.rawValue) "), - at: startOfModifiers - ) - - return openTokensFormatter.tokens + searchIndex -= 1 } + + return numberOfTrailingLinebreaks } } diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index 0fa88856b..b3898ef8b 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -999,6 +999,14 @@ struct _Descriptors { } } ) + let blankLineAfterSubgroups = OptionDescriptor( + argumentName: "groupblanklines", + displayName: "Blank Line After Subgroups", + help: "Require a blank line after each subgroup. Default: true", + keyPath: \.blankLineAfterSubgroups, + trueValues: ["true"], + falseValues: ["false"] + ) let alphabeticallySortedDeclarationPatterns = OptionDescriptor( argumentName: "sortedpatterns", displayName: "Declaration Name Patterns To Sort Alphabetically", diff --git a/Sources/Options.swift b/Sources/Options.swift index d1a40456a..da3bfd6a1 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -671,6 +671,7 @@ public struct FormatOptions: CustomStringConvertible { public var typeOrder: [String]? public var customVisibilityMarks: Set public var customTypeMarks: Set + public var blankLineAfterSubgroups: Bool public var alphabeticallySortedDeclarationPatterns: Set public var yodaSwap: YodaMode public var extensionACLPlacement: ExtensionACLPlacement @@ -795,6 +796,7 @@ public struct FormatOptions: CustomStringConvertible { typeOrder: [String]? = nil, customVisibilityMarks: Set = [], customTypeMarks: Set = [], + blankLineAfterSubgroups: Bool = true, alphabeticallySortedDeclarationPatterns: Set = [], yodaSwap: YodaMode = .always, extensionACLPlacement: ExtensionACLPlacement = .onExtension, @@ -909,6 +911,7 @@ public struct FormatOptions: CustomStringConvertible { self.typeOrder = typeOrder self.customVisibilityMarks = customVisibilityMarks self.customTypeMarks = customTypeMarks + self.blankLineAfterSubgroups = blankLineAfterSubgroups self.alphabeticallySortedDeclarationPatterns = alphabeticallySortedDeclarationPatterns self.yodaSwap = yodaSwap self.extensionACLPlacement = extensionACLPlacement diff --git a/Sources/Rules/MarkTypes.swift b/Sources/Rules/MarkTypes.swift index b8895e962..f2978bd22 100644 --- a/Sources/Rules/MarkTypes.swift +++ b/Sources/Rules/MarkTypes.swift @@ -92,7 +92,7 @@ public extension FormatRule { } } - declarations[index] = formatter.mapOpeningTokens(in: declarations[index]) { openingTokens -> [Token] in + declarations[index] = declarations[index].mapOpeningTokens { openingTokens -> [Token] in var openingFormatter = Formatter(openingTokens) guard let keywordIndex = openingFormatter.index(after: -1, where: { @@ -239,9 +239,7 @@ public extension FormatRule { // If the previous declaration doesn't end in a blank line, // add an additional linebreak to balance the mark. if index != 0 { - declarations[index - 1] = formatter.mapClosingTokens(in: declarations[index - 1]) { - formatter.endingWithBlankLine($0) - } + declarations[index - 1] = declarations[index - 1].endingWithBlankLine() } return openingFormatter.tokens diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift index 850166917..db6e8e222 100644 --- a/Sources/Rules/OrganizeDeclarations.swift +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -19,6 +19,7 @@ public extension FormatRule { "lifecycle", "organizetypes", "structthreshold", "classthreshold", "enumthreshold", "extensionlength", "organizationmode", "visibilityorder", "typeorder", "visibilitymarks", "typemarks", + "groupblanklines", ], sharedOptions: ["sortedpatterns", "lineaftermarks"] ) { formatter in diff --git a/Sources/Rules/SortDeclarations.swift b/Sources/Rules/SortDeclarations.swift index 7af49d110..29f93597a 100644 --- a/Sources/Rules/SortDeclarations.swift +++ b/Sources/Rules/SortDeclarations.swift @@ -109,7 +109,7 @@ public extension FormatRule { if declaration.tokens.last?.isLinebreak == false, nextDeclaration.tokens.first?.isLinebreak == false { - declarations[i + 1] = formatter.mapOpeningTokens(in: nextDeclaration) { openTokens in + declarations[i + 1] = nextDeclaration.mapOpeningTokens { openTokens in let openFormatter = Formatter(openTokens) openFormatter.insertLinebreak(at: 0) return openFormatter.tokens diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index 4ddc6a819..73566eb9e 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -253,6 +253,7 @@ class MetadataTests: XCTestCase { Descriptors.typeOrder, Descriptors.customVisibilityMarks, Descriptors.customTypeMarks, + Descriptors.blankLineAfterSubgroups, ] case .identifier("removeSelf"): referencedOptions += [ diff --git a/Tests/Rules/OrganizeDeclarationsTests.swift b/Tests/Rules/OrganizeDeclarationsTests.swift index 6eedaef66..0edf0b970 100644 --- a/Tests/Rules/OrganizeDeclarationsTests.swift +++ b/Tests/Rules/OrganizeDeclarationsTests.swift @@ -2927,4 +2927,164 @@ class OrganizeDeclarationsTests: XCTestCase { exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } + + func testPreservesBlockOfConsecutivePropertiesWithoutBlankLinesBetweenSubgroups1() { + let input = """ + class Foo { + init() {} + + let foo: Foo + let baaz: Baaz + static let bar: Bar + + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + static let bar: Bar + let foo: Foo + let baaz: Baaz + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(blankLineAfterSubgroups: false), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testPreservesBlockOfConsecutivePropertiesWithoutBlankLinesBetweenSubgroups2() { + let input = """ + class Foo { + init() {} + + let foo: Foo + let baaz: Baaz + static let bar: Bar + + static let quux: Quux + let fooBar: FooBar + let baazQuux: BaazQuux + + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + static let bar: Bar + static let quux: Quux + let foo: Foo + let baaz: Baaz + + let fooBar: FooBar + let baazQuux: BaazQuux + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions(blankLineAfterSubgroups: false), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testPreservesBlockOfConsecutiveProperties() { + let input = """ + class Foo { + init() {} + + let foo: Foo + let baaz: Baaz + static let bar: Bar + + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + static let bar: Bar + + let foo: Foo + let baaz: Baaz + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testPreservesBlockOfConsecutiveProperties2() { + let input = """ + class Foo { + init() {} + + let foo: Foo + let baaz: Baaz + static let bar: Bar + + static let quux: Quux + let fooBar: FooBar + let baazQuux: BaazQuux + + } + """ + + let output = """ + class Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + static let bar: Bar + static let quux: Quux + + let foo: Foo + let baaz: Baaz + + let fooBar: FooBar + let baazQuux: BaazQuux + + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } } From 1fd10401be6814cbe4581a05a0ddcf396808aa18 Mon Sep 17 00:00:00 2001 From: Miguel Jimenez Date: Tue, 30 Jul 2024 17:24:39 -0400 Subject: [PATCH 30/52] Ensure SwiftUI property reorder doesn't affect synthesized init (#1796) --- Sources/DeclarationHelpers.swift | 2 +- Tests/Rules/OrganizeDeclarationsTests.swift | 89 +++++++++++++++++++-- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 8b5c49dc8..599ae6581 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -1137,7 +1137,7 @@ extension Formatter { _ category: Category ) -> Bool { switch category.type { - case .instanceProperty: + case .swiftUIPropertyWrapper, .instanceProperty: return true case .instancePropertyWithBody: diff --git a/Tests/Rules/OrganizeDeclarationsTests.swift b/Tests/Rules/OrganizeDeclarationsTests.swift index 0edf0b970..cebacf4dc 100644 --- a/Tests/Rules/OrganizeDeclarationsTests.swift +++ b/Tests/Rules/OrganizeDeclarationsTests.swift @@ -2707,6 +2707,10 @@ class OrganizeDeclarationsTests: XCTestCase { let input = """ struct ContentView: View { + init(label: String) { + self.label = label + } + private var label: String @State @@ -2729,6 +2733,12 @@ class OrganizeDeclarationsTests: XCTestCase { let output = """ struct ContentView: View { + // MARK: Lifecycle + + init(label: String) { + self.label = label + } + // MARK: Internal @ViewBuilder @@ -2765,10 +2775,15 @@ class OrganizeDeclarationsTests: XCTestCase { let input = """ struct ContentView: View { + init(foo: Foo, baaz: Baaz) { + self.foo = foo + self.baaz = baaz + } + let foo: Foo - @State var bar: Bar + @State var bar = true let baaz: Baaz - @State var quux: Quux + @State var quux = true @ViewBuilder private var toggle: some View { @@ -2785,10 +2800,17 @@ class OrganizeDeclarationsTests: XCTestCase { let output = """ struct ContentView: View { + // MARK: Lifecycle + + init(foo: Foo, baaz: Baaz) { + self.foo = foo + self.baaz = baaz + } + // MARK: Internal - @State var bar: Bar - @State var quux: Quux + @State var bar = true + @State var quux = true let foo: Foo let baaz: Baaz @@ -2820,8 +2842,14 @@ class OrganizeDeclarationsTests: XCTestCase { let input = """ struct ContentView: View { + init(foo: Foo, baaz: Baaz, isOn: Binding) { + self.foo = foo + self.baaz = baaz + self_.isOn = isOn + } + let foo: Foo - @State private var bar: Bar + @State private var bar = 0 private let baaz: Baaz @Binding var isOn: Bool @@ -2840,6 +2868,14 @@ class OrganizeDeclarationsTests: XCTestCase { let output = """ struct ContentView: View { + // MARK: Lifecycle + + init(foo: Foo, baaz: Baaz, isOn: Binding) { + self.foo = foo + self.baaz = baaz + self_.isOn = isOn + } + // MARK: Internal @Binding var isOn: Bool @@ -2853,7 +2889,7 @@ class OrganizeDeclarationsTests: XCTestCase { // MARK: Private - @State private var bar: Bar + @State private var bar = 0 private let baaz: Baaz @@ -2877,6 +2913,11 @@ class OrganizeDeclarationsTests: XCTestCase { let input = """ struct ContentView: View { + init(foo: Foo, baaz: Baaz) { + self.foo = foo + self.baaz = baaz + } + let foo: Foo @Environment(\\.colorScheme) var colorScheme let baaz: Baaz @@ -2897,6 +2938,13 @@ class OrganizeDeclarationsTests: XCTestCase { let output = """ struct ContentView: View { + // MARK: Lifecycle + + init(foo: Foo, baaz: Baaz) { + self.foo = foo + self.baaz = baaz + } + // MARK: Internal @Environment(\\.colorScheme) var colorScheme @@ -2928,6 +2976,35 @@ class OrganizeDeclarationsTests: XCTestCase { ) } + func testSwiftUIPropertyWrappersSortDoesntBreakViewSynthesizedMemberwiseInitializer() { + let input = """ + struct ContentView: View { + + let foo: Foo + @Environment(\\.colorScheme) var colorScheme + let baaz: Baaz + @Environment(\\.quux) let quux: Quux + + @ViewBuilder + private var toggle: some View { + Toggle(label, isOn: $isOn) + } + + @ViewBuilder + var body: some View { + toggle + } + } + """ + + testFormatting( + for: input, + rule: .organizeDeclarations, + options: FormatOptions(organizeTypes: ["struct"], organizationMode: .visibility), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + func testPreservesBlockOfConsecutivePropertiesWithoutBlankLinesBetweenSubgroups1() { let input = """ class Foo { From 6a43b76c97485b8599843e06039818efe2b9ff8a Mon Sep 17 00:00:00 2001 From: Manny Lopez Date: Tue, 30 Jul 2024 14:25:05 -0700 Subject: [PATCH 31/52] Add emptyExtension rule + tests (#1792) --- Rules.md | 17 ++++ Sources/Examples.swift | 8 ++ Sources/RuleRegistry.generated.swift | 1 + Sources/Rules/EmptyExtension.swift | 44 ++++++++++ SwiftFormat.xcodeproj/project.pbxproj | 14 ++++ Tests/Rules/EmptyExtensionTests.swift | 80 +++++++++++++++++++ Tests/Rules/GenericExtensionsTests.swift | 22 ++--- Tests/Rules/MarkTypesTests.swift | 39 ++++----- .../Rules/OpaqueGenericParametersTests.swift | 2 +- Tests/Rules/TypeSugarTests.swift | 2 +- 10 files changed, 197 insertions(+), 32 deletions(-) create mode 100644 Sources/Rules/EmptyExtension.swift create mode 100644 Tests/Rules/EmptyExtensionTests.swift diff --git a/Rules.md b/Rules.md index b47410c21..31673ee61 100644 --- a/Rules.md +++ b/Rules.md @@ -19,6 +19,7 @@ * [duplicateImports](#duplicateImports) * [elseOnSameLine](#elseOnSameLine) * [emptyBraces](#emptyBraces) +* [emptyExtension](#emptyExtension) * [enumNamespaces](#enumNamespaces) * [extensionAccessControl](#extensionAccessControl) * [fileHeader](#fileHeader) @@ -803,6 +804,22 @@ Option | Description

+## emptyExtension + +Remove empty, non-conforming, extensions. + +
+Examples + +```diff +- extension String {} +- + extension String: Equatable {} +``` + +
+
+ ## enumNamespaces Convert types used for hosting only static members into enums (an empty enum is diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 4c90a8d7b..7872fb15b 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -2008,4 +2008,12 @@ private struct Examples { func foo() {} ``` """ + + let emptyExtension = """ + ```diff + - extension String {} + - + extension String: Equatable {} + ``` + """ } diff --git a/Sources/RuleRegistry.generated.swift b/Sources/RuleRegistry.generated.swift index da74a110e..154271fe4 100644 --- a/Sources/RuleRegistry.generated.swift +++ b/Sources/RuleRegistry.generated.swift @@ -33,6 +33,7 @@ let ruleRegistry: [String: FormatRule] = [ "duplicateImports": .duplicateImports, "elseOnSameLine": .elseOnSameLine, "emptyBraces": .emptyBraces, + "emptyExtension": .emptyExtension, "enumNamespaces": .enumNamespaces, "extensionAccessControl": .extensionAccessControl, "fileHeader": .fileHeader, diff --git a/Sources/Rules/EmptyExtension.swift b/Sources/Rules/EmptyExtension.swift new file mode 100644 index 000000000..f39d14d2e --- /dev/null +++ b/Sources/Rules/EmptyExtension.swift @@ -0,0 +1,44 @@ +// +// EmptyExtension.swift +// SwiftFormat +// +// Created by manny_lopez on 7/29/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation + +public extension FormatRule { + /// Remove empty, non-conforming, extensions. + static let emptyExtension = FormatRule( + help: "Remove empty, non-conforming, extensions.", + orderAfter: [.unusedPrivateDeclaration] + ) { formatter in + var emptyExtensions = [Declaration]() + + formatter.forEachRecursiveDeclaration { declaration in + let declarationModifiers = Set(declaration.modifiers) + guard declaration.keyword == "extension", + let declarationBody = declaration.body, + declarationBody.isEmpty, + // Ensure that it is not a macro + !declarationModifiers.contains(where: { $0.first == "@" }) + else { return } + + // Ensure that the extension does not conform to any protocols + let parser = Formatter(declaration.openTokens) + guard let extensionIndex = parser.index(of: .keyword("extension"), after: -1), + let typeNameIndex = parser.index(of: .nonSpaceOrLinebreak, after: extensionIndex), + let type = parser.parseType(at: typeNameIndex), + let indexAfterType = parser.index(of: .nonSpaceOrCommentOrLinebreak, after: type.range.upperBound), + parser.tokens[indexAfterType] != .delimiter(":") + else { return } + + emptyExtensions.append(declaration) + } + + for declaration in emptyExtensions.reversed() { + formatter.removeTokens(in: declaration.originalRange) + } + } +} diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index ae0a1c48e..dfe310843 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -627,6 +627,11 @@ 2E8DE7622C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */; }; 37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; }; 37D828AC24BF77DA0012FC0A /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 580496D52C584E8F004B7DBF /* EmptyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580496D42C584E8F004B7DBF /* EmptyExtension.swift */; }; + 580496D62C584E8F004B7DBF /* EmptyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580496D42C584E8F004B7DBF /* EmptyExtension.swift */; }; + 580496D72C584E8F004B7DBF /* EmptyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580496D42C584E8F004B7DBF /* EmptyExtension.swift */; }; + 580496D82C584E8F004B7DBF /* EmptyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580496D42C584E8F004B7DBF /* EmptyExtension.swift */; }; + 580496DE2C584F8C004B7DBF /* EmptyExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580496D92C584F24004B7DBF /* EmptyExtensionTests.swift */; }; 9028F7831DA4B435009FE5B4 /* SwiftFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */; }; 9028F7841DA4B435009FE5B4 /* Tokenizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */; }; 9028F7851DA4B435009FE5B4 /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3987C1D763493009ADE61 /* Formatter.swift */; }; @@ -988,6 +993,8 @@ 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantFileprivateTests.swift; sourceTree = ""; }; 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSingleLineCommentsTests.swift; sourceTree = ""; }; 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; + 580496D42C584E8F004B7DBF /* EmptyExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyExtension.swift; sourceTree = ""; }; + 580496D92C584F24004B7DBF /* EmptyExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EmptyExtensionTests.swift; path = Tests/Rules/EmptyExtensionTests.swift; sourceTree = ""; }; 90C4B6CA1DA4B04A009EB000 /* SwiftFormat for Xcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftFormat for Xcode.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 90C4B6CC1DA4B04A009EB000 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 90C4B6D01DA4B04A009EB000 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -1093,6 +1100,7 @@ 01A0EA9A1D5DB4CF00A0A8E3 = { isa = PBXGroup; children = ( + 580496D92C584F24004B7DBF /* EmptyExtensionTests.swift */, 01A0EACB1D5DB5F500A0A8E3 /* CommandLineTool */, 90C4B6F01DA4B0BF009EB000 /* EditorExtension */, 01A0EAA61D5DB4CF00A0A8E3 /* Sources */, @@ -1201,6 +1209,7 @@ 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */, 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */, 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */, + 580496D42C584E8F004B7DBF /* EmptyExtension.swift */, 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */, 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */, 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */, @@ -1813,6 +1822,7 @@ 2E2BAC2B2C57F6DD00590239 /* Todos.swift in Sources */, 2E2BAC6F2C57F6DD00590239 /* AssertionFailures.swift in Sources */, 2E2BAD5B2C57F6DD00590239 /* AnyObjectProtocol.swift in Sources */, + 580496D52C584E8F004B7DBF /* EmptyExtension.swift in Sources */, 2E2BAC072C57F6DD00590239 /* BlankLineAfterSwitchCase.swift in Sources */, 2E2BADA32C57F6DD00590239 /* RedundantTypedThrows.swift in Sources */, 2E2BAD4B2C57F6DD00590239 /* RedundantVoidReturnType.swift in Sources */, @@ -2035,6 +2045,7 @@ 2E8DE6FF2C57FEB30032BF25 /* DuplicateImportsTests.swift in Sources */, 2E8DE7112C57FEB30032BF25 /* RedundantStaticSelfTests.swift in Sources */, 2E8DE72C2C57FEB30032BF25 /* RedundantLetTests.swift in Sources */, + 580496DE2C584F8C004B7DBF /* EmptyExtensionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2165,6 +2176,7 @@ 2E2BAD502C57F6DD00590239 /* RedundantRawValues.swift in Sources */, 2E2BACAC2C57F6DD00590239 /* RedundantLetError.swift in Sources */, 01045A9A2119979400D2BE3D /* Arguments.swift in Sources */, + 580496D62C584E8F004B7DBF /* EmptyExtension.swift in Sources */, 2E2BAD482C57F6DD00590239 /* WrapLoopBodies.swift in Sources */, 2E2BAD082C57F6DD00590239 /* RedundantPattern.swift in Sources */, 2E2BAD042C57F6DD00590239 /* SpaceInsideBraces.swift in Sources */, @@ -2300,6 +2312,7 @@ 2E2BAC1D2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, 2E2BACD12C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, 2E2BAD312C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 580496D72C584E8F004B7DBF /* EmptyExtension.swift in Sources */, 2E2BAD9D2C57F6DD00590239 /* Specifiers.swift in Sources */, E4872111201D3B830014845E /* Options.swift in Sources */, 2E2BACC92C57F6DD00590239 /* HeaderFileName.swift in Sources */, @@ -2443,6 +2456,7 @@ 2E2BAC1E2C57F6DD00590239 /* RedundantExtensionACL.swift in Sources */, 2E2BACD22C57F6DD00590239 /* SpaceAroundBrackets.swift in Sources */, 2E2BAD322C57F6DD00590239 /* WrapAttributes.swift in Sources */, + 580496D82C584E8F004B7DBF /* EmptyExtension.swift in Sources */, 2E2BAD9E2C57F6DD00590239 /* Specifiers.swift in Sources */, 90F16AF81DA5EB4600EB4EA1 /* FormatFileCommand.swift in Sources */, 2E2BACCA2C57F6DD00590239 /* HeaderFileName.swift in Sources */, diff --git a/Tests/Rules/EmptyExtensionTests.swift b/Tests/Rules/EmptyExtensionTests.swift new file mode 100644 index 000000000..b98755d16 --- /dev/null +++ b/Tests/Rules/EmptyExtensionTests.swift @@ -0,0 +1,80 @@ +// +// EmptyExtensionTests.swift +// SwiftFormatTests +// +// Created by manny_lopez on 7/28/2024. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class EmptyExtensionTests: XCTestCase { + func testRemoveEmptyExtension() { + let input = """ + extension String {} + + extension String: Equatable {} + """ + let output = """ + extension String: Equatable {} + """ + testFormatting(for: input, output, rule: .emptyExtension) + } + + func testRemoveNonConformingEmptyExtension() { + let input = """ + extension [Foo: Bar] {} + + extension Array where Element: Foo {} + """ + let output = "" + testFormatting(for: input, output, rule: .emptyExtension) + } + + func testDoNotRemoveEmptyConformingExtension() { + let input = """ + extension String: Equatable {} + """ + testFormatting(for: input, rule: .emptyExtension) + } + + func testDoNotRemoveAtModifierEmptyExtension() { + let input = """ + @GenerateBoilerPlate + extension Foo {} + """ + testFormatting(for: input, rule: .emptyExtension) + } + + func testRemoveEmptyExtensionWithEmptyBody() { + let input = """ + extension Foo { } + + extension Foo { + + } + """ + let output = "" + testFormatting(for: input, output, rule: .emptyExtension) + } + + func testRemoveUnusedPrivateDeclarationThenEmptyExtension() { + let input = """ + class Foo { + init() {} + } + extension Foo { + private var bar: Bar { "bar" } + } + """ + + let output = """ + class Foo { + init() {} + } + + """ + testFormatting(for: input, [output], rules: [.unusedPrivateDeclaration, .emptyExtension]) + } +} diff --git a/Tests/Rules/GenericExtensionsTests.swift b/Tests/Rules/GenericExtensionsTests.swift index 99bea6fe9..2e4d0e7a5 100644 --- a/Tests/Rules/GenericExtensionsTests.swift +++ b/Tests/Rules/GenericExtensionsTests.swift @@ -15,7 +15,7 @@ class GenericExtensionsTests: XCTestCase { let output = "extension Array {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar, .emptyExtension]) } func testUpdatesOptionalGenericExtensionToAngleBracketSyntax() { @@ -23,7 +23,7 @@ class GenericExtensionsTests: XCTestCase { let output = "extension Optional {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar, .emptyExtension]) } func testUpdatesArrayGenericExtensionToAngleBracketSyntaxWithSelf() { @@ -31,7 +31,7 @@ class GenericExtensionsTests: XCTestCase { let output = "extension Array {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar, .emptyExtension]) } func testUpdatesArrayWithGenericElement() { @@ -39,7 +39,7 @@ class GenericExtensionsTests: XCTestCase { let output = "extension Array> {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar, .emptyExtension]) } func testUpdatesDictionaryGenericExtensionToAngleBracketSyntax() { @@ -47,7 +47,7 @@ class GenericExtensionsTests: XCTestCase { let output = "extension Dictionary {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar, .emptyExtension]) } func testRequiresAllGenericTypesToBeProvided() { @@ -55,7 +55,7 @@ class GenericExtensionsTests: XCTestCase { let input = "extension Dictionary where Key == Foo {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .genericExtensions, options: options) + testFormatting(for: input, rule: .genericExtensions, options: options, exclude: [.emptyExtension]) } func testHandlesNestedCollectionTypes() { @@ -63,7 +63,7 @@ class GenericExtensionsTests: XCTestCase { let output = "extension Array<[[Foo: Bar]]> {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar]) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.typeSugar, .emptyExtension]) } func testDoesntUpdateIneligibleConstraints() { @@ -72,7 +72,7 @@ class GenericExtensionsTests: XCTestCase { let input = "extension Optional where Wrapped: Fooable {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .genericExtensions, options: options) + testFormatting(for: input, rule: .genericExtensions, options: options, exclude: [.emptyExtension]) } func testPreservesOtherConstraintsInWhereClause() { @@ -80,7 +80,7 @@ class GenericExtensionsTests: XCTestCase { let output = "extension Collection where Index == Int {}" let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, output, rule: .genericExtensions, options: options) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.emptyExtension]) } func testSupportsUserProvidedGenericTypes() { @@ -97,7 +97,7 @@ class GenericExtensionsTests: XCTestCase { genericTypes: "LinkedList;StateStore", swiftVersion: "5.7" ) - testFormatting(for: input, output, rule: .genericExtensions, options: options) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.emptyExtension]) } func testSupportsMultilineUserProvidedGenericTypes() { @@ -116,6 +116,6 @@ class GenericExtensionsTests: XCTestCase { genericTypes: "Reducer", swiftVersion: "5.7" ) - testFormatting(for: input, output, rule: .genericExtensions, options: options) + testFormatting(for: input, output, rule: .genericExtensions, options: options, exclude: [.emptyExtension]) } } diff --git a/Tests/Rules/MarkTypesTests.swift b/Tests/Rules/MarkTypesTests.swift index e0b006a5c..ae60a7ef9 100644 --- a/Tests/Rules/MarkTypesTests.swift +++ b/Tests/Rules/MarkTypesTests.swift @@ -36,7 +36,7 @@ class MarkTypesTests: XCTestCase { protocol Quux {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testDoesntAddMarkBeforeStructWithExistingMark() { @@ -47,7 +47,7 @@ class MarkTypesTests: XCTestCase { extension Foo {} """ - testFormatting(for: input, rule: .markTypes) + testFormatting(for: input, rule: .markTypes, exclude: [.emptyExtension]) } func testCorrectsTypoInTypeMark() { @@ -65,7 +65,7 @@ class MarkTypesTests: XCTestCase { extension Foo {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testUpdatesMarkAfterTypeIsRenamed() { @@ -83,7 +83,7 @@ class MarkTypesTests: XCTestCase { extension FooBarControllerBuilder {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testAddsMarkBeforeTypeWithDocComment() { @@ -107,7 +107,7 @@ class MarkTypesTests: XCTestCase { extension Foo {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testCustomTypeMark() { @@ -125,7 +125,8 @@ class MarkTypesTests: XCTestCase { testFormatting( for: input, output, rule: .markTypes, - options: FormatOptions(typeMarkComment: "TYPE DEFINITION: %t") + options: FormatOptions(typeMarkComment: "TYPE DEFINITION: %t"), + exclude: [.emptyExtension] ) } @@ -135,7 +136,7 @@ class MarkTypesTests: XCTestCase { extension Foo {} """ - testFormatting(for: input, rule: .markTypes) + testFormatting(for: input, rule: .markTypes, exclude: [.emptyExtension]) } func preservesExistingCommentForExtensionWithNoConformances() { @@ -162,7 +163,7 @@ class MarkTypesTests: XCTestCase { extension Foo {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testUpdatesExtensionMarkToCorrectMark() { @@ -180,7 +181,7 @@ class MarkTypesTests: XCTestCase { extension Foo {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testAddsMarkCommentForExtensionWithMultipleConformances() { @@ -196,7 +197,7 @@ class MarkTypesTests: XCTestCase { extension Foo {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testUpdatesMarkCommentWithCorrectConformances() { @@ -214,7 +215,7 @@ class MarkTypesTests: XCTestCase { extension Foo {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testCustomExtensionMarkComment() { @@ -284,7 +285,7 @@ class MarkTypesTests: XCTestCase { extension MyModule.Foo {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testWhereClauseConformanceWithExactConstraint() { @@ -300,7 +301,7 @@ class MarkTypesTests: XCTestCase { extension Array {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testWhereClauseConformanceWithConformanceConstraint() { @@ -316,7 +317,7 @@ class MarkTypesTests: XCTestCase { extension Array {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testWhereClauseWithExactConstraint() { @@ -325,7 +326,7 @@ class MarkTypesTests: XCTestCase { extension Array {} """ - testFormatting(for: input, rule: .markTypes) + testFormatting(for: input, rule: .markTypes, exclude: [.emptyExtension]) } func testWhereClauseWithConformanceConstraint() { @@ -336,7 +337,7 @@ class MarkTypesTests: XCTestCase { extension Rules {} """ - testFormatting(for: input, rule: .markTypes) + testFormatting(for: input, rule: .markTypes, exclude: [.emptyExtension]) } func testPlacesMarkAfterImports() { @@ -360,7 +361,7 @@ class MarkTypesTests: XCTestCase { extension Rules {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testPlacesMarkAfterFileHeader() { @@ -384,7 +385,7 @@ class MarkTypesTests: XCTestCase { extension Rules {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testPlacesMarkAfterFileHeaderAndImports() { @@ -414,7 +415,7 @@ class MarkTypesTests: XCTestCase { extension Rules {} """ - testFormatting(for: input, output, rule: .markTypes) + testFormatting(for: input, output, rule: .markTypes, exclude: [.emptyExtension]) } func testDoesNothingIfOnlyOneDeclaration() { diff --git a/Tests/Rules/OpaqueGenericParametersTests.swift b/Tests/Rules/OpaqueGenericParametersTests.swift index d38ec9533..698de6280 100644 --- a/Tests/Rules/OpaqueGenericParametersTests.swift +++ b/Tests/Rules/OpaqueGenericParametersTests.swift @@ -513,7 +513,7 @@ class OpaqueGenericParametersTests: XCTestCase { let input = "extension Array where Element == Foo {}" let options = FormatOptions(swiftVersion: "5.6") - testFormatting(for: input, rule: .opaqueGenericParameters, options: options) + testFormatting(for: input, rule: .opaqueGenericParameters, options: options, exclude: [.emptyExtension]) } func testOpaqueGenericParametersRuleSuccessfullyTerminatesInSampleCode() { diff --git a/Tests/Rules/TypeSugarTests.swift b/Tests/Rules/TypeSugarTests.swift index 6d164bd62..5a17ba546 100644 --- a/Tests/Rules/TypeSugarTests.swift +++ b/Tests/Rules/TypeSugarTests.swift @@ -72,7 +72,7 @@ class TypeSugarTests: XCTestCase { extension [Foo: Bar] {} extension [[Foo: [Bar]]]? {} """ - testFormatting(for: input, output, rule: .typeSugar) + testFormatting(for: input, output, rule: .typeSugar, exclude: [.emptyExtension]) } // dictionaries From 657bb1dc8633be144b8247649eeffbce9a9a0388 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Tue, 30 Jul 2024 19:33:14 -0700 Subject: [PATCH 32/52] Fix issue where closing brace could turn into a comment unexpectedly (#1797) --- Sources/DeclarationHelpers.swift | 25 +++++++++------------ Tests/Rules/OrganizeDeclarationsTests.swift | 25 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 599ae6581..e9d46a44d 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -1662,13 +1662,8 @@ extension Array where Element == Token { func endingWithBlankLine() -> [Token] { let parser = Formatter(self) - var numberOfTrailingLinebreaks = self.numberOfTrailingLinebreaks() - - // Make sure there are at least two newlines, - // so we get a blank line between individual declaration types - while numberOfTrailingLinebreaks < 2 { + while parser.tokens.numberOfTrailingLinebreaks() < 2 { parser.insertLinebreak(at: parser.tokens.count) - numberOfTrailingLinebreaks += 1 } return parser.tokens @@ -1679,19 +1674,21 @@ extension Array where Element == Token { func endingWithoutBlankLine() -> [Token] { let parser = Formatter(self) - var numberOfTrailingLinebreaks = self.numberOfTrailingLinebreaks() + while parser.tokens.numberOfTrailingLinebreaks() > 1 { + guard let lastNewlineIndex = parser.lastIndex( + of: .linebreak, + in: 0 ..< parser.tokens.count + ) + else { break } - // Make sure there are at least two newlines, - // so we get a blank line between individual declaration types - while numberOfTrailingLinebreaks > 1 { - parser.removeLastToken() - numberOfTrailingLinebreaks -= 1 + parser.removeTokens(in: lastNewlineIndex ..< parser.tokens.count) } return parser.tokens } - // The number of trailing line breaks in this array of tokens + // The number of trailing newlines in this array of tokens, + // taking into account any spaces that may be between the linebreaks. func numberOfTrailingLinebreaks() -> Int { let parser = Formatter(self) @@ -1700,7 +1697,7 @@ extension Array where Element == Token { while searchIndex > 0, let token = parser.token(at: searchIndex), - token.isSpaceOrCommentOrLinebreak + token.isSpaceOrLinebreak { if token.isLinebreak { numberOfTrailingLinebreaks += 1 diff --git a/Tests/Rules/OrganizeDeclarationsTests.swift b/Tests/Rules/OrganizeDeclarationsTests.swift index cebacf4dc..3eff7914e 100644 --- a/Tests/Rules/OrganizeDeclarationsTests.swift +++ b/Tests/Rules/OrganizeDeclarationsTests.swift @@ -3164,4 +3164,29 @@ class OrganizeDeclarationsTests: XCTestCase { exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } + + func testPreservesCommentAtEndOfTypeBody() { + let input = """ + class Foo { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + let bar: Bar + let baaz: Baaz + + // Comment at end of file + + } + """ + + testFormatting( + for: input, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } } From 593c31c50aa29442004ce8d0ac9eeb2e83a0c9b0 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Tue, 30 Jul 2024 19:54:32 -0700 Subject: [PATCH 33/52] Move rule-specific helpers from DeclarationHelpers.swift to OrganizeDeclarations.swift, improve shared helpers (#1798) --- Sources/DeclarationHelpers.swift | 1150 +++--------------- Sources/Rules/ExtensionAccessControl.swift | 96 +- Sources/Rules/OrganizeDeclarations.swift | 691 +++++++++++ Sources/Rules/UnusedPrivateDeclaration.swift | 2 +- 4 files changed, 971 insertions(+), 968 deletions(-) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index e9d46a44d..2546ea219 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -451,18 +451,16 @@ enum DeclarationType: String, CaseIterable { } } -extension Formatter { +extension Declaration { /// The `DeclarationType` of the given `Declaration` - func type( - of declaration: Declaration, - allowlist availableTypes: [DeclarationType] + func declarationType( + allowlist availableTypes: [DeclarationType], + beforeMarks: Set, + lifecycleMethods: Set ) -> DeclarationType { - switch declaration { + switch self { case let .type(keyword, _, _, _, _): - return options.beforeMarks.contains(keyword) ? .beforeMarks : .nestedType - - case let .declaration(keyword, tokens, _): - return declarationType(of: keyword, with: tokens, allowlist: availableTypes) + return beforeMarks.contains(keyword) ? .beforeMarks : .nestedType case let .conditionalCompilation(_, body, _, _): // Prefer treating conditional compilation blocks as having @@ -473,140 +471,138 @@ extension Formatter { return .nestedType } - return type(of: firstDeclarationInBlock, allowlist: availableTypes) - } - } + return firstDeclarationInBlock.declarationType( + allowlist: availableTypes, + beforeMarks: beforeMarks, + lifecycleMethods: lifecycleMethods + ) - /// The `DeclarationType` of the declaration with the given keyword and tokens - func declarationType( - of keyword: String, - with tokens: [Token], - allowlist availableTypes: [DeclarationType] - ) -> DeclarationType { - guard let declarationTypeTokenIndex = tokens.firstIndex(of: .keyword(keyword)) else { - return .beforeMarks - } + case let .declaration(keyword, tokens, _): + guard let declarationTypeTokenIndex = tokens.firstIndex(of: .keyword(keyword)) else { + return .beforeMarks + } - let declarationParser = Formatter(tokens) - let declarationTypeToken = declarationParser.tokens[declarationTypeTokenIndex] + let declarationParser = Formatter(tokens) + let declarationTypeToken = declarationParser.tokens[declarationTypeTokenIndex] - if keyword == "case" || options.beforeMarks.contains(keyword) { - return .beforeMarks - } + if keyword == "case" || beforeMarks.contains(keyword) { + return .beforeMarks + } - for token in declarationParser.tokens { - if options.beforeMarks.contains(token.string) { return .beforeMarks } - } + for token in declarationParser.tokens { + if beforeMarks.contains(token.string) { return .beforeMarks } + } - let isStaticDeclaration = declarationParser.index( - of: .keyword("static"), - before: declarationTypeTokenIndex - ) != nil - - let isClassDeclaration = declarationParser.index( - of: .keyword("class"), - before: declarationTypeTokenIndex - ) != nil - - let isOverriddenDeclaration = declarationParser.index( - of: .identifier("override"), - before: declarationTypeTokenIndex - ) != nil - - let isDeclarationWithBody: Bool = { - // If there is a code block at the end of the declaration that is _not_ a closure, - // then this declaration has a body. - if let lastClosingBraceIndex = declarationParser.index(of: .endOfScope("}"), before: declarationParser.tokens.count), - let lastOpeningBraceIndex = declarationParser.index(of: .startOfScope("{"), before: lastClosingBraceIndex), - declarationTypeTokenIndex < lastOpeningBraceIndex, - declarationTypeTokenIndex < lastClosingBraceIndex, - !declarationParser.isStartOfClosure(at: lastOpeningBraceIndex) { return true } + let isStaticDeclaration = declarationParser.index( + of: .keyword("static"), + before: declarationTypeTokenIndex + ) != nil - return false - }() + let isClassDeclaration = declarationParser.index( + of: .keyword("class"), + before: declarationTypeTokenIndex + ) != nil - let isViewDeclaration: Bool = { - guard let someKeywordIndex = declarationParser.index( - of: .identifier("some"), after: declarationTypeTokenIndex - ) else { return false } + let isOverriddenDeclaration = declarationParser.index( + of: .identifier("override"), + before: declarationTypeTokenIndex + ) != nil - return declarationParser.index(of: .identifier("View"), after: someKeywordIndex) != nil - }() + let isDeclarationWithBody: Bool = { + // If there is a code block at the end of the declaration that is _not_ a closure, + // then this declaration has a body. + if let lastClosingBraceIndex = declarationParser.index(of: .endOfScope("}"), before: declarationParser.tokens.count), + let lastOpeningBraceIndex = declarationParser.index(of: .startOfScope("{"), before: lastClosingBraceIndex), + declarationTypeTokenIndex < lastOpeningBraceIndex, + declarationTypeTokenIndex < lastClosingBraceIndex, + !declarationParser.isStartOfClosure(at: lastOpeningBraceIndex) { return true } - let isSwiftUIPropertyWrapper = declarationParser - .modifiersForDeclaration(at: declarationTypeTokenIndex) { _, modifier in - swiftUIPropertyWrappers.contains(modifier) - } + return false + }() - switch declarationTypeToken { - // Properties and property-like declarations - case .keyword("let"), .keyword("var"), - .keyword("operator"), .keyword("precedencegroup"): + let isViewDeclaration: Bool = { + guard let someKeywordIndex = declarationParser.index( + of: .identifier("some"), after: declarationTypeTokenIndex + ) else { return false } - if isOverriddenDeclaration && availableTypes.contains(.overriddenProperty) { - return .overriddenProperty - } - if isStaticDeclaration && isDeclarationWithBody && availableTypes.contains(.staticPropertyWithBody) { - return .staticPropertyWithBody - } - if isStaticDeclaration && availableTypes.contains(.staticProperty) { - return .staticProperty - } - if isClassDeclaration && availableTypes.contains(.classPropertyWithBody) { - // Interestingly, Swift does not support stored class properties - // so there's no such thing as a class property without a body. - // https://forums.swift.org/t/class-properties/16539/11 - return .classPropertyWithBody - } - if isViewDeclaration && availableTypes.contains(.swiftUIProperty) { - return .swiftUIProperty - } - if !isDeclarationWithBody && isSwiftUIPropertyWrapper && availableTypes.contains(.swiftUIPropertyWrapper) { - return .swiftUIPropertyWrapper - } - if isDeclarationWithBody && availableTypes.contains(.instancePropertyWithBody) { - return .instancePropertyWithBody - } + return declarationParser.index(of: .identifier("View"), after: someKeywordIndex) != nil + }() - return .instanceProperty + let isSwiftUIPropertyWrapper = declarationParser + .modifiersForDeclaration(at: declarationTypeTokenIndex) { _, modifier in + swiftUIPropertyWrappers.contains(modifier) + } - // Functions and function-like declarations - case .keyword("func"), .keyword("subscript"): - // The user can also provide specific instance method names to place in Lifecycle - // - In the function declaration grammar, the function name always - // immediately follows the `func` keyword: - // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_function-name - let methodName = declarationParser.next(.nonSpaceOrCommentOrLinebreak, after: declarationTypeTokenIndex) - if let methodName = methodName, options.lifecycleMethods.contains(methodName.string) { - return .instanceLifecycle - } - if isOverriddenDeclaration && availableTypes.contains(.overriddenMethod) { - return .overriddenMethod - } - if isStaticDeclaration && availableTypes.contains(.staticMethod) { - return .staticMethod - } - if isClassDeclaration && availableTypes.contains(.classMethod) { - return .classMethod - } - if isViewDeclaration && availableTypes.contains(.swiftUIMethod) { - return .swiftUIMethod - } + switch declarationTypeToken { + // Properties and property-like declarations + case .keyword("let"), .keyword("var"), + .keyword("operator"), .keyword("precedencegroup"): + + if isOverriddenDeclaration && availableTypes.contains(.overriddenProperty) { + return .overriddenProperty + } + if isStaticDeclaration && isDeclarationWithBody && availableTypes.contains(.staticPropertyWithBody) { + return .staticPropertyWithBody + } + if isStaticDeclaration && availableTypes.contains(.staticProperty) { + return .staticProperty + } + if isClassDeclaration && availableTypes.contains(.classPropertyWithBody) { + // Interestingly, Swift does not support stored class properties + // so there's no such thing as a class property without a body. + // https://forums.swift.org/t/class-properties/16539/11 + return .classPropertyWithBody + } + if isViewDeclaration && availableTypes.contains(.swiftUIProperty) { + return .swiftUIProperty + } + if !isDeclarationWithBody && isSwiftUIPropertyWrapper && availableTypes.contains(.swiftUIPropertyWrapper) { + return .swiftUIPropertyWrapper + } + if isDeclarationWithBody && availableTypes.contains(.instancePropertyWithBody) { + return .instancePropertyWithBody + } + + return .instanceProperty + + // Functions and function-like declarations + case .keyword("func"), .keyword("subscript"): + // The user can also provide specific instance method names to place in Lifecycle + // - In the function declaration grammar, the function name always + // immediately follows the `func` keyword: + // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_function-name + let methodName = declarationParser.next(.nonSpaceOrCommentOrLinebreak, after: declarationTypeTokenIndex) + if let methodName = methodName, lifecycleMethods.contains(methodName.string) { + return .instanceLifecycle + } + if isOverriddenDeclaration && availableTypes.contains(.overriddenMethod) { + return .overriddenMethod + } + if isStaticDeclaration && availableTypes.contains(.staticMethod) { + return .staticMethod + } + if isClassDeclaration && availableTypes.contains(.classMethod) { + return .classMethod + } + if isViewDeclaration && availableTypes.contains(.swiftUIMethod) { + return .swiftUIMethod + } - return .instanceMethod + return .instanceMethod - case .keyword("init"), .keyword("deinit"): - return .instanceLifecycle + case .keyword("init"), .keyword("deinit"): + return .instanceLifecycle - // Type-like declarations - case .keyword("typealias"): - return .nestedType + // Type-like declarations + case .keyword("typealias"): + return .nestedType - case .keyword("case"): - return .beforeMarks + case .keyword("case"): + return .beforeMarks - default: - return .beforeMarks + default: + return .beforeMarks + } } } @@ -657,10 +653,10 @@ enum Visibility: String, CaseIterable, Comparable { } } -extension Formatter { - /// The `Visibility` of the given `Declaration` - func visibility(of declaration: Declaration) -> Visibility? { - switch declaration { +extension Declaration { + /// The explicit `Visibility` of this `Declaration` + func visibility() -> Visibility? { + switch self { case let .declaration(keyword, tokens, _), let .type(keyword, open: tokens, _, _, _): guard let keywordIndex = tokens.firstIndex(of: .keyword(keyword)) else { return nil @@ -687,699 +683,60 @@ extension Formatter { // A reasonable heuristic here is to simply use the category of the first declaration // inside the conditional compilation block. if let firstDeclarationInBlock = body.first { - return visibility(of: firstDeclarationInBlock) + return firstDeclarationInBlock.visibility() } else { return nil } } } -} - -// MARK: - Category - -/// A category of declarations used by the `organizeDeclarations` rule -struct Category: Equatable, Hashable { - var visibility: VisibilityCategory - var type: DeclarationType - var order: Int - var comment: String? = nil - - /// The comment tokens that should precede all declarations in this category - func markComment(from template: String, with mode: DeclarationOrganizationMode) -> String? { - "// " + template - .replacingOccurrences( - of: "%c", - with: comment ?? (mode == .type ? type.markComment : visibility.markComment) - ) - } - - /// Whether or not a mark comment should be added for this category, - /// given the set of existing categories with existing mark comments - func shouldBeMarked(in categoriesWithMarkComment: Set, for mode: DeclarationOrganizationMode) -> Bool { - guard type != .beforeMarks else { - return false - } - - switch mode { - case .type: - return !categoriesWithMarkComment.contains(where: { $0.type == type || $0.visibility == .explicit(type) }) - case .visibility: - return !categoriesWithMarkComment.contains(where: { $0.visibility == visibility }) - } - } -} - -/// The visibility category of a declaration -/// -/// - Note: When adding a new visibility type, remember to also update the list in `Examples.swift`. -enum VisibilityCategory: CaseIterable, Hashable, RawRepresentable { - case visibility(Visibility) - case explicit(DeclarationType) - - init?(rawValue: String) { - if let visibility = Visibility(rawValue: rawValue) { - self = .visibility(visibility) - } else if let type = DeclarationType(rawValue: rawValue) { - self = .explicit(type) - } else { - return nil - } - } - - var rawValue: String { - switch self { - case let .visibility(visibility): - return visibility.rawValue - case let .explicit(declarationType): - return declarationType.rawValue - } - } - - var markComment: String { - switch self { - case let .visibility(type): - return type.rawValue.capitalized - case let .explicit(type): - return type.markComment - } - } - - static var allCases: [VisibilityCategory] { - Visibility.allCases.map { .visibility($0) } - } - - static var essentialCases: [VisibilityCategory] { - Visibility.allCases.map { .visibility($0) } - } - - static func defaultOrdering(for mode: DeclarationOrganizationMode) -> [VisibilityCategory] { - switch mode { - case .type: - return allCases - case .visibility: - return [ - .explicit(.beforeMarks), - .explicit(.instanceLifecycle), - ] + allCases - } - } -} -extension Formatter { - /// The `Category` of the given `Declaration` - func category( - of declaration: Declaration, - for mode: DeclarationOrganizationMode, - using order: ParsedOrder - ) -> Category { - let visibility = self.visibility(of: declaration) ?? .internal - let type = self.type(of: declaration, allowlist: order.map(\.type)) - - let visibilityCategory: VisibilityCategory - switch mode { - case .visibility: - guard VisibilityCategory.allCases.contains(.explicit(type)) else { - fallthrough - } + /// Adds the given visibility keyword to the given declaration, + /// replacing any existing visibility keyword. + func add(_ visibilityKeyword: Visibility) -> Declaration { + var declaration = self - visibilityCategory = .explicit(type) - case .type: - visibilityCategory = .visibility(visibility) + if let existingVisibilityKeyword = declaration.visibility() { + declaration = declaration.remove(existingVisibilityKeyword) } - return category(from: order, for: visibilityCategory, with: type) - } - - typealias ParsedOrder = [Category] - - /// The ordering of categories to use for the given `DeclarationOrganizationMode` - func categoryOrder(for mode: DeclarationOrganizationMode) -> ParsedOrder { - typealias ParsedVisibilityMarks = [VisibilityCategory: String] - typealias ParsedTypeMarks = [DeclarationType: String] - - let VisibilityCategorys = options.visibilityOrder?.compactMap { VisibilityCategory(rawValue: $0) } - ?? VisibilityCategory.defaultOrdering(for: mode) - - let declarationTypes = options.typeOrder?.compactMap { DeclarationType(rawValue: $0) } - ?? DeclarationType.defaultOrdering(for: mode) - - // Validate that every essential declaration type is included in either `declarationTypes` or `VisibilityCategorys`. - // Otherwise, we will just crash later when we find a declaration with this type. - for essentialDeclarationType in DeclarationType.essentialCases { - guard declarationTypes.contains(essentialDeclarationType) - || VisibilityCategorys.contains(.explicit(essentialDeclarationType)) + return declaration.mapOpeningTokens { openTokens in + guard let indexOfKeyword = openTokens + .firstIndex(of: .keyword(declaration.keyword)) else { - Swift.fatalError("\(essentialDeclarationType.rawValue) must be included in either --typeorder or --visibilityorder") - } - } - - let customVisibilityMarks = options.customVisibilityMarks - let customTypeMarks = options.customTypeMarks - - let parsedVisibilityMarks: ParsedVisibilityMarks = parseMarks(for: customVisibilityMarks) - let parsedTypeMarks: ParsedTypeMarks = parseMarks(for: customTypeMarks) - - switch mode { - case .visibility: - let categoryPairings = VisibilityCategorys.flatMap { VisibilityCategory -> [(VisibilityCategory, DeclarationType)] in - switch VisibilityCategory { - case let .visibility(visibility): - // Each visibility / access control level pairs with all of the declaration types - return declarationTypes.compactMap { declarationType in - (.visibility(visibility), declarationType) - } - - case let .explicit(explicitDeclarationType): - // Each top-level declaration category pairs with all of the visibility types - return VisibilityCategorys.map { VisibilityCategory in - (VisibilityCategory, explicitDeclarationType) - } - } - } - - return categoryPairings.enumerated().map { offset, element in - Category( - visibility: element.0, - type: element.1, - order: offset, - comment: parsedVisibilityMarks[element.0] - ) - } - - case .type: - let categoryPairings = declarationTypes.flatMap { declarationType -> [(VisibilityCategory, DeclarationType)] in - VisibilityCategorys.map { VisibilityCategory in - (VisibilityCategory, declarationType) - } - } - - return categoryPairings.enumerated().map { offset, element in - Category( - visibility: element.0, - type: element.1, - order: offset, - comment: parsedTypeMarks[element.1] - ) - } - } - } - - /// The `Category` of a declaration with the given `VisibilityCategory` and `DeclarationType` - func category( - from order: ParsedOrder, - for visibility: VisibilityCategory, - with type: DeclarationType - ) -> Category { - guard let category = order.first(where: { entry in - entry.visibility == visibility && entry.type == type - || (entry.visibility == .explicit(type) && entry.type == type) - }) - else { - Swift.fatalError("Cannot determine ordering for declaration with visibility=\(visibility.rawValue) and type=\(type.rawValue).") - } - - return category - } - - private func parseMarks( - for options: Set - ) -> [T: String] where T.RawValue == String { - options.map { customMarkEntry -> (T, String)? in - let split = customMarkEntry.split(separator: ":", maxSplits: 1) - - guard split.count == 2, - let rawValue = split.first, - let mark = split.last, - let concreteType = T(rawValue: String(rawValue)) - else { return nil } - - return (concreteType, String(mark)) - } - .compactMap { $0 } - .reduce(into: [:]) { dictionary, option in - dictionary[option.0] = option.1 - } - } -} - -// MARK: - organizeDeclaration - -extension Formatter { - /// A `Declaration` that represents a Swift type - typealias TypeDeclaration = (kind: String, open: [Token], body: [Declaration], close: [Token]) - - /// Organizes the given type declaration into sorted categories - func organizeDeclaration(_ typeDeclaration: TypeDeclaration) -> TypeDeclaration { - guard options.organizeTypes.contains(typeDeclaration.kind), - typeLengthExceedsOrganizationThreshold(typeDeclaration) - else { return typeDeclaration } - - // Parse category order from options - let categoryOrder = self.categoryOrder(for: options.organizationMode) - - // Remove all of the existing category separators, so they can be re-added - // at the correct location after sorting the declarations. - var typeBody = removeExistingCategorySeparators( - from: typeDeclaration.body, - with: options.organizationMode, - using: categoryOrder - ) - - // Track the consecutive groups of property declarations so we can avoid inserting - // blank lines between elements in the group if possible. - var consecutivePropertyGroups = consecutivePropertyDeclarationGroups(in: typeDeclaration.body) - .filter { group in - // Only track declaration groups where the group as a whole is followed by a - // blank line, since otherwise the declarations can be reordered without issues. - guard let lastDeclarationInGroup = group.last else { return false } - return lastDeclarationInGroup.tokens.numberOfTrailingLinebreaks() > 1 - } - - // Remove the trailing blank line from the last declaration in each consecutive group - for (groupIndex, consecutivePropertyGroup) in consecutivePropertyGroups.enumerated() { - guard let lastDeclarationInGroup = consecutivePropertyGroup.last, - let indexOfDeclaration = typeBody.firstIndex(of: lastDeclarationInGroup) - else { continue } - - let updatedDeclaration = lastDeclarationInGroup.endingWithoutBlankLine() - let indexInGroup = consecutivePropertyGroup.indices.last! - - typeBody[indexOfDeclaration] = updatedDeclaration - consecutivePropertyGroups[groupIndex][indexInGroup] = updatedDeclaration - } - - // Categorize each of the declarations into their primary groups - let categorizedDeclarations: [CategorizedDeclaration] = typeBody - .map { declaration in - let declarationCategory = category( - of: declaration, - for: options.organizationMode, - using: categoryOrder - ) - - return (declaration: declaration, category: declarationCategory) + return openTokens } - // Sort the declarations based on their category and type - guard var sortedTypeBody = sortCategorizedDeclarations( - categorizedDeclarations, - in: typeDeclaration - ) - else { return typeDeclaration } - - // Insert a blank line after the last declaration in each original group - for consecutivePropertyGroup in consecutivePropertyGroups { - let propertiesInGroup = Set(consecutivePropertyGroup) - - guard let lastDeclarationInSortedBody = sortedTypeBody.lastIndex(where: { propertiesInGroup.contains($0.declaration) }) - else { continue } - - sortedTypeBody[lastDeclarationInSortedBody].declaration = - sortedTypeBody[lastDeclarationInSortedBody].declaration.endingWithBlankLine() - } - - // Add a mark comment for each top-level category - let sortedAndMarkedType = addCategorySeparators( - to: typeDeclaration, - sortedDeclarations: sortedTypeBody - ) - - return sortedAndMarkedType - } - - /// Whether or not the length of this types exceeds the minimum threshold to be organized - private func typeLengthExceedsOrganizationThreshold(_ typeDeclaration: TypeDeclaration) -> Bool { - let organizationThreshold: Int - switch typeDeclaration.kind { - case "class", "actor": - organizationThreshold = options.organizeClassThreshold - case "struct": - organizationThreshold = options.organizeStructThreshold - case "enum": - organizationThreshold = options.organizeEnumThreshold - case "extension": - organizationThreshold = options.organizeExtensionThreshold - default: - organizationThreshold = 0 - } - - guard organizationThreshold != 0 else { - return true - } - - let lineCount = typeDeclaration.body - .flatMap { $0.tokens } - .filter { $0.isLinebreak } - .count + let openTokensFormatter = Formatter(openTokens) + let startOfModifiers = openTokensFormatter + .startOfModifiers(at: indexOfKeyword, includingAttributes: false) - return lineCount >= organizationThreshold - } + openTokensFormatter.insert( + tokenize("\(visibilityKeyword.rawValue) "), + at: startOfModifiers + ) - private typealias CategorizedDeclaration = (declaration: Declaration, category: Category) - - /// Sorts the given categorized declarations based on the defined category ordering - private func sortCategorizedDeclarations( - _ categorizedDeclarations: [CategorizedDeclaration], - in typeDeclaration: TypeDeclaration - ) -> [CategorizedDeclaration]? { - let sortAlphabeticallyWithinSubcategories = shouldSortAlphabeticallyWithinSubcategories(in: typeDeclaration) - - var sortedDeclarations = sortDeclarations( - categorizedDeclarations, - sortAlphabeticallyWithinSubcategories: sortAlphabeticallyWithinSubcategories - ) - - // The compiler will synthesize a memberwise init for `struct` - // declarations that don't have an `init` declaration. - // We have to take care to not reorder any properties (but reordering functions etc is ok!) - if !sortAlphabeticallyWithinSubcategories, typeDeclaration.kind == "struct", - !typeDeclaration.body.contains(where: { $0.keyword == "init" }), - !preservesSynthesizedMemberwiseInitializer(categorizedDeclarations, sortedDeclarations) - { - // If sorting by category and by type could cause compilation failures - // by not correctly preserving the synthesized memberwise initializer, - // try to sort _only_ by category (so we can try to preserve the correct category separators) - sortedDeclarations = sortDeclarations(categorizedDeclarations, sortAlphabeticallyWithinSubcategories: false) - - // If sorting _only_ by category still changes the synthesized memberwise initializer, - // then there's nothing we can do to organize this struct. - if !preservesSynthesizedMemberwiseInitializer(categorizedDeclarations, sortedDeclarations) { - return nil - } + return openTokensFormatter.tokens } - - return sortedDeclarations - } - - private func sortDeclarations( - _ categorizedDeclarations: [CategorizedDeclaration], - sortAlphabeticallyWithinSubcategories: Bool - ) - -> [CategorizedDeclaration] - { - categorizedDeclarations.enumerated() - .sorted(by: { lhs, rhs in - let (lhsOriginalIndex, lhs) = lhs - let (rhsOriginalIndex, rhs) = rhs - - if lhs.category.order != rhs.category.order { - return lhs.category.order < rhs.category.order - } - - // If this type had a :sort directive, we sort alphabetically - // within the subcategories (where ordering is otherwise undefined) - if sortAlphabeticallyWithinSubcategories, - let lhsName = lhs.declaration.name, - let rhsName = rhs.declaration.name, - lhsName != rhsName - { - return lhsName.localizedCompare(rhsName) == .orderedAscending - } - - // Respect the original declaration ordering when the categories and types are the same - return lhsOriginalIndex < rhsOriginalIndex - }) - .map { $0.element } } - /// Whether or not type members should additionally be sorted alphabetically - /// within individual subcategories - private func shouldSortAlphabeticallyWithinSubcategories(in typeDeclaration: TypeDeclaration) -> Bool { - // If this type has a leading :sort directive, we sort alphabetically - // within the subcategories (where ordering is otherwise undefined) - let shouldSortAlphabeticallyBySortingMark = typeDeclaration.open.contains(where: { - $0.isCommentBody && $0.string.contains("swiftformat:sort") && !$0.string.contains(":sort:") - }) - - // If this type declaration name contains pattern — sort as well - let shouldSortAlphabeticallyByDeclarationPattern: Bool = { - let parser = Formatter(typeDeclaration.open) - - guard let kindIndex = parser.index(of: .keyword(typeDeclaration.kind), in: 0 ..< typeDeclaration.open.count), - let identifier = parser.next(.identifier, after: kindIndex) + /// Removes the given visibility keyword from the given declaration + func remove(_ visibilityKeyword: Visibility) -> Declaration { + mapOpeningTokens { openTokens in + guard let visibilityKeywordIndex = openTokens + .firstIndex(of: .keyword(visibilityKeyword.rawValue)) else { - return false - } - - return options.alphabeticallySortedDeclarationPatterns.contains { - identifier.string.contains($0) - } - }() - - return shouldSortAlphabeticallyBySortingMark - || shouldSortAlphabeticallyByDeclarationPattern - } - - // Whether or not this declaration is an instance property that can affect - // the parameters struct's synthesized memberwise initializer - private func affectsSynthesizedMemberwiseInitializer( - _ declaration: Declaration, - _ category: Category - ) -> Bool { - switch category.type { - case .swiftUIPropertyWrapper, .instanceProperty: - return true - - case .instancePropertyWithBody: - // `instancePropertyWithBody` represents some stored properties, - // but also computed properties. Only stored properties, - // not computed properties, affect the synthesized init. - // - // This is a stored property if and only if - // the declaration body has a `didSet` or `willSet` keyword, - // based on the grammar for a variable declaration: - // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_variable-declaration - let parser = Formatter(declaration.tokens) - - if let bodyOpenBrace = parser.index(of: .startOfScope("{"), after: -1), - let nextToken = parser.next(.nonSpaceOrCommentOrLinebreak, after: bodyOpenBrace), - [.identifier("willSet"), .identifier("didSet")].contains(nextToken) - { - return true - } - - return false - - default: - return false - } - } - - // Whether or not the two given declaration orderings preserve - // the same synthesized memberwise initializer - private func preservesSynthesizedMemberwiseInitializer( - _ lhs: [CategorizedDeclaration], - _ rhs: [CategorizedDeclaration] - ) -> Bool { - let lhsPropertiesOrder = lhs - .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } - .map { $0.declaration } - - let rhsPropertiesOrder = rhs - .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } - .map { $0.declaration } - - return lhsPropertiesOrder == rhsPropertiesOrder - } - - // Finds all of the consecutive groups of property declarations in the type body - private func consecutivePropertyDeclarationGroups(in body: [Declaration]) -> [[Declaration]] { - var declarationGroups: [[Declaration]] = [] - var currentGroup: [Declaration] = [] - - /// Ends the current group, ensuring that groups are only recorded - /// when they contain two or more declarations. - func endCurrentGroup(addingToExistingGroup declarationToAdd: Declaration? = nil) { - if let declarationToAdd = declarationToAdd { - currentGroup.append(declarationToAdd) - } - - if currentGroup.count >= 2 { - declarationGroups.append(currentGroup) - } - - currentGroup = [] - } - - for declaration in body { - guard declaration.keyword == "let" || declaration.keyword == "var" else { - endCurrentGroup() - continue - } - - let hasTrailingBlankLine = declaration.tokens.numberOfTrailingLinebreaks() > 1 - - if hasTrailingBlankLine { - endCurrentGroup(addingToExistingGroup: declaration) - } else { - currentGroup.append(declaration) - } - } - - endCurrentGroup() - return declarationGroups - } - - /// Adds MARK category separates to the given type - private func addCategorySeparators( - to typeDeclaration: TypeDeclaration, - sortedDeclarations: [CategorizedDeclaration] - ) - -> TypeDeclaration - { - let numberOfCategories: Int = { - switch options.organizationMode { - case .visibility: - return Set(sortedDeclarations.map(\.category).map(\.visibility)).count - case .type: - return Set(sortedDeclarations.map(\.category).map(\.type)).count - } - }() - - var typeDeclaration = typeDeclaration - var formattedCategories: [Category] = [] - var markedDeclarations: [Declaration] = [] - - for (index, (declaration, category)) in sortedDeclarations.enumerated() { - if options.markCategories, - numberOfCategories > 1, - let markComment = category.markComment(from: options.categoryMarkComment, with: options.organizationMode), - category.shouldBeMarked(in: Set(formattedCategories), for: options.organizationMode) - { - formattedCategories.append(category) - - let declarationParser = Formatter(declaration.tokens) - let indentation = declarationParser.currentIndentForLine(at: 0) - - let endMarkDeclaration = options.lineAfterMarks ? "\n\n" : "\n" - let markDeclaration = tokenize("\(indentation)\(markComment)\(endMarkDeclaration)") - - // If this declaration is the first declaration in the type scope, - // make sure the type's opening sequence of tokens ends with - // at least one blank line so the category separator appears balanced - if markedDeclarations.isEmpty { - typeDeclaration.open = typeDeclaration.open.endingWithBlankLine() - } - - markedDeclarations.append(.declaration( - kind: "comment", - tokens: markDeclaration, - originalRange: 0 ... 1 // placeholder value - )) - } - - if options.blankLineAfterSubgroups, - let lastIndexOfSameDeclaration = sortedDeclarations.map(\.category).lastIndex(of: category), - lastIndexOfSameDeclaration == index, - lastIndexOfSameDeclaration != sortedDeclarations.indices.last - { - markedDeclarations.append(declaration.endingWithBlankLine()) - } else { - markedDeclarations.append(declaration) + return openTokens } - } - typeDeclaration.body = markedDeclarations - return typeDeclaration - } + let openTokensFormatter = Formatter(openTokens) + openTokensFormatter.removeToken(at: visibilityKeywordIndex) - /// Removes any existing category separators from the given declarations - private func removeExistingCategorySeparators( - from typeBody: [Declaration], - with mode: DeclarationOrganizationMode, - using order: ParsedOrder - ) -> [Declaration] { - var typeBody = typeBody - - for (declarationIndex, declaration) in typeBody.enumerated() { - let tokensToInspect: [Token] - switch declaration { - case let .declaration(_, tokens, _): - tokensToInspect = tokens - case let .type(_, open, _, _, _), let .conditionalCompilation(open, _, _, _): - // Only inspect the opening tokens of declarations with a body - tokensToInspect = open + while openTokensFormatter.token(at: visibilityKeywordIndex)?.isSpace == true { + openTokensFormatter.removeToken(at: visibilityKeywordIndex) } - // Current amount of variants to pair visibility-type is over 300, - // so we take only categories that could provide typemark that we want to erase - let potentialCategorySeparators = ( - VisibilityCategory.allCases.map { Category(visibility: $0, type: .classMethod, order: 0) } - + DeclarationType.allCases.map { Category(visibility: .visibility(.open), type: $0, order: 0) } - + DeclarationType.allCases.map { Category(visibility: .explicit($0), type: .classMethod, order: 0) } - + order.filter { $0.comment != nil } - ).flatMap { - Array(Set([ - // The user's specific category separator template - $0.markComment(from: options.categoryMarkComment, with: mode), - // Other common variants that we would want to replace with the correct variant - $0.markComment(from: "%c", with: mode), - $0.markComment(from: "// MARK: %c", with: mode), - ])) - }.compactMap { $0 } - - let parser = Formatter(tokensToInspect) - - parser.forEach(.startOfScope("//")) { commentStartIndex, _ in - // Only look at top-level comments inside of the type body - guard parser.currentScope(at: commentStartIndex) == nil else { - return - } - - // Check if this comment matches an expected category separator comment - for potentialSeparatorComment in potentialCategorySeparators { - let potentialCategorySeparator = tokenize(potentialSeparatorComment) - let potentialSeparatorRange = commentStartIndex ..< (commentStartIndex + potentialCategorySeparator.count) - - guard parser.tokens.indices.contains(potentialSeparatorRange.upperBound), - let nextNonwhitespaceIndex = parser.index(of: .nonSpaceOrLinebreak, after: potentialSeparatorRange.upperBound) - else { continue } - - // Check the edit distance of this existing comment with the potential - // valid category separators for this category. If they are similar or identical, - // we'll want to replace the existing comment with the correct comment. - let existingComment = sourceCode(for: Array(parser.tokens[potentialSeparatorRange])) - let minimumEditDistance = Int(0.2 * Float(existingComment.count)) - - guard existingComment.lowercased().editDistance(from: potentialSeparatorComment.lowercased()) - <= minimumEditDistance - else { continue } - - // Makes sure there are only whitespace or other comments before this comment. - // Otherwise, we don't want to remove it. - let tokensBeforeComment = parser.tokens[0 ..< commentStartIndex] - guard !tokensBeforeComment.contains(where: { !$0.isSpaceOrCommentOrLinebreak }) else { - continue - } - - // If we found a matching comment, remove it and all subsequent empty lines - let startOfCommentLine = parser.startOfLine(at: commentStartIndex) - let startOfNextDeclaration = parser.startOfLine(at: nextNonwhitespaceIndex) - parser.removeTokens(in: startOfCommentLine ..< startOfNextDeclaration) - - // Move any tokens from before the category separator into the previous declaration. - // This makes sure that things like comments stay grouped in the same category. - if declarationIndex != 0, startOfCommentLine != 0 { - // Remove the tokens before the category separator from this declaration... - let rangeBeforeComment = 0 ..< startOfCommentLine - let tokensBeforeCommentLine = Array(parser.tokens[rangeBeforeComment]) - parser.removeTokens(in: rangeBeforeComment) - - // ... and append them to the end of the previous declaration - typeBody[declarationIndex - 1] = typeBody[declarationIndex - 1].mapClosingTokens { - $0 + tokensBeforeCommentLine - } - } - - // Apply the updated tokens back to this declaration - typeBody[declarationIndex] = typeBody[declarationIndex].mapOpeningTokens { _ in - parser.tokens - } - } - } + return openTokensFormatter.tokens } - - return typeBody } } @@ -1388,174 +745,35 @@ extension Formatter { extension Formatter { /// Recursively calls the `operation` for every declaration in the source file func forEachRecursiveDeclaration(_ operation: (Declaration) -> Void) { - forEachRecursiveDeclarations(parseDeclarations(), operation) - } - - /// Applies `operation` to every recursive declaration of the given declarations - func forEachRecursiveDeclarations( - _ declarations: [Declaration], - _ operation: (Declaration) -> Void - ) { - for declaration in declarations { - operation(declaration) - if let body = declaration.body { - forEachRecursiveDeclarations(body, operation) - } - } + parseDeclarations().forEachRecursiveDeclaration(operation) } /// Applies `mapRecursiveDeclarations` in place - func mapRecursiveDeclarations(with transform: (Declaration) -> Declaration) { - let updatedDeclarations = mapRecursiveDeclarations(parseDeclarations()) { declaration, _ in + func mapRecursiveDeclarations(_ transform: (Declaration) -> Declaration) { + let updatedDeclarations = parseDeclarations().mapRecursiveDeclarations { declaration in transform(declaration) } + let updatedTokens = updatedDeclarations.flatMap { $0.tokens } replaceTokens(in: tokens.indices, with: updatedTokens) } +} - /// Applies `transform` to every recursive declaration of the given declarations - func mapRecursiveDeclarations( - _ declarations: [Declaration], in stack: [Declaration] = [], - with transform: (Declaration, _ stack: [Declaration]) -> Declaration - ) -> [Declaration] { - declarations.map { declaration in - let mapped = transform(declaration, stack) - switch mapped { - case let .type(kind, open, body, close, originalRange): - return .type( - kind: kind, - open: open, - body: mapRecursiveDeclarations(body, in: stack + [mapped], with: transform), - close: close, - originalRange: originalRange - ) - - case let .conditionalCompilation(open, body, close, originalRange): - return .conditionalCompilation( - open: open, - body: mapRecursiveDeclarations(body, in: stack + [mapped], with: transform), - close: close, - originalRange: originalRange - ) - - case .declaration: - return declaration - } - } - } - - /// Performs some declaration mapping for each body declaration in this declaration - /// (including any declarations nested in conditional compilation blocks, - /// but not including declarations dested within child types). - func mapBodyDeclarations( - in declaration: Declaration, - with transform: (Declaration) -> Declaration - ) -> Declaration { - switch declaration { - case let .type(kind, open, body, close, originalRange): - return .type( - kind: kind, - open: open, - body: mapBodyDeclarations(body, with: transform), - close: close, - originalRange: originalRange - ) - - case let .conditionalCompilation(open, body, close, originalRange): - return .conditionalCompilation( - open: open, - body: mapBodyDeclarations(body, with: transform), - close: close, - originalRange: originalRange - ) - - case .declaration: - // No work to do, because plain declarations don't have bodies - return declaration - } - } - - private func mapBodyDeclarations( - _ body: [Declaration], - with transform: (Declaration) -> Declaration - ) -> [Declaration] { - body.map { bodyDeclaration in - // Apply `mapBodyDeclaration` to each declaration in the body - switch bodyDeclaration { - case .declaration, .type: - return transform(bodyDeclaration) - - // Recursively step through conditional compilation blocks - // since their body tokens are effectively body tokens of the parent type - case .conditionalCompilation: - return mapBodyDeclarations(in: bodyDeclaration, with: transform) - } - } - } - - /// Performs some generic mapping for each declaration in the given array, - /// stepping through conditional compilation blocks (but not into the body - /// of other nested types) - func mapDeclarations( - _ declarations: [Declaration], - with transform: (Declaration) -> T - ) -> [T] { - declarations.flatMap { declaration -> [T] in - switch declaration { - case .declaration, .type: - return [transform(declaration)] - case let .conditionalCompilation(_, body, _, _): - return mapDeclarations(body, with: transform) +extension Array where Element == Declaration { + /// Applies `operation` to every recursive declaration of this array of declarations + func forEachRecursiveDeclaration(_ operation: (Declaration) -> Void) { + for declaration in self { + operation(declaration) + if let body = declaration.body { + body.forEachRecursiveDeclaration(operation) } } } - /// Removes the given visibility keyword from the given declaration - func remove(_ visibilityKeyword: Visibility, from declaration: Declaration) -> Declaration { - declaration.mapOpeningTokens { openTokens in - guard let visibilityKeywordIndex = openTokens - .firstIndex(of: .keyword(visibilityKeyword.rawValue)) - else { - return openTokens - } - - let openTokensFormatter = Formatter(openTokens) - openTokensFormatter.removeToken(at: visibilityKeywordIndex) - - while openTokensFormatter.token(at: visibilityKeywordIndex)?.isSpace == true { - openTokensFormatter.removeToken(at: visibilityKeywordIndex) - } - - return openTokensFormatter.tokens - } - } - - /// Adds the given visibility keyword to the given declaration, - /// replacing any existing visibility keyword. - func add(_ visibilityKeyword: Visibility, to declaration: Declaration) -> Declaration { - var declaration = declaration - - if let existingVisibilityKeyword = visibility(of: declaration) { - declaration = remove(existingVisibilityKeyword, from: declaration) - } - - return declaration.mapOpeningTokens { openTokens in - guard let indexOfKeyword = openTokens - .firstIndex(of: .keyword(declaration.keyword)) - else { - return openTokens - } - - let openTokensFormatter = Formatter(openTokens) - let startOfModifiers = openTokensFormatter - .startOfModifiers(at: indexOfKeyword, includingAttributes: false) - - openTokensFormatter.insert( - tokenize("\(visibilityKeyword.rawValue) "), - at: startOfModifiers - ) - - return openTokensFormatter.tokens + /// Applies `transform` to every recursive declaration of this array of declarations + func mapRecursiveDeclarations(_ transform: (Declaration) -> Declaration) -> [Declaration] { + map { declaration in + transform(declaration).mapRecursiveBodyDeclarations(transform) } } } @@ -1639,6 +857,32 @@ extension Declaration { } } + /// Performs some declaration mapping for each body declaration in this declaration + func mapRecursiveBodyDeclarations(_ transform: (Declaration) -> Declaration) -> Declaration { + switch self { + case let .type(kind, open, body, close, originalRange): + return .type( + kind: kind, + open: open, + body: body.mapRecursiveDeclarations(transform), + close: close, + originalRange: originalRange + ) + + case let .conditionalCompilation(open, body, close, originalRange): + return .conditionalCompilation( + open: open, + body: body.mapRecursiveDeclarations(transform), + close: close, + originalRange: originalRange + ) + + case .declaration: + // No work to do, because plain declarations don't have bodies + return self + } + } + /// Updates the given declaration tokens so it ends with at least one blank like /// (e.g. so it ends with at least two newlines) func endingWithBlankLine() -> Declaration { diff --git a/Sources/Rules/ExtensionAccessControl.swift b/Sources/Rules/ExtensionAccessControl.swift index 1d3bb20ab..aa7b3d774 100644 --- a/Sources/Rules/ExtensionAccessControl.swift +++ b/Sources/Rules/ExtensionAccessControl.swift @@ -16,12 +16,12 @@ public extension FormatRule { guard !formatter.options.fragment else { return } let declarations = formatter.parseDeclarations() - let updatedDeclarations = formatter.mapRecursiveDeclarations(declarations) { declaration, _ in + let updatedDeclarations = declarations.mapRecursiveDeclarations { declaration in guard case let .type("extension", open, body, close, _) = declaration else { return declaration } - let visibilityKeyword = formatter.visibility(of: declaration) + let visibilityKeyword = declaration.visibility() // `private` visibility at top level of file is equivalent to `fileprivate` let extensionVisibility = (visibilityKeyword == .private) ? .fileprivate : visibilityKeyword @@ -39,8 +39,8 @@ public extension FormatRule { } let visibilityOfBodyDeclarations = formatter - .mapDeclarations(body) { - formatter.visibility(of: $0) ?? extensionVisibility ?? .internal + .mapDeclarationsExcludingTypeBodies(body) { declaration in + declaration.visibility() ?? extensionVisibility ?? .internal } .compactMap { $0 } @@ -61,7 +61,7 @@ public extension FormatRule { // Check type being extended does not have lower visibility for d in declarations where d.name == declaration.name { if case let .type(kind, _, _, _, _) = d { - if kind != "extension", formatter.visibility(of: d) ?? .internal < memberVisibility { + if kind != "extension", d.visibility() ?? .internal < memberVisibility { // Cannot make extension with greater visibility than type being extended return declaration } @@ -76,18 +76,18 @@ public extension FormatRule { { extensionWithUpdatedVisibility = declaration } else { - extensionWithUpdatedVisibility = formatter.add(memberVisibility, to: declaration) + extensionWithUpdatedVisibility = declaration.add(memberVisibility) } - return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in - let visibility = formatter.visibility(of: bodyDeclaration) + return formatter.mapBodyDeclarationsExcludingTypeBodies(in: extensionWithUpdatedVisibility) { bodyDeclaration in + let visibility = bodyDeclaration.visibility() if memberVisibility > visibility ?? extensionVisibility ?? .internal { if visibility == nil { - return formatter.add(.internal, to: bodyDeclaration) + return bodyDeclaration.add(.internal) } return bodyDeclaration } - return formatter.remove(memberVisibility, from: bodyDeclaration) + return bodyDeclaration.remove(memberVisibility) } // Move the extension's visibility keyword to each individual declaration @@ -98,15 +98,15 @@ public extension FormatRule { } // Remove the visibility keyword from the extension declaration itself - let extensionWithUpdatedVisibility = formatter.remove(visibilityKeyword!, from: declaration) + let extensionWithUpdatedVisibility = declaration.remove(visibilityKeyword!) // And apply the extension's visibility to each of its child declarations // that don't have an explicit visibility keyword - return formatter.mapBodyDeclarations(in: extensionWithUpdatedVisibility) { bodyDeclaration in - if formatter.visibility(of: bodyDeclaration) == nil { + return formatter.mapBodyDeclarationsExcludingTypeBodies(in: extensionWithUpdatedVisibility) { bodyDeclaration in + if bodyDeclaration.visibility() == nil { // If there was no explicit visibility keyword, then this declaration // was using the visibility of the extension itself. - return formatter.add(extensionVisibility, to: bodyDeclaration) + return bodyDeclaration.add(extensionVisibility) } else { // Keep the existing visibility return bodyDeclaration @@ -119,3 +119,71 @@ public extension FormatRule { formatter.replaceTokens(in: formatter.tokens.indices, with: updatedTokens) } } + +private extension Formatter { + /// Performs some generic mapping for each declaration in the given array, + /// stepping through conditional compilation blocks (but not into the body + /// of other nested types) + func mapDeclarationsExcludingTypeBodies( + _ declarations: [Declaration], + with transform: (Declaration) -> T + ) -> [T] { + declarations.flatMap { declaration -> [T] in + switch declaration { + case .declaration, .type: + return [transform(declaration)] + case let .conditionalCompilation(_, body, _, _): + return mapDeclarationsExcludingTypeBodies(body, with: transform) + } + } + } + + /// Performs some declaration mapping for each body declaration in this declaration + /// (including any declarations nested in conditional compilation blocks, + /// but not including declarations dested within child types). + func mapBodyDeclarationsExcludingTypeBodies( + in declaration: Declaration, + with transform: (Declaration) -> Declaration + ) -> Declaration { + switch declaration { + case let .type(kind, open, body, close, originalRange): + return .type( + kind: kind, + open: open, + body: mapBodyDeclarationsExcludingTypeBodies(body, with: transform), + close: close, + originalRange: originalRange + ) + + case let .conditionalCompilation(open, body, close, originalRange): + return .conditionalCompilation( + open: open, + body: mapBodyDeclarationsExcludingTypeBodies(body, with: transform), + close: close, + originalRange: originalRange + ) + + case .declaration: + // No work to do, because plain declarations don't have bodies + return declaration + } + } + + private func mapBodyDeclarationsExcludingTypeBodies( + _ body: [Declaration], + with transform: (Declaration) -> Declaration + ) -> [Declaration] { + body.map { bodyDeclaration in + // Apply `mapBodyDeclaration` to each declaration in the body + switch bodyDeclaration { + case .declaration, .type: + return transform(bodyDeclaration) + + // Recursively step through conditional compilation blocks + // since their body tokens are effectively body tokens of the parent type + case .conditionalCompilation: + return mapBodyDeclarationsExcludingTypeBodies(in: bodyDeclaration, with: transform) + } + } + } +} diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift index db6e8e222..e2539fd97 100644 --- a/Sources/Rules/OrganizeDeclarations.swift +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -44,3 +44,694 @@ public extension FormatRule { } } } + +// MARK: - organizeDeclaration + +extension Formatter { + /// A `Declaration` that represents a Swift type + typealias TypeDeclaration = (kind: String, open: [Token], body: [Declaration], close: [Token]) + + /// Organizes the given type declaration into sorted categories + func organizeDeclaration(_ typeDeclaration: TypeDeclaration) -> TypeDeclaration { + guard options.organizeTypes.contains(typeDeclaration.kind), + typeLengthExceedsOrganizationThreshold(typeDeclaration) + else { return typeDeclaration } + + // Parse category order from options + let categoryOrder = self.categoryOrder(for: options.organizationMode) + + // Remove all of the existing category separators, so they can be re-added + // at the correct location after sorting the declarations. + var typeBody = removeExistingCategorySeparators( + from: typeDeclaration.body, + with: options.organizationMode, + using: categoryOrder + ) + + // Track the consecutive groups of property declarations so we can avoid inserting + // blank lines between elements in the group if possible. + var consecutivePropertyGroups = consecutivePropertyDeclarationGroups(in: typeDeclaration.body) + .filter { group in + // Only track declaration groups where the group as a whole is followed by a + // blank line, since otherwise the declarations can be reordered without issues. + guard let lastDeclarationInGroup = group.last else { return false } + return lastDeclarationInGroup.tokens.numberOfTrailingLinebreaks() > 1 + } + + // Remove the trailing blank line from the last declaration in each consecutive group + for (groupIndex, consecutivePropertyGroup) in consecutivePropertyGroups.enumerated() { + guard let lastDeclarationInGroup = consecutivePropertyGroup.last, + let indexOfDeclaration = typeBody.firstIndex(of: lastDeclarationInGroup) + else { continue } + + let updatedDeclaration = lastDeclarationInGroup.endingWithoutBlankLine() + let indexInGroup = consecutivePropertyGroup.indices.last! + + typeBody[indexOfDeclaration] = updatedDeclaration + consecutivePropertyGroups[groupIndex][indexInGroup] = updatedDeclaration + } + + // Categorize each of the declarations into their primary groups + let categorizedDeclarations: [CategorizedDeclaration] = typeBody + .map { declaration in + let declarationCategory = category( + of: declaration, + for: options.organizationMode, + using: categoryOrder + ) + + return (declaration: declaration, category: declarationCategory) + } + + // Sort the declarations based on their category and type + guard var sortedTypeBody = sortCategorizedDeclarations( + categorizedDeclarations, + in: typeDeclaration + ) + else { return typeDeclaration } + + // Insert a blank line after the last declaration in each original group + for consecutivePropertyGroup in consecutivePropertyGroups { + let propertiesInGroup = Set(consecutivePropertyGroup) + + guard let lastDeclarationInSortedBody = sortedTypeBody.lastIndex(where: { propertiesInGroup.contains($0.declaration) }) + else { continue } + + sortedTypeBody[lastDeclarationInSortedBody].declaration = + sortedTypeBody[lastDeclarationInSortedBody].declaration.endingWithBlankLine() + } + + // Add a mark comment for each top-level category + let sortedAndMarkedType = addCategorySeparators( + to: typeDeclaration, + sortedDeclarations: sortedTypeBody + ) + + return sortedAndMarkedType + } + + /// Whether or not the length of this types exceeds the minimum threshold to be organized + func typeLengthExceedsOrganizationThreshold(_ typeDeclaration: TypeDeclaration) -> Bool { + let organizationThreshold: Int + switch typeDeclaration.kind { + case "class", "actor": + organizationThreshold = options.organizeClassThreshold + case "struct": + organizationThreshold = options.organizeStructThreshold + case "enum": + organizationThreshold = options.organizeEnumThreshold + case "extension": + organizationThreshold = options.organizeExtensionThreshold + default: + organizationThreshold = 0 + } + + guard organizationThreshold != 0 else { + return true + } + + let lineCount = typeDeclaration.body + .flatMap { $0.tokens } + .filter { $0.isLinebreak } + .count + + return lineCount >= organizationThreshold + } + + typealias CategorizedDeclaration = (declaration: Declaration, category: Category) + + /// Sorts the given categorized declarations based on the defined category ordering + func sortCategorizedDeclarations( + _ categorizedDeclarations: [CategorizedDeclaration], + in typeDeclaration: TypeDeclaration + ) -> [CategorizedDeclaration]? { + let sortAlphabeticallyWithinSubcategories = shouldSortAlphabeticallyWithinSubcategories(in: typeDeclaration) + + var sortedDeclarations = sortDeclarations( + categorizedDeclarations, + sortAlphabeticallyWithinSubcategories: sortAlphabeticallyWithinSubcategories + ) + + // The compiler will synthesize a memberwise init for `struct` + // declarations that don't have an `init` declaration. + // We have to take care to not reorder any properties (but reordering functions etc is ok!) + if !sortAlphabeticallyWithinSubcategories, typeDeclaration.kind == "struct", + !typeDeclaration.body.contains(where: { $0.keyword == "init" }), + !preservesSynthesizedMemberwiseInitializer(categorizedDeclarations, sortedDeclarations) + { + // If sorting by category and by type could cause compilation failures + // by not correctly preserving the synthesized memberwise initializer, + // try to sort _only_ by category (so we can try to preserve the correct category separators) + sortedDeclarations = sortDeclarations(categorizedDeclarations, sortAlphabeticallyWithinSubcategories: false) + + // If sorting _only_ by category still changes the synthesized memberwise initializer, + // then there's nothing we can do to organize this struct. + if !preservesSynthesizedMemberwiseInitializer(categorizedDeclarations, sortedDeclarations) { + return nil + } + } + + return sortedDeclarations + } + + func sortDeclarations( + _ categorizedDeclarations: [CategorizedDeclaration], + sortAlphabeticallyWithinSubcategories: Bool + ) -> [CategorizedDeclaration] { + categorizedDeclarations.enumerated() + .sorted(by: { lhs, rhs in + let (lhsOriginalIndex, lhs) = lhs + let (rhsOriginalIndex, rhs) = rhs + + if lhs.category.order != rhs.category.order { + return lhs.category.order < rhs.category.order + } + + // If this type had a :sort directive, we sort alphabetically + // within the subcategories (where ordering is otherwise undefined) + if sortAlphabeticallyWithinSubcategories, + let lhsName = lhs.declaration.name, + let rhsName = rhs.declaration.name, + lhsName != rhsName + { + return lhsName.localizedCompare(rhsName) == .orderedAscending + } + + // Respect the original declaration ordering when the categories and types are the same + return lhsOriginalIndex < rhsOriginalIndex + }) + .map { $0.element } + } + + /// Whether or not type members should additionally be sorted alphabetically + /// within individual subcategories + func shouldSortAlphabeticallyWithinSubcategories(in typeDeclaration: TypeDeclaration) -> Bool { + // If this type has a leading :sort directive, we sort alphabetically + // within the subcategories (where ordering is otherwise undefined) + let shouldSortAlphabeticallyBySortingMark = typeDeclaration.open.contains(where: { + $0.isCommentBody && $0.string.contains("swiftformat:sort") && !$0.string.contains(":sort:") + }) + + // If this type declaration name contains pattern — sort as well + let shouldSortAlphabeticallyByDeclarationPattern: Bool = { + let parser = Formatter(typeDeclaration.open) + + guard let kindIndex = parser.index(of: .keyword(typeDeclaration.kind), in: 0 ..< typeDeclaration.open.count), + let identifier = parser.next(.identifier, after: kindIndex) + else { + return false + } + + return options.alphabeticallySortedDeclarationPatterns.contains { + identifier.string.contains($0) + } + }() + + return shouldSortAlphabeticallyBySortingMark + || shouldSortAlphabeticallyByDeclarationPattern + } + + // Whether or not this declaration is an instance property that can affect + // the parameters struct's synthesized memberwise initializer + func affectsSynthesizedMemberwiseInitializer( + _ declaration: Declaration, + _ category: Category + ) -> Bool { + switch category.type { + case .swiftUIPropertyWrapper, .instanceProperty: + return true + + case .instancePropertyWithBody: + // `instancePropertyWithBody` represents some stored properties, + // but also computed properties. Only stored properties, + // not computed properties, affect the synthesized init. + // + // This is a stored property if and only if + // the declaration body has a `didSet` or `willSet` keyword, + // based on the grammar for a variable declaration: + // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_variable-declaration + let parser = Formatter(declaration.tokens) + + if let bodyOpenBrace = parser.index(of: .startOfScope("{"), after: -1), + let nextToken = parser.next(.nonSpaceOrCommentOrLinebreak, after: bodyOpenBrace), + [.identifier("willSet"), .identifier("didSet")].contains(nextToken) + { + return true + } + + return false + + default: + return false + } + } + + // Whether or not the two given declaration orderings preserve + // the same synthesized memberwise initializer + func preservesSynthesizedMemberwiseInitializer( + _ lhs: [CategorizedDeclaration], + _ rhs: [CategorizedDeclaration] + ) -> Bool { + let lhsPropertiesOrder = lhs + .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } + .map { $0.declaration } + + let rhsPropertiesOrder = rhs + .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } + .map { $0.declaration } + + return lhsPropertiesOrder == rhsPropertiesOrder + } + + // Finds all of the consecutive groups of property declarations in the type body + func consecutivePropertyDeclarationGroups(in body: [Declaration]) -> [[Declaration]] { + var declarationGroups: [[Declaration]] = [] + var currentGroup: [Declaration] = [] + + /// Ends the current group, ensuring that groups are only recorded + /// when they contain two or more declarations. + func endCurrentGroup(addingToExistingGroup declarationToAdd: Declaration? = nil) { + if let declarationToAdd = declarationToAdd { + currentGroup.append(declarationToAdd) + } + + if currentGroup.count >= 2 { + declarationGroups.append(currentGroup) + } + + currentGroup = [] + } + + for declaration in body { + guard declaration.keyword == "let" || declaration.keyword == "var" else { + endCurrentGroup() + continue + } + + let hasTrailingBlankLine = declaration.tokens.numberOfTrailingLinebreaks() > 1 + + if hasTrailingBlankLine { + endCurrentGroup(addingToExistingGroup: declaration) + } else { + currentGroup.append(declaration) + } + } + + endCurrentGroup() + return declarationGroups + } + + /// Adds MARK category separates to the given type + func addCategorySeparators( + to typeDeclaration: TypeDeclaration, + sortedDeclarations: [CategorizedDeclaration] + ) + -> TypeDeclaration + { + let numberOfCategories: Int = { + switch options.organizationMode { + case .visibility: + return Set(sortedDeclarations.map(\.category).map(\.visibility)).count + case .type: + return Set(sortedDeclarations.map(\.category).map(\.type)).count + } + }() + + var typeDeclaration = typeDeclaration + var formattedCategories: [Category] = [] + var markedDeclarations: [Declaration] = [] + + for (index, (declaration, category)) in sortedDeclarations.enumerated() { + if options.markCategories, + numberOfCategories > 1, + let markComment = category.markComment(from: options.categoryMarkComment, with: options.organizationMode), + category.shouldBeMarked(in: Set(formattedCategories), for: options.organizationMode) + { + formattedCategories.append(category) + + let declarationParser = Formatter(declaration.tokens) + let indentation = declarationParser.currentIndentForLine(at: 0) + + let endMarkDeclaration = options.lineAfterMarks ? "\n\n" : "\n" + let markDeclaration = tokenize("\(indentation)\(markComment)\(endMarkDeclaration)") + + // If this declaration is the first declaration in the type scope, + // make sure the type's opening sequence of tokens ends with + // at least one blank line so the category separator appears balanced + if markedDeclarations.isEmpty { + typeDeclaration.open = typeDeclaration.open.endingWithBlankLine() + } + + markedDeclarations.append(.declaration( + kind: "comment", + tokens: markDeclaration, + originalRange: 0 ... 1 // placeholder value + )) + } + + if options.blankLineAfterSubgroups, + let lastIndexOfSameDeclaration = sortedDeclarations.map(\.category).lastIndex(of: category), + lastIndexOfSameDeclaration == index, + lastIndexOfSameDeclaration != sortedDeclarations.indices.last + { + markedDeclarations.append(declaration.endingWithBlankLine()) + } else { + markedDeclarations.append(declaration) + } + } + + typeDeclaration.body = markedDeclarations + return typeDeclaration + } + + /// Removes any existing category separators from the given declarations + func removeExistingCategorySeparators( + from typeBody: [Declaration], + with mode: DeclarationOrganizationMode, + using order: ParsedOrder + ) -> [Declaration] { + var typeBody = typeBody + + for (declarationIndex, declaration) in typeBody.enumerated() { + let tokensToInspect: [Token] + switch declaration { + case let .declaration(_, tokens, _): + tokensToInspect = tokens + case let .type(_, open, _, _, _), let .conditionalCompilation(open, _, _, _): + // Only inspect the opening tokens of declarations with a body + tokensToInspect = open + } + + // Current amount of variants to pair visibility-type is over 300, + // so we take only categories that could provide typemark that we want to erase + let potentialCategorySeparators = ( + VisibilityCategory.allCases.map { Category(visibility: $0, type: .classMethod, order: 0) } + + DeclarationType.allCases.map { Category(visibility: .visibility(.open), type: $0, order: 0) } + + DeclarationType.allCases.map { Category(visibility: .explicit($0), type: .classMethod, order: 0) } + + order.filter { $0.comment != nil } + ).flatMap { + Array(Set([ + // The user's specific category separator template + $0.markComment(from: options.categoryMarkComment, with: mode), + // Other common variants that we would want to replace with the correct variant + $0.markComment(from: "%c", with: mode), + $0.markComment(from: "// MARK: %c", with: mode), + ])) + }.compactMap { $0 } + + let parser = Formatter(tokensToInspect) + + parser.forEach(.startOfScope("//")) { commentStartIndex, _ in + // Only look at top-level comments inside of the type body + guard parser.currentScope(at: commentStartIndex) == nil else { + return + } + + // Check if this comment matches an expected category separator comment + for potentialSeparatorComment in potentialCategorySeparators { + let potentialCategorySeparator = tokenize(potentialSeparatorComment) + let potentialSeparatorRange = commentStartIndex ..< (commentStartIndex + potentialCategorySeparator.count) + + guard parser.tokens.indices.contains(potentialSeparatorRange.upperBound), + let nextNonwhitespaceIndex = parser.index(of: .nonSpaceOrLinebreak, after: potentialSeparatorRange.upperBound) + else { continue } + + // Check the edit distance of this existing comment with the potential + // valid category separators for this category. If they are similar or identical, + // we'll want to replace the existing comment with the correct comment. + let existingComment = sourceCode(for: Array(parser.tokens[potentialSeparatorRange])) + let minimumEditDistance = Int(0.2 * Float(existingComment.count)) + + guard existingComment.lowercased().editDistance(from: potentialSeparatorComment.lowercased()) + <= minimumEditDistance + else { continue } + + // Makes sure there are only whitespace or other comments before this comment. + // Otherwise, we don't want to remove it. + let tokensBeforeComment = parser.tokens[0 ..< commentStartIndex] + guard !tokensBeforeComment.contains(where: { !$0.isSpaceOrCommentOrLinebreak }) else { + continue + } + + // If we found a matching comment, remove it and all subsequent empty lines + let startOfCommentLine = parser.startOfLine(at: commentStartIndex) + let startOfNextDeclaration = parser.startOfLine(at: nextNonwhitespaceIndex) + parser.removeTokens(in: startOfCommentLine ..< startOfNextDeclaration) + + // Move any tokens from before the category separator into the previous declaration. + // This makes sure that things like comments stay grouped in the same category. + if declarationIndex != 0, startOfCommentLine != 0 { + // Remove the tokens before the category separator from this declaration... + let rangeBeforeComment = 0 ..< startOfCommentLine + let tokensBeforeCommentLine = Array(parser.tokens[rangeBeforeComment]) + parser.removeTokens(in: rangeBeforeComment) + + // ... and append them to the end of the previous declaration + typeBody[declarationIndex - 1] = typeBody[declarationIndex - 1].mapClosingTokens { + $0 + tokensBeforeCommentLine + } + } + + // Apply the updated tokens back to this declaration + typeBody[declarationIndex] = typeBody[declarationIndex].mapOpeningTokens { _ in + parser.tokens + } + } + } + } + + return typeBody + } +} + +// MARK: - Category + +/// A category of declarations used by the `organizeDeclarations` rule +struct Category: Equatable, Hashable { + var visibility: VisibilityCategory + var type: DeclarationType + var order: Int + var comment: String? = nil + + /// The comment tokens that should precede all declarations in this category + func markComment(from template: String, with mode: DeclarationOrganizationMode) -> String? { + "// " + template + .replacingOccurrences( + of: "%c", + with: comment ?? (mode == .type ? type.markComment : visibility.markComment) + ) + } + + /// Whether or not a mark comment should be added for this category, + /// given the set of existing categories with existing mark comments + func shouldBeMarked(in categoriesWithMarkComment: Set, for mode: DeclarationOrganizationMode) -> Bool { + guard type != .beforeMarks else { + return false + } + + switch mode { + case .type: + return !categoriesWithMarkComment.contains(where: { $0.type == type || $0.visibility == .explicit(type) }) + case .visibility: + return !categoriesWithMarkComment.contains(where: { $0.visibility == visibility }) + } + } +} + +/// The visibility category of a declaration +/// +/// - Note: When adding a new visibility type, remember to also update the list in `Examples.swift`. +enum VisibilityCategory: CaseIterable, Hashable, RawRepresentable { + case visibility(Visibility) + case explicit(DeclarationType) + + init?(rawValue: String) { + if let visibility = Visibility(rawValue: rawValue) { + self = .visibility(visibility) + } else if let type = DeclarationType(rawValue: rawValue) { + self = .explicit(type) + } else { + return nil + } + } + + var rawValue: String { + switch self { + case let .visibility(visibility): + return visibility.rawValue + case let .explicit(declarationType): + return declarationType.rawValue + } + } + + var markComment: String { + switch self { + case let .visibility(type): + return type.rawValue.capitalized + case let .explicit(type): + return type.markComment + } + } + + static var allCases: [VisibilityCategory] { + Visibility.allCases.map { .visibility($0) } + } + + static var essentialCases: [VisibilityCategory] { + Visibility.allCases.map { .visibility($0) } + } + + static func defaultOrdering(for mode: DeclarationOrganizationMode) -> [VisibilityCategory] { + switch mode { + case .type: + return allCases + case .visibility: + return [ + .explicit(.beforeMarks), + .explicit(.instanceLifecycle), + ] + allCases + } + } +} + +extension Formatter { + /// The `Category` of the given `Declaration` + func category( + of declaration: Declaration, + for mode: DeclarationOrganizationMode, + using order: ParsedOrder + ) -> Category { + let visibility = declaration.visibility() ?? .internal + + let type = declaration.declarationType( + allowlist: order.map(\.type), + beforeMarks: options.beforeMarks, + lifecycleMethods: options.lifecycleMethods + ) + + let visibilityCategory: VisibilityCategory + switch mode { + case .visibility: + guard VisibilityCategory.allCases.contains(.explicit(type)) else { + fallthrough + } + + visibilityCategory = .explicit(type) + case .type: + visibilityCategory = .visibility(visibility) + } + + return category(from: order, for: visibilityCategory, with: type) + } + + typealias ParsedOrder = [Category] + + /// The ordering of categories to use for the given `DeclarationOrganizationMode` + func categoryOrder(for mode: DeclarationOrganizationMode) -> ParsedOrder { + typealias ParsedVisibilityMarks = [VisibilityCategory: String] + typealias ParsedTypeMarks = [DeclarationType: String] + + let VisibilityCategorys = options.visibilityOrder?.compactMap { VisibilityCategory(rawValue: $0) } + ?? VisibilityCategory.defaultOrdering(for: mode) + + let declarationTypes = options.typeOrder?.compactMap { DeclarationType(rawValue: $0) } + ?? DeclarationType.defaultOrdering(for: mode) + + // Validate that every essential declaration type is included in either `declarationTypes` or `VisibilityCategorys`. + // Otherwise, we will just crash later when we find a declaration with this type. + for essentialDeclarationType in DeclarationType.essentialCases { + guard declarationTypes.contains(essentialDeclarationType) + || VisibilityCategorys.contains(.explicit(essentialDeclarationType)) + else { + Swift.fatalError("\(essentialDeclarationType.rawValue) must be included in either --typeorder or --visibilityorder") + } + } + + let customVisibilityMarks = options.customVisibilityMarks + let customTypeMarks = options.customTypeMarks + + let parsedVisibilityMarks: ParsedVisibilityMarks = parseMarks(for: customVisibilityMarks) + let parsedTypeMarks: ParsedTypeMarks = parseMarks(for: customTypeMarks) + + switch mode { + case .visibility: + let categoryPairings = VisibilityCategorys.flatMap { VisibilityCategory -> [(VisibilityCategory, DeclarationType)] in + switch VisibilityCategory { + case let .visibility(visibility): + // Each visibility / access control level pairs with all of the declaration types + return declarationTypes.compactMap { declarationType in + (.visibility(visibility), declarationType) + } + + case let .explicit(explicitDeclarationType): + // Each top-level declaration category pairs with all of the visibility types + return VisibilityCategorys.map { VisibilityCategory in + (VisibilityCategory, explicitDeclarationType) + } + } + } + + return categoryPairings.enumerated().map { offset, element in + Category( + visibility: element.0, + type: element.1, + order: offset, + comment: parsedVisibilityMarks[element.0] + ) + } + + case .type: + let categoryPairings = declarationTypes.flatMap { declarationType -> [(VisibilityCategory, DeclarationType)] in + VisibilityCategorys.map { VisibilityCategory in + (VisibilityCategory, declarationType) + } + } + + return categoryPairings.enumerated().map { offset, element in + Category( + visibility: element.0, + type: element.1, + order: offset, + comment: parsedTypeMarks[element.1] + ) + } + } + } + + /// The `Category` of a declaration with the given `VisibilityCategory` and `DeclarationType` + func category( + from order: ParsedOrder, + for visibility: VisibilityCategory, + with type: DeclarationType + ) -> Category { + guard let category = order.first(where: { entry in + entry.visibility == visibility && entry.type == type + || (entry.visibility == .explicit(type) && entry.type == type) + }) + else { + Swift.fatalError("Cannot determine ordering for declaration with visibility=\(visibility.rawValue) and type=\(type.rawValue).") + } + + return category + } + + private func parseMarks( + for options: Set + ) -> [T: String] where T.RawValue == String { + options.map { customMarkEntry -> (T, String)? in + let split = customMarkEntry.split(separator: ":", maxSplits: 1) + + guard split.count == 2, + let rawValue = split.first, + let mark = split.last, + let concreteType = T(rawValue: String(rawValue)) + else { return nil } + + return (concreteType, String(mark)) + } + .compactMap { $0 } + .reduce(into: [:]) { dictionary, option in + dictionary[option.0] = option.1 + } + } +} diff --git a/Sources/Rules/UnusedPrivateDeclaration.swift b/Sources/Rules/UnusedPrivateDeclaration.swift index 3482c8656..748586367 100644 --- a/Sources/Rules/UnusedPrivateDeclaration.swift +++ b/Sources/Rules/UnusedPrivateDeclaration.swift @@ -36,7 +36,7 @@ public extension FormatRule { !hasDisallowedModifiers else { return } - switch formatter.visibility(of: declaration) { + switch declaration.visibility() { case .fileprivate, .private: privateDeclarations.append(declaration) case .none, .open, .public, .package, .internal: From c4f782378c5801408c16e67fd9e1e98c4cab4432 Mon Sep 17 00:00:00 2001 From: Miguel Jimenez Date: Wed, 31 Jul 2024 13:20:26 -0400 Subject: [PATCH 34/52] Add SwiftUI properties subcategory alphabetical sort (#1794) --- Rules.md | 1 + Sources/DeclarationHelpers.swift | 8 +- Sources/OptionDescriptor.swift | 9 ++ Sources/Options.swift | 3 + Sources/Rules/OrganizeDeclarations.swift | 10 +- Tests/MetadataTests.swift | 1 + Tests/Rules/OrganizeDeclarationsTests.swift | 108 ++++++++++++++++++++ 7 files changed, 137 insertions(+), 3 deletions(-) diff --git a/Rules.md b/Rules.md index 31673ee61..7e8b4dbb9 100644 --- a/Rules.md +++ b/Rules.md @@ -1410,6 +1410,7 @@ Option | Description `--visibilitymarks` | Marks for visibility groups (public:Public Fields,..) `--typemarks` | Marks for declaration type groups (classMethod:Baaz,..) `--groupblanklines` | Require a blank line after each subgroup. Default: true +`--sortswiftuiprops` | Sorts SwiftUI properties alphabetically, defaults to "false"
Examples diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 2546ea219..608faeeb2 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -149,6 +149,10 @@ enum Declaration: Hashable { }) return allModifiers } + + var swiftUIPropertyWrapper: String? { + modifiers.first(where: Declaration.swiftUIPropertyWrappers.contains) + } } extension Formatter { @@ -530,7 +534,7 @@ extension Declaration { let isSwiftUIPropertyWrapper = declarationParser .modifiersForDeclaration(at: declarationTypeTokenIndex) { _, modifier in - swiftUIPropertyWrappers.contains(modifier) + Declaration.swiftUIPropertyWrappers.contains(modifier) } switch declarationTypeToken { @@ -608,7 +612,7 @@ extension Declaration { /// Represents all the native SwiftUI property wrappers that conform to `DynamicProperty` and cause a SwiftUI view to re-render. /// Most of these are listed here: https://developer.apple.com/documentation/swiftui/dynamicproperty - private var swiftUIPropertyWrappers: Set { + fileprivate static var swiftUIPropertyWrappers: Set { [ "@AccessibilityFocusState", "@AppStorage", diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index b3898ef8b..fb59fa781 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -1200,6 +1200,15 @@ struct _Descriptors { keyPath: \.preservedPrivateDeclarations ) + let alphabetizeSwiftUIPropertyTypes = OptionDescriptor( + argumentName: "sortswiftuiprops", + displayName: "Alphabetize SwiftUI Properties", + help: "Sorts SwiftUI properties alphabetically, defaults to \"false\"", + keyPath: \.alphabetizeSwiftUIPropertyTypes, + trueValues: ["enabled", "true"], + falseValues: ["disabled", "false"] + ) + // MARK: - Internal let fragment = OptionDescriptor( diff --git a/Sources/Options.swift b/Sources/Options.swift index da3bfd6a1..80acfbc38 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -673,6 +673,7 @@ public struct FormatOptions: CustomStringConvertible { public var customTypeMarks: Set public var blankLineAfterSubgroups: Bool public var alphabeticallySortedDeclarationPatterns: Set + public var alphabetizeSwiftUIPropertyTypes: Bool public var yodaSwap: YodaMode public var extensionACLPlacement: ExtensionACLPlacement public var redundantType: RedundantType @@ -798,6 +799,7 @@ public struct FormatOptions: CustomStringConvertible { customTypeMarks: Set = [], blankLineAfterSubgroups: Bool = true, alphabeticallySortedDeclarationPatterns: Set = [], + alphabetizeSwiftUIPropertyTypes: Bool = false, yodaSwap: YodaMode = .always, extensionACLPlacement: ExtensionACLPlacement = .onExtension, redundantType: RedundantType = .inferLocalsOnly, @@ -913,6 +915,7 @@ public struct FormatOptions: CustomStringConvertible { self.customTypeMarks = customTypeMarks self.blankLineAfterSubgroups = blankLineAfterSubgroups self.alphabeticallySortedDeclarationPatterns = alphabeticallySortedDeclarationPatterns + self.alphabetizeSwiftUIPropertyTypes = alphabetizeSwiftUIPropertyTypes self.yodaSwap = yodaSwap self.extensionACLPlacement = extensionACLPlacement self.redundantType = redundantType diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift index e2539fd97..eca1e5dc6 100644 --- a/Sources/Rules/OrganizeDeclarations.swift +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -19,7 +19,7 @@ public extension FormatRule { "lifecycle", "organizetypes", "structthreshold", "classthreshold", "enumthreshold", "extensionlength", "organizationmode", "visibilityorder", "typeorder", "visibilitymarks", "typemarks", - "groupblanklines", + "groupblanklines", "sortswiftuiprops", ], sharedOptions: ["sortedpatterns", "lineaftermarks"] ) { formatter in @@ -217,6 +217,14 @@ extension Formatter { return lhsName.localizedCompare(rhsName) == .orderedAscending } + if options.alphabetizeSwiftUIPropertyTypes, + lhs.category.type == rhs.category.type, + let lhsSwiftUIProperty = lhs.declaration.swiftUIPropertyWrapper, + let rhsSwiftUIProperty = rhs.declaration.swiftUIPropertyWrapper + { + return lhsSwiftUIProperty.localizedCompare(rhsSwiftUIProperty) == .orderedAscending + } + // Respect the original declaration ordering when the categories and types are the same return lhsOriginalIndex < rhsOriginalIndex }) diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index 73566eb9e..b1cf64176 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -254,6 +254,7 @@ class MetadataTests: XCTestCase { Descriptors.customVisibilityMarks, Descriptors.customTypeMarks, Descriptors.blankLineAfterSubgroups, + Descriptors.alphabetizeSwiftUIPropertyTypes, ] case .identifier("removeSelf"): referencedOptions += [ diff --git a/Tests/Rules/OrganizeDeclarationsTests.swift b/Tests/Rules/OrganizeDeclarationsTests.swift index 3eff7914e..243a51477 100644 --- a/Tests/Rules/OrganizeDeclarationsTests.swift +++ b/Tests/Rules/OrganizeDeclarationsTests.swift @@ -3005,6 +3005,114 @@ class OrganizeDeclarationsTests: XCTestCase { ) } + func testSortSwiftUIPropertyWrappersSubCategory() { + let input = """ + struct ContentView: View { + init() {} + + @Environment(\\.colorScheme) var colorScheme + @State var foo: Foo + @Binding var isOn: Bool + @Environment(\\.quux) var quux: Quux + + @ViewBuilder + var body: some View { + Toggle(label, isOn: $isOn) + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + @Binding var isOn: Bool + @Environment(\\.colorScheme) var colorScheme + @Environment(\\.quux) var quux: Quux + @State var foo: Foo + + @ViewBuilder + var body: some View { + Toggle(label, isOn: $isOn) + } + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizeTypes: ["struct"], + organizationMode: .visibility, + blankLineAfterSubgroups: false, + alphabetizeSwiftUIPropertyTypes: true + ), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testSortSwiftUIWrappersByTypeAndMaintainGroupSpacing() { + let input = """ + struct ContentView: View { + init() {} + + @State var foo: Foo + @State var bar: Bar + + @Environment(\\.colorScheme) var colorScheme + @Environment(\\.quux) var quux: Quux + + @Binding var isOn: Bool + + @ViewBuilder + var body: some View { + Toggle(label, isOn: $isOn) + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + @Binding var isOn: Bool + + @Environment(\\.colorScheme) var colorScheme + @Environment(\\.quux) var quux: Quux + + @State var foo: Foo + @State var bar: Bar + + @ViewBuilder + var body: some View { + Toggle(label, isOn: $isOn) + } + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizeTypes: ["struct"], + organizationMode: .visibility, + blankLineAfterSubgroups: false, + alphabetizeSwiftUIPropertyTypes: true + ), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + func testPreservesBlockOfConsecutivePropertiesWithoutBlankLinesBetweenSubgroups1() { let input = """ class Foo { From ce9af67b30b42550073ddee9e9fb479e2dad2129 Mon Sep 17 00:00:00 2001 From: NikeKov Date: Thu, 1 Aug 2024 19:33:07 +0300 Subject: [PATCH 35/52] [New rule] spacingGuards (#1804) --- README.md | 3 + Rules.md | 23 +++ Sources/Examples.swift | 14 ++ Sources/Formatter.swift | 23 +++ Sources/RuleRegistry.generated.swift | 1 + Sources/Rules/SpacingGuards.swift | 35 ++++ SwiftFormat.xcodeproj/project.pbxproj | 16 +- Tests/Rules/ElseOnSameLineTests.swift | 2 +- Tests/Rules/HoistPatternLetTests.swift | 2 +- Tests/Rules/IndentTests.swift | 14 +- Tests/Rules/RedundantSelfTests.swift | 23 +-- Tests/Rules/RedundantStaticSelfTests.swift | 1 + Tests/Rules/SortSwitchCasesTests.swift | 2 +- Tests/Rules/SpacingGuardsTests.swift | 184 +++++++++++++++++++ Tests/Rules/UnusedArgumentsTests.swift | 3 + Tests/Rules/WrapArgumentsTests.swift | 24 ++- Tests/Rules/WrapConditionalBodiesTests.swift | 2 +- 17 files changed, 336 insertions(+), 36 deletions(-) create mode 100644 Sources/Rules/SpacingGuards.swift create mode 100644 Tests/Rules/SpacingGuardsTests.swift diff --git a/README.md b/README.md index 072c126b4..475729ecd 100644 --- a/README.md +++ b/README.md @@ -915,6 +915,9 @@ Q. I don't want to be surprised by new rules added when I upgrade SwiftFormat. H > A. Yes, the SwiftFormat framework can be included in an app or test target, and used for many kinds of parsing and processing of Swift source code besides formatting. The SwiftFormat framework is available as a [CocoaPod](https://cocoapods.org/pods/SwiftFormat) for easy integration. +*Q. How to create own rule?* + +> A. 1) Open `SwiftFormat.xcodeproj`; 2) Add a rule in `Sources/Rules/..`; 3) Add a test in `Tests/Rules/..`; 4) Add an example in `Sources/Examples.swift`; 5) Run all tests. Known issues --------------- diff --git a/Rules.md b/Rules.md index 7e8b4dbb9..35836acf1 100644 --- a/Rules.md +++ b/Rules.md @@ -106,6 +106,7 @@ * [propertyType](#propertyType) * [redundantProperty](#redundantProperty) * [sortSwitchCases](#sortSwitchCases) +* [spacingGuards](#spacingGuards) * [unusedPrivateDeclaration](#unusedPrivateDeclaration) * [wrapConditionalBodies](#wrapConditionalBodies) * [wrapEnumCases](#wrapEnumCases) @@ -2585,6 +2586,28 @@ Remove space inside parentheses.

+## spacingGuards + +Remove space between guard and add spaces after last guard. + +
+Examples + +```diff + guard let spicy = self.makeSpicy() else { + return + } +- + guard let soap = self.clean() else { + return + } ++ + let doTheJob = nikekov() +``` + +
+
+ ## specifiers Use consistent ordering for member modifiers. diff --git a/Sources/Examples.swift b/Sources/Examples.swift index 7872fb15b..f6f11f6b0 100644 --- a/Sources/Examples.swift +++ b/Sources/Examples.swift @@ -2016,4 +2016,18 @@ private struct Examples { extension String: Equatable {} ``` """ + + let spacingGuards = """ + ```diff + guard let spicy = self.makeSpicy() else { + return + } + - + guard let soap = self.clean() else { + return + } + + + let doTheJob = nikekov() + ``` + """ } diff --git a/Sources/Formatter.swift b/Sources/Formatter.swift index 3b8da76eb..93f67d318 100644 --- a/Sources/Formatter.swift +++ b/Sources/Formatter.swift @@ -617,6 +617,29 @@ public extension Formatter { } return .linebreak(options.linebreak, lineNumber) } + + /// Formatting linebreaks + /// Setting `linebreaksCount` linebreaks in `indexes` + func leaveOrSetLinebreaksInIndexes(_ indexes: Set, linebreaksCount: Int) { + var alreadyHasLinebreaksCount = 0 + for index in indexes { + guard let token = token(at: index) else { + return + } + if token.isLinebreak { + if alreadyHasLinebreaksCount == linebreaksCount { + removeToken(at: index) + } else { + alreadyHasLinebreaksCount += 1 + } + } + } + if alreadyHasLinebreaksCount != linebreaksCount, + let firstIndex = indexes.first + { + insertLinebreak(at: firstIndex) + } + } } extension String { diff --git a/Sources/RuleRegistry.generated.swift b/Sources/RuleRegistry.generated.swift index 154271fe4..53dd4972f 100644 --- a/Sources/RuleRegistry.generated.swift +++ b/Sources/RuleRegistry.generated.swift @@ -98,6 +98,7 @@ let ruleRegistry: [String: FormatRule] = [ "spaceInsideComments": .spaceInsideComments, "spaceInsideGenerics": .spaceInsideGenerics, "spaceInsideParens": .spaceInsideParens, + "spacingGuards": .spacingGuards, "specifiers": .specifiers, "strongOutlets": .strongOutlets, "strongifiedSelf": .strongifiedSelf, diff --git a/Sources/Rules/SpacingGuards.swift b/Sources/Rules/SpacingGuards.swift new file mode 100644 index 000000000..8362bbd6b --- /dev/null +++ b/Sources/Rules/SpacingGuards.swift @@ -0,0 +1,35 @@ +// Created by @NikeKov on 01.08.2024 +// Copyright © 2024 Nick Lockwood. All rights reserved. + +import Foundation + +public extension FormatRule { + static let spacingGuards = FormatRule(help: "Remove space between guard and add spaces after last guard.", + disabledByDefault: true) + { formatter in + formatter.forEach(.keyword("guard")) { guardIndex, _ in + guard let startOfScopeOfGuard = formatter.index(of: .startOfScope("{"), after: guardIndex), + let endOfScopeOfGuard = formatter.endOfScope(at: startOfScopeOfGuard) + else { + return + } + + guard let nextNonSpaceAndNonLinebreakIndex = formatter.index(of: .nonSpaceOrLinebreak, after: endOfScopeOfGuard) else { + return + } + + let nextNonSpaceAndNonLinebreakToken = formatter.token(at: nextNonSpaceAndNonLinebreakIndex) + + if nextNonSpaceAndNonLinebreakToken == .endOfScope("}") + || nextNonSpaceAndNonLinebreakToken?.isOperator == true + { + // Do not add space in this cases + return + } + + let isGuard = nextNonSpaceAndNonLinebreakToken == .keyword("guard") + let indexesBetween = Set(endOfScopeOfGuard + 1 ..< nextNonSpaceAndNonLinebreakIndex) + formatter.leaveOrSetLinebreaksInIndexes(indexesBetween, linebreaksCount: isGuard ? 1 : 2) + } + } +} diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index dfe310843..facc311ef 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -682,6 +682,11 @@ E4FABAD6202FEF060065716E /* OptionDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FABAD4202FEF060065716E /* OptionDescriptor.swift */; }; E4FABAD7202FEF060065716E /* OptionDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FABAD4202FEF060065716E /* OptionDescriptor.swift */; }; E4FABAD8202FEF060065716E /* OptionDescriptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FABAD4202FEF060065716E /* OptionDescriptor.swift */; }; + EBA6E7022C5B7D4800CBD360 /* SpacingGuards.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA6E7012C5B7D4800CBD360 /* SpacingGuards.swift */; }; + EBA6E7032C5B7D4800CBD360 /* SpacingGuards.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA6E7012C5B7D4800CBD360 /* SpacingGuards.swift */; }; + EBA6E7042C5B7D4800CBD360 /* SpacingGuards.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA6E7012C5B7D4800CBD360 /* SpacingGuards.swift */; }; + EBA6E7052C5B7D4800CBD360 /* SpacingGuards.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA6E7012C5B7D4800CBD360 /* SpacingGuards.swift */; }; + EBA6E70B2C5B7E8400CBD360 /* SpacingGuardsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBA6E7062C5B7DC400CBD360 /* SpacingGuardsTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1026,6 +1031,8 @@ E4E4D3C82033F17C000D7CB1 /* EnumAssociable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnumAssociable.swift; sourceTree = ""; }; E4E4D3CD2033F1EF000D7CB1 /* EnumAssociableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnumAssociableTests.swift; sourceTree = ""; }; E4FABAD4202FEF060065716E /* OptionDescriptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionDescriptor.swift; sourceTree = ""; }; + EBA6E7012C5B7D4800CBD360 /* SpacingGuards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacingGuards.swift; sourceTree = ""; }; + EBA6E7062C5B7DC400CBD360 /* SpacingGuardsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacingGuardsTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1209,7 +1216,6 @@ 2E2BAB932C57F6DD00590239 /* BlankLineAfterSwitchCase.swift */, 2E2BABA42C57F6DD00590239 /* BlankLinesAroundMark.swift */, 2E2BABC62C57F6DD00590239 /* BlankLinesAtEndOfScope.swift */, - 580496D42C584E8F004B7DBF /* EmptyExtension.swift */, 2E2BABEC2C57F6DD00590239 /* BlankLinesAtStartOfScope.swift */, 2E2BABA72C57F6DD00590239 /* BlankLinesBetweenChainedFunctions.swift */, 2E2BABD02C57F6DD00590239 /* BlankLinesBetweenImports.swift */, @@ -1225,6 +1231,7 @@ 2E2BABA12C57F6DD00590239 /* DuplicateImports.swift */, 2E2BABA02C57F6DD00590239 /* ElseOnSameLine.swift */, 2E2BABAE2C57F6DD00590239 /* EmptyBraces.swift */, + 580496D42C584E8F004B7DBF /* EmptyExtension.swift */, 2E2BABD92C57F6DD00590239 /* EnumNamespaces.swift */, 2E2BABC72C57F6DD00590239 /* ExtensionAccessControl.swift */, 2E2BABC02C57F6DD00590239 /* FileHeader.swift */, @@ -1289,6 +1296,7 @@ 2E2BABB42C57F6DD00590239 /* SpaceInsideComments.swift */, 2E2BABAC2C57F6DD00590239 /* SpaceInsideGenerics.swift */, 2E2BAB9D2C57F6DD00590239 /* SpaceInsideParens.swift */, + EBA6E7012C5B7D4800CBD360 /* SpacingGuards.swift */, 2E2BABF82C57F6DD00590239 /* Specifiers.swift */, 2E2BABE72C57F6DD00590239 /* StrongifiedSelf.swift */, 2E2BABAA2C57F6DD00590239 /* StrongOutlets.swift */, @@ -1404,6 +1412,7 @@ 2E8DE6EF2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift */, 2E8DE6A52C57FEB30032BF25 /* SpaceInsideGenericsTests.swift */, 2E8DE6E82C57FEB30032BF25 /* SpaceInsideParensTests.swift */, + EBA6E7062C5B7DC400CBD360 /* SpacingGuardsTests.swift */, 2E8DE69C2C57FEB30032BF25 /* StrongifiedSelfTests.swift */, 2E8DE6B92C57FEB30032BF25 /* StrongOutletsTests.swift */, 2E8DE6952C57FEB30032BF25 /* TodosTests.swift */, @@ -1851,6 +1860,7 @@ 2E2BAD2F2C57F6DD00590239 /* WrapAttributes.swift in Sources */, 2E2BAC932C57F6DD00590239 /* DocCommentsBeforeAttributes.swift in Sources */, A3DF48252620E03600F45A5F /* JSONReporter.swift in Sources */, + EBA6E7022C5B7D4800CBD360 /* SpacingGuards.swift in Sources */, 01A0EAC11D5DB4F700A0A8E3 /* FormatRule.swift in Sources */, 2E2BAC9F2C57F6DD00590239 /* NoExplicitOwnership.swift in Sources */, 2E2BACA72C57F6DD00590239 /* WrapSingleLineComments.swift in Sources */, @@ -1928,6 +1938,7 @@ 2E8DE75C2C57FEB30032BF25 /* WrapSwitchCasesTests.swift in Sources */, 2E8DE71B2C57FEB30032BF25 /* NumberFormattingTests.swift in Sources */, 2E8DE74C2C57FEB30032BF25 /* WrapConditionalBodiesTests.swift in Sources */, + EBA6E70B2C5B7E8400CBD360 /* SpacingGuardsTests.swift in Sources */, 2E8DE7332C57FEB30032BF25 /* SpaceAroundCommentsTests.swift in Sources */, 2E8DE7342C57FEB30032BF25 /* PropertyTypeTests.swift in Sources */, 2E8DE7162C57FEB30032BF25 /* TrailingSpaceTests.swift in Sources */, @@ -2140,6 +2151,7 @@ 2E2BAD9C2C57F6DD00590239 /* Specifiers.swift in Sources */, 2E2BAD742C57F6DD00590239 /* TrailingClosures.swift in Sources */, 2E2BAC4C2C57F6DD00590239 /* BlankLinesAroundMark.swift in Sources */, + EBA6E7032C5B7D4800CBD360 /* SpacingGuards.swift in Sources */, 2E2BAC602C57F6DD00590239 /* BlockComments.swift in Sources */, 2E2BAD0C2C57F6DD00590239 /* ApplicationMain.swift in Sources */, 01ACAE06220CD914003F3CCF /* Examples.swift in Sources */, @@ -2287,6 +2299,7 @@ 2E2BACCD2C57F6DD00590239 /* IsEmpty.swift in Sources */, 2E2BAD3D2C57F6DD00590239 /* Braces.swift in Sources */, 2E2BAD712C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + EBA6E7042C5B7D4800CBD360 /* SpacingGuards.swift in Sources */, 2E2BAC9D2C57F6DD00590239 /* RedundantInit.swift in Sources */, 2E2BACB52C57F6DD00590239 /* RedundantClosure.swift in Sources */, 2E2BAD0D2C57F6DD00590239 /* ApplicationMain.swift in Sources */, @@ -2431,6 +2444,7 @@ 2E2BACCE2C57F6DD00590239 /* IsEmpty.swift in Sources */, 2E2BAD3E2C57F6DD00590239 /* Braces.swift in Sources */, 2E2BAD722C57F6DD00590239 /* OpaqueGenericParameters.swift in Sources */, + EBA6E7052C5B7D4800CBD360 /* SpacingGuards.swift in Sources */, 2E2BAC9E2C57F6DD00590239 /* RedundantInit.swift in Sources */, 2E2BACB62C57F6DD00590239 /* RedundantClosure.swift in Sources */, 2E2BAD0E2C57F6DD00590239 /* ApplicationMain.swift in Sources */, diff --git a/Tests/Rules/ElseOnSameLineTests.swift b/Tests/Rules/ElseOnSameLineTests.swift index 532c46735..89f70cefa 100644 --- a/Tests/Rules/ElseOnSameLineTests.swift +++ b/Tests/Rules/ElseOnSameLineTests.swift @@ -377,6 +377,6 @@ class ElseOnSameLineTests: XCTestCase { """ let options = FormatOptions(elseOnNextLine: false, guardElsePosition: .nextLine) - testFormatting(for: input, output, rule: .elseOnSameLine, options: options) + testFormatting(for: input, output, rule: .elseOnSameLine, options: options, exclude: [.spacingGuards]) } } diff --git a/Tests/Rules/HoistPatternLetTests.swift b/Tests/Rules/HoistPatternLetTests.swift index 9220f1a6b..2c5109b18 100644 --- a/Tests/Rules/HoistPatternLetTests.swift +++ b/Tests/Rules/HoistPatternLetTests.swift @@ -222,7 +222,7 @@ class HoistPatternLetTests: XCTestCase { """ let options = FormatOptions(hoistPatternLet: false) testFormatting(for: input, rule: .hoistPatternLet, options: options, - exclude: [.wrapConditionalBodies]) + exclude: [.wrapConditionalBodies, .spacingGuards]) } func testNoUnhoistSwitchCaseLetFollowedByWhere() { diff --git a/Tests/Rules/IndentTests.swift b/Tests/Rules/IndentTests.swift index ec01d4d07..35d73ee28 100644 --- a/Tests/Rules/IndentTests.swift +++ b/Tests/Rules/IndentTests.swift @@ -1525,7 +1525,7 @@ class IndentTests: XCTestCase { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .balanced) testFormatting(for: input, rule: .indent, options: options, - exclude: [.wrapConditionalBodies]) + exclude: [.wrapConditionalBodies, .spacingGuards]) } func testSingleIndentTrailingClosureBody2() { @@ -1540,7 +1540,7 @@ class IndentTests: XCTestCase { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: [.wrapConditionalBodies, .wrapMultilineStatementBraces]) + exclude: [.wrapConditionalBodies, .wrapMultilineStatementBraces, .spacingGuards]) } func testDoubleIndentTrailingClosureBody() { @@ -1556,7 +1556,7 @@ class IndentTests: XCTestCase { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: [.wrapConditionalBodies, .wrapMultilineStatementBraces]) + exclude: [.wrapConditionalBodies, .wrapMultilineStatementBraces, .spacingGuards]) } func testDoubleIndentTrailingClosureBody2() { @@ -1601,7 +1601,7 @@ class IndentTests: XCTestCase { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: [.braces, .wrapConditionalBodies]) + exclude: [.braces, .wrapConditionalBodies, .spacingGuards]) } func testSingleIndentTrailingClosureBodyOfShortMethod() { @@ -1613,7 +1613,7 @@ class IndentTests: XCTestCase { """ let options = FormatOptions(wrapArguments: .disabled, closingParenPosition: .sameLine) testFormatting(for: input, rule: .indent, options: options, - exclude: [.wrapConditionalBodies]) + exclude: [.wrapConditionalBodies, .spacingGuards]) } func testNoDoubleIndentInInsideClosure() { @@ -1975,7 +1975,7 @@ class IndentTests: XCTestCase { """ let options = FormatOptions(xcodeIndentation: true) testFormatting(for: input, output, rule: .indent, - options: options, exclude: [.wrapConditionalBodies]) + options: options, exclude: [.wrapConditionalBodies, .spacingGuards]) } func testWrappedChainedFunctionsWithNestedScopeIndent() { @@ -3717,7 +3717,7 @@ class IndentTests: XCTestCase { """ let options = FormatOptions(indent: "\t", truncateBlankLines: false, tabWidth: 2) testFormatting(for: input, rule: .indent, options: options, - exclude: [.consecutiveBlankLines, .wrapConditionalBodies]) + exclude: [.consecutiveBlankLines, .wrapConditionalBodies, .spacingGuards]) } // async diff --git a/Tests/Rules/RedundantSelfTests.swift b/Tests/Rules/RedundantSelfTests.swift index 9f97447c8..ae947a154 100644 --- a/Tests/Rules/RedundantSelfTests.swift +++ b/Tests/Rules/RedundantSelfTests.swift @@ -340,7 +340,7 @@ class RedundantSelfTests: XCTestCase { func testNoRemoveSelfForVarCreatedInGuardScope() { let input = "func foo() {\n guard let bar = 5 else {}\n let baz = self.bar\n}" testFormatting(for: input, rule: .redundantSelf, - exclude: [.wrapConditionalBodies]) + exclude: [.wrapConditionalBodies, .spacingGuards]) } func testRemoveSelfForVarCreatedInIfScope() { @@ -1035,7 +1035,7 @@ class RedundantSelfTests: XCTestCase { """ let options = FormatOptions(swiftVersion: "5.8") testFormatting(for: input, output, rule: .redundantSelf, - options: options, exclude: [.redundantOptionalBinding]) + options: options, exclude: [.redundantOptionalBinding, .spacingGuards]) } func testWeakSelfNotRemovedIfNotUnwrapped() { @@ -1124,7 +1124,7 @@ class RedundantSelfTests: XCTestCase { } """ let options = FormatOptions(swiftVersion: "5.7") - testFormatting(for: input, rule: .redundantSelf, options: options) + testFormatting(for: input, rule: .redundantSelf, options: options, exclude: [.spacingGuards]) } func testNonRedundantSelfNotRemovedAfterConditionalLet() { @@ -1199,7 +1199,7 @@ class RedundantSelfTests: XCTestCase { self.bar() } """ - testFormatting(for: input, rule: .redundantSelf) + testFormatting(for: input, rule: .redundantSelf, exclude: [.spacingGuards]) } func testNoRemoveSelfInClosureInIfCondition() { @@ -1343,7 +1343,7 @@ class RedundantSelfTests: XCTestCase { }) {} """ testFormatting(for: input, rule: .redundantSelf, - exclude: [.wrapConditionalBodies]) + exclude: [.wrapConditionalBodies, .spacingGuards]) } func testStructSelfRemovedInTrailingClosureInIfCase() { @@ -1488,7 +1488,7 @@ class RedundantSelfTests: XCTestCase { } """ testFormatting(for: input, rule: .redundantSelf, - exclude: [.wrapConditionalBodies]) + exclude: [.wrapConditionalBodies, .spacingGuards]) } func testNoRemoveSelfInAssignmentInsideIfAsStatement() { @@ -1536,7 +1536,7 @@ class RedundantSelfTests: XCTestCase { } """ testFormatting(for: input, output, rule: .redundantSelf, - exclude: [.hoistPatternLet]) + exclude: [.hoistPatternLet, .spacingGuards]) } func testRedundantSelfParsingBug2() { @@ -1742,7 +1742,7 @@ class RedundantSelfTests: XCTestCase { } """ let options = FormatOptions(swiftVersion: "5.0") - testFormatting(for: input, output, rule: .redundantSelf, options: options) + testFormatting(for: input, output, rule: .redundantSelf, options: options, exclude: [.spacingGuards]) } func testShadowedSelfRemovedInGuardLet() { @@ -1762,7 +1762,7 @@ class RedundantSelfTests: XCTestCase { print(optional) } """ - testFormatting(for: input, output, rule: .redundantSelf) + testFormatting(for: input, output, rule: .redundantSelf, exclude: [.spacingGuards]) } func testShadowedStringValueNotRemovedInInit() { @@ -2799,7 +2799,7 @@ class RedundantSelfTests: XCTestCase { } } """ - testFormatting(for: input, rule: .redundantSelf) + testFormatting(for: input, rule: .redundantSelf, exclude: [.spacingGuards]) } func testSelfRemovalParsingBug2() { @@ -2889,7 +2889,7 @@ class RedundantSelfTests: XCTestCase { } } """ - testFormatting(for: input, rule: .redundantSelf) + testFormatting(for: input, rule: .redundantSelf, exclude: [.spacingGuards]) } func testSelfNotRemovedInCaseIfElse() { @@ -2980,6 +2980,7 @@ class RedundantSelfTests: XCTestCase { print(self.bar) return } + print(self.bar) } } diff --git a/Tests/Rules/RedundantStaticSelfTests.swift b/Tests/Rules/RedundantStaticSelfTests.swift index 67a960959..7153afec4 100644 --- a/Tests/Rules/RedundantStaticSelfTests.swift +++ b/Tests/Rules/RedundantStaticSelfTests.swift @@ -210,6 +210,7 @@ class RedundantStaticSelfTests: XCTestCase { guard let value = Self.location(for: warnRegion) else { return nil } + self.init(location: value) } } diff --git a/Tests/Rules/SortSwitchCasesTests.swift b/Tests/Rules/SortSwitchCasesTests.swift index 9459ba2c3..f851c41cf 100644 --- a/Tests/Rules/SortSwitchCasesTests.swift +++ b/Tests/Rules/SortSwitchCasesTests.swift @@ -30,7 +30,7 @@ class SortSwitchCasesTests: XCTestCase { } """ - testFormatting(for: input, rule: .sortSwitchCases, exclude: [.redundantSelf]) + testFormatting(for: input, rule: .sortSwitchCases, exclude: [.redundantSelf, .spacingGuards]) } func testSortedSwitchCaseMultilineWithOneComment() { diff --git a/Tests/Rules/SpacingGuardsTests.swift b/Tests/Rules/SpacingGuardsTests.swift new file mode 100644 index 000000000..d7c873208 --- /dev/null +++ b/Tests/Rules/SpacingGuardsTests.swift @@ -0,0 +1,184 @@ +// SpacingGuardsTests.swift +// SpacingGuardsTests +// +// Created by @NikeKov on 01.08.2024 +// Copyright © 2024 Nick Lockwood. All rights reserved. + +import XCTest +@testable import SwiftFormat + +final class GuardSpacingTests: XCTestCase { + func testSpacesBetweenGuard() { + let input = """ + guard let one = test.one else { + return + } + guard let two = test.two else { + return + } + + guard let three = test.three else { + return + } + + + guard let four = test.four else { + return + } + + + + + guard let five = test.five else { + return + } + """ + let output = """ + guard let one = test.one else { + return + } + guard let two = test.two else { + return + } + guard let three = test.three else { + return + } + guard let four = test.four else { + return + } + guard let five = test.five else { + return + } + """ + + testFormatting(for: input, output, rule: .spacingGuards) + } + + func testLinebreakAfterGuard() { + let input = """ + guard let one = test.one else { + return + } + let x = test() + """ + let output = """ + guard let one = test.one else { + return + } + + let x = test() + """ + + testFormatting(for: input, output, rule: .spacingGuards) + } + + func testIncludedGuard() { + let input = """ + guard let one = test.one else { + guard let two = test.two() else { + return + } + return + } + + guard let three = test.three() else { + return + } + """ + let output = """ + guard let one = test.one else { + guard let two = test.two() else { + return + } + + return + } + guard let three = test.three() else { + return + } + """ + + testFormatting(for: input, output, rule: .spacingGuards) + } + + func testEndBracketAndIf() { + let input = """ + guard let something = test.something else { + return + } + if someone == someoneElse { + guard let nextTime else { + return + } + } + """ + let output = """ + guard let something = test.something else { + return + } + + if someone == someoneElse { + guard let nextTime else { + return + } + } + """ + + testFormatting(for: input, output, rule: .spacingGuards) + } + + func testComments() { + let input = """ + guard let somethingTwo = test.somethingTwo else { + return + } + // commentOne + guard let somethingOne = test.somethingOne else { + return + } + // commentTwo + let something = xxx + """ + + let output = """ + guard let somethingTwo = test.somethingTwo else { + return + } + + // commentOne + guard let somethingOne = test.somethingOne else { + return + } + + // commentTwo + let something = xxx + """ + + testFormatting(for: input, output, rule: .spacingGuards, exclude: [.docComments]) + } + + func testNotInsertLineBreakWhenInlineFunction() { + let input = """ + let array = [1, 2, 3] + guard array.map { String($0) }.isEmpty else { + return + } + """ + testFormatting(for: input, rule: .spacingGuards, exclude: [.wrapConditionalBodies]) + } + + func testNotInsertLineBreakInChain() { + let input = """ + guard aBool, + anotherBool, + aTestArray + .map { $0 * 2 } + .filter { $0 == 4 } + .isEmpty, + yetAnotherBool + else { return } + """ + + testFormatting(for: input, rule: .spacingGuards, exclude: [.wrapConditionalBodies]) + } +} diff --git a/Tests/Rules/UnusedArgumentsTests.swift b/Tests/Rules/UnusedArgumentsTests.swift index 170623d83..16ce3e1e8 100644 --- a/Tests/Rules/UnusedArgumentsTests.swift +++ b/Tests/Rules/UnusedArgumentsTests.swift @@ -529,6 +529,7 @@ class UnusedArgumentsTests: XCTestCase { guard num > 0, locations.count >= count else { return } + print(locations) } """ @@ -948,6 +949,7 @@ class UnusedArgumentsTests: XCTestCase { else { return nil } + return History(firstParameter, secondParameter) } """ @@ -1096,6 +1098,7 @@ class UnusedArgumentsTests: XCTestCase { guard let update, error == nil else { return } + self?.configure(update) } """ diff --git a/Tests/Rules/WrapArgumentsTests.swift b/Tests/Rules/WrapArgumentsTests.swift index 3fe31d58b..1b1babd59 100644 --- a/Tests/Rules/WrapArgumentsTests.swift +++ b/Tests/Rules/WrapArgumentsTests.swift @@ -2070,7 +2070,7 @@ class WrapArgumentsTests: XCTestCase { testFormatting( for: input, rule: .wrapArguments, options: FormatOptions(indent: " ", wrapConditions: .beforeFirst), - exclude: [.elseOnSameLine, .wrapConditionalBodies] + exclude: [.elseOnSameLine, .wrapConditionalBodies, .spacingGuards] ) } @@ -2246,12 +2246,11 @@ class WrapArgumentsTests: XCTestCase { else {} """ - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) - ) + testFormatting(for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40), + exclude: [.spacingGuards]) } func testConditionsWrapAutoOptionForGuardWhenElseOnNewLine() { @@ -2297,12 +2296,11 @@ class WrapArgumentsTests: XCTestCase { else {} """ - testFormatting( - for: input, - [output], - rules: [.wrapArguments], - options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40) - ) + testFormatting(for: input, + [output], + rules: [.wrapArguments], + options: FormatOptions(indent: " ", conditionsWrap: .auto, maxWidth: 40), + exclude: [.spacingGuards]) } func testConditionsWrapAutoOptionForGuardInMethod() { diff --git a/Tests/Rules/WrapConditionalBodiesTests.swift b/Tests/Rules/WrapConditionalBodiesTests.swift index 3e761e73c..715807de8 100644 --- a/Tests/Rules/WrapConditionalBodiesTests.swift +++ b/Tests/Rules/WrapConditionalBodiesTests.swift @@ -157,7 +157,7 @@ class WrapConditionalBodiesTests: XCTestCase { return true } """ - testFormatting(for: input, output, rule: .wrapConditionalBodies) + testFormatting(for: input, output, rule: .wrapConditionalBodies, exclude: [.spacingGuards]) } func testIfElseReturnsWrap() { From c01d68e8be3064dd665fc0ed47812e7810b4418f Mon Sep 17 00:00:00 2001 From: Manny Lopez Date: Thu, 1 Aug 2024 15:43:34 -0700 Subject: [PATCH 36/52] Add test case to unusedPrivateDeclaration (#1805) --- Sources/Rules/EmptyExtension.swift | 2 +- .../Rules/UnusedPrivateDeclarationTests.swift | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Sources/Rules/EmptyExtension.swift b/Sources/Rules/EmptyExtension.swift index f39d14d2e..bd5cc79fd 100644 --- a/Sources/Rules/EmptyExtension.swift +++ b/Sources/Rules/EmptyExtension.swift @@ -2,7 +2,7 @@ // EmptyExtension.swift // SwiftFormat // -// Created by manny_lopez on 7/29/24. +// Created by manny_lopez on 7/29/24. // Copyright © 2024 Nick Lockwood. All rights reserved. // diff --git a/Tests/Rules/UnusedPrivateDeclarationTests.swift b/Tests/Rules/UnusedPrivateDeclarationTests.swift index 13851c40f..b98734fa5 100644 --- a/Tests/Rules/UnusedPrivateDeclarationTests.swift +++ b/Tests/Rules/UnusedPrivateDeclarationTests.swift @@ -352,4 +352,29 @@ class UnusedPrivateDeclarationTests: XCTestCase { """ testFormatting(for: input, rule: .unusedPrivateDeclaration) } + + func testRemoveUnusedRecursivePrivateDeclaration() { + let input = """ + struct Planet { + private typealias Dependencies = UniverseBuilderProviding // unused + private var mass: Double // unused + private func distance(to: Planet) { } // unused + private func gravitationalForce(between other: Planet) -> Double { + (G * mass * other.mass) / distance(to: other).squared() + } // unused + + var ageInBillionYears: Double { + ageInMillionYears / 1000 + } + } + """ + let output = """ + struct Planet { + var ageInBillionYears: Double { + ageInMillionYears / 1000 + } + } + """ + testFormatting(for: input, output, rule: .unusedPrivateDeclaration) + } } From 651b4d0a3ac6398b18b6082aa155ca6302c8c8af Mon Sep 17 00:00:00 2001 From: NikeKov Date: Fri, 2 Aug 2024 16:31:21 +0300 Subject: [PATCH 37/52] Updated readme about how to test own rule. (#1807) Co-authored-by: Cal Stephens --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 475729ecd..595fe42cc 100644 --- a/README.md +++ b/README.md @@ -919,6 +919,10 @@ Q. I don't want to be surprised by new rules added when I upgrade SwiftFormat. H > A. 1) Open `SwiftFormat.xcodeproj`; 2) Add a rule in `Sources/Rules/..`; 3) Add a test in `Tests/Rules/..`; 4) Add an example in `Sources/Examples.swift`; 5) Run all tests. +*Q. How do I run and debug the command line tool in Xcode while developing a new rule?* + +> A. You can run the `swiftformat` command line tool via the `Swift Format (Command Line Tool)` scheme, and you can pass in arguments like `/path/to/my/code --config /path/to/my/config` as the `Arguments Passed On Launch` in Xcode's scheme editor. More instructions are available [here](https://github.com/nicklockwood/SwiftFormat/pull/1804#issuecomment-2263079432). + Known issues --------------- From e7bb48761408081501d89350c9173f38e8344990 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sat, 3 Aug 2024 08:23:09 -0700 Subject: [PATCH 38/52] Update `MetadataTests.testRulesOptions()` to check entire rule file, remove some special cases (#1799) --- Sources/ParsingHelpers.swift | 295 ----------------------- Sources/Rules/BlockComments.swift | 73 +++--- Sources/Rules/DocComments.swift | 126 +++++----- Sources/Rules/ElseOnSameLine.swift | 19 +- Sources/Rules/HoistPatternLet.swift | 74 +++--- Sources/Rules/Indent.swift | 214 ++++++++-------- Sources/Rules/ModifierOrder.swift | 19 ++ Sources/Rules/NumberFormatting.swift | 55 +++-- Sources/Rules/RedundantFileprivate.swift | 237 +++++++++--------- Sources/Rules/RedundantNilInit.swift | 60 ++--- Sources/Rules/RedundantObjc.swift | 25 +- Sources/Rules/RedundantParens.swift | 30 +-- Sources/Rules/RedundantPattern.swift | 34 +-- Sources/Rules/RedundantReturn.swift | 105 ++++---- Sources/Rules/RedundantType.swift | 136 ++++++----- Sources/Rules/SortImports.swift | 38 +-- Sources/Rules/SortSwitchCases.swift | 117 +++++++-- Sources/Rules/SpaceAroundParens.swift | 64 ++--- Sources/Rules/UnusedArguments.swift | 273 ++++++++++----------- Sources/Rules/Wrap.swift | 120 +++++++++ Sources/Rules/WrapAttributes.swift | 34 +++ Sources/Rules/WrapEnumCases.swift | 79 ++++-- Sources/Rules/YodaConditions.swift | 207 ++++++++-------- SwiftFormat.xcodeproj/project.pbxproj | 4 +- Tests/MetadataTests.swift | 50 +--- 25 files changed, 1261 insertions(+), 1227 deletions(-) diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index f0261d3e3..0b4fe9889 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -928,38 +928,6 @@ extension Formatter { } } - /// Whether or not the attribute starting at the given index is complex. That is, has: - /// - any named arguments - /// - more than one unnamed argument - func isComplexAttribute(at attributeIndex: Int) -> Bool { - assert(tokens[attributeIndex].string.hasPrefix("@")) - - guard let startOfScopeIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: attributeIndex), - tokens[startOfScopeIndex] == .startOfScope("("), - let firstTokenInBody = index(of: .nonSpaceOrCommentOrLinebreak, after: startOfScopeIndex), - let endOfScopeIndex = endOfScope(at: startOfScopeIndex), - firstTokenInBody != endOfScopeIndex - else { return false } - - // If the first argument is named with a parameter label, then this is a complex attribute: - if tokens[firstTokenInBody].isIdentifierOrKeyword, - let followingToken = index(of: .nonSpaceOrCommentOrLinebreak, after: firstTokenInBody), - tokens[followingToken] == .delimiter(":") - { - return true - } - - // If there are any commas in the attribute body, then this attribute has - // multiple arguments and is thus complex: - for index in startOfScopeIndex ... endOfScopeIndex { - if tokens[index] == .delimiter(","), startOfScope(at: index) == startOfScopeIndex { - return true - } - } - - return false - } - /// Determine if next line after this token should be indented func isEndOfStatement(at i: Int, in scope: Token? = nil) -> Bool { guard let token = token(at: i) else { return true } @@ -2085,141 +2053,6 @@ extension Formatter { declarationIndexAndScope(at: i).scope } - /// Swift modifier keywords, in preferred order - var modifierOrder: [String] { - var priorities = [String: Int]() - for (i, modifiers) in _FormatRules.defaultModifierOrder.enumerated() { - for modifier in modifiers { - priorities[modifier] = i - } - } - var order = options.modifierOrder.flatMap { _FormatRules.mapModifiers($0) ?? [] } - for (i, modifiers) in _FormatRules.defaultModifierOrder.enumerated() { - let insertionPoint = order.firstIndex(where: { modifiers.contains($0) }) ?? - order.firstIndex(where: { (priorities[$0] ?? 0) > i }) ?? order.count - order.insert(contentsOf: modifiers.filter { !order.contains($0) }, at: insertionPoint) - } - return order - } - - /// Returns the index where the `wrap` rule should add the next linebreak in the line at the selected index. - /// - /// If the line does not need to be wrapped, this will return `nil`. - /// - /// - Note: This checks the entire line from the start of the line, the linebreak may be an index preceding the - /// `index` passed to the function. - func indexWhereLineShouldWrapInLine(at index: Int) -> Int? { - indexWhereLineShouldWrap(from: startOfLine(at: index, excludingIndent: true)) - } - - func indexWhereLineShouldWrap(from index: Int) -> Int? { - var lineLength = self.lineLength(upTo: index) - var stringLiteralDepth = 0 - var currentPriority = 0 - var lastBreakPoint: Int? - var lastBreakPointPriority = Int.min - - let maxWidth = options.maxWidth - guard maxWidth > 0 else { return nil } - - func addBreakPoint(at i: Int, relativePriority: Int) { - guard stringLiteralDepth == 0, currentPriority + relativePriority >= lastBreakPointPriority, - !isInClosureArguments(at: i + 1) - else { - return - } - let i = self.index(of: .nonSpace, before: i + 1) ?? i - if token(at: i + 1)?.isLinebreak == true || token(at: i)?.isLinebreak == true { - return - } - lastBreakPoint = i - lastBreakPointPriority = currentPriority + relativePriority - } - - var i = index - let endIndex = endOfLine(at: index) - while i < endIndex { - var token = tokens[i] - switch token { - case .linebreak: - return nil - case .keyword("#colorLiteral"), .keyword("#imageLiteral"): - guard let startIndex = self.index(of: .startOfScope("("), after: i), - let endIndex = endOfScope(at: startIndex) - else { - return nil // error - } - token = .space(spaceEquivalentToTokens(from: i, upTo: endIndex + 1)) // hack to get correct length - i = endIndex - case let .delimiter(string) where options.noWrapOperators.contains(string), - let .operator(string, .infix) where options.noWrapOperators.contains(string): - // TODO: handle as/is - break - case .delimiter(","): - addBreakPoint(at: i, relativePriority: 0) - case .operator("=", .infix) where self.token(at: i + 1)?.isSpace == true: - addBreakPoint(at: i, relativePriority: -9) - case .operator(".", .infix): - addBreakPoint(at: i - 1, relativePriority: -2) - case .operator("->", .infix): - if isInReturnType(at: i) { - currentPriority -= 5 - } - addBreakPoint(at: i - 1, relativePriority: -5) - case .operator(_, .infix) where self.token(at: i + 1)?.isSpace == true: - addBreakPoint(at: i, relativePriority: -3) - case .startOfScope("{"): - if !isStartOfClosure(at: i) || - next(.keyword, after: i) != .keyword("in"), - next(.nonSpace, after: i) != .endOfScope("}") - { - addBreakPoint(at: i, relativePriority: -6) - } - if isInReturnType(at: i) { - currentPriority += 5 - } - currentPriority -= 6 - case .endOfScope("}"): - currentPriority += 6 - if last(.nonSpace, before: i) != .startOfScope("{") { - addBreakPoint(at: i - 1, relativePriority: -6) - } - case .startOfScope("("): - currentPriority -= 7 - case .endOfScope(")"): - currentPriority += 7 - case .startOfScope("["): - currentPriority -= 8 - case .endOfScope("]"): - currentPriority += 8 - case .startOfScope("<"): - currentPriority -= 9 - case .endOfScope(">"): - currentPriority += 9 - case .startOfScope where token.isStringDelimiter: - stringLiteralDepth += 1 - case .endOfScope where token.isStringDelimiter: - stringLiteralDepth -= 1 - case .keyword("else"), .keyword("where"): - addBreakPoint(at: i - 1, relativePriority: -1) - case .keyword("in"): - if last(.keyword, before: i) == .keyword("for") { - addBreakPoint(at: i, relativePriority: -11) - break - } - addBreakPoint(at: i, relativePriority: -5 - currentPriority) - default: - break - } - lineLength += tokenLength(token) - if lineLength > maxWidth, let breakPoint = lastBreakPoint, breakPoint < i { - return breakPoint - } - i += 1 - } - return nil - } - /// Indent level to use for wrapped lines at the specified position (based on statement type) func linewrapIndent(at index: Int) -> String { guard let commaIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, before: index + 1, if: { @@ -2261,23 +2094,6 @@ extension Formatter { return spaceEquivalentToTokens(from: firstToken, upTo: nextTokenIndex) } - /// Returns the equivalent type token for a given value token - func typeToken(forValueToken token: Token) -> Token { - switch token { - case let .number(_, type): - switch type { - case .decimal: - return .identifier("Double") - default: - return .identifier("Int") - } - case let .identifier(name): - return ["true", "false"].contains(name) ? .identifier("Bool") : .identifier(name) - case let token: - return token.isStringDelimiter ? .identifier("String") : token - } - } - /// Returns end of last index of Void type declaration starting at specified index, or nil if not Void func endOfVoidType(at index: Int) -> Int? { switch tokens[index] { @@ -2405,117 +2221,6 @@ extension Formatter { return start ..< lastHeaderTokenIndex + 1 } - struct SwitchCaseRange { - let beforeDelimiterRange: Range - let delimiterToken: Token - let afterDelimiterRange: Range - } - - func parseSwitchCaseRanges() -> [[SwitchCaseRange]] { - var result: [[SwitchCaseRange]] = [] - - forEach(.endOfScope("case")) { i, _ in - var switchCaseRanges: [SwitchCaseRange] = [] - guard let lastDelimiterIndex = index(of: .startOfScope(":"), after: i), - let endIndex = index(after: lastDelimiterIndex, where: { $0.isLinebreak }) else { return } - - var idx = i - while idx < endIndex, - let startOfCaseIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: idx), - let delimiterIndex = index(after: idx, where: { - $0 == .delimiter(",") || $0 == .startOfScope(":") - }), - let delimiterToken = token(at: delimiterIndex), - let endOfCaseIndex = lastIndex( - of: .nonSpaceOrCommentOrLinebreak, - in: startOfCaseIndex ..< delimiterIndex - ) - { - let afterDelimiterRange: Range - - let startOfCommentIdx = delimiterIndex + 1 - if startOfCommentIdx <= endIndex, - token(at: startOfCommentIdx)?.isSpaceOrCommentOrLinebreak == true, - let nextNonSpaceOrComment = index(of: .nonSpaceOrComment, after: startOfCommentIdx) - { - if token(at: startOfCommentIdx)?.isLinebreak == true - || token(at: nextNonSpaceOrComment)?.isSpaceOrCommentOrLinebreak == false - { - afterDelimiterRange = startOfCommentIdx ..< (startOfCommentIdx + 1) - } else if endIndex > startOfCommentIdx { - afterDelimiterRange = startOfCommentIdx ..< (nextNonSpaceOrComment + 1) - } else { - afterDelimiterRange = endIndex ..< (endIndex + 1) - } - } else { - afterDelimiterRange = 0 ..< 0 - } - - let switchCaseRange = SwitchCaseRange( - beforeDelimiterRange: Range(startOfCaseIndex ... endOfCaseIndex), - delimiterToken: delimiterToken, - afterDelimiterRange: afterDelimiterRange - ) - - switchCaseRanges.append(switchCaseRange) - - if afterDelimiterRange.isEmpty { - idx = delimiterIndex - } else if afterDelimiterRange.count > 1 { - idx = afterDelimiterRange.upperBound - } else { - idx = afterDelimiterRange.lowerBound - } - } - result.append(switchCaseRanges) - } - return result - } - - struct EnumCaseRange: Comparable { - let value: Range - let endOfCaseRangeToken: Token - - static func < (lhs: Formatter.EnumCaseRange, rhs: Formatter.EnumCaseRange) -> Bool { - lhs.value.lowerBound < rhs.value.lowerBound - } - } - - func parseEnumCaseRanges() -> [[EnumCaseRange]] { - var indexedRanges: [Int: [EnumCaseRange]] = [:] - - forEach(.keyword("case")) { i, _ in - guard isEnumCase(at: i) else { return } - - var idx = i - while let starOfCaseRangeIdx = index(of: .identifier, after: idx), - lastSignificantKeyword(at: starOfCaseRangeIdx) == "case", - let lastCaseIndex = lastIndex(of: .keyword("case"), in: i ..< starOfCaseRangeIdx), - lastCaseIndex == i, - let endOfCaseRangeIdx = index( - after: starOfCaseRangeIdx, - where: { $0 == .delimiter(",") || $0.isLinebreak } - ), - let endOfCaseRangeToken = token(at: endOfCaseRangeIdx) - { - let startOfScopeIdx = index(of: .startOfScope, before: starOfCaseRangeIdx) ?? 0 - - var indexedCase = indexedRanges[startOfScopeIdx, default: []] - indexedCase.append( - EnumCaseRange( - value: starOfCaseRangeIdx ..< endOfCaseRangeIdx, - endOfCaseRangeToken: endOfCaseRangeToken - ) - ) - indexedRanges[startOfScopeIdx] = indexedCase - - idx = endOfCaseRangeIdx - } - } - - return Array(indexedRanges.values) - } - /// Parses the prorocol composition typealias declaration starting at the given `typealias` keyword index. /// Returns `nil` if the given index isn't a protocol composition typealias. func parseProtocolCompositionTypealias(at typealiasIndex: Int) diff --git a/Sources/Rules/BlockComments.swift b/Sources/Rules/BlockComments.swift index c35bb8799..90682c1a0 100644 --- a/Sources/Rules/BlockComments.swift +++ b/Sources/Rules/BlockComments.swift @@ -33,37 +33,6 @@ public extension FormatRule { var isDocComment = false var stripLeadingStars = true - func replaceCommentBody(at index: Int) -> Int { - var delta = 0 - var space = "" - if case let .space(s) = formatter.tokens[index] { - formatter.removeToken(at: index) - space = s - delta -= 1 - } - if case let .commentBody(body)? = formatter.token(at: index) { - var body = Substring(body) - if stripLeadingStars { - if body.hasPrefix("*") { - body = body.drop(while: { $0 == "*" }) - } else { - stripLeadingStars = false - } - } - let prefix = isDocComment ? "/" : "" - if !prefix.isEmpty || !body.isEmpty, !body.hasPrefix(" ") { - space += " " - } - formatter.replaceToken( - at: index, - with: .commentBody(prefix + space + body) - ) - } else if isDocComment { - formatter.insert(.commentBody("/"), at: index) - delta += 1 - } - return delta - } // Replace opening delimiter var startIndex = i @@ -83,7 +52,7 @@ public extension FormatRule { formatter.removeTokens(in: range) endIndex -= range.count startIndex = i + 1 - endIndex += replaceCommentBody(at: startIndex) + endIndex += formatter.replaceCommentBody(at: startIndex, isDocComment: isDocComment, stripLeadingStars: &stripLeadingStars) } // Replace ending delimiter @@ -123,7 +92,7 @@ public extension FormatRule { } index = i formatter.insert(.startOfScope("//"), at: index) - var delta = 1 + replaceCommentBody(at: index + 1) + var delta = 1 + formatter.replaceCommentBody(at: index + 1, isDocComment: isDocComment, stripLeadingStars: &stripLeadingStars) index += delta endIndex += delta default: @@ -136,3 +105,41 @@ public extension FormatRule { } } } + +extension Formatter { + func replaceCommentBody( + at index: Int, + isDocComment: Bool, + stripLeadingStars: inout Bool + ) -> Int { + var delta = 0 + var space = "" + if case let .space(s) = tokens[index] { + removeToken(at: index) + space = s + delta -= 1 + } + if case let .commentBody(body)? = token(at: index) { + var body = Substring(body) + if stripLeadingStars { + if body.hasPrefix("*") { + body = body.drop(while: { $0 == "*" }) + } else { + stripLeadingStars = false + } + } + let prefix = isDocComment ? "/" : "" + if !prefix.isEmpty || !body.isEmpty, !body.hasPrefix(" ") { + space += " " + } + replaceToken( + at: index, + with: .commentBody(prefix + space + body) + ) + } else if isDocComment { + insert(.commentBody("/"), at: index) + delta += 1 + } + return delta + } +} diff --git a/Sources/Rules/DocComments.swift b/Sources/Rules/DocComments.swift index 408d51809..e451a0df6 100644 --- a/Sources/Rules/DocComments.swift +++ b/Sources/Rules/DocComments.swift @@ -17,65 +17,8 @@ public extension FormatRule { ) { formatter in formatter.forEach(.startOfScope) { index, token in guard [.startOfScope("//"), .startOfScope("/*")].contains(token), - let endOfComment = formatter.endOfScope(at: index), - let nextDeclarationIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfComment) - else { - return - } - - func shouldBeDocComment(at index: Int, endOfComment: Int) -> Bool { - // Check if this is a special type of comment that isn't documentation - if case let .commentBody(body)? = formatter.next(.nonSpace, after: index), body.isCommentDirective { - return false - } - - // Check if this token defines a declaration that supports doc comments - var declarationToken = formatter.tokens[nextDeclarationIndex] - if declarationToken.isAttribute || declarationToken.isModifierKeyword, - let index = formatter.index(after: nextDeclarationIndex, where: { $0.isDeclarationTypeKeyword }) - { - declarationToken = formatter.tokens[index] - } - guard declarationToken.isDeclarationTypeKeyword(excluding: ["import"]) else { - return false - } - - // Only use doc comments on declarations in type bodies, or top-level declarations - if let startOfEnclosingScope = formatter.index(of: .startOfScope, before: index) { - switch formatter.tokens[startOfEnclosingScope] { - case .startOfScope("#if"): - break - case .startOfScope("{"): - guard let scope = formatter.lastSignificantKeyword(at: startOfEnclosingScope, excluding: ["where"]), - ["class", "actor", "struct", "enum", "protocol", "extension"].contains(scope) - else { - return false - } - default: - return false - } - } - - // If there are blank lines between comment and declaration, comment is not treated as doc comment - let trailingTokens = formatter.tokens[(endOfComment - 1) ... nextDeclarationIndex] - let lines = trailingTokens.split(omittingEmptySubsequences: false, whereSeparator: \.isLinebreak) - if lines.contains(where: { $0.allSatisfy(\.isSpace) }) { - return false - } - - // Only comments at the start of a line can be doc comments - if let previousToken = formatter.index(of: .nonSpaceOrLinebreak, before: index) { - let commentLine = formatter.startOfLine(at: index) - let previousTokenLine = formatter.startOfLine(at: previousToken) - - if commentLine == previousTokenLine { - return false - } - } - - // Comments inside conditional statements are not doc comments - return !formatter.isConditionalStatement(at: index) - } + let endOfComment = formatter.endOfScope(at: index) + else { return } var commentIndices = [index] if token == .startOfScope("//") { @@ -97,9 +40,9 @@ public extension FormatRule { } } - let useDocComment = shouldBeDocComment(at: index, endOfComment: endOfComment) + let useDocComment = formatter.shouldBeDocComment(at: index, endOfComment: endOfComment) guard commentIndices.allSatisfy({ - shouldBeDocComment(at: $0, endOfComment: endOfComment) == useDocComment + formatter.shouldBeDocComment(at: $0, endOfComment: endOfComment) == useDocComment }) else { return } @@ -175,3 +118,64 @@ public extension FormatRule { } } } + +extension Formatter { + func shouldBeDocComment( + at index: Int, + endOfComment: Int + ) -> Bool { + guard let nextDeclarationIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, after: endOfComment) else { return false } + + // Check if this is a special type of comment that isn't documentation + if case let .commentBody(body)? = next(.nonSpace, after: index), body.isCommentDirective { + return false + } + + // Check if this token defines a declaration that supports doc comments + var declarationToken = tokens[nextDeclarationIndex] + if declarationToken.isAttribute || declarationToken.isModifierKeyword, + let index = self.index(after: nextDeclarationIndex, where: { $0.isDeclarationTypeKeyword }) + { + declarationToken = tokens[index] + } + guard declarationToken.isDeclarationTypeKeyword(excluding: ["import"]) else { + return false + } + + // Only use doc comments on declarations in type bodies, or top-level declarations + if let startOfEnclosingScope = self.index(of: .startOfScope, before: index) { + switch tokens[startOfEnclosingScope] { + case .startOfScope("#if"): + break + case .startOfScope("{"): + guard let scope = lastSignificantKeyword(at: startOfEnclosingScope, excluding: ["where"]), + ["class", "actor", "struct", "enum", "protocol", "extension"].contains(scope) + else { + return false + } + default: + return false + } + } + + // If there are blank lines between comment and declaration, comment is not treated as doc comment + let trailingTokens = tokens[(endOfComment - 1) ... nextDeclarationIndex] + let lines = trailingTokens.split(omittingEmptySubsequences: false, whereSeparator: \.isLinebreak) + if lines.contains(where: { $0.allSatisfy(\.isSpace) }) { + return false + } + + // Only comments at the start of a line can be doc comments + if let previousToken = self.index(of: .nonSpaceOrLinebreak, before: index) { + let commentLine = startOfLine(at: index) + let previousTokenLine = startOfLine(at: previousToken) + + if commentLine == previousTokenLine { + return false + } + } + + // Comments inside conditional statements are not doc comments + return !isConditionalStatement(at: index) + } +} diff --git a/Sources/Rules/ElseOnSameLine.swift b/Sources/Rules/ElseOnSameLine.swift index ad3c22b5b..45d6004d0 100644 --- a/Sources/Rules/ElseOnSameLine.swift +++ b/Sources/Rules/ElseOnSameLine.swift @@ -21,12 +21,6 @@ public extension FormatRule { options: ["elseposition", "guardelse"], sharedOptions: ["allman", "linebreaks"] ) { formatter in - func bracesContainLinebreak(_ endIndex: Int) -> Bool { - guard let startIndex = formatter.index(of: .startOfScope("{"), before: endIndex) else { - return false - } - return (startIndex ..< endIndex).contains(where: { formatter.tokens[$0].isLinebreak }) - } formatter.forEachToken { i, token in switch token { case .keyword("while"): @@ -97,14 +91,14 @@ public extension FormatRule { if !shouldWrap, formatter.tokens[prevIndex].isLinebreak { if let prevBraceIndex = formatter.index(of: .nonSpaceOrLinebreak, before: prevIndex, if: { $0 == .endOfScope("}") - }), bracesContainLinebreak(prevBraceIndex) { + }), formatter.bracesContainLinebreak(prevBraceIndex) { formatter.replaceTokens(in: prevBraceIndex + 1 ..< i, with: .space(" ")) } } else if shouldWrap, let token = formatter.token(at: prevIndex), !token.isLinebreak, let prevBraceIndex = (token == .endOfScope("}")) ? prevIndex : formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: prevIndex, if: { $0 == .endOfScope("}") - }), bracesContainLinebreak(prevBraceIndex) + }), formatter.bracesContainLinebreak(prevBraceIndex) { formatter.replaceTokens(in: prevIndex + 1 ..< i, with: formatter.linebreakToken(for: prevIndex + 1)) @@ -116,3 +110,12 @@ public extension FormatRule { } } } + +extension Formatter { + func bracesContainLinebreak(_ endIndex: Int) -> Bool { + guard let startIndex = index(of: .startOfScope("{"), before: endIndex) else { + return false + } + return (startIndex ..< endIndex).contains(where: { tokens[$0].isLinebreak }) + } +} diff --git a/Sources/Rules/HoistPatternLet.swift b/Sources/Rules/HoistPatternLet.swift index 96a3982a0..13c5c4937 100644 --- a/Sources/Rules/HoistPatternLet.swift +++ b/Sources/Rules/HoistPatternLet.swift @@ -14,40 +14,6 @@ public extension FormatRule { help: "Reposition `let` or `var` bindings within pattern.", options: ["patternlet"] ) { formatter in - func indicesOf(_ keyword: String, in range: CountableRange) -> [Int]? { - var indices = [Int]() - var keywordFound = false, identifierFound = false - var count = 0 - for index in range { - switch formatter.tokens[index] { - case .keyword(keyword): - indices.append(index) - keywordFound = true - case .identifier("_"): - break - case .identifier where formatter.last(.nonSpaceOrComment, before: index) != .operator(".", .prefix): - identifierFound = true - if keywordFound { - count += 1 - } - case .delimiter(","): - guard keywordFound || !identifierFound else { - return nil - } - keywordFound = false - identifierFound = false - case .startOfScope("{"): - return nil - case .startOfScope("<"): - // See: https://github.com/nicklockwood/SwiftFormat/issues/768 - return nil - default: - break - } - } - return (keywordFound || !identifierFound) && count > 0 ? indices : nil - } - formatter.forEach(.startOfScope("(")) { i, _ in let hoist = formatter.options.hoistPatternLet // Check if pattern already starts with let/var @@ -136,9 +102,9 @@ public extension FormatRule { // Find let/var keyword indices var keyword = "let" guard let indices: [Int] = { - guard let indices = indicesOf(keyword, in: i + 1 ..< endIndex) else { + guard let indices = formatter.indicesOf(keyword, in: i + 1 ..< endIndex) else { keyword = "var" - return indicesOf(keyword, in: i + 1 ..< endIndex) + return formatter.indicesOf(keyword, in: i + 1 ..< endIndex) } return indices }() else { @@ -165,3 +131,39 @@ public extension FormatRule { } } } + +extension Formatter { + func indicesOf(_ keyword: String, in range: CountableRange) -> [Int]? { + var indices = [Int]() + var keywordFound = false, identifierFound = false + var count = 0 + for index in range { + switch tokens[index] { + case .keyword(keyword): + indices.append(index) + keywordFound = true + case .identifier("_"): + break + case .identifier where last(.nonSpaceOrComment, before: index) != .operator(".", .prefix): + identifierFound = true + if keywordFound { + count += 1 + } + case .delimiter(","): + guard keywordFound || !identifierFound else { + return nil + } + keywordFound = false + identifierFound = false + case .startOfScope("{"): + return nil + case .startOfScope("<"): + // See: https://github.com/nicklockwood/SwiftFormat/issues/768 + return nil + default: + break + } + } + return (keywordFound || !identifierFound) && count > 0 ? indices : nil + } +} diff --git a/Sources/Rules/Indent.swift b/Sources/Rules/Indent.swift index 9a54d12ca..76567fdc4 100644 --- a/Sources/Rules/Indent.swift +++ b/Sources/Rules/Indent.swift @@ -28,30 +28,6 @@ public extension FormatRule { var linewrapStack = [false] var lineIndex = 0 - func inFunctionDeclarationWhereReturnTypeIsWrappedToStartOfLine(at i: Int) -> Bool { - guard let returnOperatorIndex = formatter.startOfReturnType(at: i) else { - return false - } - return formatter.last(.nonSpaceOrComment, before: returnOperatorIndex)?.isLinebreak == true - } - - func isFirstStackedClosureArgument(at i: Int) -> Bool { - assert(formatter.tokens[i] == .startOfScope("{")) - if let prevIndex = formatter.index(of: .nonSpace, before: i), - let prevToken = formatter.token(at: prevIndex), prevToken == .startOfScope("(") || - (prevToken == .delimiter(":") && formatter.token(at: prevIndex - 1)?.isIdentifier == true - && formatter.last(.nonSpace, before: prevIndex - 1) == .startOfScope("(")), - let endIndex = formatter.endOfScope(at: i), - let commaIndex = formatter.index(of: .nonSpace, after: endIndex, if: { - $0 == .delimiter(",") - }), - formatter.next(.nonSpaceOrComment, after: commaIndex)?.isLinebreak == true - { - return true - } - return false - } - if formatter.options.fragment, let firstIndex = formatter.index(of: .nonSpaceOrLinebreak, after: -1), let indentToken = formatter.token(at: firstIndex - 1), case let .space(string) = indentToken @@ -72,22 +48,6 @@ public extension FormatRule { scopeStack.removeLast() } - func stringBodyIndent(at i: Int) -> String { - var space = "" - let start = formatter.startOfLine(at: i) - if let index = formatter.index(of: .nonSpace, in: start ..< i), - case let .stringBody(string) = formatter.tokens[index], - string.unicodeScalars.first?.isSpace == true - { - var index = string.startIndex - while index < string.endIndex, string[index].unicodeScalars.first!.isSpace { - space.append(string[index]) - index = string.index(after: index) - } - } - return space - } - var i = i switch token { case let .startOfScope(string): @@ -151,7 +111,7 @@ public extension FormatRule { case .outdent: i += formatter.insertSpaceIfEnabled("", at: formatter.startOfLine(at: i)) } - case "{" where isFirstStackedClosureArgument(at: i): + case "{" where formatter.isFirstStackedClosureArgument(at: i): guard var prevIndex = formatter.index(of: .nonSpace, before: i) else { assertionFailure() break @@ -162,7 +122,7 @@ public extension FormatRule { $0 == .startOfScope("(") }) else { - let stringIndent = stringBodyIndent(at: i) + let stringIndent = formatter.stringBodyIndent(at: i) stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent indent += stringIndent + formatter.options.indent break @@ -189,7 +149,7 @@ public extension FormatRule { } indent = prevIndent } - let stringIndent = stringBodyIndent(at: i) + let stringIndent = formatter.stringBodyIndent(at: i) stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent indent += stringIndent + formatter.options.indent case _ where token.isStringDelimiter, "//": @@ -215,7 +175,7 @@ public extension FormatRule { } indent = formatter.spaceEquivalentToTokens(from: start, upTo: nextIndex) default: - let stringIndent = stringBodyIndent(at: i) + let stringIndent = formatter.stringBodyIndent(at: i) stringBodyIndentStack[stringBodyIndentStack.count - 1] = stringIndent indent += stringIndent + formatter.options.indent } @@ -346,29 +306,12 @@ public extension FormatRule { if firstIndex != i { break } - func isInIfdef() -> Bool { - guard scopeStack.last == .startOfScope("#if") else { - return false - } - var index = i - 1 - while index > 0 { - switch formatter.tokens[index] { - case .keyword("switch"): - return false - case .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): - return true - default: - index -= 1 - } - } - return false - } if token == .endOfScope("#endif"), formatter.options.ifdefIndent == .outdent { i += formatter.insertSpaceIfEnabled("", at: start) } else { var indent = indentStack.last ?? "" if token.isSwitchCaseOrDefault, - formatter.options.indentCase, !isInIfdef() + formatter.options.indentCase, !formatter.isInIfdef(at: i, scopeStack: scopeStack) { indent += formatter.options.indent } @@ -474,28 +417,6 @@ public extension FormatRule { } lineIndex += 1 - func shouldIndentNextLine(at i: Int) -> Bool { - // If there is a linebreak after certain symbols, we should add - // an additional indentation to the lines at the same indention scope - // after this line. - let endOfLine = formatter.endOfLine(at: i) - switch formatter.token(at: endOfLine - 1) { - case .keyword("return")?, .operator("=", .infix)?: - let endOfNextLine = formatter.endOfLine(at: endOfLine + 1) - switch formatter.last(.nonSpaceOrCommentOrLinebreak, before: endOfNextLine) { - case .operator(_, .infix)?, .delimiter(",")?: - return false - case .endOfScope(")")?: - return !formatter.options.xcodeIndentation - default: - return formatter.lastIndex(of: .startOfScope, - in: i ..< endOfNextLine) == nil - } - default: - return false - } - } - guard var nextNonSpaceIndex = formatter.index(of: .nonSpace, after: i), let nextToken = formatter.token(at: nextNonSpaceIndex) else { @@ -606,25 +527,6 @@ public extension FormatRule { } } } else if linewrapped { - func isWrappedDeclaration() -> Bool { - guard let keywordIndex = formatter - .indexOfLastSignificantKeyword(at: i, excluding: [ - "where", "throws", "rethrows", - ]), !formatter.tokens[keywordIndex ..< i].contains(.endOfScope("}")), - case let .keyword(keyword) = formatter.tokens[keywordIndex], - ["class", "actor", "struct", "enum", "protocol", "extension", - "func"].contains(keyword) - else { - return false - } - - let end = formatter.endOfLine(at: i + 1) - guard let lastToken = formatter.last(.nonSpaceOrCommentOrLinebreak, before: end + 1), - [.startOfScope("{"), .endOfScope("}")].contains(lastToken) else { return false } - - return true - } - // Don't indent line starting with dot if previous line was just a closing brace var lastToken = formatter.tokens[lastNonSpaceOrLinebreakIndex] if formatter.options.allmanBraces, nextToken == .startOfScope("{"), @@ -661,10 +563,10 @@ public extension FormatRule { { indent += formatter.options.indent } - } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { + } else if !formatter.options.xcodeIndentation || !formatter.isWrappedDeclaration(at: i) { indent += formatter.linewrapIndent(at: i) } - } else if !formatter.options.xcodeIndentation || !isWrappedDeclaration() { + } else if !formatter.options.xcodeIndentation || !formatter.isWrappedDeclaration(at: i) { indent += formatter.linewrapIndent(at: i) } @@ -750,7 +652,7 @@ public extension FormatRule { formatter.insertSpaceIfEnabled(indent, at: i + 1) } - if linewrapped, shouldIndentNextLine(at: i) { + if linewrapped, formatter.shouldIndentNextLine(at: i) { indentStack[indentStack.count - 1] += formatter.options.indent } default: @@ -795,3 +697,103 @@ public extension FormatRule { } } } + +extension Formatter { + func inFunctionDeclarationWhereReturnTypeIsWrappedToStartOfLine(at i: Int) -> Bool { + guard let returnOperatorIndex = startOfReturnType(at: i) else { + return false + } + return last(.nonSpaceOrComment, before: returnOperatorIndex)?.isLinebreak == true + } + + func isFirstStackedClosureArgument(at i: Int) -> Bool { + assert(tokens[i] == .startOfScope("{")) + if let prevIndex = index(of: .nonSpace, before: i), + let prevToken = token(at: prevIndex), prevToken == .startOfScope("(") || + (prevToken == .delimiter(":") && token(at: prevIndex - 1)?.isIdentifier == true + && last(.nonSpace, before: prevIndex - 1) == .startOfScope("(")), + let endIndex = endOfScope(at: i), + let commaIndex = index(of: .nonSpace, after: endIndex, if: { + $0 == .delimiter(",") + }), + next(.nonSpaceOrComment, after: commaIndex)?.isLinebreak == true + { + return true + } + return false + } + + func stringBodyIndent(at i: Int) -> String { + var space = "" + let start = startOfLine(at: i) + if let index = index(of: .nonSpace, in: start ..< i), + case let .stringBody(string) = tokens[index], + string.unicodeScalars.first?.isSpace == true + { + var index = string.startIndex + while index < string.endIndex, string[index].unicodeScalars.first!.isSpace { + space.append(string[index]) + index = string.index(after: index) + } + } + return space + } + + func shouldIndentNextLine(at i: Int) -> Bool { + // If there is a linebreak after certain symbols, we should add + // an additional indentation to the lines at the same indention scope + // after this line. + let endOfLine = self.endOfLine(at: i) + switch token(at: endOfLine - 1) { + case .keyword("return")?, .operator("=", .infix)?: + let endOfNextLine = self.endOfLine(at: endOfLine + 1) + switch last(.nonSpaceOrCommentOrLinebreak, before: endOfNextLine) { + case .operator(_, .infix)?, .delimiter(",")?: + return false + case .endOfScope(")")?: + return !options.xcodeIndentation + default: + return lastIndex(of: .startOfScope, + in: i ..< endOfNextLine) == nil + } + default: + return false + } + } + + func isInIfdef(at i: Int, scopeStack: [Token]) -> Bool { + guard scopeStack.last == .startOfScope("#if") else { + return false + } + var index = i - 1 + while index > 0 { + switch tokens[index] { + case .keyword("switch"): + return false + case .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"): + return true + default: + index -= 1 + } + } + return false + } + + func isWrappedDeclaration(at i: Int) -> Bool { + guard let keywordIndex = indexOfLastSignificantKeyword(at: i, excluding: [ + "where", "throws", "rethrows", + ]), !tokens[keywordIndex ..< i].contains(.endOfScope("}")), + case let .keyword(keyword) = tokens[keywordIndex], + ["class", "actor", "struct", "enum", "protocol", "extension", + "func"].contains(keyword) + else { + return false + } + + let end = endOfLine(at: i + 1) + guard let lastToken = last(.nonSpaceOrCommentOrLinebreak, before: end + 1), + [.startOfScope("{"), .endOfScope("}")].contains(lastToken) else { return false } + + return true + } +} diff --git a/Sources/Rules/ModifierOrder.swift b/Sources/Rules/ModifierOrder.swift index b4de405ca..11b3887ac 100644 --- a/Sources/Rules/ModifierOrder.swift +++ b/Sources/Rules/ModifierOrder.swift @@ -72,3 +72,22 @@ public extension FormatRule { } } } + +extension Formatter { + /// Swift modifier keywords, in preferred order + var modifierOrder: [String] { + var priorities = [String: Int]() + for (i, modifiers) in _FormatRules.defaultModifierOrder.enumerated() { + for modifier in modifiers { + priorities[modifier] = i + } + } + var order = options.modifierOrder.flatMap { _FormatRules.mapModifiers($0) ?? [] } + for (i, modifiers) in _FormatRules.defaultModifierOrder.enumerated() { + let insertionPoint = order.firstIndex(where: { modifiers.contains($0) }) ?? + order.firstIndex(where: { (priorities[$0] ?? 0) > i }) ?? order.count + order.insert(contentsOf: modifiers.filter { !order.contains($0) }, at: insertionPoint) + } + return order + } +} diff --git a/Sources/Rules/NumberFormatting.swift b/Sources/Rules/NumberFormatting.swift index 5504c1372..44c271b0b 100644 --- a/Sources/Rules/NumberFormatting.swift +++ b/Sources/Rules/NumberFormatting.swift @@ -20,29 +20,6 @@ public extension FormatRule { options: ["decimalgrouping", "binarygrouping", "octalgrouping", "hexgrouping", "fractiongrouping", "exponentgrouping", "hexliteralcase", "exponentcase"] ) { formatter in - func applyGrouping(_ grouping: Grouping, to number: inout String) { - switch grouping { - case .none, .group: - number = number.replacingOccurrences(of: "_", with: "") - case .ignore: - return - } - guard case let .group(group, threshold) = grouping, group > 0, number.count >= threshold else { - return - } - var output = Substring() - var index = number.endIndex - var count = 0 - repeat { - index = number.index(before: index) - if count > 0, count % group == 0 { - output.insert("_", at: output.startIndex) - } - count += 1 - output.insert(number[index], at: output.startIndex) - } while index != number.startIndex - number = String(output) - } formatter.forEachToken { i, token in guard case let .number(number, type) = token else { return @@ -85,12 +62,12 @@ public extension FormatRule { default: break } - applyGrouping(grouping, to: &main) + formatter.applyGrouping(grouping, to: &main) if formatter.options.fractionGrouping { - applyGrouping(grouping, to: &fraction) + formatter.applyGrouping(grouping, to: &fraction) } if formatter.options.exponentGrouping { - applyGrouping(grouping, to: &exponent) + formatter.applyGrouping(grouping, to: &exponent) } var result = prefix + main if !fraction.isEmpty { @@ -103,3 +80,29 @@ public extension FormatRule { } } } + +extension Formatter { + func applyGrouping(_ grouping: Grouping, to number: inout String) { + switch grouping { + case .none, .group: + number = number.replacingOccurrences(of: "_", with: "") + case .ignore: + return + } + guard case let .group(group, threshold) = grouping, group > 0, number.count >= threshold else { + return + } + var output = Substring() + var index = number.endIndex + var count = 0 + repeat { + index = number.index(before: index) + if count > 0, count % group == 0 { + output.insert("_", at: output.startIndex) + } + count += 1 + output.insert(number[index], at: output.startIndex) + } while index != number.startIndex + number = String(output) + } +} diff --git a/Sources/Rules/RedundantFileprivate.swift b/Sources/Rules/RedundantFileprivate.swift index 4b3c1a759..4a8c91ca0 100644 --- a/Sources/Rules/RedundantFileprivate.swift +++ b/Sources/Rules/RedundantFileprivate.swift @@ -29,111 +29,7 @@ public extension FormatRule { } let importRanges = formatter.parseImports() var fileJustContainsOneType: Bool? - func ifCodeInRange(_ range: CountableRange) -> Bool { - var index = range.lowerBound - while index < range.upperBound, let nextIndex = - formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: index ..< range.upperBound) - { - guard let importRange = importRanges.first(where: { - $0.contains(where: { $0.range.contains(nextIndex) }) - }) else { - return true - } - index = importRange.last!.range.upperBound + 1 - } - return false - } - func isTypeInitialized(_ name: String, in range: CountableRange) -> Bool { - for i in range { - switch formatter.tokens[i] { - case .identifier(name): - guard let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i) else { - break - } - switch formatter.tokens[nextIndex] { - case .operator(".", .infix): - if formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("init") { - return true - } - case .startOfScope("("): - return true - case .startOfScope("{"): - if formatter.isStartOfClosure(at: nextIndex) { - return true - } - default: - break - } - case .identifier("init"): - // TODO: this will return true if *any* type is initialized using type inference. - // Is there a way to narrow this down a bit? - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator(".", .prefix) { - return true - } - default: - break - } - } - return false - } - // TODO: improve this logic to handle shadowing - func areMembers(_ names: [String], of type: String, - referencedIn range: CountableRange) -> Bool - { - var i = range.lowerBound - while i < range.upperBound { - switch formatter.tokens[i] { - case .keyword("struct"), .keyword("extension"), .keyword("enum"), .keyword("actor"), - .keyword("class") where formatter.declarationType(at: i) == "class": - guard let startIndex = formatter.index(of: .startOfScope("{"), after: i), - let endIndex = formatter.endOfScope(at: startIndex) - else { - break - } - guard let nameIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - formatter.tokens[nameIndex] != .identifier(type) - else { - i = endIndex - break - } - for case let .identifier(name) in formatter.tokens[startIndex ..< endIndex] - where names.contains(name) - { - return true - } - i = endIndex - case let .identifier(name) where names.contains(name): - if let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { - $0 == .operator(".", .infix) - }), formatter.last(.nonSpaceOrCommentOrLinebreak, before: dotIndex) - != .identifier("self") - { - return true - } - default: - break - } - i += 1 - } - return false - } - func isInitOverridden(for type: String, in range: CountableRange) -> Bool { - for i in range { - if case .keyword("init") = formatter.tokens[i], - let scopeStart = formatter.index(of: .startOfScope("{"), after: i), - formatter.index(of: .identifier("super"), after: scopeStart) != nil, - let scopeIndex = formatter.index(of: .startOfScope("{"), before: i), - let colonIndex = formatter.index(of: .delimiter(":"), before: scopeIndex), - formatter.next( - .nonSpaceOrCommentOrLinebreak, - in: colonIndex + 1 ..< scopeIndex - ) == .identifier(type) - { - return true - } - } - return false - } + formatter.forEach(.keyword("fileprivate")) { i, _ in // Check if definition is a member of a file-scope type guard formatter.options.swiftVersion >= "4", @@ -165,8 +61,8 @@ public extension FormatRule { // Check for code outside of main type definition let startIndex = formatter.startOfModifiers(at: typeIndex, includingAttributes: true) if fileJustContainsOneType == nil { - fileJustContainsOneType = !ifCodeInRange(0 ..< startIndex) && - !ifCodeInRange(endIndex + 1 ..< formatter.tokens.count) + fileJustContainsOneType = !formatter.ifCodeInRange(0 ..< startIndex, importRanges: importRanges) && + !formatter.ifCodeInRange(endIndex + 1 ..< formatter.tokens.count, importRanges: importRanges) } if fileJustContainsOneType == true { formatter.replaceToken(at: i, with: .keyword("private")) @@ -176,28 +72,139 @@ public extension FormatRule { // change any fileprivate members in case we break memberwise initializer // TODO: check if struct contains an overridden init; if so we can skip this check if formatter.tokens[typeIndex] == .keyword("struct"), - isTypeInitialized(typeName, in: 0 ..< startIndex) || - isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count) + formatter.isTypeInitialized(typeName, in: 0 ..< startIndex) || + formatter.isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count) { return } // Check if member is referenced outside type if memberType == "init" { // Make initializer private if it's not called anywhere - if !isTypeInitialized(typeName, in: 0 ..< startIndex), - !isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count), - !isInitOverridden(for: typeName, in: 0 ..< startIndex), - !isInitOverridden(for: typeName, in: endIndex + 1 ..< formatter.tokens.count) + if !formatter.isTypeInitialized(typeName, in: 0 ..< startIndex), + !formatter.isTypeInitialized(typeName, in: endIndex + 1 ..< formatter.tokens.count), + !formatter.isInitOverridden(for: typeName, in: 0 ..< startIndex), + !formatter.isInitOverridden(for: typeName, in: endIndex + 1 ..< formatter.tokens.count) { formatter.replaceToken(at: i, with: .keyword("private")) } } else if let _names = formatter.namesInDeclaration(at: keywordIndex), case let names = _names + _names.map({ "$\($0)" }), - !areMembers(names, of: typeName, referencedIn: 0 ..< startIndex), - !areMembers(names, of: typeName, referencedIn: endIndex + 1 ..< formatter.tokens.count) + !formatter.areMembers(names, of: typeName, referencedIn: 0 ..< startIndex), + !formatter.areMembers(names, of: typeName, referencedIn: endIndex + 1 ..< formatter.tokens.count) { formatter.replaceToken(at: i, with: .keyword("private")) } } } } + +extension Formatter { + func ifCodeInRange(_ range: CountableRange, importRanges: [[ImportRange]]) -> Bool { + var index = range.lowerBound + while index < range.upperBound, let nextIndex = + self.index(of: .nonSpaceOrCommentOrLinebreak, in: index ..< range.upperBound) + { + guard let importRange = importRanges.first(where: { + $0.contains(where: { $0.range.contains(nextIndex) }) + }) else { + return true + } + index = importRange.last!.range.upperBound + 1 + } + return false + } + + func isTypeInitialized(_ name: String, in range: CountableRange) -> Bool { + for i in range { + switch tokens[i] { + case .identifier(name): + guard let nextIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: i) else { + break + } + switch tokens[nextIndex] { + case .operator(".", .infix): + if next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("init") { + return true + } + case .startOfScope("("): + return true + case .startOfScope("{"): + if isStartOfClosure(at: nextIndex) { + return true + } + default: + break + } + case .identifier("init"): + // TODO: this will return true if *any* type is initialized using type inference. + // Is there a way to narrow this down a bit? + if last(.nonSpaceOrCommentOrLinebreak, before: i) == .operator(".", .prefix) { + return true + } + default: + break + } + } + return false + } + + // TODO: improve this logic to handle shadowing + func areMembers(_ names: [String], of type: String, + referencedIn range: CountableRange) -> Bool + { + var i = range.lowerBound + while i < range.upperBound { + switch tokens[i] { + case .keyword("struct"), .keyword("extension"), .keyword("enum"), .keyword("actor"), + .keyword("class") where declarationType(at: i) == "class": + guard let startIndex = index(of: .startOfScope("{"), after: i), + let endIndex = endOfScope(at: startIndex) + else { + break + } + guard let nameIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: i), + tokens[nameIndex] != .identifier(type) + else { + i = endIndex + break + } + for case let .identifier(name) in tokens[startIndex ..< endIndex] + where names.contains(name) + { + return true + } + i = endIndex + case let .identifier(name) where names.contains(name): + if let dotIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: i, if: { + $0 == .operator(".", .infix) + }), last(.nonSpaceOrCommentOrLinebreak, before: dotIndex) + != .identifier("self") + { + return true + } + default: + break + } + i += 1 + } + return false + } + + func isInitOverridden(for type: String, in range: CountableRange) -> Bool { + for i in range { + if case .keyword("init") = tokens[i], + let scopeStart = index(of: .startOfScope("{"), after: i), + index(of: .identifier("super"), after: scopeStart) != nil, + let scopeIndex = index(of: .startOfScope("{"), before: i), + let colonIndex = index(of: .delimiter(":"), before: scopeIndex), + next( + .nonSpaceOrCommentOrLinebreak, + in: colonIndex + 1 ..< scopeIndex + ) == .identifier(type) + { + return true + } + } + return false + } +} diff --git a/Sources/Rules/RedundantNilInit.swift b/Sources/Rules/RedundantNilInit.swift index 36f202c0b..a8744aa5c 100644 --- a/Sources/Rules/RedundantNilInit.swift +++ b/Sources/Rules/RedundantNilInit.swift @@ -14,34 +14,6 @@ public extension FormatRule { help: "Remove/insert redundant `nil` default value (Optional vars are nil by default).", options: ["nilinit"] ) { formatter in - func search(from index: Int, isStoredProperty: Bool) { - if let optionalIndex = formatter.index(of: .unwrapOperator, after: index) { - if formatter.index(of: .endOfStatement, in: index + 1 ..< optionalIndex) != nil { - return - } - let previousToken = formatter.tokens[optionalIndex - 1] - if !previousToken.isSpaceOrCommentOrLinebreak && previousToken != .keyword("as") { - let equalsIndex = formatter.index(of: .nonSpaceOrLinebreak, after: optionalIndex, if: { - $0 == .operator("=", .infix) - }) - switch formatter.options.nilInit { - case .remove: - if let equalsIndex = equalsIndex, let nilIndex = formatter.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { - $0 == .identifier("nil") - }) { - formatter.removeTokens(in: optionalIndex + 1 ... nilIndex) - } - case .insert: - if isStoredProperty && equalsIndex == nil { - let tokens: [Token] = [.space(" "), .operator("=", .infix), .space(" "), .identifier("nil")] - formatter.insert(tokens, at: optionalIndex + 1) - } - } - } - search(from: optionalIndex, isStoredProperty: isStoredProperty) - } - } - // Check modifiers don't include `lazy` formatter.forEach(.keyword("var")) { i, _ in if formatter.modifiersForDeclaration(at: i, contains: { @@ -70,7 +42,37 @@ public extension FormatRule { } } // Find the nil - search(from: i, isStoredProperty: formatter.isStoredProperty(atIntroducerIndex: i)) + formatter.search(from: i, isStoredProperty: formatter.isStoredProperty(atIntroducerIndex: i)) + } + } +} + +extension Formatter { + func search(from index: Int, isStoredProperty: Bool) { + if let optionalIndex = self.index(of: .unwrapOperator, after: index) { + if self.index(of: .endOfStatement, in: index + 1 ..< optionalIndex) != nil { + return + } + let previousToken = tokens[optionalIndex - 1] + if !previousToken.isSpaceOrCommentOrLinebreak, previousToken != .keyword("as") { + let equalsIndex = self.index(of: .nonSpaceOrLinebreak, after: optionalIndex, if: { + $0 == .operator("=", .infix) + }) + switch options.nilInit { + case .remove: + if let equalsIndex = equalsIndex, let nilIndex = self.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { + $0 == .identifier("nil") + }) { + removeTokens(in: optionalIndex + 1 ... nilIndex) + } + case .insert: + if isStoredProperty, equalsIndex == nil { + let tokens: [Token] = [.space(" "), .operator("=", .infix), .space(" "), .identifier("nil")] + insert(tokens, at: optionalIndex + 1) + } + } + } + search(from: optionalIndex, isStoredProperty: isStoredProperty) } } } diff --git a/Sources/Rules/RedundantObjc.swift b/Sources/Rules/RedundantObjc.swift index 7b56d7429..b2cc80af2 100644 --- a/Sources/Rules/RedundantObjc.swift +++ b/Sources/Rules/RedundantObjc.swift @@ -49,20 +49,12 @@ public extension FormatRule { } index = nextIndex } - func removeAttribute() { - formatter.removeToken(at: i) - if formatter.token(at: i)?.isSpace == true { - formatter.removeToken(at: i) - } else if formatter.token(at: i - 1)?.isSpace == true { - formatter.removeToken(at: i - 1) - } - } if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i, if: { $0.isAttribute && objcAttributes.contains($0.string) }) != nil || formatter.next(.nonSpaceOrCommentOrLinebreak, after: i, if: { $0.isAttribute && objcAttributes.contains($0.string) }) != nil { - removeAttribute() + formatter.removeAttribute(at: i) return } guard let scopeStart = formatter.index(of: .startOfScope("{"), before: i), @@ -73,11 +65,11 @@ public extension FormatRule { switch formatter.tokens[keywordIndex] { case .keyword("class"), .keyword("actor"): if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objcMembers") { - removeAttribute() + formatter.removeAttribute(at: i) } case .keyword("extension"): if formatter.modifiersForDeclaration(at: keywordIndex, contains: "@objc") { - removeAttribute() + formatter.removeAttribute(at: i) } default: break @@ -85,3 +77,14 @@ public extension FormatRule { } } } + +extension Formatter { + func removeAttribute(at i: Int) { + removeToken(at: i) + if token(at: i)?.isSpace == true { + removeToken(at: i) + } else if token(at: i - 1)?.isSpace == true { + removeToken(at: i - 1) + } + } +} diff --git a/Sources/Rules/RedundantParens.swift b/Sources/Rules/RedundantParens.swift index adf298a54..a0f6bbc15 100644 --- a/Sources/Rules/RedundantParens.swift +++ b/Sources/Rules/RedundantParens.swift @@ -13,17 +13,6 @@ public extension FormatRule { static let redundantParens = FormatRule( help: "Remove redundant parentheses." ) { formatter in - func nestedParens(in range: ClosedRange) -> ClosedRange? { - guard let startIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: range.lowerBound, if: { - $0 == .startOfScope("(") - }), let endIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: range.upperBound, if: { - $0 == .endOfScope(")") - }), formatter.index(of: .endOfScope(")"), after: startIndex) == endIndex else { - return nil - } - return startIndex ... endIndex - } - // TODO: unify with conditionals logic in trailingClosures let conditionals = Set(["in", "while", "if", "case", "switch", "where", "for", "guard"]) @@ -33,14 +22,14 @@ public extension FormatRule { else { return } - var innerParens = nestedParens(in: i ... closingIndex) - while let range = innerParens, nestedParens(in: range) != nil { + var innerParens = formatter.nestedParens(in: i ... closingIndex) + while let range = innerParens, formatter.nestedParens(in: range) != nil { // TODO: this could be a lot more efficient if we kept track of the // removed token indices instead of recalculating paren positions every time formatter.removeParen(at: range.upperBound) formatter.removeParen(at: range.lowerBound) closingIndex = formatter.index(of: .endOfScope(")"), after: i)! - innerParens = nestedParens(in: i ... closingIndex) + innerParens = formatter.nestedParens(in: i ... closingIndex) } var isClosure = false let previousIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: i) ?? -1 @@ -226,3 +215,16 @@ public extension FormatRule { } } } + +extension Formatter { + func nestedParens(in range: ClosedRange) -> ClosedRange? { + guard let startIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: range.lowerBound, if: { + $0 == .startOfScope("(") + }), let endIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: range.upperBound, if: { + $0 == .endOfScope(")") + }), index(of: .endOfScope(")"), after: startIndex) == endIndex else { + return nil + } + return startIndex ... endIndex + } +} diff --git a/Sources/Rules/RedundantPattern.swift b/Sources/Rules/RedundantPattern.swift index 411770fa3..8482b1ec2 100644 --- a/Sources/Rules/RedundantPattern.swift +++ b/Sources/Rules/RedundantPattern.swift @@ -13,21 +13,6 @@ public extension FormatRule { static let redundantPattern = FormatRule( help: "Remove redundant pattern matching parameter syntax." ) { formatter in - func redundantBindings(in range: Range) -> Bool { - var isEmpty = true - for token in formatter.tokens[range.lowerBound ..< range.upperBound] { - switch token { - case .identifier("_"): - isEmpty = false - case .space, .linebreak, .delimiter(","), .keyword("let"), .keyword("var"): - break - default: - return false - } - } - return !isEmpty - } - formatter.forEach(.startOfScope("(")) { i, _ in let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i) if let prevIndex = prevIndex, let prevToken = formatter.token(at: prevIndex), @@ -39,7 +24,7 @@ public extension FormatRule { guard let endIndex = formatter.index(of: .endOfScope(")"), after: i), let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex), [.startOfScope(":"), .operator("=", .infix)].contains(nextToken), - redundantBindings(in: i + 1 ..< endIndex) + formatter.redundantBindings(in: i + 1 ..< endIndex) else { return } @@ -70,3 +55,20 @@ public extension FormatRule { } } } + +extension Formatter { + func redundantBindings(in range: Range) -> Bool { + var isEmpty = true + for token in tokens[range.lowerBound ..< range.upperBound] { + switch token { + case .identifier("_"): + isEmpty = false + case .space, .linebreak, .delimiter(","), .keyword("let"), .keyword("var"): + break + default: + return false + } + } + return !isEmpty + } +} diff --git a/Sources/Rules/RedundantReturn.swift b/Sources/Rules/RedundantReturn.swift index 9d176344c..6c04bc073 100644 --- a/Sources/Rules/RedundantReturn.swift +++ b/Sources/Rules/RedundantReturn.swift @@ -152,67 +152,66 @@ public extension FormatRule { // Find all of the return keywords to remove before we remove any of them, // so we can apply additional validation first. - var returnKeywordRangesToRemove = [Range]() - var hasReturnThatCantBeRemoved = false + guard let returnKeywordRangesToRemove = formatter.returnKeywordRangesToRemove(atStartOfScope: startOfScopeIndex, returnIndices: &returnIndices) else { return } - /// Finds the return keywords to remove and stores them in `returnKeywordRangesToRemove` - func removeReturn(atStartOfScope startOfScopeIndex: Int) { - // If this scope is a single-statement if or switch statement then we have to recursively - // remove the return from each branch of the if statement - let startOfBody = formatter.startOfBody(atStartOfScope: startOfScopeIndex) + for returnKeywordRangeToRemove in returnKeywordRangesToRemove.sorted(by: { $0.startIndex > $1.startIndex }) { + formatter.removeTokens(in: returnKeywordRangeToRemove) + } + } + } +} - if let firstTokenInBody = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: startOfBody), - let conditionalBranches = formatter.conditionalBranches(at: firstTokenInBody) +extension Formatter { + func returnKeywordRangesToRemove(atStartOfScope startOfScopeIndex: Int, returnIndices: inout [Int]) -> [Range]? { + var returnKeywordRangesToRemove = [Range]() + + // If this scope is a single-statement if or switch statement then we have to recursively + // remove the return from each branch of the if statement + let startOfBody = self.startOfBody(atStartOfScope: startOfScopeIndex) + + if let firstTokenInBody = index(of: .nonSpaceOrCommentOrLinebreak, after: startOfBody), + let conditionalBranches = conditionalBranches(at: firstTokenInBody) + { + for branch in conditionalBranches.reversed() { + // In Swift 5.9, there's a bug that prevents you from writing an + // if or switch expression using an `as?` on one of the branches: + // https://github.com/apple/swift/issues/68764 + // + // if condition { + // foo as? String + // } else { + // "bar" + // } + // + if conditionalBranchHasUnsupportedCastOperator( + startOfScopeIndex: branch.startOfBranch) { - for branch in conditionalBranches.reversed() { - // In Swift 5.9, there's a bug that prevents you from writing an - // if or switch expression using an `as?` on one of the branches: - // https://github.com/apple/swift/issues/68764 - // - // if condition { - // foo as? String - // } else { - // "bar" - // } - // - if formatter.conditionalBranchHasUnsupportedCastOperator( - startOfScopeIndex: branch.startOfBranch) - { - hasReturnThatCantBeRemoved = true - return - } - - removeReturn(atStartOfScope: branch.startOfBranch) - } + return nil } - // Otherwise this is a simple case with a single return at the start of the scope - else if let endOfScopeIndex = formatter.endOfScope(at: startOfScopeIndex), - let returnIndex = formatter.index(of: .keyword("return"), after: startOfScopeIndex), - returnIndices.contains(returnIndex), - returnIndex < endOfScopeIndex, - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: returnIndex), - formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: returnIndex)! < endOfScopeIndex - { - let range = returnIndex ..< nextIndex - for (i, index) in returnIndices.enumerated().reversed() { - if range.contains(index) { - returnIndices.remove(at: i) - } else if index > returnIndex { - returnIndices[i] -= range.count - } - } - returnKeywordRangesToRemove.append(range) - } + returnKeywordRangesToRemove.append(contentsOf: self.returnKeywordRangesToRemove(atStartOfScope: branch.startOfBranch, returnIndices: &returnIndices) ?? []) } + } - removeReturn(atStartOfScope: startOfScopeIndex) - - guard !hasReturnThatCantBeRemoved else { return } - - for returnKeywordRangeToRemove in returnKeywordRangesToRemove.sorted(by: { $0.startIndex > $1.startIndex }) { - formatter.removeTokens(in: returnKeywordRangeToRemove) + // Otherwise this is a simple case with a single return at the start of the scope + else if let endOfScopeIndex = endOfScope(at: startOfScopeIndex), + let returnIndex = index(of: .keyword("return"), after: startOfScopeIndex), + returnIndices.contains(returnIndex), + returnIndex < endOfScopeIndex, + let nextIndex = index(of: .nonSpaceOrLinebreak, after: returnIndex), + index(of: .nonSpaceOrCommentOrLinebreak, after: returnIndex)! < endOfScopeIndex + { + let range = returnIndex ..< nextIndex + for (i, index) in returnIndices.enumerated().reversed() { + if range.contains(index) { + returnIndices.remove(at: i) + } else if index > returnIndex { + returnIndices[i] -= range.count + } } + returnKeywordRangesToRemove.append(range) } + + return returnKeywordRangesToRemove } } diff --git a/Sources/Rules/RedundantType.swift b/Sources/Rules/RedundantType.swift index 1dabb689a..7f1fcc3e6 100644 --- a/Sources/Rules/RedundantType.swift +++ b/Sources/Rules/RedundantType.swift @@ -28,60 +28,6 @@ public extension FormatRule { let typeEndIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: equalsIndex) else { return } - // Compares whether or not two types are equivalent - func compare(typeStartingAfter j: Int, withTypeStartingAfter i: Int) - -> (matches: Bool, i: Int, j: Int, wasValue: Bool) - { - var i = i, j = j, wasValue = false - - while let typeIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i), - typeIndex <= typeEndIndex, - let valueIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: j) - { - let typeToken = formatter.tokens[typeIndex] - let valueToken = formatter.tokens[valueIndex] - if !wasValue { - switch valueToken { - case _ where valueToken.isStringDelimiter, .number, - .identifier("true"), .identifier("false"): - if formatter.options.redundantType == .explicit { - // We never remove the value in this case, so exit early - return (false, i, j, wasValue) - } - wasValue = true - default: - break - } - } - guard typeToken == formatter.typeToken(forValueToken: valueToken) else { - return (false, i, j, wasValue) - } - // Avoid introducing "inferred to have type 'Void'" warning - if formatter.options.redundantType == .inferred, typeToken == .identifier("Void") || - typeToken == .endOfScope(")") && formatter.tokens[i] == .startOfScope("(") - { - return (false, i, j, wasValue) - } - i = typeIndex - j = valueIndex - if formatter.tokens[j].isStringDelimiter, let next = formatter.endOfScope(at: j) { - j = next - } - } - guard i == typeEndIndex else { - return (false, i, j, wasValue) - } - - // Check for ternary - if let endOfExpression = formatter.endOfExpression(at: j, upTo: [.operator("?", .infix)]), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: endOfExpression) == .operator("?", .infix) - { - return (false, i, j, wasValue) - } - - return (true, i, j, wasValue) - } - // The implementation of RedundantType uses inferred or explicit, // potentially depending on the context. let isInferred: Bool @@ -157,7 +103,7 @@ public extension FormatRule { formatter.allRecursiveConditionalBranches( in: conditionalBranches, satisfy: { branch in - compare(typeStartingAfter: branch.startOfBranch, withTypeStartingAfter: colonIndex).matches + formatter.compare(typeStartingAfter: branch.startOfBranch, withTypeStartingAfter: colonIndex, typeEndIndex: typeEndIndex).matches } ) { @@ -168,9 +114,10 @@ public extension FormatRule { } } else { formatter.forEachRecursiveConditionalBranch(in: conditionalBranches) { branch in - let (_, i, j, wasValue) = compare( + let (_, i, j, wasValue) = formatter.compare( typeStartingAfter: branch.startOfBranch, - withTypeStartingAfter: colonIndex + withTypeStartingAfter: colonIndex, + typeEndIndex: typeEndIndex ) removeType(after: branch.startOfBranch, i: i, j: j, wasValue: wasValue) @@ -180,7 +127,7 @@ public extension FormatRule { // Otherwise this is just a simple assignment expression where the RHS is a single value else { - let (matches, i, j, wasValue) = compare(typeStartingAfter: equalsIndex, withTypeStartingAfter: colonIndex) + let (matches, i, j, wasValue) = formatter.compare(typeStartingAfter: equalsIndex, withTypeStartingAfter: colonIndex, typeEndIndex: typeEndIndex) if matches { removeType(after: equalsIndex, i: i, j: j, wasValue: wasValue) } @@ -188,3 +135,76 @@ public extension FormatRule { } } } + +extension Formatter { + // Compares whether or not two types are equivalent + func compare(typeStartingAfter j: Int, withTypeStartingAfter i: Int, typeEndIndex: Int) + -> (matches: Bool, i: Int, j: Int, wasValue: Bool) + { + var i = i, j = j, wasValue = false + + while let typeIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: i), + typeIndex <= typeEndIndex, + let valueIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: j) + { + let typeToken = tokens[typeIndex] + let valueToken = tokens[valueIndex] + if !wasValue { + switch valueToken { + case _ where valueToken.isStringDelimiter, .number, + .identifier("true"), .identifier("false"): + if options.redundantType == .explicit { + // We never remove the value in this case, so exit early + return (false, i, j, wasValue) + } + wasValue = true + default: + break + } + } + guard typeToken == self.typeToken(forValueToken: valueToken) else { + return (false, i, j, wasValue) + } + // Avoid introducing "inferred to have type 'Void'" warning + if options.redundantType == .inferred, typeToken == .identifier("Void") || + typeToken == .endOfScope(")") && tokens[i] == .startOfScope("(") + { + return (false, i, j, wasValue) + } + i = typeIndex + j = valueIndex + if tokens[j].isStringDelimiter, let next = endOfScope(at: j) { + j = next + } + } + guard i == typeEndIndex else { + return (false, i, j, wasValue) + } + + // Check for ternary + if let endOfExpression = endOfExpression(at: j, upTo: [.operator("?", .infix)]), + next(.nonSpaceOrCommentOrLinebreak, after: endOfExpression) == .operator("?", .infix) + { + return (false, i, j, wasValue) + } + + return (true, i, j, wasValue) + } + + /// Returns the equivalent type token for a given value token + func typeToken(forValueToken token: Token) -> Token { + switch token { + case let .number(_, type): + switch type { + case .decimal: + return .identifier("Double") + default: + return .identifier("Int") + } + case let .identifier(name): + return ["true", "false"].contains(name) ? .identifier("Bool") : .identifier(name) + case let token: + return token.isStringDelimiter ? .identifier("String") : token + } + } +} diff --git a/Sources/Rules/SortImports.swift b/Sources/Rules/SortImports.swift index 615068dea..a1634fe10 100644 --- a/Sources/Rules/SortImports.swift +++ b/Sources/Rules/SortImports.swift @@ -15,27 +15,10 @@ public extension FormatRule { options: ["importgrouping"], sharedOptions: ["linebreaks"] ) { formatter in - func sortRanges(_ ranges: [Formatter.ImportRange]) -> [Formatter.ImportRange] { - if case .alpha = formatter.options.importGrouping { - return ranges.sorted(by: <) - } else if case .length = formatter.options.importGrouping { - return ranges.sorted { $0.module.count < $1.module.count } - } - // Group @testable imports at the top or bottom - // TODO: need more general solution for handling other import attributes - return ranges.sorted { - // If both have a @testable keyword, or neither has one, just sort alphabetically - guard $0.isTestable != $1.isTestable else { - return $0 < $1 - } - return formatter.options.importGrouping == .testableFirst ? $0.isTestable : $1.isTestable - } - } - for var importRanges in formatter.parseImports().reversed() { guard importRanges.count > 1 else { continue } let range: Range = importRanges.first!.range.lowerBound ..< importRanges.last!.range.upperBound - let sortedRanges = sortRanges(importRanges) + let sortedRanges = formatter.sortRanges(importRanges) var insertedLinebreak = false var sortedTokens = sortedRanges.flatMap { inputRange -> [Token] in var tokens = Array(formatter.tokens[inputRange.range]) @@ -52,3 +35,22 @@ public extension FormatRule { } } } + +extension Formatter { + func sortRanges(_ ranges: [Formatter.ImportRange]) -> [Formatter.ImportRange] { + if case .alpha = options.importGrouping { + return ranges.sorted(by: <) + } else if case .length = options.importGrouping { + return ranges.sorted { $0.module.count < $1.module.count } + } + // Group @testable imports at the top or bottom + // TODO: need more general solution for handling other import attributes + return ranges.sorted { + // If both have a @testable keyword, or neither has one, just sort alphabetically + guard $0.isTestable != $1.isTestable else { + return $0 < $1 + } + return options.importGrouping == .testableFirst ? $0.isTestable : $1.isTestable + } + } +} diff --git a/Sources/Rules/SortSwitchCases.swift b/Sources/Rules/SortSwitchCases.swift index ef183eae8..5f45abebc 100644 --- a/Sources/Rules/SortSwitchCases.swift +++ b/Sources/Rules/SortSwitchCases.swift @@ -23,33 +23,11 @@ public extension FormatRule { let indentCounts = switchCaseRanges.map { formatter.currentIndentForLine(at: $0.beforeDelimiterRange.lowerBound).count } let maxIndentCount = indentCounts.max() ?? 0 - func sortableValue(for token: Token) -> String? { - switch token { - case let .identifier(name): - return name - case let .stringBody(body): - return body - case let .number(value, .hex): - return Int(value.dropFirst(2), radix: 16) - .map(String.init) ?? value - case let .number(value, .octal): - return Int(value.dropFirst(2), radix: 8) - .map(String.init) ?? value - case let .number(value, .binary): - return Int(value.dropFirst(2), radix: 2) - .map(String.init) ?? value - case let .number(value, _): - return value - default: - return nil - } - } - let sorted = switchCaseRanges.sorted { case1, case2 -> Bool in let lhs = formatter.tokens[case1.beforeDelimiterRange] - .compactMap(sortableValue) + .compactMap(formatter.sortableValue) let rhs = formatter.tokens[case2.beforeDelimiterRange] - .compactMap(sortableValue) + .compactMap(formatter.sortableValue) for (lhs, rhs) in zip(lhs, rhs) { switch lhs.localizedStandardCompare(rhs) { case .orderedAscending: @@ -90,3 +68,94 @@ public extension FormatRule { } } } + +extension Formatter { + struct SwitchCaseRange { + let beforeDelimiterRange: Range + let delimiterToken: Token + let afterDelimiterRange: Range + } + + func parseSwitchCaseRanges() -> [[SwitchCaseRange]] { + var result: [[SwitchCaseRange]] = [] + + forEach(.endOfScope("case")) { i, _ in + var switchCaseRanges: [SwitchCaseRange] = [] + guard let lastDelimiterIndex = index(of: .startOfScope(":"), after: i), + let endIndex = index(after: lastDelimiterIndex, where: { $0.isLinebreak }) else { return } + + var idx = i + while idx < endIndex, + let startOfCaseIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: idx), + let delimiterIndex = index(after: idx, where: { + $0 == .delimiter(",") || $0 == .startOfScope(":") + }), + let delimiterToken = token(at: delimiterIndex), + let endOfCaseIndex = lastIndex( + of: .nonSpaceOrCommentOrLinebreak, + in: startOfCaseIndex ..< delimiterIndex + ) + { + let afterDelimiterRange: Range + + let startOfCommentIdx = delimiterIndex + 1 + if startOfCommentIdx <= endIndex, + token(at: startOfCommentIdx)?.isSpaceOrCommentOrLinebreak == true, + let nextNonSpaceOrComment = index(of: .nonSpaceOrComment, after: startOfCommentIdx) + { + if token(at: startOfCommentIdx)?.isLinebreak == true + || token(at: nextNonSpaceOrComment)?.isSpaceOrCommentOrLinebreak == false + { + afterDelimiterRange = startOfCommentIdx ..< (startOfCommentIdx + 1) + } else if endIndex > startOfCommentIdx { + afterDelimiterRange = startOfCommentIdx ..< (nextNonSpaceOrComment + 1) + } else { + afterDelimiterRange = endIndex ..< (endIndex + 1) + } + } else { + afterDelimiterRange = 0 ..< 0 + } + + let switchCaseRange = SwitchCaseRange( + beforeDelimiterRange: Range(startOfCaseIndex ... endOfCaseIndex), + delimiterToken: delimiterToken, + afterDelimiterRange: afterDelimiterRange + ) + + switchCaseRanges.append(switchCaseRange) + + if afterDelimiterRange.isEmpty { + idx = delimiterIndex + } else if afterDelimiterRange.count > 1 { + idx = afterDelimiterRange.upperBound + } else { + idx = afterDelimiterRange.lowerBound + } + } + result.append(switchCaseRanges) + } + return result + } + + func sortableValue(for token: Token) -> String? { + switch token { + case let .identifier(name): + return name + case let .stringBody(body): + return body + case let .number(value, .hex): + return Int(value.dropFirst(2), radix: 16) + .map(String.init) ?? value + case let .number(value, .octal): + return Int(value.dropFirst(2), radix: 8) + .map(String.init) ?? value + case let .number(value, .binary): + return Int(value.dropFirst(2), radix: 2) + .map(String.init) ?? value + case let .number(value, _): + return value + default: + return nil + } + } +} diff --git a/Sources/Rules/SpaceAroundParens.swift b/Sources/Rules/SpaceAroundParens.swift index 17ca42623..e396ccd12 100644 --- a/Sources/Rules/SpaceAroundParens.swift +++ b/Sources/Rules/SpaceAroundParens.swift @@ -20,42 +20,13 @@ public extension FormatRule { static let spaceAroundParens = FormatRule( help: "Add or remove space around parentheses." ) { formatter in - func spaceAfter(_ keywordOrAttribute: String, index: Int) -> Bool { - switch keywordOrAttribute { - case "@autoclosure": - if formatter.options.swiftVersion < "3", - let nextIndex = formatter.index(of: .nonSpaceOrLinebreak, after: index), - formatter.next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("escaping") - { - assert(formatter.tokens[nextIndex] == .startOfScope("(")) - return false - } - return true - case "@escaping", "@noescape", "@Sendable": - return true - case _ where keywordOrAttribute.hasPrefix("@"): - if let i = formatter.index(of: .startOfScope("("), after: index) { - return formatter.isParameterList(at: i) - } - return false - case "private", "fileprivate", "internal", - "init", "subscript", "throws": - return false - case "await": - return formatter.options.swiftVersion >= "5.5" || - formatter.options.swiftVersion == .undefined - default: - return keywordOrAttribute.first.map { !"@#".contains($0) } ?? true - } - } - formatter.forEach(.startOfScope("(")) { i, _ in let index = i - 1 guard let prevToken = formatter.token(at: index) else { return } switch prevToken { - case let .keyword(string) where spaceAfter(string, index: index): + case let .keyword(string) where formatter.spaceAfter(string, index: index): fallthrough case .endOfScope("]") where formatter.isInClosureArguments(at: index), .endOfScope(")") where formatter.isAttribute(at: index), @@ -79,7 +50,7 @@ public extension FormatRule { .identifier("isolated") where formatter.isTypePosition(at: index), .identifier("sending") where formatter.isTypePosition(at: index): break - case let .keyword(string) where !spaceAfter(string, index: index): + case let .keyword(string) where !formatter.spaceAfter(string, index: index): fallthrough case .number, .identifier: fallthrough @@ -109,3 +80,34 @@ public extension FormatRule { } } } + +extension Formatter { + func spaceAfter(_ keywordOrAttribute: String, index: Int) -> Bool { + switch keywordOrAttribute { + case "@autoclosure": + if options.swiftVersion < "3", + let nextIndex = self.index(of: .nonSpaceOrLinebreak, after: index), + next(.nonSpaceOrCommentOrLinebreak, after: nextIndex) == .identifier("escaping") + { + assert(tokens[nextIndex] == .startOfScope("(")) + return false + } + return true + case "@escaping", "@noescape", "@Sendable": + return true + case _ where keywordOrAttribute.hasPrefix("@"): + if let i = self.index(of: .startOfScope("("), after: index) { + return isParameterList(at: i) + } + return false + case "private", "fileprivate", "internal", + "init", "subscript", "throws": + return false + case "await": + return options.swiftVersion >= "5.5" || + options.swiftVersion == .undefined + default: + return keywordOrAttribute.first.map { !"@#".contains($0) } ?? true + } + } +} diff --git a/Sources/Rules/UnusedArguments.swift b/Sources/Rules/UnusedArguments.swift index 4db129b8e..55091c8b2 100644 --- a/Sources/Rules/UnusedArguments.swift +++ b/Sources/Rules/UnusedArguments.swift @@ -16,139 +16,6 @@ public extension FormatRule { ) { formatter in guard !formatter.options.fragment else { return } - func removeUsed(from argNames: inout [String], with associatedData: inout [T], - locals: Set = [], in range: CountableRange) - { - var isDeclaration = false - var wasDeclaration = false - var isConditional = false - var isGuard = false - var locals = locals - var tempLocals = Set() - func pushLocals() { - if isDeclaration, isConditional { - for name in tempLocals { - if let index = argNames.firstIndex(of: name), - !locals.contains(name) - { - argNames.remove(at: index) - associatedData.remove(at: index) - } - } - } - wasDeclaration = isDeclaration - isDeclaration = false - locals.formUnion(tempLocals) - tempLocals.removeAll() - } - var i = range.lowerBound - while i < range.upperBound { - if formatter.isStartOfStatement(at: i, treatingCollectionKeysAsStart: false), - // Immediately following an `=` operator, if or switch keywords - // are expressions rather than statements. - formatter.lastToken(before: i, where: { !$0.isSpaceOrCommentOrLinebreak })?.isOperator("=") != true - { - pushLocals() - wasDeclaration = false - } - let token = formatter.tokens[i] - outer: switch token { - case .keyword("guard"): - isGuard = true - case .keyword("let"), .keyword("var"), .keyword("func"), .keyword("for"): - isDeclaration = true - var i = i - while let scopeStart = formatter.index(of: .startOfScope("("), before: i) { - i = scopeStart - } - isConditional = formatter.isConditionalStatement(at: i) - case .identifier: - let name = token.unescaped() - guard let index = argNames.firstIndex(of: name), !locals.contains(name) else { - break - } - if formatter.last(.nonSpaceOrCommentOrLinebreak, before: i)?.isOperator(".") == false, - formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":") || - [.startOfScope("("), .startOfScope("[")].contains(formatter.currentScope(at: i) ?? .space("")) - { - if isDeclaration { - switch formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) { - case .endOfScope(")")?, .operator("=", .infix)?, - .delimiter(",")? where !isConditional: - tempLocals.insert(name) - break outer - default: - break - } - } - argNames.remove(at: index) - associatedData.remove(at: index) - if argNames.isEmpty { - return - } - } - case .startOfScope("{"): - guard let endIndex = formatter.endOfScope(at: i) else { - return formatter.fatalError("Expected }", at: i) - } - if formatter.isStartOfClosure(at: i) { - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - } else if isGuard { - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - pushLocals() - } else { - let prevLocals = locals - pushLocals() - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - locals = prevLocals - } - - isGuard = false - i = endIndex - case .endOfScope("case"), .endOfScope("default"): - pushLocals() - guard let colonIndex = formatter.index(of: .startOfScope(":"), after: i) else { - return formatter.fatalError("Expected :", at: i) - } - guard let endIndex = formatter.endOfScope(at: colonIndex) else { - return formatter.fatalError("Expected end of case statement", - at: colonIndex) - } - removeUsed(from: &argNames, with: &associatedData, - locals: locals, in: i + 1 ..< endIndex) - i = endIndex - 1 - case .operator("=", .infix), .delimiter(":"), .startOfScope(":"), - .keyword("in"), .keyword("where"): - wasDeclaration = isDeclaration - isDeclaration = false - case .delimiter(","): - if let scope = formatter.currentScope(at: i), [ - .startOfScope("("), .startOfScope("["), .startOfScope("<"), - ].contains(scope) { - break - } - if isConditional { - if isGuard, wasDeclaration { - pushLocals() - } - wasDeclaration = false - } else { - let _wasDeclaration = wasDeclaration - pushLocals() - isDeclaration = _wasDeclaration - } - case .delimiter(";"): - pushLocals() - wasDeclaration = false - default: - break - } - i += 1 - } - } // Closure arguments formatter.forEach(.keyword("in")) { i, _ in var argNames = [String]() @@ -214,7 +81,7 @@ public extension FormatRule { guard !argNames.isEmpty, let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: i) else { return } - removeUsed(from: &argNames, with: &nameIndexPairs, in: i + 1 ..< bodyEndIndex) + formatter.removeUsed(from: &argNames, with: &nameIndexPairs, in: i + 1 ..< bodyEndIndex) for pair in nameIndexPairs { if case .identifier("_") = formatter.tokens[pair.0], pair.0 != pair.1 { formatter.removeToken(at: pair.1) @@ -284,7 +151,7 @@ public extension FormatRule { let bodyEndIndex = formatter.index(of: .endOfScope("}"), after: bodyStartIndex) else { return } - removeUsed(from: &argNames, with: &nameIndexPairs, in: bodyStartIndex + 1 ..< bodyEndIndex) + formatter.removeUsed(from: &argNames, with: &nameIndexPairs, in: bodyStartIndex + 1 ..< bodyEndIndex) for pair in nameIndexPairs.reversed() { if pair.0 == pair.1 { if isOperator { @@ -305,3 +172,139 @@ public extension FormatRule { } } } + +extension Formatter { + func removeUsed(from argNames: inout [String], with associatedData: inout [T], + locals: Set = [], in range: CountableRange) + { + var isDeclaration = false + var wasDeclaration = false + var isConditional = false + var isGuard = false + var locals = locals + var tempLocals = Set() + func pushLocals() { + if isDeclaration, isConditional { + for name in tempLocals { + if let index = argNames.firstIndex(of: name), + !locals.contains(name) + { + argNames.remove(at: index) + associatedData.remove(at: index) + } + } + } + wasDeclaration = isDeclaration + isDeclaration = false + locals.formUnion(tempLocals) + tempLocals.removeAll() + } + var i = range.lowerBound + while i < range.upperBound { + if isStartOfStatement(at: i, treatingCollectionKeysAsStart: false), + // Immediately following an `=` operator, if or switch keywords + // are expressions rather than statements. + lastToken(before: i, where: { !$0.isSpaceOrCommentOrLinebreak })?.isOperator("=") != true + { + pushLocals() + wasDeclaration = false + } + let token = tokens[i] + outer: switch token { + case .keyword("guard"): + isGuard = true + case .keyword("let"), .keyword("var"), .keyword("func"), .keyword("for"): + isDeclaration = true + var i = i + while let scopeStart = index(of: .startOfScope("("), before: i) { + i = scopeStart + } + isConditional = isConditionalStatement(at: i) + case .identifier: + let name = token.unescaped() + guard let index = argNames.firstIndex(of: name), !locals.contains(name) else { + break + } + if last(.nonSpaceOrCommentOrLinebreak, before: i)?.isOperator(".") == false, + next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":") || + [.startOfScope("("), .startOfScope("[")].contains(currentScope(at: i) ?? .space("")) + { + if isDeclaration { + switch next(.nonSpaceOrCommentOrLinebreak, after: i) { + case .endOfScope(")")?, .operator("=", .infix)?, + .delimiter(",")? where !isConditional: + tempLocals.insert(name) + break outer + default: + break + } + } + argNames.remove(at: index) + associatedData.remove(at: index) + if argNames.isEmpty { + return + } + } + case .startOfScope("{"): + guard let endIndex = endOfScope(at: i) else { + return fatalError("Expected }", at: i) + } + if isStartOfClosure(at: i) { + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + } else if isGuard { + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + pushLocals() + } else { + let prevLocals = locals + pushLocals() + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + locals = prevLocals + } + + isGuard = false + i = endIndex + case .endOfScope("case"), .endOfScope("default"): + pushLocals() + guard let colonIndex = index(of: .startOfScope(":"), after: i) else { + return fatalError("Expected :", at: i) + } + guard let endIndex = endOfScope(at: colonIndex) else { + return fatalError("Expected end of case statement", + at: colonIndex) + } + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) + i = endIndex - 1 + case .operator("=", .infix), .delimiter(":"), .startOfScope(":"), + .keyword("in"), .keyword("where"): + wasDeclaration = isDeclaration + isDeclaration = false + case .delimiter(","): + if let scope = currentScope(at: i), [ + .startOfScope("("), .startOfScope("["), .startOfScope("<"), + ].contains(scope) { + break + } + if isConditional { + if isGuard, wasDeclaration { + pushLocals() + } + wasDeclaration = false + } else { + let _wasDeclaration = wasDeclaration + pushLocals() + isDeclaration = _wasDeclaration + } + case .delimiter(";"): + pushLocals() + wasDeclaration = false + default: + break + } + i += 1 + } + } +} diff --git a/Sources/Rules/Wrap.swift b/Sources/Rules/Wrap.swift index 4f431b8d9..328272b92 100644 --- a/Sources/Rules/Wrap.swift +++ b/Sources/Rules/Wrap.swift @@ -66,3 +66,123 @@ public extension FormatRule { wrapSingleArguments: true) } } + +extension Formatter { + /// Returns the index where the `wrap` rule should add the next linebreak in the line at the selected index. + /// + /// If the line does not need to be wrapped, this will return `nil`. + /// + /// - Note: This checks the entire line from the start of the line, the linebreak may be an index preceding the + /// `index` passed to the function. + func indexWhereLineShouldWrapInLine(at index: Int) -> Int? { + indexWhereLineShouldWrap(from: startOfLine(at: index, excludingIndent: true)) + } + + func indexWhereLineShouldWrap(from index: Int) -> Int? { + var lineLength = self.lineLength(upTo: index) + var stringLiteralDepth = 0 + var currentPriority = 0 + var lastBreakPoint: Int? + var lastBreakPointPriority = Int.min + + let maxWidth = options.maxWidth + guard maxWidth > 0 else { return nil } + + func addBreakPoint(at i: Int, relativePriority: Int) { + guard stringLiteralDepth == 0, currentPriority + relativePriority >= lastBreakPointPriority, + !isInClosureArguments(at: i + 1) + else { + return + } + let i = self.index(of: .nonSpace, before: i + 1) ?? i + if token(at: i + 1)?.isLinebreak == true || token(at: i)?.isLinebreak == true { + return + } + lastBreakPoint = i + lastBreakPointPriority = currentPriority + relativePriority + } + + var i = index + let endIndex = endOfLine(at: index) + while i < endIndex { + var token = tokens[i] + switch token { + case .linebreak: + return nil + case .keyword("#colorLiteral"), .keyword("#imageLiteral"): + guard let startIndex = self.index(of: .startOfScope("("), after: i), + let endIndex = endOfScope(at: startIndex) + else { + return nil // error + } + token = .space(spaceEquivalentToTokens(from: i, upTo: endIndex + 1)) // hack to get correct length + i = endIndex + case let .delimiter(string) where options.noWrapOperators.contains(string), + let .operator(string, .infix) where options.noWrapOperators.contains(string): + // TODO: handle as/is + break + case .delimiter(","): + addBreakPoint(at: i, relativePriority: 0) + case .operator("=", .infix) where self.token(at: i + 1)?.isSpace == true: + addBreakPoint(at: i, relativePriority: -9) + case .operator(".", .infix): + addBreakPoint(at: i - 1, relativePriority: -2) + case .operator("->", .infix): + if isInReturnType(at: i) { + currentPriority -= 5 + } + addBreakPoint(at: i - 1, relativePriority: -5) + case .operator(_, .infix) where self.token(at: i + 1)?.isSpace == true: + addBreakPoint(at: i, relativePriority: -3) + case .startOfScope("{"): + if !isStartOfClosure(at: i) || + next(.keyword, after: i) != .keyword("in"), + next(.nonSpace, after: i) != .endOfScope("}") + { + addBreakPoint(at: i, relativePriority: -6) + } + if isInReturnType(at: i) { + currentPriority += 5 + } + currentPriority -= 6 + case .endOfScope("}"): + currentPriority += 6 + if last(.nonSpace, before: i) != .startOfScope("{") { + addBreakPoint(at: i - 1, relativePriority: -6) + } + case .startOfScope("("): + currentPriority -= 7 + case .endOfScope(")"): + currentPriority += 7 + case .startOfScope("["): + currentPriority -= 8 + case .endOfScope("]"): + currentPriority += 8 + case .startOfScope("<"): + currentPriority -= 9 + case .endOfScope(">"): + currentPriority += 9 + case .startOfScope where token.isStringDelimiter: + stringLiteralDepth += 1 + case .endOfScope where token.isStringDelimiter: + stringLiteralDepth -= 1 + case .keyword("else"), .keyword("where"): + addBreakPoint(at: i - 1, relativePriority: -1) + case .keyword("in"): + if last(.keyword, before: i) == .keyword("for") { + addBreakPoint(at: i, relativePriority: -11) + break + } + addBreakPoint(at: i, relativePriority: -5 - currentPriority) + default: + break + } + lineLength += tokenLength(token) + if lineLength > maxWidth, let breakPoint = lastBreakPoint, breakPoint < i { + return breakPoint + } + i += 1 + } + return nil + } +} diff --git a/Sources/Rules/WrapAttributes.swift b/Sources/Rules/WrapAttributes.swift index 3a0c8451f..3584614ae 100644 --- a/Sources/Rules/WrapAttributes.swift +++ b/Sources/Rules/WrapAttributes.swift @@ -111,3 +111,37 @@ public extension FormatRule { } } } + +extension Formatter { + /// Whether or not the attribute starting at the given index is complex. That is, has: + /// - any named arguments + /// - more than one unnamed argument + func isComplexAttribute(at attributeIndex: Int) -> Bool { + assert(tokens[attributeIndex].string.hasPrefix("@")) + + guard let startOfScopeIndex = index(of: .nonSpaceOrCommentOrLinebreak, after: attributeIndex), + tokens[startOfScopeIndex] == .startOfScope("("), + let firstTokenInBody = index(of: .nonSpaceOrCommentOrLinebreak, after: startOfScopeIndex), + let endOfScopeIndex = endOfScope(at: startOfScopeIndex), + firstTokenInBody != endOfScopeIndex + else { return false } + + // If the first argument is named with a parameter label, then this is a complex attribute: + if tokens[firstTokenInBody].isIdentifierOrKeyword, + let followingToken = index(of: .nonSpaceOrCommentOrLinebreak, after: firstTokenInBody), + tokens[followingToken] == .delimiter(":") + { + return true + } + + // If there are any commas in the attribute body, then this attribute has + // multiple arguments and is thus complex: + for index in startOfScopeIndex ... endOfScopeIndex { + if tokens[index] == .delimiter(","), startOfScope(at: index) == startOfScopeIndex { + return true + } + } + + return false + } +} diff --git a/Sources/Rules/WrapEnumCases.swift b/Sources/Rules/WrapEnumCases.swift index ef330f5ff..39ef101b2 100644 --- a/Sources/Rules/WrapEnumCases.swift +++ b/Sources/Rules/WrapEnumCases.swift @@ -16,24 +16,8 @@ public extension FormatRule { options: ["wrapenumcases"], sharedOptions: ["linebreaks"] ) { formatter in - - func shouldWrapCaseRangeGroup(_ caseRangeGroup: [Formatter.EnumCaseRange]) -> Bool { - guard let firstIndex = caseRangeGroup.first?.value.lowerBound, - let scopeStart = formatter.startOfScope(at: firstIndex), - formatter.tokens[scopeStart ..< firstIndex].contains(where: { $0.isLinebreak }) - else { - // Don't wrap if first case is on same line as opening `{` - return false - } - return formatter.options.wrapEnumCases == .always || caseRangeGroup.contains(where: { - formatter.tokens[$0.value].contains(where: { - [.startOfScope("("), .operator("=", .infix)].contains($0) - }) - }) - } - formatter.parseEnumCaseRanges() - .filter(shouldWrapCaseRangeGroup) + .filter(formatter.shouldWrapCaseRangeGroup) .flatMap { $0 } .filter { $0.endOfCaseRangeToken == .delimiter(",") } .reversed() @@ -68,3 +52,64 @@ public extension FormatRule { } } } + +extension Formatter { + struct EnumCaseRange: Comparable { + let value: Range + let endOfCaseRangeToken: Token + + static func < (lhs: Formatter.EnumCaseRange, rhs: Formatter.EnumCaseRange) -> Bool { + lhs.value.lowerBound < rhs.value.lowerBound + } + } + + func parseEnumCaseRanges() -> [[EnumCaseRange]] { + var indexedRanges: [Int: [EnumCaseRange]] = [:] + + forEach(.keyword("case")) { i, _ in + guard isEnumCase(at: i) else { return } + + var idx = i + while let starOfCaseRangeIdx = index(of: .identifier, after: idx), + lastSignificantKeyword(at: starOfCaseRangeIdx) == "case", + let lastCaseIndex = lastIndex(of: .keyword("case"), in: i ..< starOfCaseRangeIdx), + lastCaseIndex == i, + let endOfCaseRangeIdx = index( + after: starOfCaseRangeIdx, + where: { $0 == .delimiter(",") || $0.isLinebreak } + ), + let endOfCaseRangeToken = token(at: endOfCaseRangeIdx) + { + let startOfScopeIdx = index(of: .startOfScope, before: starOfCaseRangeIdx) ?? 0 + + var indexedCase = indexedRanges[startOfScopeIdx, default: []] + indexedCase.append( + EnumCaseRange( + value: starOfCaseRangeIdx ..< endOfCaseRangeIdx, + endOfCaseRangeToken: endOfCaseRangeToken + ) + ) + indexedRanges[startOfScopeIdx] = indexedCase + + idx = endOfCaseRangeIdx + } + } + + return Array(indexedRanges.values) + } + + func shouldWrapCaseRangeGroup(_ caseRangeGroup: [Formatter.EnumCaseRange]) -> Bool { + guard let firstIndex = caseRangeGroup.first?.value.lowerBound, + let scopeStart = startOfScope(at: firstIndex), + tokens[scopeStart ..< firstIndex].contains(where: { $0.isLinebreak }) + else { + // Don't wrap if first case is on same line as opening `{` + return false + } + return options.wrapEnumCases == .always || caseRangeGroup.contains(where: { + tokens[$0.value].contains(where: { + [.startOfScope("("), .operator("=", .infix)].contains($0) + }) + }) + } +} diff --git a/Sources/Rules/YodaConditions.swift b/Sources/Rules/YodaConditions.swift index 46b6c8b27..10629e5e3 100644 --- a/Sources/Rules/YodaConditions.swift +++ b/Sources/Rules/YodaConditions.swift @@ -14,126 +14,131 @@ public extension FormatRule { help: "Prefer constant values to be on the right-hand-side of expressions.", options: ["yodaswap"] ) { formatter in - func valuesInRangeAreConstant(_ range: CountableRange) -> Bool { - var index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: range) - while var i = index { - switch formatter.tokens[i] { - case .startOfScope where isConstant(at: i): - guard let endIndex = formatter.index(of: .endOfScope, after: i) else { - return false - } - i = endIndex - fallthrough - case _ where isConstant(at: i), .delimiter(","), .delimiter(":"): - index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< range.upperBound) - case .identifier: - guard let nextIndex = - formatter.index(of: .nonSpaceOrComment, in: i + 1 ..< range.upperBound), - formatter.tokens[nextIndex] == .delimiter(":") - else { - return false - } - // Identifier is a label - index = nextIndex - default: - return false - } + formatter.forEachToken { i, token in + guard case let .operator(op, .infix) = token, + let opIndex = ["==", "!=", "<", "<=", ">", ">="].firstIndex(of: op), + let prevIndex = formatter.index(of: .nonSpace, before: i), + formatter.isConstant(at: prevIndex), let startIndex = formatter.startOfValue(at: prevIndex), + !formatter.isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex)), + let nextIndex = formatter.index(of: .nonSpace, after: i), !formatter.isConstant(at: nextIndex) || + formatter.isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nextIndex)), + let endIndex = formatter.endOfExpression(at: nextIndex, upTo: [ + .operator("&&", .infix), .operator("||", .infix), + .operator("?", .infix), .operator(":", .infix), + ]) + else { + return } - return true + let inverseOp = ["==", "!=", ">", ">=", "<", "<="][opIndex] + let expression = Array(formatter.tokens[nextIndex ... endIndex]) + let constant = Array(formatter.tokens[startIndex ... prevIndex]) + formatter.replaceTokens(in: nextIndex ... endIndex, with: constant) + formatter.replaceToken(at: i, with: .operator(inverseOp, .infix)) + formatter.replaceTokens(in: startIndex ... prevIndex, with: expression) } - func isConstant(at index: Int) -> Bool { - var index = index - while case .operator(_, .postfix) = formatter.tokens[index] { - index -= 1 - } - guard let token = formatter.token(at: index) else { - return false - } - switch token { - case .number, .identifier("true"), .identifier("false"), .identifier("nil"): - return true - case .endOfScope("]"), .endOfScope(")"): - guard let startIndex = formatter.index(of: .startOfScope, before: index), - !formatter.isSubscriptOrFunctionCall(at: startIndex) - else { + } +} + +extension Formatter { + func valuesInRangeAreConstant(_ range: CountableRange) -> Bool { + var index = self.index(of: .nonSpaceOrCommentOrLinebreak, in: range) + while var i = index { + switch tokens[i] { + case .startOfScope where isConstant(at: i): + guard let endIndex = self.index(of: .endOfScope, after: i) else { return false } - return valuesInRangeAreConstant(startIndex + 1 ..< index) - case .startOfScope("["), .startOfScope("("): - guard !formatter.isSubscriptOrFunctionCall(at: index), - let endIndex = formatter.index(of: .endOfScope, after: index) + i = endIndex + fallthrough + case _ where isConstant(at: i), .delimiter(","), .delimiter(":"): + index = self.index(of: .nonSpaceOrCommentOrLinebreak, in: i + 1 ..< range.upperBound) + case .identifier: + guard let nextIndex = + self.index(of: .nonSpaceOrComment, in: i + 1 ..< range.upperBound), + tokens[nextIndex] == .delimiter(":") else { return false } - return valuesInRangeAreConstant(index + 1 ..< endIndex) - case .startOfScope, .endOfScope: - // TODO: what if string contains interpolation? - return token.isStringDelimiter - case _ where formatter.options.yodaSwap == .literalsOnly: - // Don't treat .members as constant - return false - case .operator(".", .prefix) where formatter.token(at: index + 1)?.isIdentifier == true, - .identifier where formatter.token(at: index - 1) == .operator(".", .prefix) && - formatter.token(at: index - 2) != .operator("\\", .prefix): - return true + // Identifier is a label + index = nextIndex default: return false } } - func isOperator(at index: Int?) -> Bool { - guard let index = index else { + return true + } + + func isConstant(at index: Int) -> Bool { + var index = index + while case .operator(_, .postfix) = tokens[index] { + index -= 1 + } + guard let token = token(at: index) else { + return false + } + switch token { + case .number, .identifier("true"), .identifier("false"), .identifier("nil"): + return true + case .endOfScope("]"), .endOfScope(")"): + guard let startIndex = self.index(of: .startOfScope, before: index), + !isSubscriptOrFunctionCall(at: startIndex) + else { return false } - switch formatter.tokens[index] { - // Discount operators with higher precedence than == - case .operator("=", .infix), - .operator("&&", .infix), .operator("||", .infix), - .operator("?", .infix), .operator(":", .infix): - return false - case .operator(_, .infix), .keyword("as"), .keyword("is"): - return true - default: + return valuesInRangeAreConstant(startIndex + 1 ..< index) + case .startOfScope("["), .startOfScope("("): + guard !isSubscriptOrFunctionCall(at: index), + let endIndex = self.index(of: .endOfScope, after: index) + else { return false } + return valuesInRangeAreConstant(index + 1 ..< endIndex) + case .startOfScope, .endOfScope: + // TODO: what if string contains interpolation? + return token.isStringDelimiter + case _ where options.yodaSwap == .literalsOnly: + // Don't treat .members as constant + return false + case .operator(".", .prefix) where self.token(at: index + 1)?.isIdentifier == true, + .identifier where self.token(at: index - 1) == .operator(".", .prefix) && + self.token(at: index - 2) != .operator("\\", .prefix): + return true + default: + return false } - func startOfValue(at index: Int) -> Int? { - var index = index - while case .operator(_, .postfix)? = formatter.token(at: index) { - index -= 1 - } - if case .endOfScope? = formatter.token(at: index) { - guard let i = formatter.index(of: .startOfScope, before: index) else { - return nil - } - index = i - } - while case .operator(_, .prefix)? = formatter.token(at: index - 1) { - index -= 1 - } - return index + } + + func isOperator(at index: Int?) -> Bool { + guard let index = index else { + return false + } + switch tokens[index] { + // Discount operators with higher precedence than == + case .operator("=", .infix), + .operator("&&", .infix), .operator("||", .infix), + .operator("?", .infix), .operator(":", .infix): + return false + case .operator(_, .infix), .keyword("as"), .keyword("is"): + return true + default: + return false } + } - formatter.forEachToken { i, token in - guard case let .operator(op, .infix) = token, - let opIndex = ["==", "!=", "<", "<=", ">", ">="].firstIndex(of: op), - let prevIndex = formatter.index(of: .nonSpace, before: i), - isConstant(at: prevIndex), let startIndex = startOfValue(at: prevIndex), - !isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: startIndex)), - let nextIndex = formatter.index(of: .nonSpace, after: i), !isConstant(at: nextIndex) || - isOperator(at: formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: nextIndex)), - let endIndex = formatter.endOfExpression(at: nextIndex, upTo: [ - .operator("&&", .infix), .operator("||", .infix), - .operator("?", .infix), .operator(":", .infix), - ]) - else { - return + func startOfValue(at index: Int) -> Int? { + var index = index + while case .operator(_, .postfix)? = token(at: index) { + index -= 1 + } + if case .endOfScope? = token(at: index) { + guard let i = self.index(of: .startOfScope, before: index) else { + return nil } - let inverseOp = ["==", "!=", ">", ">=", "<", "<="][opIndex] - let expression = Array(formatter.tokens[nextIndex ... endIndex]) - let constant = Array(formatter.tokens[startIndex ... prevIndex]) - formatter.replaceTokens(in: nextIndex ... endIndex, with: constant) - formatter.replaceToken(at: i, with: .operator(inverseOp, .infix)) - formatter.replaceTokens(in: startIndex ... prevIndex, with: expression) + index = i + } + while case .operator(_, .prefix)? = token(at: index - 1) { + index -= 1 } + return index } } diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index facc311ef..586f84a0e 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -999,7 +999,7 @@ 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSingleLineCommentsTests.swift; sourceTree = ""; }; 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; 580496D42C584E8F004B7DBF /* EmptyExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyExtension.swift; sourceTree = ""; }; - 580496D92C584F24004B7DBF /* EmptyExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EmptyExtensionTests.swift; path = Tests/Rules/EmptyExtensionTests.swift; sourceTree = ""; }; + 580496D92C584F24004B7DBF /* EmptyExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyExtensionTests.swift; sourceTree = ""; }; 90C4B6CA1DA4B04A009EB000 /* SwiftFormat for Xcode.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SwiftFormat for Xcode.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 90C4B6CC1DA4B04A009EB000 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 90C4B6D01DA4B04A009EB000 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -1107,7 +1107,6 @@ 01A0EA9A1D5DB4CF00A0A8E3 = { isa = PBXGroup; children = ( - 580496D92C584F24004B7DBF /* EmptyExtensionTests.swift */, 01A0EACB1D5DB5F500A0A8E3 /* CommandLineTool */, 90C4B6F01DA4B0BF009EB000 /* EditorExtension */, 01A0EAA61D5DB4CF00A0A8E3 /* Sources */, @@ -1350,6 +1349,7 @@ 2E8DE6942C57FEB30032BF25 /* DuplicateImportsTests.swift */, 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */, 2E8DE6B62C57FEB30032BF25 /* EmptyBracesTests.swift */, + 580496D92C584F24004B7DBF /* EmptyExtensionTests.swift */, 2E8DE6972C57FEB30032BF25 /* EnumNamespacesTests.swift */, 2E8DE6992C57FEB30032BF25 /* ExtensionAccessControlTests.swift */, 2E8DE6962C57FEB30032BF25 /* FileHeaderTests.swift */, diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index b1cf64176..33621de0f 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -174,7 +174,6 @@ class MetadataTests: XCTestCase { guard formatter.next(.nonSpaceOrLinebreak, after: i) == .startOfScope("("), case let .identifier(name)? = formatter.last(.identifier, before: i), let scopeStart = formatter.index(of: .startOfScope("{"), after: i), - let scopeEnd = formatter.index(of: .endOfScope("}"), after: scopeStart), let rule = FormatRules.byName[name] else { return @@ -189,13 +188,22 @@ class MetadataTests: XCTestCase { allOptions.subtract(rule.options) allSharedOptions.subtract(ruleOptions) var referencedOptions = [OptionDescriptor]() - for index in scopeStart + 1 ..< scopeEnd { - guard formatter.token(at: index - 1) == .operator(".", .infix), - formatter.token(at: index - 2) == .identifier("formatter") + for index in scopeStart + 1 ..< formatter.tokens.count { + guard (formatter.token(at: index - 1) == .operator(".", .infix) + && formatter.token(at: index - 2) == .identifier("formatter")) + || (formatter.token(at: index) == .identifier("options") && formatter.token(at: index - 1)?.isOperator(".") == false) else { continue } switch formatter.tokens[index] { + // Find all of the options called via `options.optionName` + case .identifier("options") where formatter.token(at: index + 1) == .operator(".", .infix): + if case let .identifier(property)? = formatter.token(at: index + 2), + let option = optionsByProperty[property] + { + referencedOptions.append(option) + } + // Special-case shared helpers that also access options on the formatter case .identifier("spaceEquivalentToWidth"), .identifier("spaceEquivalentToTokens"): referencedOptions += [ @@ -222,40 +230,6 @@ class MetadataTests: XCTestCase { ] case .identifier("wrapStatementBody"): referencedOptions += [Descriptors.indent, Descriptors.linebreak] - case .identifier("indexWhereLineShouldWrapInLine"), .identifier("indexWhereLineShouldWrap"): - referencedOptions += [ - Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, - Descriptors.noWrapOperators, - ] - case .identifier("modifierOrder"): - referencedOptions.append(Descriptors.modifierOrder) - case .identifier("options") where formatter.token(at: index + 1) == .operator(".", .infix): - if case let .identifier(property)? = formatter.token(at: index + 2), - let option = optionsByProperty[property] - { - referencedOptions.append(option) - } - case .identifier("organizeDeclaration"): - referencedOptions += [ - Descriptors.categoryMarkComment, - Descriptors.markCategories, - Descriptors.beforeMarks, - Descriptors.lifecycleMethods, - Descriptors.organizeTypes, - Descriptors.organizeStructThreshold, - Descriptors.organizeClassThreshold, - Descriptors.organizeEnumThreshold, - Descriptors.organizeExtensionThreshold, - Descriptors.lineAfterMarks, - Descriptors.organizationMode, - Descriptors.alphabeticallySortedDeclarationPatterns, - Descriptors.visibilityOrder, - Descriptors.typeOrder, - Descriptors.customVisibilityMarks, - Descriptors.customTypeMarks, - Descriptors.blankLineAfterSubgroups, - Descriptors.alphabetizeSwiftUIPropertyTypes, - ] case .identifier("removeSelf"): referencedOptions += [ Descriptors.selfRequired, From c78035251e4c7736b366f43012516d7c2f8507e2 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Tue, 6 Aug 2024 10:27:17 -0700 Subject: [PATCH 39/52] Add --outputtokens option to print output tokens instead of source code (#1811) --- Sources/Arguments.swift | 1 + Sources/CommandLine.swift | 47 ++++++++++++++++++++++++---- Sources/Tokenizer.swift | 50 ++++++++++++++++++++++++++++-- Tests/CommandLineTests.swift | 59 ++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 8 deletions(-) diff --git a/Sources/Arguments.swift b/Sources/Arguments.swift index c6770de1a..e03c33776 100644 --- a/Sources/Arguments.swift +++ b/Sources/Arguments.swift @@ -675,6 +675,7 @@ let commandLineArguments = [ "ruleinfo", "dateformat", "timezone", + "outputtokens", ] + optionsArguments let deprecatedArguments = Descriptors.all.compactMap { diff --git a/Sources/CommandLine.swift b/Sources/CommandLine.swift index 3e31001f9..f6e658d3b 100644 --- a/Sources/CommandLine.swift +++ b/Sources/CommandLine.swift @@ -204,6 +204,7 @@ func printHelp(as type: CLI.OutputType) { --strict Emit errors for unformatted code when formatting --verbose Display detailed formatting output and warnings/errors --quiet Disables non-critical output messages and warnings + --outputtokens Outputs an array of tokens instead of text when using stdin SwiftFormat has a number of rules that can be enabled or disabled. By default most rules are enabled. Use --rules to display all enabled/disabled rules. @@ -342,6 +343,9 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in // Dry run let dryrun = lint || (args["dryrun"] != nil) + // Whether or not to output tokens instead of source code + let printTokens = args["outputtokens"] != nil + // Warnings for warning in warningsForArguments(args) { print("warning: \(warning)", as: .warning) @@ -699,17 +703,23 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in return } } - let output = try applyRules( + let outputTokens = try applyRules( input, options: options, lineRange: lineRange, verbose: verbose, lint: lint, reporter: reporter ) + let output = sourceCode(for: outputTokens) if let outputURL = outputURL, !useStdout { if !dryrun, (try? String(contentsOf: outputURL)) != output { try write(output, to: outputURL) } } else if !lint { // Write to stdout - print(dryrun ? input : output, as: .raw) + if printTokens { + let tokensToPrint = dryrun ? tokenize(input) : outputTokens + try print(OutputTokensData.encodedString(for: tokensToPrint), as: .raw) + } else { + print(dryrun ? input : output, as: .raw) + } } else if let reporterOutput = try reporter.write() { if let reportURL = reportURL { print("Writing report file to \(reportURL.path)", as: .info) @@ -911,7 +921,7 @@ func computeHash(_ source: String) -> String { } func applyRules(_ source: String, options: Options, lineRange: ClosedRange?, - verbose: Bool, lint: Bool, reporter: Reporter?) throws -> String + verbose: Bool, lint: Bool, reporter: Reporter?) throws -> [Token] { // Parse source var tokens = tokenize(source) @@ -953,7 +963,7 @@ func applyRules(_ source: String, options: Options, lineRange: ClosedRange? } // Output - return updatedSource + return tokens } func processInput(_ inputURLs: [URL], @@ -1054,8 +1064,9 @@ func processInput(_ inputURLs: [URL], print("-- no changes (cached)", as: .success) } } else { - output = try applyRules(input, options: options, lineRange: lineRange, - verbose: verbose, lint: lint, reporter: reporter) + let outputTokens = try applyRules(input, options: options, lineRange: lineRange, + verbose: verbose, lint: lint, reporter: reporter) + output = sourceCode(for: outputTokens) if output != input { sourceHash = nil } @@ -1171,3 +1182,27 @@ func processInput(_ inputURLs: [URL], } return (outputFlags, errors) } + +/// The data format used with `--outputtokens` +private struct OutputTokensData: Encodable { + init(tokens: [Token]) { + self.tokens = tokens + version = swiftFormatVersion + } + + /// The SwiftFormat version that this data originated from + let version: String + /// A representation of the output in `Tokens` + let tokens: [Token] + + /// Creates the `OutputTokensData` and encodes it to a JSON string + static func encodedString(for tokens: [Token]) throws -> String { + let outputData = OutputTokensData(tokens: tokens) + + let encoder = JSONEncoder() + encoder.outputFormatting = .sortedKeys + + let encodedData = try encoder.encode(outputData) + return String(data: encodedData, encoding: .utf8)! + } +} diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index b3af04832..828f8cb77 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -114,7 +114,7 @@ public enum TokenType { } /// Numeric literal types -public enum NumberType { +public enum NumberType: String { case integer case decimal case binary @@ -123,7 +123,7 @@ public enum NumberType { } /// Operator/operator types -public enum OperatorType { +public enum OperatorType: String { case none case infix case prefix @@ -1991,3 +1991,49 @@ public func tokenize(_ source: String) -> [Token] { return tokens } + +extension Token: Encodable { + private enum CodingKeys: CodingKey { + // Properties shared by all tokens + case type + case string + // Properties unique to individual tokens + case originalLine + case numberType + case operatorType + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(typeName, forKey: .type) + try container.encode(string, forKey: .string) + + switch self { + case let .linebreak(_, originalLine): + try container.encode(originalLine, forKey: .originalLine) + case let .number(_, numberType): + try container.encode(numberType.rawValue, forKey: .numberType) + case let .operator(_, operatorType): + try container.encode(operatorType.rawValue, forKey: .operatorType) + default: + break + } + } + + private var typeName: String { + switch self { + case .number: return "number" + case .linebreak: return "linebreak" + case .startOfScope: return "startOfScope" + case .endOfScope: return "endOfScope" + case .delimiter: return "delimiter" + case .operator: return "operator" + case .stringBody: return "stringBody" + case .keyword: return "keyword" + case .identifier: return "identifier" + case .space: return "space" + case .commentBody: return "commentBody" + case .error: return "error" + } + } +} diff --git a/Tests/CommandLineTests.swift b/Tests/CommandLineTests.swift index dee617d14..8ab5d25f5 100644 --- a/Tests/CommandLineTests.swift +++ b/Tests/CommandLineTests.swift @@ -107,6 +107,65 @@ class CommandLineTests: XCTestCase { _ = processArguments(["", "stdin"], in: "") } + func testStdinOutputTokens() { + CLI.print = { message, type in + switch type { + case .raw, .content: + XCTAssertEqual(message, """ + {"tokens":[\ + {"string":"func","type":"keyword"},\ + {"string":" ","type":"space"},\ + {"string":"foo","type":"identifier"},\ + {"string":"(","type":"startOfScope"},\ + {"string":")","type":"endOfScope"},\ + {"string":" ","type":"space"},\ + {"string":"{","type":"startOfScope"},\ + {"originalLine":2,"string":"\\n","type":"linebreak"},\ + {"string":" ","type":"space"},\ + {"string":"bar","type":"identifier"},\ + {"string":"(","type":"startOfScope"},\ + {"string":")","type":"endOfScope"},\ + {"string":" ","type":"space"},\ + {"operatorType":"infix","string":"+","type":"operator"},\ + {"string":" ","type":"space"},\ + {"string":"baaz","type":"identifier"},\ + {"string":"(","type":"startOfScope"},\ + {"string":")","type":"endOfScope"},\ + {"string":" ","type":"space"},\ + {"operatorType":"infix","string":"+","type":"operator"},\ + {"string":" ","type":"space"},\ + {"numberType":"integer","string":"25","type":"number"},\ + {"originalLine":3,"string":"\\n","type":"linebreak"},\ + {"string":"}","type":"endOfScope"},\ + {"originalLine":4,"string":"\\n","type":"linebreak"}\ + ],"version":"\(swiftFormatVersion)"} + """) + case .error, .warning: + XCTFail() + case .info, .success: + break + } + } + var readCount = 0 + CLI.readLine = { + readCount += 1 + switch readCount { + case 1: + return "func foo()\n" + case 2: + return "{\n" + case 3: + return "bar() + baaz() + 25\n" + case 4: + return "}" + default: + return nil + } + } + + _ = processArguments(["", "stdin", "--outputtokens"], in: "") + } + func testExcludeStdinPath() throws { CLI.print = { message, type in switch type { From a4cf33bb683bd407b95cc377ea0402d929edf23f Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 8 Aug 2024 20:36:01 -0700 Subject: [PATCH 40/52] Fix issue where organizeDeclarations would add extra blank lines if type had blank lines with spaces (#1810) --- Sources/DeclarationHelpers.swift | 13 +++++- Sources/Rules/OrganizeDeclarations.swift | 4 +- Tests/Rules/OrganizeDeclarationsTests.swift | 50 +++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 608faeeb2..3df27cfec 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -344,10 +344,19 @@ extension Formatter { } // Prefer keeping linebreaks at the end of a declaration's tokens, - // instead of the start of the next delaration's tokens + // instead of the start of the next delaration's tokens. + // - This inclues any spaces on blank lines, but doesn't include the + // indentation associated with the next declaration. while let linebreakSearchIndex = endOfDeclaration, - token(at: linebreakSearchIndex + 1)?.isLinebreak == true + token(at: linebreakSearchIndex + 1)?.isSpaceOrLinebreak == true { + // Only spaces between linebreaks (e.g. spaces on blank lines) are included + if token(at: linebreakSearchIndex + 1)?.isSpace == true { + guard token(at: linebreakSearchIndex)?.isLinebreak == true, + token(at: linebreakSearchIndex + 2)?.isLinebreak == true + else { break } + } + endOfDeclaration = linebreakSearchIndex + 1 } diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift index eca1e5dc6..ae815b73d 100644 --- a/Sources/Rules/OrganizeDeclarations.swift +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -353,9 +353,7 @@ extension Formatter { func addCategorySeparators( to typeDeclaration: TypeDeclaration, sortedDeclarations: [CategorizedDeclaration] - ) - -> TypeDeclaration - { + ) -> TypeDeclaration { let numberOfCategories: Int = { switch options.organizationMode { case .visibility: diff --git a/Tests/Rules/OrganizeDeclarationsTests.swift b/Tests/Rules/OrganizeDeclarationsTests.swift index 243a51477..d0840678c 100644 --- a/Tests/Rules/OrganizeDeclarationsTests.swift +++ b/Tests/Rules/OrganizeDeclarationsTests.swift @@ -3297,4 +3297,54 @@ class OrganizeDeclarationsTests: XCTestCase { exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } + + func testDoesntAddUnexpectedBlankLinesDueToBlankLinesWithSpaces() { + // The blank lines in this input code are indented with four spaces. + // Done using string interpolation in the input code to make this + // more clear, and to prevent the spaces from being removed automatically. + let input = """ + public class TestClass { + var variable01 = 1 + var variable02 = 2 + var variable03 = 3 + var variable04 = 4 + var variable05 = 5 + \(" ") + public func foo() {} + \(" ") + func bar() {} + \(" ") + private func baz() {} + } + """ + + let output = """ + public class TestClass { + + // MARK: Public + + public func foo() {} + \(" ") + // MARK: Internal + + var variable01 = 1 + var variable02 = 2 + var variable03 = 3 + var variable04 = 4 + var variable05 = 5 + \(" ") + func bar() {} + \(" ") + // MARK: Private + + private func baz() {} + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope, .consecutiveBlankLines, .trailingSpace, .consecutiveSpaces, .indent] + ) + } } From 6310701da45042979ac106517bf0fd6551ad89f0 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sun, 11 Aug 2024 08:32:07 -0700 Subject: [PATCH 41/52] Fix issue where unusedArguments didn't handle conditional assignment shadowing (#1815) --- Sources/ParsingHelpers.swift | 10 ++++++++++ Sources/Rules/UnusedArguments.swift | 8 ++++++++ Tests/Rules/UnusedArgumentsTests.swift | 26 ++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index 0b4fe9889..5602e49ea 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -700,6 +700,16 @@ extension Formatter { startOfConditionalStatement(at: i, excluding: excluding) != nil } + /// Returns true if the token at the specified index is part of a conditional assignment + /// (e.g. an if or switch expression following an `=` token) + func isConditionalAssignment(at i: Int) -> Bool { + guard let startOfConditional = startOfConditionalStatement(at: i), + let previousToken = lastToken(before: startOfConditional, where: { !$0.isSpaceOrCommentOrLinebreak }) + else { return false } + + return previousToken.isOperator("=") + } + /// If the token at the specified index is part of a conditional statement, returns the index of the first /// token in the statement (e.g. `if`, `guard`, `while`, etc.), otherwise returns nil func startOfConditionalStatement(at i: Int, excluding: Set = []) -> Int? { diff --git a/Sources/Rules/UnusedArguments.swift b/Sources/Rules/UnusedArguments.swift index 55091c8b2..1ffb48931 100644 --- a/Sources/Rules/UnusedArguments.swift +++ b/Sources/Rules/UnusedArguments.swift @@ -245,6 +245,14 @@ extension Formatter { return } } + case .keyword("if"), .keyword("switch"): + guard isConditionalAssignment(at: i), + let conditinalBranches = conditionalBranches(at: i), + let endIndex = conditinalBranches.last?.endOfBranch + else { fallthrough } + + removeUsed(from: &argNames, with: &associatedData, + locals: locals, in: i + 1 ..< endIndex) case .startOfScope("{"): guard let endIndex = endOfScope(at: i) else { return fatalError("Expected }", at: i) diff --git a/Tests/Rules/UnusedArgumentsTests.swift b/Tests/Rules/UnusedArgumentsTests.swift index 16ce3e1e8..d91926ccb 100644 --- a/Tests/Rules/UnusedArgumentsTests.swift +++ b/Tests/Rules/UnusedArgumentsTests.swift @@ -1092,6 +1092,32 @@ class UnusedArgumentsTests: XCTestCase { testFormatting(for: input, rule: .unusedArguments) } + func testIssue1688_1() { + let input = #""" + func urlTestContains(path: String, strict _: Bool = true) -> Bool { + let path = if path.hasSuffix("/") { path } else { "\(path)/" } + + return false + } + """# + testFormatting(for: input, rule: .unusedArguments, exclude: [.wrapConditionalBodies]) + } + + func testIssue1688_2() { + let input = """ + enum Sample { + func invite(lang: String, randomValue: Int) -> String { + let flag: String? = if randomValue > 0 { "hello" } else { nil } + + let lang = if let flag { flag } else { lang } + + return lang + } + } + """ + testFormatting(for: input, rule: .unusedArguments, exclude: [.wrapConditionalBodies, .redundantProperty]) + } + func testIssue1694() { let input = """ listenForUpdates() { [weak self] update, error in From ab4d327a6a11bd50577ce620589b5c407b84f73d Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sun, 11 Aug 2024 08:33:21 -0700 Subject: [PATCH 42/52] Add tests related to code organization and rule helper usage (#1808) --- Sources/FormattingHelpers.swift | 118 ++++++++++++++ Sources/ParsingHelpers.swift | 70 ++++++++ Sources/Rules/ConditionalAssignment.swift | 6 +- Sources/Rules/EnumNamespaces.swift | 2 +- Sources/Rules/ExtensionAccessControl.swift | 4 +- Sources/Rules/ModifierOrder.swift | 4 +- Sources/Rules/OrganizeDeclarations.swift | 2 +- Sources/Rules/PreferForLoop.swift | 2 +- Sources/Rules/Void.swift | 2 +- Sources/Rules/Wrap.swift | 120 -------------- SwiftFormat.xcodeproj/project.pbxproj | 32 ++-- Tests/CodeOrganizationTests.swift | 177 +++++++++++++++++++++ Tests/CommandLineTests.swift | 3 - Tests/Info.plist | 24 --- Tests/MetadataTests.swift | 39 +---- Tests/OptionDescriptorTests.swift | 3 - Tests/ParsingHelpersTests.swift | 58 ++++++- Tests/ProjectFilePaths.swift | 50 ++++++ Tests/Rules/SpacingGuardsTests.swift | 2 +- Tests/ZRegressionTests.swift | 3 - 20 files changed, 505 insertions(+), 216 deletions(-) create mode 100644 Tests/CodeOrganizationTests.swift delete mode 100644 Tests/Info.plist create mode 100644 Tests/ProjectFilePaths.swift diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index de173b079..b045264e3 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -988,6 +988,124 @@ extension Formatter { } } + /// Returns the index where the `wrap` rule should add the next linebreak in the line at the selected index. + /// + /// If the line does not need to be wrapped, this will return `nil`. + /// + /// - Note: This checks the entire line from the start of the line, the linebreak may be an index preceding the + /// `index` passed to the function. + func indexWhereLineShouldWrapInLine(at index: Int) -> Int? { + indexWhereLineShouldWrap(from: startOfLine(at: index, excludingIndent: true)) + } + + func indexWhereLineShouldWrap(from index: Int) -> Int? { + var lineLength = self.lineLength(upTo: index) + var stringLiteralDepth = 0 + var currentPriority = 0 + var lastBreakPoint: Int? + var lastBreakPointPriority = Int.min + + let maxWidth = options.maxWidth + guard maxWidth > 0 else { return nil } + + func addBreakPoint(at i: Int, relativePriority: Int) { + guard stringLiteralDepth == 0, currentPriority + relativePriority >= lastBreakPointPriority, + !isInClosureArguments(at: i + 1) + else { + return + } + let i = self.index(of: .nonSpace, before: i + 1) ?? i + if token(at: i + 1)?.isLinebreak == true || token(at: i)?.isLinebreak == true { + return + } + lastBreakPoint = i + lastBreakPointPriority = currentPriority + relativePriority + } + + var i = index + let endIndex = endOfLine(at: index) + while i < endIndex { + var token = tokens[i] + switch token { + case .linebreak: + return nil + case .keyword("#colorLiteral"), .keyword("#imageLiteral"): + guard let startIndex = self.index(of: .startOfScope("("), after: i), + let endIndex = endOfScope(at: startIndex) + else { + return nil // error + } + token = .space(spaceEquivalentToTokens(from: i, upTo: endIndex + 1)) // hack to get correct length + i = endIndex + case let .delimiter(string) where options.noWrapOperators.contains(string), + let .operator(string, .infix) where options.noWrapOperators.contains(string): + // TODO: handle as/is + break + case .delimiter(","): + addBreakPoint(at: i, relativePriority: 0) + case .operator("=", .infix) where self.token(at: i + 1)?.isSpace == true: + addBreakPoint(at: i, relativePriority: -9) + case .operator(".", .infix): + addBreakPoint(at: i - 1, relativePriority: -2) + case .operator("->", .infix): + if isInReturnType(at: i) { + currentPriority -= 5 + } + addBreakPoint(at: i - 1, relativePriority: -5) + case .operator(_, .infix) where self.token(at: i + 1)?.isSpace == true: + addBreakPoint(at: i, relativePriority: -3) + case .startOfScope("{"): + if !isStartOfClosure(at: i) || + next(.keyword, after: i) != .keyword("in"), + next(.nonSpace, after: i) != .endOfScope("}") + { + addBreakPoint(at: i, relativePriority: -6) + } + if isInReturnType(at: i) { + currentPriority += 5 + } + currentPriority -= 6 + case .endOfScope("}"): + currentPriority += 6 + if last(.nonSpace, before: i) != .startOfScope("{") { + addBreakPoint(at: i - 1, relativePriority: -6) + } + case .startOfScope("("): + currentPriority -= 7 + case .endOfScope(")"): + currentPriority += 7 + case .startOfScope("["): + currentPriority -= 8 + case .endOfScope("]"): + currentPriority += 8 + case .startOfScope("<"): + currentPriority -= 9 + case .endOfScope(">"): + currentPriority += 9 + case .startOfScope where token.isStringDelimiter: + stringLiteralDepth += 1 + case .endOfScope where token.isStringDelimiter: + stringLiteralDepth -= 1 + case .keyword("else"), .keyword("where"): + addBreakPoint(at: i - 1, relativePriority: -1) + case .keyword("in"): + if last(.keyword, before: i) == .keyword("for") { + addBreakPoint(at: i, relativePriority: -11) + break + } + addBreakPoint(at: i, relativePriority: -5 - currentPriority) + default: + break + } + lineLength += tokenLength(token) + if lineLength > maxWidth, let breakPoint = lastBreakPoint, breakPoint < i { + return breakPoint + } + i += 1 + } + return nil + } + /// Wrap a single-line statement body onto multiple lines func wrapStatementBody(at i: Int) { assert(token(at: i) == .startOfScope("{")) diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index 5602e49ea..e9b9690e5 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -2268,6 +2268,76 @@ extension Formatter { return (equalsIndex, andTokenIndices, lastTypeEndIndex) } + + /// Parses the external parameter labels of the function with its `(` start of scope + /// token at the given index. + func parseFunctionDeclarationArgumentLabels(startOfScope: Int) -> [String?] { + assert(tokens[startOfScope] == .startOfScope("(")) + guard let endOfScope = endOfScope(at: startOfScope) else { return [] } + + var argumentLabels: [String?] = [] + + var currentIndex = startOfScope + while let nextArgumentColon = index(of: .delimiter(":"), in: (currentIndex + 1) ..< endOfScope) { + currentIndex = nextArgumentColon + + // If there is only one label, the param has the same internal and external label. + // If there are two labels, the first one is the external label. + guard let internalLabelIndex = index(of: .nonSpaceOrComment, before: nextArgumentColon), + tokens[internalLabelIndex].isIdentifier || tokens[internalLabelIndex].string == "_" + else { continue } + + var externalLabelToken = tokens[internalLabelIndex] + + if let externalLabelIndex = index(of: .nonSpaceOrComment, before: internalLabelIndex), + tokens[externalLabelIndex].isIdentifier || tokens[externalLabelIndex].string == "_" + { + externalLabelToken = tokens[externalLabelIndex] + } + + if externalLabelToken.string == "_" { + argumentLabels.append(nil) + } else { + argumentLabels.append(externalLabelToken.string) + } + } + + return argumentLabels + } + + /// Parses the parameter labels of the function call with its `(` start of scope + /// token at the given index. + func parseFunctionCallArgumentLabels(startOfScope: Int) -> [String?] { + assert(tokens[startOfScope] == .startOfScope("(")) + guard let endOfScope = endOfScope(at: startOfScope), + index(of: .nonSpaceOrCommentOrLinebreak, after: startOfScope) != endOfScope + else { return [] } + + var argumentLabels: [String?] = [] + + var currentIndex = startOfScope + repeat { + let endOfPreviousArgument = currentIndex + let endOfCurrentArgument = index(of: .delimiter(","), in: endOfPreviousArgument + 1 ..< endOfScope) ?? endOfScope + + if let colonIndex = index(of: .delimiter(":"), in: (endOfPreviousArgument + 1) ..< endOfCurrentArgument), + let argumentLabelIndex = index(of: .nonSpaceOrCommentOrLinebreak, before: colonIndex), + tokens[argumentLabelIndex].isIdentifier + { + argumentLabels.append(tokens[argumentLabelIndex].string) + } else { + argumentLabels.append(nil) + } + + if endOfCurrentArgument >= endOfScope { + break + } else { + currentIndex = endOfCurrentArgument + } + } while true + + return argumentLabels + } } extension _FormatRules { diff --git a/Sources/Rules/ConditionalAssignment.swift b/Sources/Rules/ConditionalAssignment.swift index f1593dc6a..8e0145df6 100644 --- a/Sources/Rules/ConditionalAssignment.swift +++ b/Sources/Rules/ConditionalAssignment.swift @@ -135,15 +135,13 @@ public extension FormatRule { } } -private extension Formatter { +extension Formatter { // Whether or not the conditional statement that starts at the given index // has branches that are exhaustive func conditionalBranchesAreExhaustive( conditionKeywordIndex: Int, branches: [Formatter.ConditionalBranch] - ) - -> Bool - { + ) -> Bool { // Switch statements are compiler-guaranteed to be exhaustive if tokens[conditionKeywordIndex] == .keyword("switch") { return true diff --git a/Sources/Rules/EnumNamespaces.swift b/Sources/Rules/EnumNamespaces.swift index 8fb6af65b..4344dce48 100644 --- a/Sources/Rules/EnumNamespaces.swift +++ b/Sources/Rules/EnumNamespaces.swift @@ -60,7 +60,7 @@ public extension FormatRule { } } -private extension Formatter { +extension Formatter { func rangeHostsOnlyStaticMembersAtTopLevel(_ range: Range) -> Bool { // exit for empty declarations guard next(.nonSpaceOrCommentOrLinebreak, in: range) != nil else { diff --git a/Sources/Rules/ExtensionAccessControl.swift b/Sources/Rules/ExtensionAccessControl.swift index aa7b3d774..a9809d95f 100644 --- a/Sources/Rules/ExtensionAccessControl.swift +++ b/Sources/Rules/ExtensionAccessControl.swift @@ -120,7 +120,7 @@ public extension FormatRule { } } -private extension Formatter { +extension Formatter { /// Performs some generic mapping for each declaration in the given array, /// stepping through conditional compilation blocks (but not into the body /// of other nested types) @@ -169,7 +169,7 @@ private extension Formatter { } } - private func mapBodyDeclarationsExcludingTypeBodies( + func mapBodyDeclarationsExcludingTypeBodies( _ body: [Declaration], with transform: (Declaration) -> Declaration ) -> [Declaration] { diff --git a/Sources/Rules/ModifierOrder.swift b/Sources/Rules/ModifierOrder.swift index 11b3887ac..a0dca840a 100644 --- a/Sources/Rules/ModifierOrder.swift +++ b/Sources/Rules/ModifierOrder.swift @@ -63,7 +63,7 @@ public extension FormatRule { pushModifier() guard !modifiers.isEmpty else { return } var sortedModifiers = [Token]() - for modifier in formatter.modifierOrder { + for modifier in formatter.preferredModifierOrder { if let tokens = modifiers[modifier] { sortedModifiers += tokens } @@ -75,7 +75,7 @@ public extension FormatRule { extension Formatter { /// Swift modifier keywords, in preferred order - var modifierOrder: [String] { + var preferredModifierOrder: [String] { var priorities = [String: Int]() for (i, modifiers) in _FormatRules.defaultModifierOrder.enumerated() { for modifier in modifiers { diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift index ae815b73d..239a82898 100644 --- a/Sources/Rules/OrganizeDeclarations.swift +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -721,7 +721,7 @@ extension Formatter { return category } - private func parseMarks( + func parseMarks( for options: Set ) -> [T: String] where T.RawValue == String { options.map { customMarkEntry -> (T, String)? in diff --git a/Sources/Rules/PreferForLoop.swift b/Sources/Rules/PreferForLoop.swift index abfd109a9..e2fe70766 100644 --- a/Sources/Rules/PreferForLoop.swift +++ b/Sources/Rules/PreferForLoop.swift @@ -234,7 +234,7 @@ public extension FormatRule { } } -private extension Formatter { +extension Formatter { // Returns the start index of the chain component ending at the given index func startOfChainComponent(at index: Int, forLoopSubjectIdentifier: inout String?) -> Int? { // The previous item in a dot chain can either be: diff --git a/Sources/Rules/Void.swift b/Sources/Rules/Void.swift index 7290b8783..033ff80d9 100644 --- a/Sources/Rules/Void.swift +++ b/Sources/Rules/Void.swift @@ -92,7 +92,7 @@ public extension FormatRule { } } -private extension Formatter { +extension Formatter { func isArgumentToken(at index: Int) -> Bool { guard let nextToken = next(.nonSpaceOrCommentOrLinebreak, after: index) else { return false diff --git a/Sources/Rules/Wrap.swift b/Sources/Rules/Wrap.swift index 328272b92..4f431b8d9 100644 --- a/Sources/Rules/Wrap.swift +++ b/Sources/Rules/Wrap.swift @@ -66,123 +66,3 @@ public extension FormatRule { wrapSingleArguments: true) } } - -extension Formatter { - /// Returns the index where the `wrap` rule should add the next linebreak in the line at the selected index. - /// - /// If the line does not need to be wrapped, this will return `nil`. - /// - /// - Note: This checks the entire line from the start of the line, the linebreak may be an index preceding the - /// `index` passed to the function. - func indexWhereLineShouldWrapInLine(at index: Int) -> Int? { - indexWhereLineShouldWrap(from: startOfLine(at: index, excludingIndent: true)) - } - - func indexWhereLineShouldWrap(from index: Int) -> Int? { - var lineLength = self.lineLength(upTo: index) - var stringLiteralDepth = 0 - var currentPriority = 0 - var lastBreakPoint: Int? - var lastBreakPointPriority = Int.min - - let maxWidth = options.maxWidth - guard maxWidth > 0 else { return nil } - - func addBreakPoint(at i: Int, relativePriority: Int) { - guard stringLiteralDepth == 0, currentPriority + relativePriority >= lastBreakPointPriority, - !isInClosureArguments(at: i + 1) - else { - return - } - let i = self.index(of: .nonSpace, before: i + 1) ?? i - if token(at: i + 1)?.isLinebreak == true || token(at: i)?.isLinebreak == true { - return - } - lastBreakPoint = i - lastBreakPointPriority = currentPriority + relativePriority - } - - var i = index - let endIndex = endOfLine(at: index) - while i < endIndex { - var token = tokens[i] - switch token { - case .linebreak: - return nil - case .keyword("#colorLiteral"), .keyword("#imageLiteral"): - guard let startIndex = self.index(of: .startOfScope("("), after: i), - let endIndex = endOfScope(at: startIndex) - else { - return nil // error - } - token = .space(spaceEquivalentToTokens(from: i, upTo: endIndex + 1)) // hack to get correct length - i = endIndex - case let .delimiter(string) where options.noWrapOperators.contains(string), - let .operator(string, .infix) where options.noWrapOperators.contains(string): - // TODO: handle as/is - break - case .delimiter(","): - addBreakPoint(at: i, relativePriority: 0) - case .operator("=", .infix) where self.token(at: i + 1)?.isSpace == true: - addBreakPoint(at: i, relativePriority: -9) - case .operator(".", .infix): - addBreakPoint(at: i - 1, relativePriority: -2) - case .operator("->", .infix): - if isInReturnType(at: i) { - currentPriority -= 5 - } - addBreakPoint(at: i - 1, relativePriority: -5) - case .operator(_, .infix) where self.token(at: i + 1)?.isSpace == true: - addBreakPoint(at: i, relativePriority: -3) - case .startOfScope("{"): - if !isStartOfClosure(at: i) || - next(.keyword, after: i) != .keyword("in"), - next(.nonSpace, after: i) != .endOfScope("}") - { - addBreakPoint(at: i, relativePriority: -6) - } - if isInReturnType(at: i) { - currentPriority += 5 - } - currentPriority -= 6 - case .endOfScope("}"): - currentPriority += 6 - if last(.nonSpace, before: i) != .startOfScope("{") { - addBreakPoint(at: i - 1, relativePriority: -6) - } - case .startOfScope("("): - currentPriority -= 7 - case .endOfScope(")"): - currentPriority += 7 - case .startOfScope("["): - currentPriority -= 8 - case .endOfScope("]"): - currentPriority += 8 - case .startOfScope("<"): - currentPriority -= 9 - case .endOfScope(">"): - currentPriority += 9 - case .startOfScope where token.isStringDelimiter: - stringLiteralDepth += 1 - case .endOfScope where token.isStringDelimiter: - stringLiteralDepth -= 1 - case .keyword("else"), .keyword("where"): - addBreakPoint(at: i - 1, relativePriority: -1) - case .keyword("in"): - if last(.keyword, before: i) == .keyword("for") { - addBreakPoint(at: i, relativePriority: -11) - break - } - addBreakPoint(at: i, relativePriority: -5 - currentPriority) - default: - break - } - lineLength += tokenLength(token) - if lineLength > maxWidth, let breakPoint = lastBreakPoint, breakPoint < i { - return breakPoint - } - i += 1 - } - return nil - } -} diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index 586f84a0e..93cab7bce 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -604,7 +604,6 @@ 2E8DE74B2C57FEB30032BF25 /* LeadingDelimitersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E02C57FEB30032BF25 /* LeadingDelimitersTests.swift */; }; 2E8DE74C2C57FEB30032BF25 /* WrapConditionalBodiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E12C57FEB30032BF25 /* WrapConditionalBodiesTests.swift */; }; 2E8DE74D2C57FEB30032BF25 /* OrganizeDeclarationsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E22C57FEB30032BF25 /* OrganizeDeclarationsTests.swift */; }; - 2E8DE74E2C57FEB30032BF25 /* DocCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */; }; 2E8DE74F2C57FEB30032BF25 /* ElseOnSameLineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */; }; 2E8DE7502C57FEB30032BF25 /* RedundantReturnTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E52C57FEB30032BF25 /* RedundantReturnTests.swift */; }; 2E8DE7512C57FEB30032BF25 /* LinebreaksTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6E62C57FEB30032BF25 /* LinebreaksTests.swift */; }; @@ -625,6 +624,9 @@ 2E8DE7602C57FEB30032BF25 /* InitCoderUnavailableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F52C57FEB30032BF25 /* InitCoderUnavailableTests.swift */; }; 2E8DE7612C57FEB30032BF25 /* RedundantFileprivateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */; }; 2E8DE7622C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */; }; + 2EF737522C5E881C00128F91 /* CodeOrganizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF737512C5E881C00128F91 /* CodeOrganizationTests.swift */; }; + 2EF737542C5E897800128F91 /* ProjectFilePaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF737532C5E897800128F91 /* ProjectFilePaths.swift */; }; + 2EF737562C5ED19600128F91 /* DocCommentsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF737552C5ED19600128F91 /* DocCommentsTests.swift */; }; 37D828AB24BF77DA0012FC0A /* XcodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; }; 37D828AC24BF77DA0012FC0A /* XcodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 580496D52C584E8F004B7DBF /* EmptyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580496D42C584E8F004B7DBF /* EmptyExtension.swift */; }; @@ -759,7 +761,6 @@ 01A0EAA91D5DB4CF00A0A8E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 01A0EAAE1D5DB4D000A0A8E3 /* SwiftFormatTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftFormatTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 01A0EAB31D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "XCTestCase+testFormatting.swift"; sourceTree = ""; }; - 01A0EAB51D5DB4D000A0A8E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 01A0EABE1D5DB4F700A0A8E3 /* FormatRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormatRule.swift; sourceTree = ""; }; 01A0EABF1D5DB4F700A0A8E3 /* Tokenizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tokenizer.swift; sourceTree = ""; }; 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftFormat.swift; sourceTree = ""; }; @@ -976,7 +977,6 @@ 2E8DE6E02C57FEB30032BF25 /* LeadingDelimitersTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeadingDelimitersTests.swift; sourceTree = ""; }; 2E8DE6E12C57FEB30032BF25 /* WrapConditionalBodiesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapConditionalBodiesTests.swift; sourceTree = ""; }; 2E8DE6E22C57FEB30032BF25 /* OrganizeDeclarationsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganizeDeclarationsTests.swift; sourceTree = ""; }; - 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocCommentsTests.swift; sourceTree = ""; }; 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ElseOnSameLineTests.swift; sourceTree = ""; }; 2E8DE6E52C57FEB30032BF25 /* RedundantReturnTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantReturnTests.swift; sourceTree = ""; }; 2E8DE6E62C57FEB30032BF25 /* LinebreaksTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinebreaksTests.swift; sourceTree = ""; }; @@ -997,6 +997,9 @@ 2E8DE6F52C57FEB30032BF25 /* InitCoderUnavailableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InitCoderUnavailableTests.swift; sourceTree = ""; }; 2E8DE6F62C57FEB30032BF25 /* RedundantFileprivateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RedundantFileprivateTests.swift; sourceTree = ""; }; 2E8DE6F72C57FEB30032BF25 /* WrapSingleLineCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WrapSingleLineCommentsTests.swift; sourceTree = ""; }; + 2EF737512C5E881C00128F91 /* CodeOrganizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeOrganizationTests.swift; sourceTree = ""; }; + 2EF737532C5E897800128F91 /* ProjectFilePaths.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectFilePaths.swift; sourceTree = ""; }; + 2EF737552C5ED19600128F91 /* DocCommentsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocCommentsTests.swift; sourceTree = ""; }; 37D828AA24BF77DA0012FC0A /* XcodeKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XcodeKit.framework; path = Library/Frameworks/XcodeKit.framework; sourceTree = DEVELOPER_DIR; }; 580496D42C584E8F004B7DBF /* EmptyExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyExtension.swift; sourceTree = ""; }; 580496D92C584F24004B7DBF /* EmptyExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyExtensionTests.swift; sourceTree = ""; }; @@ -1165,23 +1168,24 @@ isa = PBXGroup; children = ( 2E8DE68C2C57FEB30032BF25 /* Rules */, - 01BEC5762236E1A700D0DD83 /* MetadataTests.swift */, 01045A9C2119A21000D2BE3D /* ArgumentsTests.swift */, + 2EF737512C5E881C00128F91 /* CodeOrganizationTests.swift */, 01F17E841E258A4900DCD359 /* CommandLineTests.swift */, + E4E4D3CD2033F1EF000D7CB1 /* EnumAssociableTests.swift */, 01B3987A1D763424009ADE61 /* FormatterTests.swift */, + 01BBD85D21DAA30700457380 /* GlobsTests.swift */, + 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */, + 01BEC5762236E1A700D0DD83 /* MetadataTests.swift */, + E43EF47B202FF47C00E523BD /* OptionDescriptorTests.swift */, 01426E4D23AA29B100E7D871 /* ParsingHelpersTests.swift */, + 2EF737532C5E897800128F91 /* ProjectFilePaths.swift */, 015F83FA2BF1448D0060A07E /* ReporterTests.swift */, - 01A0EAB51D5DB4D000A0A8E3 /* Info.plist */, - E4E4D3CD2033F1EF000D7CB1 /* EnumAssociableTests.swift */, - E43EF47B202FF47C00E523BD /* OptionDescriptorTests.swift */, - 01F3DF8F1DBA003E00454944 /* InferenceTests.swift */, 015CE8B02B448CCE00924504 /* SingularizeTests.swift */, 0142F06E1D72FE10007D66CC /* SwiftFormatTests.swift */, - 01BBD85D21DAA30700457380 /* GlobsTests.swift */, 018E82741D62E730008CA0F8 /* TokenizerTests.swift */, 011A53E921FFAA3A00DD9268 /* VersionTests.swift */, - 01C4D3282BB518D400BDF1AF /* ZRegressionTests.swift */, 01A0EAB31D5DB4D000A0A8E3 /* XCTestCase+testFormatting.swift */, + 01C4D3282BB518D400BDF1AF /* ZRegressionTests.swift */, ); path = Tests; sourceTree = ""; @@ -1345,7 +1349,7 @@ 2E8DE6D72C57FEB30032BF25 /* ConsecutiveSpacesTests.swift */, 2E8DE6D12C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift */, 2E8DE6F32C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift */, - 2E8DE6E32C57FEB30032BF25 /* DocCommentsTests.swift */, + 2EF737552C5ED19600128F91 /* DocCommentsTests.swift */, 2E8DE6942C57FEB30032BF25 /* DuplicateImportsTests.swift */, 2E8DE6E42C57FEB30032BF25 /* ElseOnSameLineTests.swift */, 2E8DE6B62C57FEB30032BF25 /* EmptyBracesTests.swift */, @@ -1956,6 +1960,7 @@ 2E8DE6FD2C57FEB30032BF25 /* RedundantSelfTests.swift in Sources */, 018E82751D62E730008CA0F8 /* TokenizerTests.swift in Sources */, 2E8DE74D2C57FEB30032BF25 /* OrganizeDeclarationsTests.swift in Sources */, + 2EF737562C5ED19600128F91 /* DocCommentsTests.swift in Sources */, 2E8DE71E2C57FEB30032BF25 /* SpaceAroundBracesTests.swift in Sources */, 2E8DE73C2C57FEB30032BF25 /* ConsistentSwitchCaseSpacingTests.swift in Sources */, 2E8DE75B2C57FEB30032BF25 /* AndOperatorTests.swift in Sources */, @@ -1977,12 +1982,14 @@ 2E8DE7272C57FEB30032BF25 /* RedundantBreakTests.swift in Sources */, 2E8DE7232C57FEB30032BF25 /* BlockCommentsTests.swift in Sources */, 2E8DE75A2C57FEB30032BF25 /* SpaceInsideCommentsTests.swift in Sources */, + 2EF737542C5E897800128F91 /* ProjectFilePaths.swift in Sources */, 2E8DE75D2C57FEB30032BF25 /* SpaceInsideBracketsTests.swift in Sources */, 2E8DE72D2C57FEB30032BF25 /* VoidTests.swift in Sources */, 2E8DE72B2C57FEB30032BF25 /* SpaceAroundParensTests.swift in Sources */, 2E8DE73F2C57FEB30032BF25 /* NoExplicitOwnershipTests.swift in Sources */, 01C4D3292BB518D400BDF1AF /* ZRegressionTests.swift in Sources */, 2E8DE7092C57FEB30032BF25 /* OpaqueGenericParametersTests.swift in Sources */, + 2EF737522C5E881C00128F91 /* CodeOrganizationTests.swift in Sources */, 2E8DE7202C57FEB30032BF25 /* SortSwitchCasesTests.swift in Sources */, 2E8DE7452C57FEB30032BF25 /* WrapMultilineStatementBracesTests.swift in Sources */, 2E8DE7242C57FEB30032BF25 /* StrongOutletsTests.swift in Sources */, @@ -2050,7 +2057,6 @@ 2E8DE7042C57FEB30032BF25 /* ExtensionAccessControlTests.swift in Sources */, 015F83FB2BF1448D0060A07E /* ReporterTests.swift in Sources */, 2E8DE70E2C57FEB30032BF25 /* HoistPatternLetTests.swift in Sources */, - 2E8DE74E2C57FEB30032BF25 /* DocCommentsTests.swift in Sources */, 2E8DE7522C57FEB30032BF25 /* MarkTypesTests.swift in Sources */, 2E8DE75E2C57FEB30032BF25 /* DocCommentsBeforeAttributesTests.swift in Sources */, 2E8DE6FF2C57FEB30032BF25 /* DuplicateImportsTests.swift in Sources */, @@ -2758,7 +2764,6 @@ buildSettings = { COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; - INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -2777,7 +2782,6 @@ CLANG_ENABLE_CODE_COVERAGE = NO; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; - INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", diff --git a/Tests/CodeOrganizationTests.swift b/Tests/CodeOrganizationTests.swift new file mode 100644 index 000000000..13e399243 --- /dev/null +++ b/Tests/CodeOrganizationTests.swift @@ -0,0 +1,177 @@ +// +// CodeOrganizationTests.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 8/3/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import XCTest +@testable import SwiftFormat + +class CodeOrganizationTests: XCTestCase { + func testRuleFileCodeOrganization() throws { + for ruleFile in allRuleFiles { + let fileName = ruleFile.lastPathComponent + let titleCaseRuleName = fileName.replacingOccurrences(of: ".swift", with: "") + let ruleName = titleCaseRuleName.first!.lowercased() + titleCaseRuleName.dropFirst() + + let content = try String(contentsOf: ruleFile) + let formatter = Formatter(tokenize(content)) + let declarations = formatter.parseDeclarations() + let extensions = declarations.filter { $0.keyword == "extension" } + + for extensionDecl in extensions { + let extendedType = extensionDecl.name! + let extensionVisibility = extensionDecl.visibility() ?? .internal + + if extendedType == "FormatRule" { + XCTAssertEqual(extensionVisibility, .public, """ + Rule implementation in \(fileName) should be public. + """) + + for bodyDeclaration in extensionDecl.body ?? [] { + XCTAssertEqual(bodyDeclaration.name, ruleName, """ + A FormatRule named \(ruleName) should be the only declaration in \ + the FormatRule extension in \(fileName). + """) + + let declarationVisibility = bodyDeclaration.visibility() ?? extensionVisibility + XCTAssertEqual(declarationVisibility, .public, """ + Rule implementation in \(fileName) should be public. + """) + } + continue + } + + XCTAssertEqual(extensionVisibility, .internal, """ + \(extendedType) extension in \(fileName) should be internal, \ + to improve discoverability of helpers. + """) + + for bodyDeclaration in extensionDecl.body ?? [] { + let declarationVisibility = bodyDeclaration.visibility() ?? extensionVisibility + XCTAssertEqual(declarationVisibility, .internal, """ + \(bodyDeclaration.name!) helper in \(fileName) should be internal, \ + to improve discoverability of helpers. + """) + } + } + } + } + + func testRuleFileHelpersNotUsedByOtherRules() throws { + // Collect the name of all of the helpers defined in individual rule files + var allRuleFileHelpers: [(name: String, fileName: String, funcArgLabels: [String?]?)] = [] + + for ruleFile in allRuleFiles { + let fileName = ruleFile.lastPathComponent + let content = try String(contentsOf: ruleFile) + let formatter = Formatter(tokenize(content)) + + for declaration in formatter.parseDeclarations() { + guard declaration.keyword == "extension", let extendedType = declaration.name, extendedType != "FormatRule" else { + continue + } + + for bodyDeclaration in declaration.body ?? [] { + guard let helperName = bodyDeclaration.name else { continue } + + var helperFuncArgLabels: [String?]? = nil + if bodyDeclaration.keyword == "func", let startOfScope = formatter.index(of: .startOfScope("("), after: bodyDeclaration.originalRange.lowerBound) { + helperFuncArgLabels = formatter.parseFunctionDeclarationArgumentLabels(startOfScope: startOfScope) + } + + allRuleFileHelpers.append((name: helperName, fileName: fileName, funcArgLabels: helperFuncArgLabels)) + } + } + } + + // Verify that none of the helpers defined in rule files are used in other files + let ruleFileHelperNames = Set(allRuleFileHelpers.map(\.name)) + + for file in allSourceFiles { + let fileName = file.lastPathComponent + let content = try String(contentsOf: file) + let formatter = Formatter(tokenize(content)) + + formatter.forEach(.identifier) { index, identifierToken in + let identifier = identifierToken.string + + guard ruleFileHelperNames.contains(identifier) else { return } + + // If this is a function call, parse the labels to disambiguate + // between methods with the same base name + var functionCallArguments: [String?]? + if let functionCallStartOfScope = formatter.index(of: .startOfScope("("), after: index) { + functionCallArguments = formatter.parseFunctionCallArgumentLabels(startOfScope: functionCallStartOfScope) + } + + guard let matchingHelper = allRuleFileHelpers.first(where: { helper in + helper.name == identifier + && helper.funcArgLabels == functionCallArguments + }), matchingHelper.fileName != fileName + else { return } + + let fullHelperName: String + if let argumentLabels = matchingHelper.funcArgLabels { + let argumentLabelStrings = argumentLabels.map { label -> String in + if let label = label { + return label + ":" + } else { + return "_:" + } + } + + fullHelperName = matchingHelper.name + "(" + argumentLabelStrings.joined() + ")" + } else { + fullHelperName = matchingHelper.name + } + + XCTFail(""" + \(fullHelperName) helper defined in \(matchingHelper.fileName) is also used in \(fileName). \ + Shared helpers should be defined in a shared file like FormattingHelpers.swift, + ParsingHelpers.swift, or DeclarationHelpers.swift. + """) + } + } + } + + func testRuleTestFilesHaveMatchingRule() { + let allRuleNames = Set(allRuleFiles.map { ruleFile -> String in + let fileName = ruleFile.lastPathComponent + let titleCaseRuleName = fileName.replacingOccurrences(of: ".swift", with: "") + return titleCaseRuleName.first!.lowercased() + titleCaseRuleName.dropFirst() + }) + + for testFile in allRuleTestFiles { + let testFileName = testFile.lastPathComponent + let expectedTestClassName = testFileName.replacingOccurrences(of: ".swift", with: "") + let titleCaseRuleName = expectedTestClassName.replacingOccurrences(of: "Tests", with: "") + let ruleName = titleCaseRuleName.first!.lowercased() + titleCaseRuleName.dropFirst() + + XCTAssert(allRuleNames.contains(ruleName), """ + \(testFileName) has no matching rule named \(ruleName). + """) + } + } + + func testAllTestClassesMatchFileName() throws { + for testFile in allTestFiles { + let testFileName = testFile.lastPathComponent + let content = try String(contentsOf: testFile) + let formatter = Formatter(tokenize(content)) + let declarations = formatter.parseDeclarations() + + guard let testClass = declarations.first(where: { + $0.openTokens.string.contains("XCTestCase") && $0.keyword == "class" + }) else { continue } + + let expectedTestClassName = testFileName.replacingOccurrences(of: ".swift", with: "") + + XCTAssertEqual(testClass.name!, expectedTestClassName, """ + class \(testClass.name!) and file \(testFileName) should have same name. + """) + } + } +} diff --git a/Tests/CommandLineTests.swift b/Tests/CommandLineTests.swift index 8ab5d25f5..9a9dd53ce 100644 --- a/Tests/CommandLineTests.swift +++ b/Tests/CommandLineTests.swift @@ -32,9 +32,6 @@ import XCTest @testable import SwiftFormat -private let projectDirectory = URL(fileURLWithPath: #file) - .deletingLastPathComponent().deletingLastPathComponent() - private func createTmpFile(_ path: String? = nil, contents: String) throws -> URL { let path = path ?? (UUID().uuidString + ".swift") let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(path) diff --git a/Tests/Info.plist b/Tests/Info.plist deleted file mode 100644 index 2cafdf8ed..000000000 --- a/Tests/Info.plist +++ /dev/null @@ -1,24 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - BNDL - CFBundleShortVersionString - $(CURRENT_PROJECT_VERSION) - CFBundleSignature - ???? - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - - diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index 33621de0f..d57b1955f 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -9,40 +9,6 @@ import XCTest @testable import SwiftFormat -private let projectDirectory = URL(fileURLWithPath: #file) - .deletingLastPathComponent().deletingLastPathComponent() - -private let projectURL = projectDirectory - .appendingPathComponent("SwiftFormat.xcodeproj") - .appendingPathComponent("project.pbxproj") - -private let changeLogURL = - projectDirectory.appendingPathComponent("CHANGELOG.md") - -private let podspecURL = - projectDirectory.appendingPathComponent("SwiftFormat.podspec.json") - -private let rulesURL = - projectDirectory.appendingPathComponent("Rules.md") - -private let rulesFile = - try! String(contentsOf: rulesURL, encoding: .utf8) - -private let ruleRegistryURL = - projectDirectory.appendingPathComponent("Sources/RuleRegistry.generated.swift") - -private let allRuleFiles: [URL] = { - var rulesFiles: [URL] = [] - let rulesDirectory = projectDirectory.appendingPathComponent("Sources/Rules") - _ = enumerateFiles(withInputURL: rulesDirectory) { ruleFileURL, _, _ in - { - guard ruleFileURL.pathExtension == "swift" else { return } - rulesFiles.append(ruleFileURL) - } - } - return rulesFiles -}() - private let swiftFormatVersion: String = { let string = try! String(contentsOf: projectURL) let start = string.range(of: "MARKETING_VERSION = ")!.upperBound @@ -230,6 +196,11 @@ class MetadataTests: XCTestCase { ] case .identifier("wrapStatementBody"): referencedOptions += [Descriptors.indent, Descriptors.linebreak] + case .identifier("indexWhereLineShouldWrapInLine"), .identifier("indexWhereLineShouldWrap"): + referencedOptions += [ + Descriptors.indent, Descriptors.tabWidth, Descriptors.assetLiteralWidth, + Descriptors.noWrapOperators, + ] case .identifier("removeSelf"): referencedOptions += [ Descriptors.selfRequired, diff --git a/Tests/OptionDescriptorTests.swift b/Tests/OptionDescriptorTests.swift index 08acc8122..595d74617 100644 --- a/Tests/OptionDescriptorTests.swift +++ b/Tests/OptionDescriptorTests.swift @@ -9,9 +9,6 @@ import XCTest @testable import SwiftFormat -private let projectDirectory = URL(fileURLWithPath: #file) - .deletingLastPathComponent().deletingLastPathComponent() - class OptionDescriptorTests: XCTestCase { private typealias OptionArgumentMapping = (optionValue: T, argumentValue: String) diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index 4c70d6f1f..4005b9a27 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -777,7 +777,7 @@ class ParsingHelpersTests: XCTestCase { func testModifierOrder() { let options = FormatOptions(modifierOrder: ["convenience", "override"]) let formatter = Formatter([], options: options) - XCTAssertEqual(formatter.modifierOrder, [ + XCTAssertEqual(formatter.preferredModifierOrder, [ "private", "fileprivate", "internal", "package", "public", "open", "private(set)", "fileprivate(set)", "internal(set)", "package(set)", "public(set)", "open(set)", "final", @@ -801,7 +801,7 @@ class ParsingHelpersTests: XCTestCase { "lazy", "final", "required", "convenience", "typeMethods", "owned", ]) let formatter = Formatter([], options: options) - XCTAssertEqual(formatter.modifierOrder, [ + XCTAssertEqual(formatter.preferredModifierOrder, [ "override", "private", "fileprivate", "internal", "package", "public", "open", "private(set)", "fileprivate(set)", "internal(set)", "package(set)", "public(set)", "open(set)", @@ -2390,4 +2390,58 @@ class ParsingHelpersTests: XCTestCase { let formatter = Formatter(tokenize(input)) return formatter.isStoredProperty(atIntroducerIndex: index) } + + // MARK: parseFunctionDeclarationArgumentLabels + + func testParseFunctionDeclarationArgumentLabels() { + let input = """ + func foo(_ foo: Foo, bar: Bar, quux _: Quux, last baaz: Baaz) {} + func bar() {} + """ + + let formatter = Formatter(tokenize(input)) + XCTAssertEqual( + formatter.parseFunctionDeclarationArgumentLabels(startOfScope: 3), // foo(...) + [nil, "bar", "quux", "last"] + ) + + XCTAssertEqual( + formatter.parseFunctionDeclarationArgumentLabels(startOfScope: 40), // bar() + [] + ) + } + + func testParseFunctionCallArgumentLabels() { + let input = """ + foo(Foo(foo: foo), bar: Bar(bar), foo, quux: Quux(), last: Baaz(foo: foo)) + + print(formatter.isOperator(at: 0)) + """ + + let formatter = Formatter(tokenize(input)) + XCTAssertEqual( + formatter.parseFunctionCallArgumentLabels(startOfScope: 1), // foo(...) + [nil, "bar", nil, "quux", "last"] + ) + + XCTAssertEqual( + formatter.parseFunctionCallArgumentLabels(startOfScope: 3), // Foo(...) + ["foo"] + ) + + XCTAssertEqual( + formatter.parseFunctionCallArgumentLabels(startOfScope: 15), // Bar(...) + [nil] + ) + + XCTAssertEqual( + formatter.parseFunctionCallArgumentLabels(startOfScope: 27), // Quux() + [] + ) + + XCTAssertEqual( + formatter.parseFunctionCallArgumentLabels(startOfScope: 49), // isOperator(...) + ["at"] + ) + } } diff --git a/Tests/ProjectFilePaths.swift b/Tests/ProjectFilePaths.swift new file mode 100644 index 000000000..f29cfe256 --- /dev/null +++ b/Tests/ProjectFilePaths.swift @@ -0,0 +1,50 @@ +// +// ProjectFilePaths.swift +// SwiftFormatTests +// +// Created by Cal Stephens on 8/3/24. +// Copyright © 2024 Nick Lockwood. All rights reserved. +// + +import Foundation +@testable import SwiftFormat + +let projectDirectory = URL(fileURLWithPath: #file) + .deletingLastPathComponent().deletingLastPathComponent() + +let projectURL = projectDirectory + .appendingPathComponent("SwiftFormat.xcodeproj") + .appendingPathComponent("project.pbxproj") + +let allSourceFiles = allSwiftFiles(inDirectory: "Sources") +let allRuleFiles = allSwiftFiles(inDirectory: "Sources/Rules") + +let allTestFiles = allSwiftFiles(inDirectory: "Tests") +let allRuleTestFiles = allSwiftFiles(inDirectory: "Tests/Rules") + +let changeLogURL = + projectDirectory.appendingPathComponent("CHANGELOG.md") + +let podspecURL = + projectDirectory.appendingPathComponent("SwiftFormat.podspec.json") + +let rulesURL = + projectDirectory.appendingPathComponent("Rules.md") + +let rulesFile = + try! String(contentsOf: rulesURL, encoding: .utf8) + +let ruleRegistryURL = + projectDirectory.appendingPathComponent("Sources/RuleRegistry.generated.swift") + +private func allSwiftFiles(inDirectory directory: String) -> [URL] { + var swiftFiles: [URL] = [] + let directory = projectDirectory.appendingPathComponent(directory) + _ = enumerateFiles(withInputURL: directory) { fileURL, _, _ in + { + guard fileURL.pathExtension == "swift" else { return } + swiftFiles.append(fileURL) + } + } + return swiftFiles +} diff --git a/Tests/Rules/SpacingGuardsTests.swift b/Tests/Rules/SpacingGuardsTests.swift index d7c873208..e4976eaa5 100644 --- a/Tests/Rules/SpacingGuardsTests.swift +++ b/Tests/Rules/SpacingGuardsTests.swift @@ -7,7 +7,7 @@ import XCTest @testable import SwiftFormat -final class GuardSpacingTests: XCTestCase { +final class SpacingGuardsTests: XCTestCase { func testSpacesBetweenGuard() { let input = """ guard let one = test.one else { diff --git a/Tests/ZRegressionTests.swift b/Tests/ZRegressionTests.swift index 5213afb23..480618efa 100644 --- a/Tests/ZRegressionTests.swift +++ b/Tests/ZRegressionTests.swift @@ -9,9 +9,6 @@ import XCTest @testable import SwiftFormat -private let projectDirectory = URL(fileURLWithPath: #file) - .deletingLastPathComponent().deletingLastPathComponent() - private let projectFiles: [String] = { var files = [String]() _ = enumerateFiles(withInputURL: projectDirectory) { url, _, _ in From fc6857dc5ced1fd959996348cbd0966ffc1b6bee Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Mon, 12 Aug 2024 16:03:49 -0700 Subject: [PATCH 43/52] Add preserve option to spaceAroundRangeOperators and spaceAroundOperatorDeclarations (#1817) --- PerformanceTests/PerformanceTests.swift | 4 +- Rules.md | 4 +- Sources/Inference.swift | 6 ++- Sources/OptionDescriptor.swift | 38 ++++++++++++--- Sources/Options.swift | 15 ++++-- Sources/Rules/SpaceAroundOperators.swift | 9 ++-- Tests/ArgumentsTests.swift | 2 +- Tests/InferenceTests.swift | 4 +- Tests/Rules/SpaceAroundOperatorsTests.swift | 52 +++++++++++++++------ 9 files changed, 98 insertions(+), 36 deletions(-) diff --git a/PerformanceTests/PerformanceTests.swift b/PerformanceTests/PerformanceTests.swift index 8092abd21..ab0d1c738 100644 --- a/PerformanceTests/PerformanceTests.swift +++ b/PerformanceTests/PerformanceTests.swift @@ -96,8 +96,8 @@ class PerformanceTests: XCTestCase { let tokens = files.map { tokenize($0) } let options = FormatOptions( linebreak: "\r\n", - spaceAroundRangeOperators: false, - spaceAroundOperatorDeclarations: false, + spaceAroundRangeOperators: .remove, + spaceAroundOperatorDeclarations: .remove, useVoid: false, indentCase: true, trailingCommas: false, diff --git a/Rules.md b/Rules.md index 35836acf1..cc854a34e 100644 --- a/Rules.md +++ b/Rules.md @@ -2460,9 +2460,9 @@ Add or remove space around operators or delimiters. Option | Description --- | --- -`--operatorfunc` | Spacing for operator funcs: "spaced" (default) or "no-space" +`--operatorfunc` | Operator funcs: "spaced" (default), "no-space", or "preserve" `--nospaceoperators` | Comma-delimited list of operators without surrounding space -`--ranges` | Spacing for ranges: "spaced" (default) or "no-space" +`--ranges` | Range spaces: "spaced" (default) or "no-space", or "preserve" `--typedelimiter` | "space-after" (default), "spaced" or "no-space"
diff --git a/Sources/Inference.swift b/Sources/Inference.swift index 456c0e952..6571e2682 100644 --- a/Sources/Inference.swift +++ b/Sources/Inference.swift @@ -1273,7 +1273,11 @@ private struct Inference { nospace += 1 } } - options.spaceAroundOperatorDeclarations = (nospace <= space) + if nospace <= space { + options.spaceAroundOperatorDeclarations = .insert + } else { + options.spaceAroundOperatorDeclarations = .remove + } } let elseOnNextLine = OptionInferrer { formatter, options in diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index fb59fa781..e3bcb92ef 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -466,10 +466,23 @@ struct _Descriptors { let spaceAroundOperatorDeclarations = OptionDescriptor( argumentName: "operatorfunc", displayName: "Operator Functions", - help: "Spacing for operator funcs: \"spaced\" (default) or \"no-space\"", + help: "Operator funcs: \"spaced\" (default), \"no-space\", or \"preserve\"", keyPath: \.spaceAroundOperatorDeclarations, - trueValues: ["spaced", "space", "spaces"], - falseValues: ["no-space", "nospace"] + fromArgument: { argument in + switch argument { + case "spaced", "space", "spaces": + return .insert + case "no-space", "nospace": + return .remove + case "preserve", "preserve-spaces", "preservespaces": + return .preserve + default: + return nil + } + }, + toArgument: { value in + value.rawValue + } ) let useVoid = OptionDescriptor( argumentName: "voidtype", @@ -793,10 +806,23 @@ struct _Descriptors { let spaceAroundRangeOperators = OptionDescriptor( argumentName: "ranges", displayName: "Ranges", - help: "Spacing for ranges: \"spaced\" (default) or \"no-space\"", + help: "Range spaces: \"spaced\" (default) or \"no-space\", or \"preserve\"", keyPath: \.spaceAroundRangeOperators, - trueValues: ["spaced", "space", "spaces"], - falseValues: ["no-space", "nospace"] + fromArgument: { argument in + switch argument { + case "spaced", "space", "spaces": + return .insert + case "no-space", "nospace": + return .remove + case "preserve", "preserve-spaces", "preservespaces": + return .preserve + default: + return nil + } + }, + toArgument: { value in + value.rawValue + } ) let noWrapOperators = OptionDescriptor( argumentName: "nowrapoperators", diff --git a/Sources/Options.swift b/Sources/Options.swift index 80acfbc38..348cef3dd 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -172,6 +172,13 @@ public enum ClosureVoidReturn: String, CaseIterable { case preserve } +/// Whether to insert, remove, or preserve spaces around operators +public enum OperatorSpacingMode: String, CaseIterable { + case insert = "spaced" + case remove = "no-space" + case preserve +} + /// Version number wrapper public struct Version: RawRepresentable, Comparable, ExpressibleByStringLiteral, CustomStringConvertible { public let rawValue: String @@ -594,8 +601,8 @@ public struct FormatOptions: CustomStringConvertible { public var indent: String public var linebreak: String public var allowInlineSemicolons: Bool - public var spaceAroundRangeOperators: Bool - public var spaceAroundOperatorDeclarations: Bool + public var spaceAroundRangeOperators: OperatorSpacingMode + public var spaceAroundOperatorDeclarations: OperatorSpacingMode public var useVoid: Bool public var indentCase: Bool public var trailingCommas: Bool @@ -719,8 +726,8 @@ public struct FormatOptions: CustomStringConvertible { indent: String = " ", linebreak: String = "\n", allowInlineSemicolons: Bool = true, - spaceAroundRangeOperators: Bool = true, - spaceAroundOperatorDeclarations: Bool = true, + spaceAroundRangeOperators: OperatorSpacingMode = .insert, + spaceAroundOperatorDeclarations: OperatorSpacingMode = .insert, useVoid: Bool = true, indentCase: Bool = false, trailingCommas: Bool = true, diff --git a/Sources/Rules/SpaceAroundOperators.swift b/Sources/Rules/SpaceAroundOperators.swift index 4e3f8956a..101474227 100644 --- a/Sources/Rules/SpaceAroundOperators.swift +++ b/Sources/Rules/SpaceAroundOperators.swift @@ -24,12 +24,12 @@ public extension FormatRule { case .operator(_, .none): switch formatter.token(at: i + 1) { case nil, .linebreak?, .endOfScope?, .operator?, .delimiter?, - .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: + .startOfScope("(")? where formatter.options.spaceAroundOperatorDeclarations != .insert: break case .space?: switch formatter.next(.nonSpaceOrLinebreak, after: i) { case nil, .linebreak?, .endOfScope?, .delimiter?, - .startOfScope("(")? where !formatter.options.spaceAroundOperatorDeclarations: + .startOfScope("(")? where formatter.options.spaceAroundOperatorDeclarations == .remove: formatter.removeToken(at: i + 1) default: break @@ -79,7 +79,7 @@ public extension FormatRule { case .operator("?", .infix): break // Spacing around ternary ? is not optional case let .operator(name, .infix) where formatter.options.noSpaceOperators.contains(name) || - (!formatter.options.spaceAroundRangeOperators && token.isRangeOperator): + (formatter.options.spaceAroundRangeOperators == .remove && token.isRangeOperator): if formatter.token(at: i + 1)?.isSpace == true, formatter.token(at: i - 1)?.isSpace == true, let nextToken = formatter.next(.nonSpace, after: i), @@ -91,6 +91,9 @@ public extension FormatRule { formatter.removeToken(at: i - 1) } case .operator(_, .infix): + if token.isRangeOperator, formatter.options.spaceAroundRangeOperators != .insert { + break + } if formatter.token(at: i + 1)?.isSpaceOrLinebreak == false { formatter.insert(.space(" "), at: i + 1) } diff --git a/Tests/ArgumentsTests.swift b/Tests/ArgumentsTests.swift index 7f27fcc29..a13bb54ab 100644 --- a/Tests/ArgumentsTests.swift +++ b/Tests/ArgumentsTests.swift @@ -702,7 +702,7 @@ class ArgumentsTests: XCTestCase { func testParseDeprecatedOption() throws { let options = try Options(["ranges": "nospace"], in: "") - XCTAssertEqual(options.formatOptions?.spaceAroundRangeOperators, false) + XCTAssertEqual(options.formatOptions?.spaceAroundRangeOperators, .remove) } func testParseNoSpaceOperatorsOption() throws { diff --git a/Tests/InferenceTests.swift b/Tests/InferenceTests.swift index 4ddbba5d3..fc6f7ca9d 100644 --- a/Tests/InferenceTests.swift +++ b/Tests/InferenceTests.swift @@ -567,13 +567,13 @@ class InferenceTests: XCTestCase { func testInferSpaceAfterOperatorFunc() { let input = "func == (lhs: Int, rhs: Int) -> Bool {}" let options = inferFormatOptions(from: tokenize(input)) - XCTAssertTrue(options.spaceAroundOperatorDeclarations) + XCTAssertEqual(options.spaceAroundOperatorDeclarations, .insert) } func testInferNoSpaceAfterOperatorFunc() { let input = "func ==(lhs: Int, rhs: Int) -> Bool {}" let options = inferFormatOptions(from: tokenize(input)) - XCTAssertFalse(options.spaceAroundOperatorDeclarations) + XCTAssertEqual(options.spaceAroundOperatorDeclarations, .remove) } // MARK: elseOnNextLine diff --git a/Tests/Rules/SpaceAroundOperatorsTests.swift b/Tests/Rules/SpaceAroundOperatorsTests.swift index 5e20fcde9..5edae271a 100644 --- a/Tests/Rules/SpaceAroundOperatorsTests.swift +++ b/Tests/Rules/SpaceAroundOperatorsTests.swift @@ -372,10 +372,19 @@ class SpaceAroundOperatorsTests: XCTestCase { func testRemoveSpaceAfterFuncEquals() { let input = "func == (lhs: Int, rhs: Int) -> Bool { return lhs === rhs }" let output = "func ==(lhs: Int, rhs: Int) -> Bool { return lhs === rhs }" - let options = FormatOptions(spaceAroundOperatorDeclarations: false) + let options = FormatOptions(spaceAroundOperatorDeclarations: .remove) testFormatting(for: input, output, rule: .spaceAroundOperators, options: options) } + func testPreserveSpaceAfterFuncEquals() { + let input = """ + func == (lhs: Int, rhs: Int) -> Bool { return lhs === rhs } + func !=(lhs: Int, rhs: Int) -> Bool { return lhs !== rhs } + """ + let options = FormatOptions(spaceAroundOperatorDeclarations: .preserve) + testFormatting(for: input, rule: .spaceAroundOperators, options: options) + } + func testAddSpaceAfterOperatorEquals() { let input = "operator =={}" let output = "operator == {}" @@ -384,7 +393,7 @@ class SpaceAroundOperatorsTests: XCTestCase { func testNoRemoveSpaceAfterOperatorEqualsWhenSpaceAroundOperatorDeclarationsFalse() { let input = "operator == {}" - let options = FormatOptions(spaceAroundOperatorDeclarations: false) + let options = FormatOptions(spaceAroundOperatorDeclarations: .remove) testFormatting(for: input, rule: .spaceAroundOperators, options: options) } @@ -612,86 +621,99 @@ class SpaceAroundOperatorsTests: XCTestCase { testFormatting(for: input, output, rule: .spaceAroundOperators, options: options) } - // spaceAroundRangeOperators = false + // spaceAroundRangeOperators: .remove func testNoSpaceAroundRangeOperatorsWithCustomOptions() { let input = "foo ..< bar" let output = "foo.. Date: Mon, 19 Aug 2024 21:01:17 -0600 Subject: [PATCH 44/52] Add option to sort SwiftUI properties by the first property appearance (#1821) --- Rules.md | 2 +- Sources/OptionDescriptor.swift | 10 +- Sources/Options.swift | 25 ++++- Sources/Rules/OrganizeDeclarations.swift | 52 ++++++++- Tests/Rules/OrganizeDeclarationsTests.swift | 116 +++++++++++++++++++- 5 files changed, 186 insertions(+), 19 deletions(-) diff --git a/Rules.md b/Rules.md index cc854a34e..dee786692 100644 --- a/Rules.md +++ b/Rules.md @@ -1411,7 +1411,7 @@ Option | Description `--visibilitymarks` | Marks for visibility groups (public:Public Fields,..) `--typemarks` | Marks for declaration type groups (classMethod:Baaz,..) `--groupblanklines` | Require a blank line after each subgroup. Default: true -`--sortswiftuiprops` | Sorts SwiftUI properties alphabetically, defaults to "false" +`--sortswiftuiprops` | Sort SwiftUI props: none, alphabetize, first-appearance-sort
Examples diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index e3bcb92ef..5efd3a338 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -1226,13 +1226,11 @@ struct _Descriptors { keyPath: \.preservedPrivateDeclarations ) - let alphabetizeSwiftUIPropertyTypes = OptionDescriptor( + let swiftUIPropertiesSortMode = OptionDescriptor( argumentName: "sortswiftuiprops", - displayName: "Alphabetize SwiftUI Properties", - help: "Sorts SwiftUI properties alphabetically, defaults to \"false\"", - keyPath: \.alphabetizeSwiftUIPropertyTypes, - trueValues: ["enabled", "true"], - falseValues: ["disabled", "false"] + displayName: "Sort SwiftUI Dynamic Properties", + help: "Sort SwiftUI props: none, alphabetize, first-appearance-sort", + keyPath: \.swiftUIPropertiesSortMode ) // MARK: - Internal diff --git a/Sources/Options.swift b/Sources/Options.swift index 348cef3dd..540e79e04 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -594,6 +594,14 @@ public enum ClosingParenPosition: String, CaseIterable { case `default` } +public enum SwiftUIPropertiesSortMode: String, CaseIterable { + /// Sorts all SwiftUI dynamic properties alphabetically + case alphabetize + /// Sorts SwiftUI dynamic properties by grouping all dynamic properties of the same type by using the first time each property appears + /// as the sort order. + case firstAppearanceSort = "first-appearance-sort" +} + /// 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 { @@ -680,7 +688,7 @@ public struct FormatOptions: CustomStringConvertible { public var customTypeMarks: Set public var blankLineAfterSubgroups: Bool public var alphabeticallySortedDeclarationPatterns: Set - public var alphabetizeSwiftUIPropertyTypes: Bool + public var swiftUIPropertiesSortMode: SwiftUIPropertiesSortMode? public var yodaSwap: YodaMode public var extensionACLPlacement: ExtensionACLPlacement public var redundantType: RedundantType @@ -806,7 +814,7 @@ public struct FormatOptions: CustomStringConvertible { customTypeMarks: Set = [], blankLineAfterSubgroups: Bool = true, alphabeticallySortedDeclarationPatterns: Set = [], - alphabetizeSwiftUIPropertyTypes: Bool = false, + swiftUIPropertiesSortMode: SwiftUIPropertiesSortMode? = nil, yodaSwap: YodaMode = .always, extensionACLPlacement: ExtensionACLPlacement = .onExtension, redundantType: RedundantType = .inferLocalsOnly, @@ -922,7 +930,7 @@ public struct FormatOptions: CustomStringConvertible { self.customTypeMarks = customTypeMarks self.blankLineAfterSubgroups = blankLineAfterSubgroups self.alphabeticallySortedDeclarationPatterns = alphabeticallySortedDeclarationPatterns - self.alphabetizeSwiftUIPropertyTypes = alphabetizeSwiftUIPropertyTypes + self.swiftUIPropertiesSortMode = swiftUIPropertiesSortMode self.yodaSwap = yodaSwap self.extensionACLPlacement = extensionACLPlacement self.redundantType = redundantType @@ -1063,3 +1071,14 @@ public struct Options { fileOptions?.shouldSkipFile(inputURL) ?? false } } + +extension Optional: RawRepresentable where Wrapped: RawRepresentable, Wrapped.RawValue == String { + public init?(rawValue: String) { + self = Wrapped(rawValue: rawValue) + } + + public var rawValue: String { + guard let wrapped = self else { return "none" } + return wrapped.rawValue + } +} diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift index 239a82898..0d91a6e89 100644 --- a/Sources/Rules/OrganizeDeclarations.swift +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -198,7 +198,8 @@ extension Formatter { _ categorizedDeclarations: [CategorizedDeclaration], sortAlphabeticallyWithinSubcategories: Bool ) -> [CategorizedDeclaration] { - categorizedDeclarations.enumerated() + let customDeclarationSortOrder = customDeclarationSortOrderList(from: categorizedDeclarations) + return categorizedDeclarations.enumerated() .sorted(by: { lhs, rhs in let (lhsOriginalIndex, lhs) = lhs let (rhsOriginalIndex, rhs) = rhs @@ -217,20 +218,33 @@ extension Formatter { return lhsName.localizedCompare(rhsName) == .orderedAscending } - if options.alphabetizeSwiftUIPropertyTypes, + if let swiftUIPropertiesSortMode = options.swiftUIPropertiesSortMode, lhs.category.type == rhs.category.type, let lhsSwiftUIProperty = lhs.declaration.swiftUIPropertyWrapper, let rhsSwiftUIProperty = rhs.declaration.swiftUIPropertyWrapper { - return lhsSwiftUIProperty.localizedCompare(rhsSwiftUIProperty) == .orderedAscending + switch swiftUIPropertiesSortMode { + case .alphabetize: + return lhsSwiftUIProperty.localizedCompare(rhsSwiftUIProperty) == .orderedAscending + case .firstAppearanceSort: + return customDeclarationSortOrder.areInRelativeOrder(lhs: lhsSwiftUIProperty, rhs: rhsSwiftUIProperty) + } + } else { + // Respect the original declaration ordering when the categories and types are the same + return lhsOriginalIndex < rhsOriginalIndex } - // Respect the original declaration ordering when the categories and types are the same - return lhsOriginalIndex < rhsOriginalIndex }) .map { $0.element } } + func customDeclarationSortOrderList(from categorizedDeclarations: [CategorizedDeclaration]) -> [String] { + guard options.swiftUIPropertiesSortMode == .firstAppearanceSort else { return [] } + return categorizedDeclarations + .compactMap(\.declaration.swiftUIPropertyWrapper) + .firstElementAppearanceOrder() + } + /// Whether or not type members should additionally be sorted alphabetically /// within individual subcategories func shouldSortAlphabeticallyWithinSubcategories(in typeDeclaration: TypeDeclaration) -> Bool { @@ -741,3 +755,31 @@ extension Formatter { } } } + +extension Array where Element: Equatable & Hashable { + /// Sort function to sort an array based on the order of the elements on Self + /// - Parameters: + /// - lhs: Sort closure left hand side element + /// - rhs: Sort closure right hand side element + /// - Returns: Whether the elements are sorted or not. + func areInRelativeOrder(lhs: Element, rhs: Element) -> Bool { + guard let lhsIndex = firstIndex(of: lhs) else { return false } + guard let rhsIndex = firstIndex(of: rhs) else { return true } + return lhsIndex < rhsIndex + } + + /// Creates a list without duplicates and ordered by the first time the element appeared in Self + /// For example, this function would transform [1,2,3,1,2] into [1,2,3] + func firstElementAppearanceOrder() -> [Element] { + var appeared: Set = [] + var appearedList: [Element] = [] + + for element in self { + if !appeared.contains(element) { + appeared.insert(element) + appearedList.append(element) + } + } + return appearedList + } +} diff --git a/Tests/Rules/OrganizeDeclarationsTests.swift b/Tests/Rules/OrganizeDeclarationsTests.swift index d0840678c..17adecfc5 100644 --- a/Tests/Rules/OrganizeDeclarationsTests.swift +++ b/Tests/Rules/OrganizeDeclarationsTests.swift @@ -3005,7 +3005,7 @@ class OrganizeDeclarationsTests: XCTestCase { ) } - func testSortSwiftUIPropertyWrappersSubCategory() { + func testSortSwiftUIPropertyWrappersSubCategoryAlphabetically() { let input = """ struct ContentView: View { init() {} @@ -3050,13 +3050,13 @@ class OrganizeDeclarationsTests: XCTestCase { organizeTypes: ["struct"], organizationMode: .visibility, blankLineAfterSubgroups: false, - alphabetizeSwiftUIPropertyTypes: true + swiftUIPropertiesSortMode: .alphabetize ), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) } - func testSortSwiftUIWrappersByTypeAndMaintainGroupSpacing() { + func testSortSwiftUIWrappersByTypeAndMaintainGroupSpacingAlphabetically() { let input = """ struct ContentView: View { init() {} @@ -3107,7 +3107,115 @@ class OrganizeDeclarationsTests: XCTestCase { organizeTypes: ["struct"], organizationMode: .visibility, blankLineAfterSubgroups: false, - alphabetizeSwiftUIPropertyTypes: true + swiftUIPropertiesSortMode: .alphabetize + ), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testSortSwiftUIPropertyWrappersSubCategoryPreservingGroupPosition() { + let input = """ + struct ContentView: View { + init() {} + + @Environment(\\.colorScheme) var colorScheme + @State var foo: Foo + @Binding var isOn: Bool + @Environment(\\.quux) var quux: Quux + + @ViewBuilder + var body: some View { + Toggle(label, isOn: $isOn) + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + @Environment(\\.colorScheme) var colorScheme + @Environment(\\.quux) var quux: Quux + @State var foo: Foo + @Binding var isOn: Bool + + @ViewBuilder + var body: some View { + Toggle(label, isOn: $isOn) + } + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizeTypes: ["struct"], + organizationMode: .visibility, + blankLineAfterSubgroups: false, + swiftUIPropertiesSortMode: .firstAppearanceSort + ), + exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] + ) + } + + func testSortSwiftUIWrappersByTypeAndMaintainGroupSpacingAndPosition() { + let input = """ + struct ContentView: View { + init() {} + + @State var foo: Foo + @State var bar: Bar + + @Environment(\\.colorScheme) var colorScheme + @Environment(\\.quux) var quux: Quux + + @Binding var isOn: Bool + + @ViewBuilder + var body: some View { + Toggle(label, isOn: $isOn) + } + } + """ + + let output = """ + struct ContentView: View { + + // MARK: Lifecycle + + init() {} + + // MARK: Internal + + @State var foo: Foo + @State var bar: Bar + + @Environment(\\.colorScheme) var colorScheme + @Environment(\\.quux) var quux: Quux + + @Binding var isOn: Bool + + @ViewBuilder + var body: some View { + Toggle(label, isOn: $isOn) + } + } + """ + + testFormatting( + for: input, output, + rule: .organizeDeclarations, + options: FormatOptions( + organizeTypes: ["struct"], + organizationMode: .visibility, + blankLineAfterSubgroups: false, + swiftUIPropertiesSortMode: .firstAppearanceSort ), exclude: [.blankLinesAtStartOfScope, .blankLinesAtEndOfScope] ) From 16af7f5fabf80eace7031d1f76b541bbc3aa2aad Mon Sep 17 00:00:00 2001 From: Miguel Jimenez Date: Wed, 21 Aug 2024 19:31:47 -0600 Subject: [PATCH 45/52] Remove redundant Void from protocol definitions (#1824) --- Sources/Rules/RedundantVoidReturnType.swift | 18 +++++++++++++--- .../Rules/BlankLinesBetweenScopesTests.swift | 2 +- .../Rules/RedundantVoidReturnTypeTests.swift | 21 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/Sources/Rules/RedundantVoidReturnType.swift b/Sources/Rules/RedundantVoidReturnType.swift index c8c4013a2..fabfe77af 100644 --- a/Sources/Rules/RedundantVoidReturnType.swift +++ b/Sources/Rules/RedundantVoidReturnType.swift @@ -30,8 +30,13 @@ public extension FormatRule { return } - guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) == .startOfScope("{") - else { return } + guard let nextToken = formatter.next(.nonSpaceOrCommentOrLinebreak, after: endIndex) else { return } + + let isInProtocol = nextToken == .endOfScope("}") || (nextToken.isKeywordOrAttribute && nextToken != .keyword("in")) + + // After a `Void` we could see the start of a function's body, or if the function is inside a protocol declaration + // we can find a keyword related to other declarations or the end scope of the protocol definition. + guard nextToken == .startOfScope("{") || isInProtocol else { return } guard let prevIndex = formatter.index(of: .endOfScope(")"), before: i), let parenIndex = formatter.index(of: .startOfScope("("), before: prevIndex), @@ -40,7 +45,14 @@ public extension FormatRule { else { return } - formatter.removeTokens(in: i ..< formatter.index(of: .nonSpace, after: endIndex)!) + + let startRemoveIndex: Int + if isInProtocol, formatter.token(at: i - 1)?.isSpace == true { + startRemoveIndex = i - 1 + } else { + startRemoveIndex = i + } + formatter.removeTokens(in: startRemoveIndex ..< formatter.index(of: .nonSpace, after: endIndex)!) } } } diff --git a/Tests/Rules/BlankLinesBetweenScopesTests.swift b/Tests/Rules/BlankLinesBetweenScopesTests.swift index c25c1dbfb..822b81f91 100644 --- a/Tests/Rules/BlankLinesBetweenScopesTests.swift +++ b/Tests/Rules/BlankLinesBetweenScopesTests.swift @@ -49,7 +49,7 @@ class BlankLinesBetweenScopesTests: XCTestCase { } func testNoBlankLineBetweenFunctionsInProtocol() { - let input = "protocol Foo {\n func bar() -> Void\n func baz() -> Int\n}" + let input = "protocol Foo {\n func bar()\n func baz() -> Int\n}" testFormatting(for: input, rule: .blankLinesBetweenScopes) } diff --git a/Tests/Rules/RedundantVoidReturnTypeTests.swift b/Tests/Rules/RedundantVoidReturnTypeTests.swift index 34f10c168..d56a77f2c 100644 --- a/Tests/Rules/RedundantVoidReturnTypeTests.swift +++ b/Tests/Rules/RedundantVoidReturnTypeTests.swift @@ -91,4 +91,25 @@ class RedundantVoidReturnTypeTests: XCTestCase { let options = FormatOptions(closureVoidReturn: .preserve) testFormatting(for: input, rule: .redundantVoidReturnType, options: options) } + + func testRemoveRedundantVoidInProtocolDeclaration() { + let input = """ + protocol Foo { + func foo() -> Void + func bar() -> () + var baz: Int { get } + func bazz() -> ( ) + } + """ + + let output = """ + protocol Foo { + func foo() + func bar() + var baz: Int { get } + func bazz() + } + """ + testFormatting(for: input, [output], rules: [.void, .redundantVoidReturnType]) + } } From 7689140133c7e5859fde49bfe82cf5b65d1830b2 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Sat, 24 Aug 2024 11:05:08 +0100 Subject: [PATCH 46/52] Fix wrapped array type extension mistaken for collection --- Sources/ParsingHelpers.swift | 2 +- Tests/ParsingHelpersTests.swift | 8 ++++++++ Tests/Rules/TrailingCommasTests.swift | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index e9b9690e5..a366b0fa5 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -361,7 +361,7 @@ extension Formatter { { isType = true } - case .operator("->", _), .startOfScope("<"): + case .operator("->", _), .startOfScope("<"), .keyword("extension"): isType = true case .startOfScope("["), .startOfScope("("): guard let type = scopeType(at: prevIndex) else { diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index 4005b9a27..7ffcd066f 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -2444,4 +2444,12 @@ class ParsingHelpersTests: XCTestCase { ["at"] ) } + + // MARK: scopeType + + func testScopeTypeForArrayExtension() { + let input = "extension [Int] {}" + let formatter = Formatter(tokenize(input)) + XCTAssertEqual(formatter.scopeType(at: 2), .arrayType) + } } diff --git a/Tests/Rules/TrailingCommasTests.swift b/Tests/Rules/TrailingCommasTests.swift index 528c2eb90..523647637 100644 --- a/Tests/Rules/TrailingCommasTests.swift +++ b/Tests/Rules/TrailingCommasTests.swift @@ -247,6 +247,17 @@ class TrailingCommasTests: XCTestCase { testFormatting(for: input, rule: .trailingCommas) } + func testTrailingCommaNotAddedToArrayExtension() { + let input = """ + extension [ + Int + ] { + func foo() {} + } + """ + testFormatting(for: input, rule: .trailingCommas) + } + // trailingCommas = false func testCommaNotAddedToLastItem() { From d94a4a772f81523bdea9717a2ccd7c2adb860daa Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Sat, 24 Aug 2024 17:48:44 +0100 Subject: [PATCH 47/52] Move examples into rules files --- Rules.md | 2 +- Sources/Examples.swift | 2033 ----------------- Sources/FormatRule.swift | 3 + Sources/Rules/Acronyms.swift | 13 + Sources/Rules/AndOperator.swift | 23 +- Sources/Rules/AnyObjectProtocol.swift | 12 +- Sources/Rules/AssertionFailures.swift | 16 + Sources/Rules/BlankLineAfterImports.swift | 13 +- Sources/Rules/BlankLineAfterSwitchCase.swift | 22 + Sources/Rules/BlankLinesAroundMark.swift | 21 + Sources/Rules/BlankLinesAtEndOfScope.swift | 27 + Sources/Rules/BlankLinesAtStartOfScope.swift | 27 + .../BlankLinesBetweenChainedFunctions.swift | 4 +- Sources/Rules/BlankLinesBetweenImports.swift | 12 + Sources/Rules/BlankLinesBetweenScopes.swift | 23 + Sources/Rules/BlockComments.swift | 21 + Sources/Rules/Braces.swift | 19 + Sources/Rules/ConditionalAssignment.swift | 35 + Sources/Rules/ConsecutiveBlankLines.swift | 18 +- Sources/Rules/ConsecutiveSpaces.swift | 8 +- .../Rules/ConsistentSwitchCaseSpacing.swift | 48 + Sources/Rules/DocComments.swift | 14 + .../Rules/DocCommentsBeforeAttributes.swift | 8 + Sources/Rules/DuplicateImports.swift | 17 +- Sources/Rules/ElseOnSameLine.swift | 46 + Sources/Rules/EmptyBraces.swift | 9 + Sources/Rules/EmptyExtension.swift | 7 + Sources/Rules/ExtensionAccessControl.swift | 31 + Sources/Rules/FileHeader.swift | 86 + Sources/Rules/GenericExtensions.swift | 29 + Sources/Rules/HoistAwait.swift | 11 + Sources/Rules/HoistPatternLet.swift | 16 + Sources/Rules/HoistTry.swift | 11 + Sources/Rules/Indent.swift | 41 + Sources/Rules/InitCoderUnavailable.swift | 8 + Sources/Rules/IsEmpty.swift | 17 + Sources/Rules/LeadingDelimiters.swift | 9 + Sources/Rules/MarkTypes.swift | 15 + Sources/Rules/ModifierOrder.swift | 19 + Sources/Rules/NoExplicitOwnership.swift | 6 + Sources/Rules/NumberFormatting.swift | 11 + Sources/Rules/OpaqueGenericParameters.swift | 24 + Sources/Rules/OrganizeDeclarations.swift | 105 + Sources/Rules/PreferForLoop.swift | 28 + Sources/Rules/PreferKeyPath.swift | 11 +- Sources/Rules/PropertyType.swift | 27 + Sources/Rules/RedundantBackticks.swift | 13 +- Sources/Rules/RedundantBreak.swift | 14 +- Sources/Rules/RedundantClosure.swift | 15 + Sources/Rules/RedundantExtensionACL.swift | 13 +- Sources/Rules/RedundantFileprivate.swift | 24 +- Sources/Rules/RedundantGet.swift | 15 +- Sources/Rules/RedundantInit.swift | 6 + Sources/Rules/RedundantInternal.swift | 19 +- Sources/Rules/RedundantLet.swift | 8 +- Sources/Rules/RedundantLetError.swift | 9 +- Sources/Rules/RedundantNilInit.swift | 25 + Sources/Rules/RedundantObjc.swift | 18 +- Sources/Rules/RedundantOptionalBinding.swift | 13 + Sources/Rules/RedundantParens.swift | 23 +- Sources/Rules/RedundantPattern.swift | 13 +- Sources/Rules/RedundantProperty.swift | 9 + Sources/Rules/RedundantRawValues.swift | 15 +- Sources/Rules/RedundantReturn.swift | 25 +- Sources/Rules/RedundantSelf.swift | 41 + Sources/Rules/RedundantType.swift | 39 + Sources/Rules/RedundantTypedThrows.swift | 19 +- Sources/Rules/RedundantVoidReturnType.swift | 11 + Sources/Rules/Semicolons.swift | 18 + Sources/Rules/SortDeclarations.swift | 60 + Sources/Rules/SortImports.swift | 23 + Sources/Rules/SortTypealiases.swift | 16 +- Sources/Rules/SpaceAroundBraces.swift | 13 +- Sources/Rules/SpaceAroundBrackets.swift | 13 +- Sources/Rules/SpaceAroundComments.swift | 13 +- Sources/Rules/SpaceAroundGenerics.swift | 8 +- Sources/Rules/SpaceAroundOperators.swift | 16 + Sources/Rules/SpaceAroundParens.swift | 13 +- Sources/Rules/SpaceInsideBraces.swift | 8 +- Sources/Rules/SpaceInsideBrackets.swift | 8 +- Sources/Rules/SpaceInsideComments.swift | 13 +- Sources/Rules/SpaceInsideGenerics.swift | 8 +- Sources/Rules/SpaceInsideParens.swift | 8 +- Sources/Rules/SpacingGuards.swift | 20 +- Sources/Rules/StrongOutlets.swift | 11 +- Sources/Rules/StrongifiedSelf.swift | 12 +- Sources/Rules/Todos.swift | 13 +- Sources/Rules/TrailingClosures.swift | 11 + Sources/Rules/TrailingCommas.swift | 15 + Sources/Rules/TypeSugar.swift | 16 + Sources/Rules/UnusedArguments.swift | 31 + Sources/Rules/UnusedPrivateDeclaration.swift | 9 + Sources/Rules/Void.swift | 26 + Sources/Rules/WrapArguments.swift | 68 + Sources/Rules/WrapAttributes.swift | 37 + Sources/Rules/WrapConditionalBodies.swift | 15 + Sources/Rules/WrapEnumCases.swift | 12 + Sources/Rules/WrapLoopBodies.swift | 15 + .../WrapMultilineConditionalAssignment.swift | 15 + .../Rules/WrapMultilineStatementBraces.swift | 55 + Sources/Rules/WrapSwitchCases.swift | 14 + SwiftFormat.xcodeproj/project.pbxproj | 10 - Tests/MetadataTests.swift | 8 - 103 files changed, 1949 insertions(+), 2095 deletions(-) delete mode 100644 Sources/Examples.swift diff --git a/Rules.md b/Rules.md index dee786692..622bbb702 100644 --- a/Rules.md +++ b/Rules.md @@ -2588,7 +2588,7 @@ Remove space inside parentheses. ## spacingGuards -Remove space between guard and add spaces after last guard. +Remove space between guard statements, and add spaces after last guard.
Examples diff --git a/Sources/Examples.swift b/Sources/Examples.swift deleted file mode 100644 index f6f11f6b0..000000000 --- a/Sources/Examples.swift +++ /dev/null @@ -1,2033 +0,0 @@ -// -// Examples.swift -// SwiftFormat -// -// Created by Nick Lockwood on 07/02/2019. -// Copyright © 2019 Nick Lockwood. All rights reserved. -// - -import Foundation - -extension FormatRule { - var examples: String? { - examplesByRuleName[name] - } -} - -extension _FormatRules { - var examplesByName: [String: String] { - examplesByRuleName - } -} - -private let examplesByRuleName: [String: String] = { - var examples = [String: String]() - for (label, value) in Mirror(reflecting: Examples()).children { - guard let name = label, let text = value as? String, !text.isEmpty else { - continue - } - examples[name] = text - } - return examples -}() - -private struct Examples { - let andOperator = """ - ```diff - - if true && true { - + if true, true { - ``` - - ```diff - - guard true && true else { - + guard true, true else { - ``` - - ```diff - - if functionReturnsBool() && true { - + if functionReturnsBool(), true { - ``` - - ```diff - - if functionReturnsBool() && variable { - + if functionReturnsBool(), variable { - ``` - """ - - let anyObjectProtocol = """ - ```diff - - protocol Foo: class {} - + protocol Foo: AnyObject {} - ``` - - **NOTE:** The guideline to use `AnyObject` instead of `class` was only - introduced in Swift 4.1, so the `anyObjectProtocol` rule is disabled unless the - swift version is set to 4.1 or above. - """ - - let blankLinesAtEndOfScope = """ - ```diff - func foo() { - // foo - - - } - - func foo() { - // foo - } - ``` - - ```diff - array = [ - foo, - bar, - baz, - - - ] - - array = [ - foo, - bar, - baz, - ] - ``` - """ - - let blankLinesAtStartOfScope = """ - ```diff - func foo() { - - - // foo - } - - func foo() { - // foo - } - ``` - - ```diff - array = [ - - - foo, - bar, - baz, - ] - - array = [ - foo, - bar, - baz, - ] - ``` - """ - - let blankLinesBetweenImports = """ - ```diff - import A - - - import B - import C - - - - - @testable import D - import E - ``` - """ - - let blankLineAfterImports = """ - ```diff - import A - import B - @testable import D - + - class Foo { - // foo - } - ``` - """ - - let blankLinesBetweenScopes = """ - ```diff - func foo() { - // foo - } - func bar() { - // bar - } - var baz: Bool - var quux: Int - - func foo() { - // foo - } - + - func bar() { - // bar - } - + - var baz: Bool - var quux: Int - ``` - """ - - let blankLinesAroundMark = """ - ```diff - func foo() { - // foo - } - // MARK: bar - func bar() { - // bar - } - - func foo() { - // foo - } - + - // MARK: bar - + - func bar() { - // bar - } - ``` - """ - - let braces = """ - ```diff - - if x - - { - // foo - } - - else - - { - // bar - } - - + if x { - // foo - } - + else { - // bar - } - ``` - """ - - let consecutiveBlankLines = """ - ```diff - func foo() { - let x = "bar" - - - - print(x) - } - - func foo() { - let x = "bar" - - print(x) - } - ``` - """ - - let consecutiveSpaces = """ - ```diff - - let foo = 5 - + let foo = 5 - ``` - """ - - let duplicateImports = """ - ```diff - import Foo - import Bar - - import Foo - ``` - - ```diff - import B - #if os(iOS) - import A - - import B - #endif - ``` - """ - - let elseOnSameLine = """ - ```diff - if x { - // foo - - } - - else { - // bar - } - - if x { - // foo - + } else { - // bar - } - ``` - - ```diff - do { - // try foo - - } - - catch { - // bar - } - - do { - // try foo - + } catch { - // bar - } - ``` - - ```diff - repeat { - // foo - - } - - while { - // bar - } - - repeat { - // foo - + } while { - // bar - } - ``` - """ - - let emptyBraces = """ - ```diff - - func foo() { - - - - } - - + func foo() {} - ``` - """ - - let wrapConditionalBodies = """ - ```diff - - guard let foo = bar else { return baz } - + guard let foo = bar else { - + return baz - + } - ``` - - ```diff - - if foo { return bar } - + if foo { - + return bar - + } - ``` - """ - - let wrapLoopBodies = """ - ```diff - - for foo in array { print(foo) } - + for foo in array { - + print(foo) - + } - ``` - - ```diff - - while let foo = bar.next() { print(foo) } - + while let foo = bar.next() { - + print(foo) - + } - ``` - """ - - let hoistPatternLet = """ - ```diff - - (let foo, let bar) = baz() - + let (foo, bar) = baz() - ``` - - ```diff - - if case .foo(let bar, let baz) = quux { - // inner foo - } - - + if case let .foo(bar, baz) = quux { - // inner foo - } - ``` - """ - - let hoistAwait = """ - ```diff - - greet(await forename, await surname) - + await greet(forename, surname) - ``` - - ```diff - - let foo = String(try await getFoo()) - + let foo = await String(try getFoo()) - ``` - """ - - let hoistTry = """ - ```diff - - foo(try bar(), try baz()) - + try foo(bar(), baz()) - ``` - - ```diff - - let foo = String(try await getFoo()) - + let foo = try String(await getFoo()) - ``` - """ - - let indent = """ - ```diff - if x { - - // foo - } else { - - // bar - - } - - if x { - + // foo - } else { - + // bar - + } - ``` - - ```diff - let array = [ - foo, - - bar, - - baz - - ] - - let array = [ - foo, - + bar, - + baz - + ] - ``` - - ```diff - switch foo { - - case bar: break - - case baz: break - } - - switch foo { - + case bar: break - + case baz: break - } - ``` - """ - - let isEmpty = """ - ```diff - - if foo.count == 0 { - + if foo.isEmpty { - - - if foo.count > 0 { - + if !foo.isEmpty { - - - if foo?.count == 0 { - + if foo?.isEmpty == true { - ``` - - ***NOTE:*** In rare cases, the `isEmpty` rule may insert an `isEmpty` call for - a type that doesn't implement that property, breaking the program. For this - reason, the rule is disabled by default, and must be manually enabled via the - `--enable isEmpty` option. - """ - - let initCoderUnavailable = """ - ```diff - + @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - ``` - """ - - let numberFormatting = """ - ```diff - - let color = 0xFF77A5 - + let color = 0xff77a5 - ``` - - ```diff - - let big = 123456.123 - + let big = 123_456.123 - ``` - """ - - let redundantBackticks = """ - ```diff - - let `infix` = bar - + let infix = bar - ``` - - ```diff - - func foo(with `default`: Int) {} - + func foo(with default: Int) {} - ``` - """ - - let redundantBreak = """ - ```diff - switch foo { - case bar: - print("bar") - - break - default: - print("default") - - break - } - ``` - """ - - let redundantGet = """ - ```diff - var foo: Int { - - get { - - return 5 - - } - } - - var foo: Int { - + return 5 - } - ``` - """ - - let redundantExtensionACL = """ - ```diff - public extension URL { - - public func queryParameter(_ name: String) -> String { ... } - } - - public extension URL { - + func queryParameter(_ name: String) -> String { ... } - } - ``` - """ - - let redundantFileprivate = """ - ```diff - - fileprivate let someConstant = "someConstant" - + private let someConstant = "someConstant" - ``` - - In Swift 4 and above, `fileprivate` can also be replaced with `private` for - members that are only accessed from extensions in the same file: - - ```diff - class Foo { - - fileprivate var foo = "foo" - + private var foo = "foo" - } - - extension Foo { - func bar() { - print(self.foo) - } - } - ``` - """ - - let redundantLet = """ - ```diff - - let _ = foo() - + _ = foo() - ``` - """ - - let redundantLetError = """ - ```diff - - do { ... } catch let error { log(error) } - + do { ... } catch { log(error) } - ``` - """ - - let redundantNilInit = """ - `--nilinit remove` - - ```diff - - var foo: Int? = nil - + var foo: Int? - ``` - - ```diff - // doesn't apply to `let` properties - let foo: Int? = nil - ``` - - ```diff - // doesn't affect non-nil initialization - var foo: Int? = 0 - ``` - - `--nilinit insert` - - ```diff - - var foo: Int? - + var foo: Int? = nil - ``` - """ - - let redundantObjc = """ - ```diff - - @objc @IBOutlet var label: UILabel! - + @IBOutlet var label: UILabel! - ``` - - ```diff - - @IBAction @objc func goBack() {} - + @IBAction func goBack() {} - ``` - - ```diff - - @objc @NSManaged private var foo: String? - + @NSManaged private var foo: String? - ``` - """ - - let redundantParens = """ - ```diff - - if (foo == true) {} - + if foo == true {} - ``` - - ```diff - - while (i < bar.count) {} - + while i < bar.count {} - ``` - - ```diff - - queue.async() { ... } - + queue.async { ... } - ``` - - ```diff - - let foo: Int = ({ ... })() - + let foo: Int = { ... }() - ``` - """ - - let redundantPattern = """ - ```diff - - if case .foo(_, _) = bar {} - + if case .foo = bar {} - ``` - - ```diff - - let (_, _) = bar - + let _ = bar - ``` - """ - - let redundantRawValues = """ - ```diff - enum Foo: String { - - case bar = "bar" - case baz = "quux" - } - - enum Foo: String { - + case bar - case baz = "quux" - } - ``` - """ - - let redundantReturn = """ - ```diff - - array.filter { return $0.foo == bar } - + array.filter { $0.foo == bar } - - // Swift 5.1+ (SE-0255) - var foo: String { - - return "foo" - + "foo" - } - - // Swift 5.9+ (SE-0380) and with conditionalAssignment rule enabled - func foo(_ condition: Bool) -> String { - if condition { - - return "foo" - + "foo" - } else { - - return "bar" - + "bar" - } - } - ``` - """ - - let redundantSelf = """ - ```diff - func foobar(foo: Int, bar: Int) { - self.foo = foo - self.bar = bar - - self.baz = 42 - } - - func foobar(foo: Int, bar: Int) { - self.foo = foo - self.bar = bar - + baz = 42 - } - ``` - - In the rare case of functions with `@autoclosure` arguments, `self` may be - required at the call site, but SwiftFormat is unable to detect this - automatically. You can use the `--selfrequired` command-line option to specify - a list of such methods, and the `redundantSelf` rule will then ignore them. - - An example of such a method is the `expect()` function in the Nimble unit - testing framework (https://github.com/Quick/Nimble), which is common enough that - SwiftFormat excludes it by default. - - There is also an option to always use explicit `self` but *only* inside `init`, - by using `--self init-only`: - - ```diff - init(foo: Int, bar: Int) { - self.foo = foo - self.bar = bar - - baz = 42 - } - - init(foo: Int, bar: Int) { - self.foo = foo - self.bar = bar - + self.baz = 42 - } - ``` - """ - - let redundantVoidReturnType = """ - ```diff - - func foo() -> Void { - // returns nothing - } - - + func foo() { - // returns nothing - } - ``` - """ - - let redundantInit = """ - ```diff - - String.init("text") - + String("text") - ``` - """ - - let semicolons = """ - ```diff - - let foo = 5; - + let foo = 5 - ``` - - ```diff - - let foo = 5; let bar = 6 - + let foo = 5 - + let bar = 6 - ``` - - ```diff - // semicolon is not removed if it would affect the behavior of the code - return; - goto(fail) - ``` - """ - - let sortImports = """ - ```diff - - import Foo - - import Bar - + import Bar - + import Foo - ``` - - ```diff - - import B - - import A - - #if os(iOS) - - import Foo-iOS - - import Bar-iOS - - #endif - + import A - + import B - + #if os(iOS) - + import Bar-iOS - + import Foo-iOS - + #endif - ``` - """ - - let spaceAroundBraces = """ - ```diff - - foo.filter{ return true }.map{ $0 } - + foo.filter { return true }.map { $0 } - ``` - - ```diff - - foo( {} ) - + foo({}) - ``` - """ - - let spaceAroundBrackets = """ - ```diff - - foo as[String] - + foo as [String] - ``` - - ```diff - - foo = bar [5] - + foo = bar[5] - ``` - """ - - let spaceAroundComments = """ - ```diff - - let a = 5// assignment - + let a = 5 // assignment - ``` - - ```diff - - func foo() {/* ... */} - + func foo() { /* ... */ } - ``` - """ - - let spaceAroundGenerics = """ - ```diff - - Foo () - + Foo() - ``` - """ - - let spaceAroundOperators = """ - ```diff - - foo . bar() - + foo.bar() - ``` - - ```diff - - a+b+c - + a + b + c - ``` - - ```diff - - func ==(lhs: Int, rhs: Int) -> Bool - + func == (lhs: Int, rhs: Int) -> Bool - ``` - """ - - let spaceAroundParens = """ - ```diff - - init (foo) - + init(foo) - ``` - - ```diff - - switch(x){ - + switch (x) { - ``` - """ - - let spaceInsideBraces = """ - ```diff - - foo.filter {return true} - + foo.filter { return true } - ``` - """ - - let spaceInsideBrackets = """ - ```diff - - [ 1, 2, 3 ] - + [1, 2, 3] - ``` - """ - - let spaceInsideComments = """ - ```diff - - let a = 5 //assignment - + let a = 5 // assignment - ``` - - ```diff - - func foo() { /*...*/ } - + func foo() { /* ... */ } - ``` - """ - - let spaceInsideGenerics = """ - ```diff - - Foo< Bar, Baz > - + Foo - ``` - """ - - let spaceInsideParens = """ - ```diff - - ( a, b) - + (a, b) - ``` - """ - - let redundantType = """ - ```diff - // inferred - - let view: UIView = UIView() - + let view = UIView() - - // explicit - - let view: UIView = UIView() - + let view: UIView = .init() - - // infer-locals-only - class Foo { - - let view: UIView = UIView() - + let view: UIView = .init() - - func method() { - - let view: UIView = UIView() - + let view = UIView() - } - } - - // Swift 5.9+, inferred (SE-0380) - - let foo: Foo = if condition { - + let foo = if condition { - Foo("foo") - } else { - Foo("bar") - } - - // Swift 5.9+, explicit (SE-0380) - let foo: Foo = if condition { - - Foo("foo") - + .init("foo") - } else { - - Foo("bar") - + .init("foo") - } - ``` - """ - - let modifierOrder = """ - ```diff - - lazy public weak private(set) var foo: UIView? - + public private(set) lazy weak var foo: UIView? - ``` - - ```diff - - final public override func foo() - + override public final func foo() - ``` - - ```diff - - convenience private init() - + private convenience init() - ``` - - **NOTE:** If the `--modifierorder` option isn't set, the default order will be: - `\(_FormatRules.defaultModifierOrder.flatMap { $0 }.joined(separator: "`, `"))` - """ - - let strongifiedSelf = """ - ```diff - - guard let `self` = self else { return } - + guard let self = self else { return } - ``` - - **NOTE:** assignment to un-escaped `self` is only supported in Swift 4.2 and - above, so the `strongifiedSelf` rule is disabled unless the Swift version is - set to 4.2 or above. - """ - - let strongOutlets = """ - As per Apple's recommendation - (https://developer.apple.com/videos/play/wwdc2015/407/ @ 32:30). - - ```diff - - @IBOutlet weak var label: UILabel! - + @IBOutlet var label: UILabel! - ``` - """ - - let trailingClosures = """ - ```diff - - DispatchQueue.main.async(execute: { ... }) - + DispatchQueue.main.async { - ``` - - ```diff - - let foo = bar.map({ ... }).joined() - + let foo = bar.map { ... }.joined() - ``` - """ - - let trailingCommas = """ - ```diff - let array = [ - foo, - bar, - - baz - ] - - let array = [ - foo, - bar, - + baz, - ] - ``` - """ - - let todos = """ - ```diff - - /* TODO fix this properly */ - + /* TODO: fix this properly */ - ``` - - ```diff - - // MARK - UIScrollViewDelegate - + // MARK: - UIScrollViewDelegate - ``` - """ - - let typeSugar = """ - ```diff - - var foo: Array - + var foo: [String] - ``` - - ```diff - - var foo: Dictionary - + var foo: [String: Int] - ``` - - ```diff - - var foo: Optional<(Int) -> Void> - + var foo: ((Int) -> Void)? - ``` - """ - - let unusedArguments = """ - ```diff - - func foo(bar: Int, baz: String) { - print("Hello \\(baz)") - } - - + func foo(bar _: Int, baz: String) { - print("Hello \\(baz)") - } - ``` - - ```diff - - func foo(_ bar: Int) { - ... - } - - + func foo(_: Int) { - ... - } - ``` - - ```diff - - request { response, data in - self.data += data - } - - + request { _, data in - self.data += data - } - ``` - """ - - let wrapEnumCases = """ - ```diff - enum Foo { - - case bar, baz - } - - enum Foo { - + case bar - + case baz - } - ``` - """ - - let wrapSwitchCases = """ - ```diff - switch foo { - - case .bar, .baz: - break - } - - switch foo { - + case .foo, - + .bar: - break - } - ``` - """ - - let void = """ - ```diff - - let foo: () -> () - + let foo: () -> Void - ``` - - ```diff - - let bar: Void -> Void - + let bar: () -> Void - ``` - - ```diff - - let baz: (Void) -> Void - + let baz: () -> Void - ``` - - ```diff - - func quux() -> (Void) - + func quux() -> Void - ``` - - ```diff - - callback = { _ in Void() } - + callback = { _ in () } - ``` - """ - - let wrapArguments = """ - **NOTE:** For backwards compatibility with previous versions, if no value is - provided for `--wrapparameters`, the value for `--wraparguments` will be used. - - `--wraparguments before-first` - - ```diff - - foo(bar: Int, - - baz: String) - - + foo( - + bar: Int, - + baz: String - + ) - ``` - - ```diff - - class Foo - - + class Foo< - + Bar, - + Baz - + > - ``` - - `--wrapparameters after-first` - - ```diff - - func foo( - - bar: Int, - - baz: String - - ) { - ... - } - - + func foo(bar: Int, - + baz: String) - + { - ... - } - ``` - - `--wrapcollections before-first`: - - ```diff - - let foo = [bar, - baz, - - quuz] - - + let foo = [ - + bar, - baz, - + quuz - + ] - ``` - - `--conditionswrap auto`: - - ```diff - - guard let foo = foo, let bar = bar, let third = third - + guard let foo = foo, - + let bar = bar, - + let third = third - else {} - ``` - - """ - - let wrapMultilineStatementBraces = """ - ```diff - if foo, - - bar { - // ... - } - - if foo, - + bar - + { - // ... - } - ``` - - ```diff - guard foo, - - bar else { - // ... - } - - guard foo, - + bar else - + { - // ... - } - ``` - - ```diff - func foo( - bar: Int, - - baz: Int) { - // ... - } - - func foo( - bar: Int, - + baz: Int) - + { - // ... - } - ``` - - ```diff - class Foo: NSObject, - - BarProtocol { - // ... - } - - class Foo: NSObject, - + BarProtocol - + { - // ... - } - ``` - """ - - let leadingDelimiters = """ - ```diff - - guard let foo = maybeFoo // first - - , let bar = maybeBar else { ... } - - + guard let foo = maybeFoo, // first - + let bar = maybeBar else { ... } - ``` - """ - - let wrapAttributes = """ - `--funcattributes prev-line` - - ```diff - - @objc func foo() {} - - + @objc - + func foo() { } - ``` - - `--funcattributes same-line` - - ```diff - - @objc - - func foo() { } - - + @objc func foo() {} - ``` - - `--typeattributes prev-line` - - ```diff - - @objc class Foo {} - - + @objc - + class Foo { } - ``` - - `--typeattributes same-line` - - ```diff - - @objc - - enum Foo { } - - + @objc enum Foo {} - ``` - """ - - let preferKeyPath = """ - ```diff - - let barArray = fooArray.map { $0.bar } - + let barArray = fooArray.map(\\.bar) - - - let barArray = fooArray.compactMap { $0.optionalBar } - + let barArray = fooArray.compactMap(\\.optionalBar) - ``` - """ - - let organizeDeclarations = """ - Default value for `--visibilityorder` when using `--organizationmode visibility`: - `\(VisibilityCategory.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` - - Default value for `--visibilityorder` when using `--organizationmode type`: - `\(VisibilityCategory.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` - - **NOTE:** When providing custom arguments for `--visibilityorder` the following entries must be included: - `\(VisibilityCategory.essentialCases.map { $0.rawValue }.joined(separator: ", "))` - - Default value for `--typeorder` when using `--organizationmode visibility`: - `\(DeclarationType.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` - - Default value for `--typeorder` when using `--organizationmode type`: - `\(DeclarationType.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` - - **NOTE:** The follow declaration types must be included in either `--typeorder` or `--visibilityorder`: - `\(DeclarationType.essentialCases.map { $0.rawValue }.joined(separator: ", "))` - - `--organizationmode visibility` (default) - - ```diff - public class Foo { - - public func c() -> String {} - - - - public let a: Int = 1 - - private let g: Int = 2 - - let e: Int = 2 - - public let b: Int = 3 - - - - public func d() {} - - func f() {} - - init() {} - - deinit() {} - } - - public class Foo { - + - + // MARK: Lifecycle - + - + init() {} - + deinit() {} - + - + // MARK: Public - + - + public let a: Int = 1 - + public let b: Int = 3 - + - + public func c() -> String {} - + public func d() {} - + - + // MARK: Internal - + - + let e: Int = 2 - + - + func f() {} - + - + // MARK: Private - + - + private let g: Int = 2 - + - } - ``` - - `--organizationmode type` - - ```diff - public class Foo { - - public func c() -> String {} - - - - public let a: Int = 1 - - private let g: Int = 2 - - let e: Int = 2 - - public let b: Int = 3 - - - - public func d() {} - - func f() {} - - init() {} - - deinit() {} - } - - public class Foo { - + - + // MARK: Properties - + - + public let a: Int = 1 - + public let b: Int = 3 - + - + let e: Int = 2 - + - + private let g: Int = 2 - + - + // MARK: Lifecycle - + - + init() {} - + deinit() {} - + - + // MARK: Functions - + - + public func c() -> String {} - + public func d() {} - + - } - ``` - """ - - let extensionAccessControl = """ - `--extensionacl on-extension` (default) - - ```diff - - extension Foo { - - public func bar() {} - - public func baz() {} - } - - + public extension Foo { - + func bar() {} - + func baz() {} - } - ``` - - `--extensionacl on-declarations` - - ```diff - - public extension Foo { - - func bar() {} - - func baz() {} - - internal func quux() {} - } - - + extension Foo { - + public func bar() {} - + public func baz() {} - + func quux() {} - } - ``` - """ - - let markTypes = """ - ```diff - + // MARK: - FooViewController - + - final class FooViewController: UIViewController { } - - + // MARK: UICollectionViewDelegate - + - extension FooViewController: UICollectionViewDelegate { } - - + // MARK: - String + FooProtocol - + - extension String: FooProtocol { } - ``` - """ - - let assertionFailures = """ - ```diff - - assert(false) - + assertionFailure() - ``` - - ```diff - - assert(false, "message", 2, 1) - + assertionFailure("message", 2, 1) - ``` - - ```diff - - precondition(false, "message", 2, 1) - + preconditionFailure("message", 2, 1) - ``` - """ - - let acronyms = """ - ```diff - - let destinationUrl: URL - - let urlRouter: UrlRouter - - let screenId: String - - let entityUuid: UUID - - + let destinationURL: URL - + let urlRouter: URLRouter - + let screenID: String - + let entityUUID: UUID - ``` - """ - - let blockComments = """ - ```diff - - /* - - * foo - - * bar - - */ - - + // foo - + // bar - ``` - - ```diff - - /** - - * foo - - * bar - - */ - - + /// foo - + /// bar - ``` - """ - - let redundantClosure = """ - ```diff - - let foo = { Foo() }() - + let foo = Foo() - ``` - - ```diff - - lazy var bar = { - - Bar(baaz: baaz, - - quux: quux) - - }() - + lazy var bar = Bar(baaz: baaz, - + quux: quux) - ``` - """ - - let sortDeclarations = """ - ```diff - // swiftformat:sort - enum FeatureFlags { - - case upsellB - - case fooFeature - - case barFeature - - case upsellA( - - fooConfiguration: Foo, - - barConfiguration: Bar) - + case barFeature - + case fooFeature - + case upsellA( - + fooConfiguration: Foo, - + barConfiguration: Bar) - + case upsellB - } - - config: - ``` - sortedpatterns: 'Feature' - ``` - - enum FeatureFlags { - - case upsellB - - case fooFeature - - case barFeature - - case upsellA( - - fooConfiguration: Foo, - - barConfiguration: Bar) - + case barFeature - + case fooFeature - + case upsellA( - + fooConfiguration: Foo, - + barConfiguration: Bar) - + case upsellB - } - - enum FeatureFlags { - // swiftformat:sort:begin - - case upsellB - - case fooFeature - - case barFeature - - case upsellA( - - fooConfiguration: Foo, - - barConfiguration: Bar) - + case barFeature - + case fooFeature - + case upsellA( - + fooConfiguration: Foo, - + barConfiguration: Bar) - + case upsellB - // swiftformat:sort:end - - var anUnsortedProperty: Foo { - Foo() - } - } - ``` - """ - - let redundantOptionalBinding = """ - ```diff - - if let foo = foo { - + if let foo { - print(foo) - } - - - guard let self = self else { - + guard let self else { - return - } - ``` - """ - - let opaqueGenericParameters = """ - ```diff - - func handle(_ value: T) { - + func handle(_ value: some Fooable) { - print(value) - } - - - func handle(_ value: T) where T: Fooable, T: Barable { - + func handle(_ value: some Fooable & Barable) { - print(value) - } - - - func handle(_ value: T) where T.Element == Foo { - + func handle(_ value: some Collection) { - print(value) - } - - // With `--someany enabled` (the default) - - func handle(_ value: T) { - + func handle(_ value: some Any) { - print(value) - } - ``` - """ - - let genericExtensions = """ - ```diff - - extension Array where Element == Foo {} - - extension Optional where Wrapped == Foo {} - - extension Dictionary where Key == Foo, Value == Bar {} - - extension Collection where Element == Foo {} - + extension Array {} - + extension Optional {} - + extension Dictionary {} - + extension Collection {} - - // With `typeSugar` also enabled: - - extension Array where Element == Foo {} - - extension Optional where Wrapped == Foo {} - - extension Dictionary where Key == Foo, Value == Bar {} - + extension [Foo] {} - + extension Foo? {} - + extension [Key: Value] {} - - // Also supports user-defined types! - - extension LinkedList where Element == Foo {} - - extension Reducer where - - State == FooState, - - Action == FooAction, - - Environment == FooEnvironment {} - + extension LinkedList {} - + extension Reducer {} - ``` - """ - - let docComments = """ - ```diff - - // A placeholder type used to demonstrate syntax rules - + /// A placeholder type used to demonstrate syntax rules - class Foo { - - // This function doesn't really do anything - + /// This function doesn't really do anything - func bar() { - - /// TODO: implement Foo.bar() algorithm - + // TODO: implement Foo.bar() algorithm - } - } - ``` - """ - - let fileHeader = """ - You can use the following tokens in the text: - - Token | Description - --- | --- - `{file}` | File name - `{year}` | Current year - `{created}` | File creation date - `{created.year}` | File creation year - `{author}` | Name and email of the user who first committed the file - `{author.name}` | Name of the user who first committed the file - `{author.email}` | Email of the user who first committed the file - - **Example**: - - `--header \\n {file}\\n\\n Copyright © {created.year} {author.name}.\\n` - - ```diff - - // SomeFile.swift - - + // - + // SomeFile.swift - + // Copyright © 2023 Tim Apple. - + // - ``` - - You can use the following built-in formats for `--dateformat`: - - Token | Description - --- | --- - system | Use the local system locale - iso | ISO 8601 (yyyy-MM-dd) - dmy | Date/Month/Year (dd/MM/yyyy) - mdy | Month/Day/Year (MM/dd/yyyy) - - Custom formats are defined using - [Unicode symbols](https://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Field_Symbol_Table). - - `--dateformat iso` - - ```diff - - // Created {created} - + // Created 2023-08-10 - ``` - - `--dateformat dmy` - - ```diff - - // Created {created} - + // Created 10/08/2023 - ``` - - `--dateformat mdy` - - ```diff - - // Created {created} - + // Created 08/10/2023 - ``` - - `--dateformat 'yyyy.MM.dd.HH.mm'` - - ```diff - - // Created {created} - + // Created 2023.08.10.11.00 - ``` - - Setting a time zone enforces consistent date formatting across environments - around the world. By default the local system locale is used and for convenience - `gmt` and `utc` can be used. The time zone can be further customized by - setting it to a abbreviation/time zone identifier supported by the Swift - standard library. - - `--dateformat 'yyyy-MM-dd HH:mm ZZZZ' --timezone utc` - - ```diff - - // Created {created} - + // Created 2023-08-10 11:00 GMT - ``` - - `--dateformat 'yyyy-MM-dd HH:mm ZZZZ' --timezone Pacific/Fiji` - - ```diff - - // Created 2023-08-10 11:00 GMT - + // Created 2023-08-10 23:00 GMT+12:00 - ``` - """ - - let conditionalAssignment = """ - ```diff - - let foo: String - - if condition { - + let foo = if condition { - - foo = "foo" - + "foo" - } else { - - foo = "bar" - + "bar" - } - - - let foo: String - - switch condition { - + let foo = switch condition { - case true: - - foo = "foo" - + "foo" - case false: - - foo = "bar" - + "bar" - } - - // With --condassignment always (disabled by default) - - switch condition { - + foo.bar = switch condition { - case true: - - foo.bar = "baaz" - + "baaz" - case false: - - foo.bar = "quux" - + "quux" - } - ``` - """ - - let sortTypealiases = """ - ```diff - - typealias Placeholders = Foo & Bar & Baaz & Quux - + typealias Placeholders = Baaz & Bar & Foo & Quux - - typealias Dependencies - - = FooProviding - + = BaazProviding - & BarProviding - - & BaazProviding - + & FooProviding - & QuuxProviding - ``` - """ - - let redundantInternal = """ - ```diff - - internal class Foo { - + class Foo { - - internal let bar: String - + let bar: String - - - internal func baaz() {} - + func baaz() {} - - - internal init() { - + init() { - bar = "bar" - } - } - ``` - """ - - let preferForLoop = """ - ```diff - let strings = ["foo", "bar", "baaz"] - - strings.forEach { placeholder in - + for placeholder in strings { - print(placeholder) - } - - // Supports anonymous closures - - strings.forEach { - + for string in strings { - - print($0) - + print(string) - } - - - foo.item().bar[2].baazValues(option: true).forEach { - + for baazValue in foo.item().bar[2].baazValues(option: true) { - - print($0) - + print(baazValue) - } - - // Doesn't affect long multiline functional chains - placeholderStrings - .filter { $0.style == .fooBar } - .map { $0.uppercased() } - .forEach { print($0) } - ``` - """ - - let noExplicitOwnership = """ - ```diff - - borrowing func foo(_ bar: consuming Bar) { ... } - + func foo(_ bar: Bar) { ... } - ``` - """ - - let blankLineAfterSwitchCase = #""" - ```diff - func handle(_ action: SpaceshipAction) { - switch action { - case .engageWarpDrive: - navigationComputer.destination = targetedDestination - await warpDrive.spinUp() - warpDrive.activate() - + - case let .scanPlanet(planet): - scanner.target = planet - scanner.scanAtmosphere() - scanner.scanBiosphere() - scanner.scanForArticialLife() - + - case .handleIncomingEnergyBlast: - await energyShields.prepare() - energyShields.engage() - } - } - ``` - """# - - let wrapMultilineConditionalAssignment = #""" - ```diff - - let planetLocation = if let star = planet.star { - - "The \(star.name) system" - - } else { - - "Rogue planet" - - } - + let planetLocation = - + if let star = planet.star { - + "The \(star.name) system" - + } else { - + "Rogue planet" - + } - ``` - """# - - let consistentSwitchCaseSpacing = #""" - ```diff - func handle(_ action: SpaceshipAction) { - switch action { - case .engageWarpDrive: - navigationComputer.destination = targetedDestination - await warpDrive.spinUp() - warpDrive.activate() - - case .enableArtificialGravity: - artificialGravityEngine.enable(strength: .oneG) - + - case let .scanPlanet(planet): - scanner.target = planet - scanner.scanAtmosphere() - scanner.scanBiosphere() - scanner.scanForArtificialLife() - - case .handleIncomingEnergyBlast: - energyShields.engage() - } - } - ``` - - ```diff - var name: PlanetType { - switch self { - case .mercury: - "Mercury" - - - case .venus: - "Venus" - case .earth: - "Earth" - case .mars: - "Mars" - - - case .jupiter: - "Jupiter" - case .saturn: - "Saturn" - case .uranus: - "Uranus" - case .neptune: - "Neptune" - } - ``` - """# - - let redundantProperty = """ - ```diff - func foo() -> Foo { - - let foo = Foo() - - return foo - + return Foo() - } - ``` - """ - - let redundantTypedThrows = """ - ```diff - - func foo() throws(Never) -> Int { - + func foo() -> Int { - return 0 - } - - - func foo() throws(any Error) -> Int { - + func foo() throws -> Int { - throw MyError.foo - } - ``` - """ - - let propertyType = """ - ```diff - - let foo: Foo = .init() - + let foo: Foo = .init() - - - let bar: Bar = .defaultValue - + let bar = .defaultValue - - - let baaz: Baaz = .buildBaaz(foo: foo, bar: bar) - + let baaz = Baaz.buildBaaz(foo: foo, bar: bar) - - let float: CGFloat = 10.0 - let array: [String] = [] - let anyFoo: AnyFoo = foo - - // with --inferredtypes always: - - let foo: Foo = - + let foo = - if condition { - - .init(bar) - + Foo(bar) - } else { - - .init(baaz) - + Foo(baaz) - } - ``` - """ - - let unusedPrivateDeclaration = """ - ```diff - struct Foo { - - fileprivate var foo = "foo" - - fileprivate var baz = "baz" - var bar = "bar" - } - ``` - """ - - let docCommentsBeforeAttributes = """ - ```diff - + /// Doc comment on this function declaration - @MainActor - - /// Doc comment on this function declaration - func foo() {} - ``` - """ - - let emptyExtension = """ - ```diff - - extension String {} - - - extension String: Equatable {} - ``` - """ - - let spacingGuards = """ - ```diff - guard let spicy = self.makeSpicy() else { - return - } - - - guard let soap = self.clean() else { - return - } - + - let doTheJob = nikekov() - ``` - """ -} diff --git a/Sources/FormatRule.swift b/Sources/FormatRule.swift index 5965a09e0..297628f03 100644 --- a/Sources/FormatRule.swift +++ b/Sources/FormatRule.swift @@ -36,6 +36,7 @@ public final class FormatRule: Equatable, Comparable, CustomStringConvertible { fileprivate(set) var name = "[unnamed rule]" fileprivate(set) var index = 0 let help: String + let examples: String? let runOnceOnly: Bool let disabledByDefault: Bool let orderAfter: [FormatRule] @@ -55,6 +56,7 @@ public final class FormatRule: Equatable, Comparable, CustomStringConvertible { } init(help: String, + examples: String? = nil, deprecationMessage: String? = nil, runOnceOnly: Bool = false, disabledByDefault: Bool = false, @@ -71,6 +73,7 @@ public final class FormatRule: Equatable, Comparable, CustomStringConvertible { self.options = options self.sharedOptions = sharedOptions self.deprecationMessage = deprecationMessage + self.examples = examples } public func apply(with formatter: Formatter) { diff --git a/Sources/Rules/Acronyms.swift b/Sources/Rules/Acronyms.swift index 399b74ec0..6847f86eb 100644 --- a/Sources/Rules/Acronyms.swift +++ b/Sources/Rules/Acronyms.swift @@ -11,6 +11,19 @@ import Foundation public extension FormatRule { static let acronyms = FormatRule( help: "Capitalize acronyms when the first character is capitalized.", + examples: """ + ```diff + - let destinationUrl: URL + - let urlRouter: UrlRouter + - let screenId: String + - let entityUuid: UUID + + + let destinationURL: URL + + let urlRouter: URLRouter + + let screenID: String + + let entityUUID: UUID + ``` + """, disabledByDefault: true, options: ["acronyms"] ) { formatter in diff --git a/Sources/Rules/AndOperator.swift b/Sources/Rules/AndOperator.swift index f46524f6d..9f15adb47 100644 --- a/Sources/Rules/AndOperator.swift +++ b/Sources/Rules/AndOperator.swift @@ -11,7 +11,28 @@ import Foundation public extension FormatRule { /// Replace the `&&` operator with `,` where applicable static let andOperator = FormatRule( - help: "Prefer comma over `&&` in `if`, `guard` or `while` conditions." + help: "Prefer comma over `&&` in `if`, `guard` or `while` conditions.", + examples: """ + ```diff + - if true && true { + + if true, true { + ``` + + ```diff + - guard true && true else { + + guard true, true else { + ``` + + ```diff + - if functionReturnsBool() && true { + + if functionReturnsBool(), true { + ``` + + ```diff + - if functionReturnsBool() && variable { + + if functionReturnsBool(), variable { + ``` + """ ) { formatter in formatter.forEachToken { i, token in switch token { diff --git a/Sources/Rules/AnyObjectProtocol.swift b/Sources/Rules/AnyObjectProtocol.swift index dd1e3f20a..746813e36 100644 --- a/Sources/Rules/AnyObjectProtocol.swift +++ b/Sources/Rules/AnyObjectProtocol.swift @@ -11,7 +11,17 @@ import Foundation public extension FormatRule { /// Prefer `AnyObject` over `class` for class-based protocols static let anyObjectProtocol = FormatRule( - help: "Prefer `AnyObject` over `class` in protocol definitions." + help: "Prefer `AnyObject` over `class` in protocol definitions.", + examples: """ + ```diff + - protocol Foo: class {} + + protocol Foo: AnyObject {} + ``` + + **NOTE:** The guideline to use `AnyObject` instead of `class` was only + introduced in Swift 4.1, so the `anyObjectProtocol` rule is disabled unless the + swift version is set to 4.1 or above. + """ ) { formatter in formatter.forEach(.keyword("protocol")) { i, _ in guard formatter.options.swiftVersion >= "4.1", diff --git a/Sources/Rules/AssertionFailures.swift b/Sources/Rules/AssertionFailures.swift index 96f1e2a05..0d92f6b54 100644 --- a/Sources/Rules/AssertionFailures.swift +++ b/Sources/Rules/AssertionFailures.swift @@ -13,6 +13,22 @@ public extension FormatRule { help: """ Changes all instances of assert(false, ...) to assertionFailure(...) and precondition(false, ...) to preconditionFailure(...). + """, + examples: """ + ```diff + - assert(false) + + assertionFailure() + ``` + + ```diff + - assert(false, "message", 2, 1) + + assertionFailure("message", 2, 1) + ``` + + ```diff + - precondition(false, "message", 2, 1) + + preconditionFailure("message", 2, 1) + ``` """ ) { formatter in formatter.forEachToken { i, token in diff --git a/Sources/Rules/BlankLineAfterImports.swift b/Sources/Rules/BlankLineAfterImports.swift index 4aead8ef4..ad448522d 100644 --- a/Sources/Rules/BlankLineAfterImports.swift +++ b/Sources/Rules/BlankLineAfterImports.swift @@ -11,8 +11,17 @@ import Foundation public extension FormatRule { /// Insert blank line after import statements static let blankLineAfterImports = FormatRule( - help: """ - Insert blank line after import statements. + help: "Insert blank line after import statements.", + examples: """ + ```diff + import A + import B + @testable import D + + + class Foo { + // foo + } + ``` """, sharedOptions: ["linebreaks"] ) { formatter in diff --git a/Sources/Rules/BlankLineAfterSwitchCase.swift b/Sources/Rules/BlankLineAfterSwitchCase.swift index 9543c8e46..d8bad2627 100644 --- a/Sources/Rules/BlankLineAfterSwitchCase.swift +++ b/Sources/Rules/BlankLineAfterSwitchCase.swift @@ -14,6 +14,28 @@ public extension FormatRule { Insert a blank line after multiline switch cases (excluding the last case, which is followed by a closing brace). """, + examples: #""" + ```diff + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArticialLife() + + + case .handleIncomingEnergyBlast: + await energyShields.prepare() + energyShields.engage() + } + } + ``` + """#, disabledByDefault: true, orderAfter: [.redundantBreak] ) { formatter in diff --git a/Sources/Rules/BlankLinesAroundMark.swift b/Sources/Rules/BlankLinesAroundMark.swift index fffa239fd..4fcbfd03c 100644 --- a/Sources/Rules/BlankLinesAroundMark.swift +++ b/Sources/Rules/BlankLinesAroundMark.swift @@ -12,6 +12,27 @@ public extension FormatRule { /// Adds a blank line around MARK: comments static let blankLinesAroundMark = FormatRule( help: "Insert blank line before and after `MARK:` comments.", + examples: """ + ```diff + func foo() { + // foo + } + // MARK: bar + func bar() { + // bar + } + + func foo() { + // foo + } + + + // MARK: bar + + + func bar() { + // bar + } + ``` + """, options: ["lineaftermarks"], sharedOptions: ["linebreaks"] ) { formatter in diff --git a/Sources/Rules/BlankLinesAtEndOfScope.swift b/Sources/Rules/BlankLinesAtEndOfScope.swift index 982dec99e..f292e6d94 100644 --- a/Sources/Rules/BlankLinesAtEndOfScope.swift +++ b/Sources/Rules/BlankLinesAtEndOfScope.swift @@ -13,6 +13,33 @@ public extension FormatRule { /// unless it's followed by more code on the same line (e.g. } else { ) static let blankLinesAtEndOfScope = FormatRule( help: "Remove trailing blank line at the end of a scope.", + examples: """ + ```diff + func foo() { + // foo + - + } + + func foo() { + // foo + } + ``` + + ```diff + array = [ + foo, + bar, + baz, + - + ] + + array = [ + foo, + bar, + baz, + ] + ``` + """, orderAfter: [.organizeDeclarations], sharedOptions: ["typeblanklines"] ) { formatter in diff --git a/Sources/Rules/BlankLinesAtStartOfScope.swift b/Sources/Rules/BlankLinesAtStartOfScope.swift index c787d7c39..771c17f6f 100644 --- a/Sources/Rules/BlankLinesAtStartOfScope.swift +++ b/Sources/Rules/BlankLinesAtStartOfScope.swift @@ -12,6 +12,33 @@ public extension FormatRule { /// Remove blank lines immediately after an opening brace, bracket, paren or chevron static let blankLinesAtStartOfScope = FormatRule( help: "Remove leading blank line at the start of a scope.", + examples: """ + ```diff + func foo() { + - + // foo + } + + func foo() { + // foo + } + ``` + + ```diff + array = [ + - + foo, + bar, + baz, + ] + + array = [ + foo, + bar, + baz, + ] + ``` + """, orderAfter: [.organizeDeclarations], options: ["typeblanklines"] ) { formatter in diff --git a/Sources/Rules/BlankLinesBetweenChainedFunctions.swift b/Sources/Rules/BlankLinesBetweenChainedFunctions.swift index 9f7cef0c2..2c0648c32 100644 --- a/Sources/Rules/BlankLinesBetweenChainedFunctions.swift +++ b/Sources/Rules/BlankLinesBetweenChainedFunctions.swift @@ -11,9 +11,7 @@ import Foundation public extension FormatRule { /// Remove blank lines between chained functions but keep the linebreaks static let blankLinesBetweenChainedFunctions = FormatRule( - help: """ - Remove blank lines between chained functions but keep the linebreaks. - """ + help: "Remove blank lines between chained functions but keep the linebreaks." ) { formatter in formatter.forEach(.operator(".", .infix)) { i, _ in let endOfLine = formatter.endOfLine(at: i) diff --git a/Sources/Rules/BlankLinesBetweenImports.swift b/Sources/Rules/BlankLinesBetweenImports.swift index 9b751c073..e10995954 100644 --- a/Sources/Rules/BlankLinesBetweenImports.swift +++ b/Sources/Rules/BlankLinesBetweenImports.swift @@ -14,6 +14,18 @@ public extension FormatRule { help: """ Remove blank lines between import statements. """, + examples: """ + ```diff + import A + - + import B + import C + - + - + @testable import D + import E + ``` + """, disabledByDefault: true, sharedOptions: ["linebreaks"] ) { formatter in diff --git a/Sources/Rules/BlankLinesBetweenScopes.swift b/Sources/Rules/BlankLinesBetweenScopes.swift index 304edd4f3..f263b4817 100644 --- a/Sources/Rules/BlankLinesBetweenScopes.swift +++ b/Sources/Rules/BlankLinesBetweenScopes.swift @@ -15,6 +15,29 @@ public extension FormatRule { Insert blank line before class, struct, enum, extension, protocol or function declarations. """, + examples: """ + ```diff + func foo() { + // foo + } + func bar() { + // bar + } + var baz: Bool + var quux: Int + + func foo() { + // foo + } + + + func bar() { + // bar + } + + + var baz: Bool + var quux: Int + ``` + """, sharedOptions: ["linebreaks"] ) { formatter in var spaceableScopeStack = [true] diff --git a/Sources/Rules/BlockComments.swift b/Sources/Rules/BlockComments.swift index 90682c1a0..4c50999fc 100644 --- a/Sources/Rules/BlockComments.swift +++ b/Sources/Rules/BlockComments.swift @@ -11,6 +11,27 @@ import Foundation public extension FormatRule { static let blockComments = FormatRule( help: "Convert block comments to consecutive single line comments.", + examples: """ + ```diff + - /* + - * foo + - * bar + - */ + + + // foo + + // bar + ``` + + ```diff + - /** + - * foo + - * bar + - */ + + + /// foo + + /// bar + ``` + """, disabledByDefault: true ) { formatter in formatter.forEachToken { i, token in diff --git a/Sources/Rules/Braces.swift b/Sources/Rules/Braces.swift index 7020978a3..b2685ebc9 100644 --- a/Sources/Rules/Braces.swift +++ b/Sources/Rules/Braces.swift @@ -12,6 +12,25 @@ public extension FormatRule { /// Implement brace-wrapping rules static let braces = FormatRule( help: "Wrap braces in accordance with selected style (K&R or Allman).", + examples: """ + ```diff + - if x + - { + // foo + } + - else + - { + // bar + } + + + if x { + // foo + } + + else { + // bar + } + ``` + """, options: ["allman"], sharedOptions: ["linebreaks", "maxwidth", "indent", "tabwidth", "assetliterals"] ) { formatter in diff --git a/Sources/Rules/ConditionalAssignment.swift b/Sources/Rules/ConditionalAssignment.swift index 8e0145df6..5b32830cf 100644 --- a/Sources/Rules/ConditionalAssignment.swift +++ b/Sources/Rules/ConditionalAssignment.swift @@ -11,6 +11,41 @@ import Foundation public extension FormatRule { static let conditionalAssignment = FormatRule( help: "Assign properties using if / switch expressions.", + examples: """ + ```diff + - let foo: String + - if condition { + + let foo = if condition { + - foo = "foo" + + "foo" + } else { + - foo = "bar" + + "bar" + } + + - let foo: String + - switch condition { + + let foo = switch condition { + case true: + - foo = "foo" + + "foo" + case false: + - foo = "bar" + + "bar" + } + + // With --condassignment always (disabled by default) + - switch condition { + + foo.bar = switch condition { + case true: + - foo.bar = "baaz" + + "baaz" + case false: + - foo.bar = "quux" + + "quux" + } + ``` + """, orderAfter: [.redundantReturn], options: ["condassignment"] ) { formatter in diff --git a/Sources/Rules/ConsecutiveBlankLines.swift b/Sources/Rules/ConsecutiveBlankLines.swift index 65650506f..263282c88 100644 --- a/Sources/Rules/ConsecutiveBlankLines.swift +++ b/Sources/Rules/ConsecutiveBlankLines.swift @@ -11,7 +11,23 @@ import Foundation public extension FormatRule { /// Collapse all consecutive blank lines into a single blank line static let consecutiveBlankLines = FormatRule( - help: "Replace consecutive blank lines with a single blank line." + help: "Replace consecutive blank lines with a single blank line.", + examples: """ + ```diff + func foo() { + let x = "bar" + - + + print(x) + } + + func foo() { + let x = "bar" + + print(x) + } + ``` + """ ) { formatter in formatter.forEach(.linebreak) { i, _ in guard let prevIndex = formatter.index(of: .nonSpace, before: i, if: { $0.isLinebreak }) else { diff --git a/Sources/Rules/ConsecutiveSpaces.swift b/Sources/Rules/ConsecutiveSpaces.swift index 396ee4a0e..b1e1e4145 100644 --- a/Sources/Rules/ConsecutiveSpaces.swift +++ b/Sources/Rules/ConsecutiveSpaces.swift @@ -13,7 +13,13 @@ public extension FormatRule { /// the start of a line or inside a comment or string, as these have no semantic /// meaning and lead to noise in commits. static let consecutiveSpaces = FormatRule( - help: "Replace consecutive spaces with a single space." + help: "Replace consecutive spaces with a single space.", + examples: """ + ```diff + - let foo = 5 + + let foo = 5 + ``` + """ ) { formatter in formatter.forEach(.space) { i, token in switch token { diff --git a/Sources/Rules/ConsistentSwitchCaseSpacing.swift b/Sources/Rules/ConsistentSwitchCaseSpacing.swift index 6ba72d51c..1051de288 100644 --- a/Sources/Rules/ConsistentSwitchCaseSpacing.swift +++ b/Sources/Rules/ConsistentSwitchCaseSpacing.swift @@ -11,6 +11,54 @@ import Foundation public extension FormatRule { static let consistentSwitchCaseSpacing = FormatRule( help: "Ensures consistent spacing among all of the cases in a switch statement.", + examples: #""" + ```diff + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + navigationComputer.destination = targetedDestination + await warpDrive.spinUp() + warpDrive.activate() + + case .enableArtificialGravity: + artificialGravityEngine.enable(strength: .oneG) + + + case let .scanPlanet(planet): + scanner.target = planet + scanner.scanAtmosphere() + scanner.scanBiosphere() + scanner.scanForArtificialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + } + ``` + + ```diff + var name: PlanetType { + switch self { + case .mercury: + "Mercury" + - + case .venus: + "Venus" + case .earth: + "Earth" + case .mars: + "Mars" + - + case .jupiter: + "Jupiter" + case .saturn: + "Saturn" + case .uranus: + "Uranus" + case .neptune: + "Neptune" + } + ``` + """#, orderAfter: [.blankLineAfterSwitchCase] ) { formatter in formatter.forEach(.keyword("switch")) { switchIndex, _ in diff --git a/Sources/Rules/DocComments.swift b/Sources/Rules/DocComments.swift index e451a0df6..5e7d0e50f 100644 --- a/Sources/Rules/DocComments.swift +++ b/Sources/Rules/DocComments.swift @@ -11,6 +11,20 @@ import Foundation public extension FormatRule { static let docComments = FormatRule( help: "Use doc comments for API declarations, otherwise use regular comments.", + examples: """ + ```diff + - // A placeholder type used to demonstrate syntax rules + + /// A placeholder type used to demonstrate syntax rules + class Foo { + - // This function doesn't really do anything + + /// This function doesn't really do anything + func bar() { + - /// TODO: implement Foo.bar() algorithm + + // TODO: implement Foo.bar() algorithm + } + } + ``` + """, disabledByDefault: true, orderAfter: [.fileHeader], options: ["doccomments"] diff --git a/Sources/Rules/DocCommentsBeforeAttributes.swift b/Sources/Rules/DocCommentsBeforeAttributes.swift index 1c52ac2ed..db641a9ab 100644 --- a/Sources/Rules/DocCommentsBeforeAttributes.swift +++ b/Sources/Rules/DocCommentsBeforeAttributes.swift @@ -11,6 +11,14 @@ import Foundation public extension FormatRule { static let docCommentsBeforeAttributes = FormatRule( help: "Place doc comments on declarations before any attributes.", + examples: """ + ```diff + + /// Doc comment on this function declaration + @MainActor + - /// Doc comment on this function declaration + func foo() {} + ``` + """, orderAfter: [.docComments] ) { formatter in formatter.forEachToken(where: \.isDeclarationTypeKeyword) { keywordIndex, _ in diff --git a/Sources/Rules/DuplicateImports.swift b/Sources/Rules/DuplicateImports.swift index 3e8c2c1ad..fcf3ab373 100644 --- a/Sources/Rules/DuplicateImports.swift +++ b/Sources/Rules/DuplicateImports.swift @@ -11,7 +11,22 @@ import Foundation public extension FormatRule { /// Remove duplicate import statements static let duplicateImports = FormatRule( - help: "Remove duplicate import statements." + help: "Remove duplicate import statements.", + examples: """ + ```diff + import Foo + import Bar + - import Foo + ``` + + ```diff + import B + #if os(iOS) + import A + - import B + #endif + ``` + """ ) { formatter in for var importRanges in formatter.parseImports().reversed() { for i in importRanges.indices.reversed() { diff --git a/Sources/Rules/ElseOnSameLine.swift b/Sources/Rules/ElseOnSameLine.swift index 45d6004d0..55fb0aefb 100644 --- a/Sources/Rules/ElseOnSameLine.swift +++ b/Sources/Rules/ElseOnSameLine.swift @@ -17,6 +17,52 @@ public extension FormatRule { Place `else`, `catch` or `while` keyword in accordance with current style (same or next line). """, + examples: """ + ```diff + if x { + // foo + - } + - else { + // bar + } + + if x { + // foo + + } else { + // bar + } + ``` + + ```diff + do { + // try foo + - } + - catch { + // bar + } + + do { + // try foo + + } catch { + // bar + } + ``` + + ```diff + repeat { + // foo + - } + - while { + // bar + } + + repeat { + // foo + + } while { + // bar + } + ``` + """, orderAfter: [.wrapMultilineStatementBraces], options: ["elseposition", "guardelse"], sharedOptions: ["allman", "linebreaks"] diff --git a/Sources/Rules/EmptyBraces.swift b/Sources/Rules/EmptyBraces.swift index f02a4932b..ffde4f71d 100644 --- a/Sources/Rules/EmptyBraces.swift +++ b/Sources/Rules/EmptyBraces.swift @@ -12,6 +12,15 @@ public extension FormatRule { /// Remove white-space between empty braces static let emptyBraces = FormatRule( help: "Remove whitespace inside empty braces.", + examples: """ + ```diff + - func foo() { + - + - } + + + func foo() {} + ``` + """, options: ["emptybraces"], sharedOptions: ["linebreaks"] ) { formatter in diff --git a/Sources/Rules/EmptyExtension.swift b/Sources/Rules/EmptyExtension.swift index bd5cc79fd..e59407370 100644 --- a/Sources/Rules/EmptyExtension.swift +++ b/Sources/Rules/EmptyExtension.swift @@ -12,6 +12,13 @@ public extension FormatRule { /// Remove empty, non-conforming, extensions. static let emptyExtension = FormatRule( help: "Remove empty, non-conforming, extensions.", + examples: """ + ```diff + - extension String {} + - + extension String: Equatable {} + ``` + """, orderAfter: [.unusedPrivateDeclaration] ) { formatter in var emptyExtensions = [Declaration]() diff --git a/Sources/Rules/ExtensionAccessControl.swift b/Sources/Rules/ExtensionAccessControl.swift index a9809d95f..bfa76e2ee 100644 --- a/Sources/Rules/ExtensionAccessControl.swift +++ b/Sources/Rules/ExtensionAccessControl.swift @@ -11,6 +11,37 @@ import Foundation public extension FormatRule { static let extensionAccessControl = FormatRule( help: "Configure the placement of an extension's access control keyword.", + examples: """ + `--extensionacl on-extension` (default) + + ```diff + - extension Foo { + - public func bar() {} + - public func baz() {} + } + + + public extension Foo { + + func bar() {} + + func baz() {} + } + ``` + + `--extensionacl on-declarations` + + ```diff + - public extension Foo { + - func bar() {} + - func baz() {} + - internal func quux() {} + } + + + extension Foo { + + public func bar() {} + + public func baz() {} + + func quux() {} + } + ``` + """, options: ["extensionacl"] ) { formatter in guard !formatter.options.fragment else { return } diff --git a/Sources/Rules/FileHeader.swift b/Sources/Rules/FileHeader.swift index 970477918..822d0dd13 100644 --- a/Sources/Rules/FileHeader.swift +++ b/Sources/Rules/FileHeader.swift @@ -12,6 +12,92 @@ public extension FormatRule { /// Strip header comments from the file static let fileHeader = FormatRule( help: "Use specified source file header template for all files.", + examples: """ + You can use the following tokens in the text: + + Token | Description + --- | --- + `{file}` | File name + `{year}` | Current year + `{created}` | File creation date + `{created.year}` | File creation year + `{author}` | Name and email of the user who first committed the file + `{author.name}` | Name of the user who first committed the file + `{author.email}` | Email of the user who first committed the file + + **Example**: + + `--header \\n {file}\\n\\n Copyright © {created.year} {author.name}.\\n` + + ```diff + - // SomeFile.swift + + + // + + // SomeFile.swift + + // Copyright © 2023 Tim Apple. + + // + ``` + + You can use the following built-in formats for `--dateformat`: + + Token | Description + --- | --- + system | Use the local system locale + iso | ISO 8601 (yyyy-MM-dd) + dmy | Date/Month/Year (dd/MM/yyyy) + mdy | Month/Day/Year (MM/dd/yyyy) + + Custom formats are defined using + [Unicode symbols](https://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Field_Symbol_Table). + + `--dateformat iso` + + ```diff + - // Created {created} + + // Created 2023-08-10 + ``` + + `--dateformat dmy` + + ```diff + - // Created {created} + + // Created 10/08/2023 + ``` + + `--dateformat mdy` + + ```diff + - // Created {created} + + // Created 08/10/2023 + ``` + + `--dateformat 'yyyy.MM.dd.HH.mm'` + + ```diff + - // Created {created} + + // Created 2023.08.10.11.00 + ``` + + Setting a time zone enforces consistent date formatting across environments + around the world. By default the local system locale is used and for convenience + `gmt` and `utc` can be used. The time zone can be further customized by + setting it to a abbreviation/time zone identifier supported by the Swift + standard library. + + `--dateformat 'yyyy-MM-dd HH:mm ZZZZ' --timezone utc` + + ```diff + - // Created {created} + + // Created 2023-08-10 11:00 GMT + ``` + + `--dateformat 'yyyy-MM-dd HH:mm ZZZZ' --timezone Pacific/Fiji` + + ```diff + - // Created 2023-08-10 11:00 GMT + + // Created 2023-08-10 23:00 GMT+12:00 + ``` + """, runOnceOnly: true, options: ["header", "dateformat", "timezone"], sharedOptions: ["linebreaks"] diff --git a/Sources/Rules/GenericExtensions.swift b/Sources/Rules/GenericExtensions.swift index e81f60f18..818f8701a 100644 --- a/Sources/Rules/GenericExtensions.swift +++ b/Sources/Rules/GenericExtensions.swift @@ -14,6 +14,35 @@ public extension FormatRule { Use angle brackets (`extension Array`) for generic type extensions instead of type constraints (`extension Array where Element == Foo`). """, + examples: """ + ```diff + - extension Array where Element == Foo {} + - extension Optional where Wrapped == Foo {} + - extension Dictionary where Key == Foo, Value == Bar {} + - extension Collection where Element == Foo {} + + extension Array {} + + extension Optional {} + + extension Dictionary {} + + extension Collection {} + + // With `typeSugar` also enabled: + - extension Array where Element == Foo {} + - extension Optional where Wrapped == Foo {} + - extension Dictionary where Key == Foo, Value == Bar {} + + extension [Foo] {} + + extension Foo? {} + + extension [Key: Value] {} + + // Also supports user-defined types! + - extension LinkedList where Element == Foo {} + - extension Reducer where + - State == FooState, + - Action == FooAction, + - Environment == FooEnvironment {} + + extension LinkedList {} + + extension Reducer {} + ``` + """, options: ["generictypes"] ) { formatter in formatter.forEach(.keyword("extension")) { extensionIndex, _ in diff --git a/Sources/Rules/HoistAwait.swift b/Sources/Rules/HoistAwait.swift index 73a445008..bb08c36c4 100644 --- a/Sources/Rules/HoistAwait.swift +++ b/Sources/Rules/HoistAwait.swift @@ -12,6 +12,17 @@ public extension FormatRule { /// Reposition `await` keyword outside of the current scope. static let hoistAwait = FormatRule( help: "Move inline `await` keyword(s) to start of expression.", + examples: """ + ```diff + - greet(await forename, await surname) + + await greet(forename, surname) + ``` + + ```diff + - let foo = String(try await getFoo()) + + let foo = await String(try getFoo()) + ``` + """, options: ["asynccapturing"] ) { formatter in guard formatter.options.swiftVersion >= "5.5" else { return } diff --git a/Sources/Rules/HoistPatternLet.swift b/Sources/Rules/HoistPatternLet.swift index 13c5c4937..0977e1a1f 100644 --- a/Sources/Rules/HoistPatternLet.swift +++ b/Sources/Rules/HoistPatternLet.swift @@ -12,6 +12,22 @@ public extension FormatRule { /// Move `let` and `var` inside patterns to the beginning static let hoistPatternLet = FormatRule( help: "Reposition `let` or `var` bindings within pattern.", + examples: """ + ```diff + - (let foo, let bar) = baz() + + let (foo, bar) = baz() + ``` + + ```diff + - if case .foo(let bar, let baz) = quux { + // inner foo + } + + + if case let .foo(bar, baz) = quux { + // inner foo + } + ``` + """, options: ["patternlet"] ) { formatter in formatter.forEach(.startOfScope("(")) { i, _ in diff --git a/Sources/Rules/HoistTry.swift b/Sources/Rules/HoistTry.swift index 52a327e95..fab431df3 100644 --- a/Sources/Rules/HoistTry.swift +++ b/Sources/Rules/HoistTry.swift @@ -11,6 +11,17 @@ import Foundation public extension FormatRule { static let hoistTry = FormatRule( help: "Move inline `try` keyword(s) to start of expression.", + examples: """ + ```diff + - foo(try bar(), try baz()) + + try foo(bar(), baz()) + ``` + + ```diff + - let foo = String(try await getFoo()) + + let foo = try String(await getFoo()) + ``` + """, options: ["throwcapturing"] ) { formatter in let names = formatter.options.throwCapturing.union(["expect"]) diff --git a/Sources/Rules/Indent.swift b/Sources/Rules/Indent.swift index 76567fdc4..4adeda8ce 100644 --- a/Sources/Rules/Indent.swift +++ b/Sources/Rules/Indent.swift @@ -14,6 +14,47 @@ public extension FormatRule { /// indenting can be configured with the `options` parameter of the formatter. static let indent = FormatRule( help: "Indent code in accordance with the scope level.", + examples: """ + ```diff + if x { + - // foo + } else { + - // bar + - } + + if x { + + // foo + } else { + + // bar + + } + ``` + + ```diff + let array = [ + foo, + - bar, + - baz + - ] + + let array = [ + foo, + + bar, + + baz + + ] + ``` + + ```diff + switch foo { + - case bar: break + - case baz: break + } + + switch foo { + + case bar: break + + case baz: break + } + ``` + """, orderAfter: [.trailingSpace, .wrap, .wrapArguments], options: ["indent", "tabwidth", "smarttabs", "indentcase", "ifdef", "xcodeindentation", "indentstrings"], sharedOptions: ["trimwhitespace", "allman", "wrapconditions", "wrapternary"] diff --git a/Sources/Rules/InitCoderUnavailable.swift b/Sources/Rules/InitCoderUnavailable.swift index 6df612e8c..43c8ad1b9 100644 --- a/Sources/Rules/InitCoderUnavailable.swift +++ b/Sources/Rules/InitCoderUnavailable.swift @@ -15,6 +15,14 @@ public extension FormatRule { Add `@available(*, unavailable)` attribute to required `init(coder:)` when it hasn't been implemented. """, + examples: """ + ```diff + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + ``` + """, options: ["initcodernil"], sharedOptions: ["linebreaks"] ) { formatter in diff --git a/Sources/Rules/IsEmpty.swift b/Sources/Rules/IsEmpty.swift index 36b72f628..a21cc097c 100644 --- a/Sources/Rules/IsEmpty.swift +++ b/Sources/Rules/IsEmpty.swift @@ -12,6 +12,23 @@ public extension FormatRule { /// Replace count == 0 with isEmpty static let isEmpty = FormatRule( help: "Prefer `isEmpty` over comparing `count` against zero.", + examples: """ + ```diff + - if foo.count == 0 { + + if foo.isEmpty { + + - if foo.count > 0 { + + if !foo.isEmpty { + + - if foo?.count == 0 { + + if foo?.isEmpty == true { + ``` + + ***NOTE:*** In rare cases, the `isEmpty` rule may insert an `isEmpty` call for + a type that doesn't implement that property, breaking the program. For this + reason, the rule is disabled by default, and must be manually enabled via the + `--enable isEmpty` option. + """, disabledByDefault: true ) { formatter in formatter.forEach(.identifier("count")) { i, _ in diff --git a/Sources/Rules/LeadingDelimiters.swift b/Sources/Rules/LeadingDelimiters.swift index 76912cf83..943dbea01 100644 --- a/Sources/Rules/LeadingDelimiters.swift +++ b/Sources/Rules/LeadingDelimiters.swift @@ -11,6 +11,15 @@ import Foundation public extension FormatRule { static let leadingDelimiters = FormatRule( help: "Move leading delimiters to the end of the previous line.", + examples: """ + ```diff + - guard let foo = maybeFoo // first + - , let bar = maybeBar else { ... } + + + guard let foo = maybeFoo, // first + + let bar = maybeBar else { ... } + ``` + """, sharedOptions: ["linebreaks"] ) { formatter in formatter.forEach(.delimiter) { i, _ in diff --git a/Sources/Rules/MarkTypes.swift b/Sources/Rules/MarkTypes.swift index f2978bd22..b5494acce 100644 --- a/Sources/Rules/MarkTypes.swift +++ b/Sources/Rules/MarkTypes.swift @@ -11,6 +11,21 @@ import Foundation public extension FormatRule { static let markTypes = FormatRule( help: "Add a MARK comment before top-level types and extensions.", + examples: """ + ```diff + + // MARK: - FooViewController + + + final class FooViewController: UIViewController { } + + + // MARK: UICollectionViewDelegate + + + extension FooViewController: UICollectionViewDelegate { } + + + // MARK: - String + FooProtocol + + + extension String: FooProtocol { } + ``` + """, runOnceOnly: true, disabledByDefault: true, options: ["marktypes", "typemark", "markextensions", "extensionmark", "groupedextension"], diff --git a/Sources/Rules/ModifierOrder.swift b/Sources/Rules/ModifierOrder.swift index a0dca840a..e5d92a797 100644 --- a/Sources/Rules/ModifierOrder.swift +++ b/Sources/Rules/ModifierOrder.swift @@ -12,6 +12,25 @@ public extension FormatRule { /// Standardise the order of property modifiers static let modifierOrder = FormatRule( help: "Use consistent ordering for member modifiers.", + examples: """ + ```diff + - lazy public weak private(set) var foo: UIView? + + public private(set) lazy weak var foo: UIView? + ``` + + ```diff + - final public override func foo() + + override public final func foo() + ``` + + ```diff + - convenience private init() + + private convenience init() + ``` + + **NOTE:** If the `--modifierorder` option isn't set, the default order will be: + `\(_FormatRules.defaultModifierOrder.flatMap { $0 }.joined(separator: "`, `"))` + """, options: ["modifierorder"] ) { formatter in formatter.forEach(.keyword) { i, token in diff --git a/Sources/Rules/NoExplicitOwnership.swift b/Sources/Rules/NoExplicitOwnership.swift index e443875bb..000a44948 100644 --- a/Sources/Rules/NoExplicitOwnership.swift +++ b/Sources/Rules/NoExplicitOwnership.swift @@ -11,6 +11,12 @@ import Foundation public extension FormatRule { static let noExplicitOwnership = FormatRule( help: "Don't use explicit ownership modifiers (borrowing / consuming).", + examples: """ + ```diff + - borrowing func foo(_ bar: consuming Bar) { ... } + + func foo(_ bar: Bar) { ... } + ``` + """, disabledByDefault: true ) { formatter in formatter.forEachToken { keywordIndex, token in diff --git a/Sources/Rules/NumberFormatting.swift b/Sources/Rules/NumberFormatting.swift index 44c271b0b..d2ec058c2 100644 --- a/Sources/Rules/NumberFormatting.swift +++ b/Sources/Rules/NumberFormatting.swift @@ -17,6 +17,17 @@ public extension FormatRule { size (the number of digits in each group) and a threshold (the minimum number of digits in a number before grouping is applied). """, + examples: """ + ```diff + - let color = 0xFF77A5 + + let color = 0xff77a5 + ``` + + ```diff + - let big = 123456.123 + + let big = 123_456.123 + ``` + """, options: ["decimalgrouping", "binarygrouping", "octalgrouping", "hexgrouping", "fractiongrouping", "exponentgrouping", "hexliteralcase", "exponentcase"] ) { formatter in diff --git a/Sources/Rules/OpaqueGenericParameters.swift b/Sources/Rules/OpaqueGenericParameters.swift index d12f024d6..fadd1a445 100644 --- a/Sources/Rules/OpaqueGenericParameters.swift +++ b/Sources/Rules/OpaqueGenericParameters.swift @@ -16,6 +16,30 @@ public extension FormatRule { primary associated types for common standard library types, so definitions like `T where T: Collection, T.Element == Foo` are updated to `some Collection`. """, + examples: """ + ```diff + - func handle(_ value: T) { + + func handle(_ value: some Fooable) { + print(value) + } + + - func handle(_ value: T) where T: Fooable, T: Barable { + + func handle(_ value: some Fooable & Barable) { + print(value) + } + + - func handle(_ value: T) where T.Element == Foo { + + func handle(_ value: some Collection) { + print(value) + } + + // With `--someany enabled` (the default) + - func handle(_ value: T) { + + func handle(_ value: some Any) { + print(value) + } + ``` + """, options: ["someany"] ) { formatter in formatter.forEach(.keyword) { keywordIndex, keyword in diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift index 0d91a6e89..e2374a1aa 100644 --- a/Sources/Rules/OrganizeDeclarations.swift +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -11,6 +11,111 @@ import Foundation public extension FormatRule { static let organizeDeclarations = FormatRule( help: "Organize declarations within class, struct, enum, actor, and extension bodies.", + examples: """ + Default value for `--visibilityorder` when using `--organizationmode visibility`: + `\(VisibilityCategory.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` + + Default value for `--visibilityorder` when using `--organizationmode type`: + `\(VisibilityCategory.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` + + **NOTE:** When providing custom arguments for `--visibilityorder` the following entries must be included: + `\(VisibilityCategory.essentialCases.map { $0.rawValue }.joined(separator: ", "))` + + Default value for `--typeorder` when using `--organizationmode visibility`: + `\(DeclarationType.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` + + Default value for `--typeorder` when using `--organizationmode type`: + `\(DeclarationType.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` + + **NOTE:** The follow declaration types must be included in either `--typeorder` or `--visibilityorder`: + `\(DeclarationType.essentialCases.map { $0.rawValue }.joined(separator: ", "))` + + `--organizationmode visibility` (default) + + ```diff + public class Foo { + - public func c() -> String {} + - + - public let a: Int = 1 + - private let g: Int = 2 + - let e: Int = 2 + - public let b: Int = 3 + - + - public func d() {} + - func f() {} + - init() {} + - deinit() {} + } + + public class Foo { + + + + // MARK: Lifecycle + + + + init() {} + + deinit() {} + + + + // MARK: Public + + + + public let a: Int = 1 + + public let b: Int = 3 + + + + public func c() -> String {} + + public func d() {} + + + + // MARK: Internal + + + + let e: Int = 2 + + + + func f() {} + + + + // MARK: Private + + + + private let g: Int = 2 + + + } + ``` + + `--organizationmode type` + + ```diff + public class Foo { + - public func c() -> String {} + - + - public let a: Int = 1 + - private let g: Int = 2 + - let e: Int = 2 + - public let b: Int = 3 + - + - public func d() {} + - func f() {} + - init() {} + - deinit() {} + } + + public class Foo { + + + + // MARK: Properties + + + + public let a: Int = 1 + + public let b: Int = 3 + + + + let e: Int = 2 + + + + private let g: Int = 2 + + + + // MARK: Lifecycle + + + + init() {} + + deinit() {} + + + + // MARK: Functions + + + + public func c() -> String {} + + public func d() {} + + + } + ``` + """, runOnceOnly: true, disabledByDefault: true, orderAfter: [.extensionAccessControl, .redundantFileprivate], diff --git a/Sources/Rules/PreferForLoop.swift b/Sources/Rules/PreferForLoop.swift index e2fe70766..38d9288b1 100644 --- a/Sources/Rules/PreferForLoop.swift +++ b/Sources/Rules/PreferForLoop.swift @@ -11,6 +11,34 @@ import Foundation public extension FormatRule { static let preferForLoop = FormatRule( help: "Convert functional `forEach` calls to for loops.", + examples: """ + ```diff + let strings = ["foo", "bar", "baaz"] + - strings.forEach { placeholder in + + for placeholder in strings { + print(placeholder) + } + + // Supports anonymous closures + - strings.forEach { + + for string in strings { + - print($0) + + print(string) + } + + - foo.item().bar[2].baazValues(option: true).forEach { + + for baazValue in foo.item().bar[2].baazValues(option: true) { + - print($0) + + print(baazValue) + } + + // Doesn't affect long multiline functional chains + placeholderStrings + .filter { $0.style == .fooBar } + .map { $0.uppercased() } + .forEach { print($0) } + ``` + """, options: ["anonymousforeach", "onelineforeach"] ) { formatter in formatter.forEach(.identifier("forEach")) { forEachIndex, _ in diff --git a/Sources/Rules/PreferKeyPath.swift b/Sources/Rules/PreferKeyPath.swift index 81b4a179b..3405bf63a 100644 --- a/Sources/Rules/PreferKeyPath.swift +++ b/Sources/Rules/PreferKeyPath.swift @@ -10,7 +10,16 @@ import Foundation public extension FormatRule { static let preferKeyPath = FormatRule( - help: "Convert trivial `map { $0.foo }` closures to keyPath-based syntax." + help: "Convert trivial `map { $0.foo }` closures to keyPath-based syntax.", + examples: """ + ```diff + - let barArray = fooArray.map { $0.bar } + + let barArray = fooArray.map(\\.bar) + + - let barArray = fooArray.compactMap { $0.optionalBar } + + let barArray = fooArray.compactMap(\\.optionalBar) + ``` + """ ) { formatter in formatter.forEach(.startOfScope("{")) { i, _ in guard formatter.options.swiftVersion >= "5.2", diff --git a/Sources/Rules/PropertyType.swift b/Sources/Rules/PropertyType.swift index 79631a3aa..15af98b9a 100644 --- a/Sources/Rules/PropertyType.swift +++ b/Sources/Rules/PropertyType.swift @@ -11,6 +11,33 @@ import Foundation public extension FormatRule { static let propertyType = FormatRule( help: "Convert property declarations to use inferred types (`let foo = Foo()`) or explicit types (`let foo: Foo = .init()`).", + examples: """ + ```diff + - let foo: Foo = .init() + + let foo: Foo = .init() + + - let bar: Bar = .defaultValue + + let bar = .defaultValue + + - let baaz: Baaz = .buildBaaz(foo: foo, bar: bar) + + let baaz = Baaz.buildBaaz(foo: foo, bar: bar) + + let float: CGFloat = 10.0 + let array: [String] = [] + let anyFoo: AnyFoo = foo + + // with --inferredtypes always: + - let foo: Foo = + + let foo = + if condition { + - .init(bar) + + Foo(bar) + } else { + - .init(baaz) + + Foo(baaz) + } + ``` + """, disabledByDefault: true, orderAfter: [.redundantType], options: ["inferredtypes", "preservesymbols"], diff --git a/Sources/Rules/RedundantBackticks.swift b/Sources/Rules/RedundantBackticks.swift index 869afe2bc..3add7bda9 100644 --- a/Sources/Rules/RedundantBackticks.swift +++ b/Sources/Rules/RedundantBackticks.swift @@ -11,7 +11,18 @@ import Foundation public extension FormatRule { /// Remove redundant backticks around non-keywords, or in places where keywords don't need escaping static let redundantBackticks = FormatRule( - help: "Remove redundant backticks around identifiers." + help: "Remove redundant backticks around identifiers.", + examples: """ + ```diff + - let `infix` = bar + + let infix = bar + ``` + + ```diff + - func foo(with `default`: Int) {} + + func foo(with default: Int) {} + ``` + """ ) { formatter in formatter.forEach(.identifier) { i, token in guard token.string.first == "`", !formatter.backticksRequired(at: i) else { diff --git a/Sources/Rules/RedundantBreak.swift b/Sources/Rules/RedundantBreak.swift index bb4c5fc79..8ada2f938 100644 --- a/Sources/Rules/RedundantBreak.swift +++ b/Sources/Rules/RedundantBreak.swift @@ -11,7 +11,19 @@ import Foundation public extension FormatRule { /// Remove redundant `break` keyword from switch cases static let redundantBreak = FormatRule( - help: "Remove redundant `break` in switch case." + help: "Remove redundant `break` in switch case.", + examples: """ + ```diff + switch foo { + case bar: + print("bar") + - break + default: + print("default") + - break + } + ``` + """ ) { formatter in formatter.forEach(.keyword("break")) { i, _ in guard formatter.last(.nonSpaceOrCommentOrLinebreak, before: i) != .startOfScope(":"), diff --git a/Sources/Rules/RedundantClosure.swift b/Sources/Rules/RedundantClosure.swift index bd5e3bb7c..797900fad 100644 --- a/Sources/Rules/RedundantClosure.swift +++ b/Sources/Rules/RedundantClosure.swift @@ -14,6 +14,21 @@ public extension FormatRule { Removes redundant closures bodies, containing a single statement, which are called immediately. """, + examples: """ + ```diff + - let foo = { Foo() }() + + let foo = Foo() + ``` + + ```diff + - lazy var bar = { + - Bar(baaz: baaz, + - quux: quux) + - }() + + lazy var bar = Bar(baaz: baaz, + + quux: quux) + ``` + """, disabledByDefault: false, orderAfter: [.redundantReturn] ) { formatter in diff --git a/Sources/Rules/RedundantExtensionACL.swift b/Sources/Rules/RedundantExtensionACL.swift index d26f8b3be..800f2d1f2 100644 --- a/Sources/Rules/RedundantExtensionACL.swift +++ b/Sources/Rules/RedundantExtensionACL.swift @@ -11,7 +11,18 @@ import Foundation public extension FormatRule { /// Remove redundant access control level modifiers in extensions static let redundantExtensionACL = FormatRule( - help: "Remove redundant access control modifiers." + help: "Remove redundant access control modifiers.", + examples: """ + ```diff + public extension URL { + - public func queryParameter(_ name: String) -> String { ... } + } + + public extension URL { + + func queryParameter(_ name: String) -> String { ... } + } + ``` + """ ) { formatter in formatter.forEach(.keyword("extension")) { i, _ in var acl = "" diff --git a/Sources/Rules/RedundantFileprivate.swift b/Sources/Rules/RedundantFileprivate.swift index 4a8c91ca0..ce8f1d9d0 100644 --- a/Sources/Rules/RedundantFileprivate.swift +++ b/Sources/Rules/RedundantFileprivate.swift @@ -11,7 +11,29 @@ import Foundation public extension FormatRule { /// Replace `fileprivate` with `private` where possible static let redundantFileprivate = FormatRule( - help: "Prefer `private` over `fileprivate` where equivalent." + help: "Prefer `private` over `fileprivate` where equivalent.", + examples: """ + ```diff + - fileprivate let someConstant = "someConstant" + + private let someConstant = "someConstant" + ``` + + In Swift 4 and above, `fileprivate` can also be replaced with `private` for + members that are only accessed from extensions in the same file: + + ```diff + class Foo { + - fileprivate var foo = "foo" + + private var foo = "foo" + } + + extension Foo { + func bar() { + print(self.foo) + } + } + ``` + """ ) { formatter in guard !formatter.options.fragment else { return } diff --git a/Sources/Rules/RedundantGet.swift b/Sources/Rules/RedundantGet.swift index 333228fec..37e1af2b0 100644 --- a/Sources/Rules/RedundantGet.swift +++ b/Sources/Rules/RedundantGet.swift @@ -11,7 +11,20 @@ import Foundation public extension FormatRule { /// Remove redundant `get {}` clause inside read-only computed property static let redundantGet = FormatRule( - help: "Remove unneeded `get` clause inside computed properties." + help: "Remove unneeded `get` clause inside computed properties.", + examples: """ + ```diff + var foo: Int { + - get { + - return 5 + - } + } + + var foo: Int { + + return 5 + } + ``` + """ ) { formatter in formatter.forEach(.identifier("get")) { i, _ in if formatter.isAccessorKeyword(at: i, checkKeyword: false), diff --git a/Sources/Rules/RedundantInit.swift b/Sources/Rules/RedundantInit.swift index 21423bc12..af93522cf 100644 --- a/Sources/Rules/RedundantInit.swift +++ b/Sources/Rules/RedundantInit.swift @@ -12,6 +12,12 @@ public extension FormatRule { /// Strip redundant `.init` from type instantiations static let redundantInit = FormatRule( help: "Remove explicit `init` if not required.", + examples: """ + ```diff + - String.init("text") + + String("text") + ``` + """, orderAfter: [.propertyType] ) { formatter in formatter.forEach(.identifier("init")) { initIndex, _ in diff --git a/Sources/Rules/RedundantInternal.swift b/Sources/Rules/RedundantInternal.swift index 570e3c7f8..eebf71b3f 100644 --- a/Sources/Rules/RedundantInternal.swift +++ b/Sources/Rules/RedundantInternal.swift @@ -10,7 +10,24 @@ import Foundation public extension FormatRule { static let redundantInternal = FormatRule( - help: "Remove redundant internal access control." + help: "Remove redundant internal access control.", + examples: """ + ```diff + - internal class Foo { + + class Foo { + - internal let bar: String + + let bar: String + + - internal func baaz() {} + + func baaz() {} + + - internal init() { + + init() { + bar = "bar" + } + } + ``` + """ ) { formatter in formatter.forEach(.keyword("internal")) { internalKeywordIndex, _ in // Don't remove import acl diff --git a/Sources/Rules/RedundantLet.swift b/Sources/Rules/RedundantLet.swift index 54c45f79d..b398c7e78 100644 --- a/Sources/Rules/RedundantLet.swift +++ b/Sources/Rules/RedundantLet.swift @@ -11,7 +11,13 @@ import Foundation public extension FormatRule { /// Remove redundant let/var for unnamed variables static let redundantLet = FormatRule( - help: "Remove redundant `let`/`var` from ignored variables." + help: "Remove redundant `let`/`var` from ignored variables.", + examples: """ + ```diff + - let _ = foo() + + _ = foo() + ``` + """ ) { formatter in formatter.forEach(.identifier("_")) { i, _ in guard formatter.next(.nonSpaceOrCommentOrLinebreak, after: i) != .delimiter(":"), diff --git a/Sources/Rules/RedundantLetError.swift b/Sources/Rules/RedundantLetError.swift index cc6cd358d..40216bad6 100644 --- a/Sources/Rules/RedundantLetError.swift +++ b/Sources/Rules/RedundantLetError.swift @@ -11,7 +11,14 @@ import Foundation public extension FormatRule { /// Remove redundant `let error` from `catch` statements static let redundantLetError = FormatRule( - help: "Remove redundant `let error` from `catch` clause." + help: "Remove redundant `let error` from `catch` clause.", + examples: """ + ```diff + - do { ... } catch let error { log(error) } + + do { ... } catch { log(error) } + ``` + """ + ) { formatter in formatter.forEach(.keyword("catch")) { i, _ in if let letIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: i, if: { diff --git a/Sources/Rules/RedundantNilInit.swift b/Sources/Rules/RedundantNilInit.swift index a8744aa5c..63dec0e84 100644 --- a/Sources/Rules/RedundantNilInit.swift +++ b/Sources/Rules/RedundantNilInit.swift @@ -12,6 +12,31 @@ public extension FormatRule { /// Remove or insert redundant `= nil` initialization for Optional properties static let redundantNilInit = FormatRule( help: "Remove/insert redundant `nil` default value (Optional vars are nil by default).", + examples: """ + `--nilinit remove` + + ```diff + - var foo: Int? = nil + + var foo: Int? + ``` + + ```diff + // doesn't apply to `let` properties + let foo: Int? = nil + ``` + + ```diff + // doesn't affect non-nil initialization + var foo: Int? = 0 + ``` + + `--nilinit insert` + + ```diff + - var foo: Int? + + var foo: Int? = nil + ``` + """, options: ["nilinit"] ) { formatter in // Check modifiers don't include `lazy` diff --git a/Sources/Rules/RedundantObjc.swift b/Sources/Rules/RedundantObjc.swift index b2cc80af2..1473e21ca 100644 --- a/Sources/Rules/RedundantObjc.swift +++ b/Sources/Rules/RedundantObjc.swift @@ -11,7 +11,23 @@ import Foundation public extension FormatRule { /// Remove redundant @objc annotation static let redundantObjc = FormatRule( - help: "Remove redundant `@objc` annotations." + help: "Remove redundant `@objc` annotations.", + examples: """ + ```diff + - @objc @IBOutlet var label: UILabel! + + @IBOutlet var label: UILabel! + ``` + + ```diff + - @IBAction @objc func goBack() {} + + @IBAction func goBack() {} + ``` + + ```diff + - @objc @NSManaged private var foo: String? + + @NSManaged private var foo: String? + ``` + """ ) { formatter in let objcAttributes = [ "@IBOutlet", "@IBAction", "@IBSegueAction", diff --git a/Sources/Rules/RedundantOptionalBinding.swift b/Sources/Rules/RedundantOptionalBinding.swift index 6c36fb8e2..56095312c 100644 --- a/Sources/Rules/RedundantOptionalBinding.swift +++ b/Sources/Rules/RedundantOptionalBinding.swift @@ -13,6 +13,19 @@ public extension FormatRule { help: "Remove redundant identifiers in optional binding conditions.", // We can convert `if let foo = self.foo` to just `if let foo`, // but only if `redundantSelf` can first remove the `self.`. + examples: """ + ```diff + - if let foo = foo { + + if let foo { + print(foo) + } + + - guard let self = self else { + + guard let self else { + return + } + ``` + """, orderAfter: [.redundantSelf] ) { formatter in formatter.forEachToken { i, token in diff --git a/Sources/Rules/RedundantParens.swift b/Sources/Rules/RedundantParens.swift index a0f6bbc15..bc4573c39 100644 --- a/Sources/Rules/RedundantParens.swift +++ b/Sources/Rules/RedundantParens.swift @@ -11,7 +11,28 @@ import Foundation public extension FormatRule { /// Remove redundant parens around the arguments for loops, if statements, closures, etc. static let redundantParens = FormatRule( - help: "Remove redundant parentheses." + help: "Remove redundant parentheses.", + examples: """ + ```diff + - if (foo == true) {} + + if foo == true {} + ``` + + ```diff + - while (i < bar.count) {} + + while i < bar.count {} + ``` + + ```diff + - queue.async() { ... } + + queue.async { ... } + ``` + + ```diff + - let foo: Int = ({ ... })() + + let foo: Int = { ... }() + ``` + """ ) { formatter in // TODO: unify with conditionals logic in trailingClosures let conditionals = Set(["in", "while", "if", "case", "switch", "where", "for", "guard"]) diff --git a/Sources/Rules/RedundantPattern.swift b/Sources/Rules/RedundantPattern.swift index 8482b1ec2..1f9af587e 100644 --- a/Sources/Rules/RedundantPattern.swift +++ b/Sources/Rules/RedundantPattern.swift @@ -11,7 +11,18 @@ import Foundation public extension FormatRule { /// Remove redundant pattern in case statements static let redundantPattern = FormatRule( - help: "Remove redundant pattern matching parameter syntax." + help: "Remove redundant pattern matching parameter syntax.", + examples: """ + ```diff + - if case .foo(_, _) = bar {} + + if case .foo = bar {} + ``` + + ```diff + - let (_, _) = bar + + let _ = bar + ``` + """ ) { formatter in formatter.forEach(.startOfScope("(")) { i, _ in let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i) diff --git a/Sources/Rules/RedundantProperty.swift b/Sources/Rules/RedundantProperty.swift index dfeee6bac..50b109979 100644 --- a/Sources/Rules/RedundantProperty.swift +++ b/Sources/Rules/RedundantProperty.swift @@ -11,6 +11,15 @@ import Foundation public extension FormatRule { static let redundantProperty = FormatRule( help: "Simplifies redundant property definitions that are immediately returned.", + examples: """ + ```diff + func foo() -> Foo { + - let foo = Foo() + - return foo + + return Foo() + } + ``` + """, disabledByDefault: true, orderAfter: [.propertyType] ) { formatter in diff --git a/Sources/Rules/RedundantRawValues.swift b/Sources/Rules/RedundantRawValues.swift index 1cf3a3a2e..889fc843a 100644 --- a/Sources/Rules/RedundantRawValues.swift +++ b/Sources/Rules/RedundantRawValues.swift @@ -11,7 +11,20 @@ import Foundation public extension FormatRule { /// Remove redundant raw string values for case statements static let redundantRawValues = FormatRule( - help: "Remove redundant raw string values for enum cases." + help: "Remove redundant raw string values for enum cases.", + examples: """ + ```diff + enum Foo: String { + - case bar = "bar" + case baz = "quux" + } + + enum Foo: String { + + case bar + case baz = "quux" + } + ``` + """ ) { formatter in formatter.forEach(.keyword("enum")) { i, _ in guard let nameIndex = formatter.index( diff --git a/Sources/Rules/RedundantReturn.swift b/Sources/Rules/RedundantReturn.swift index 6c04bc073..885605569 100644 --- a/Sources/Rules/RedundantReturn.swift +++ b/Sources/Rules/RedundantReturn.swift @@ -11,7 +11,30 @@ import Foundation public extension FormatRule { /// Remove redundant return keyword static let redundantReturn = FormatRule( - help: "Remove unneeded `return` keyword." + help: "Remove unneeded `return` keyword.", + examples: """ + ```diff + - array.filter { return $0.foo == bar } + + array.filter { $0.foo == bar } + + // Swift 5.1+ (SE-0255) + var foo: String { + - return "foo" + + "foo" + } + + // Swift 5.9+ (SE-0380) and with conditionalAssignment rule enabled + func foo(_ condition: Bool) -> String { + if condition { + - return "foo" + + "foo" + } else { + - return "bar" + + "bar" + } + } + ``` + """ ) { formatter in // indices of returns that are safe to remove var returnIndices = [Int]() diff --git a/Sources/Rules/RedundantSelf.swift b/Sources/Rules/RedundantSelf.swift index 0ffeba016..3e0f69e57 100644 --- a/Sources/Rules/RedundantSelf.swift +++ b/Sources/Rules/RedundantSelf.swift @@ -12,6 +12,47 @@ public extension FormatRule { /// Insert or remove redundant self keyword static let redundantSelf = FormatRule( help: "Insert/remove explicit `self` where applicable.", + examples: """ + ```diff + func foobar(foo: Int, bar: Int) { + self.foo = foo + self.bar = bar + - self.baz = 42 + } + + func foobar(foo: Int, bar: Int) { + self.foo = foo + self.bar = bar + + baz = 42 + } + ``` + + In the rare case of functions with `@autoclosure` arguments, `self` may be + required at the call site, but SwiftFormat is unable to detect this + automatically. You can use the `--selfrequired` command-line option to specify + a list of such methods, and the `redundantSelf` rule will then ignore them. + + An example of such a method is the `expect()` function in the Nimble unit + testing framework (https://github.com/Quick/Nimble), which is common enough that + SwiftFormat excludes it by default. + + There is also an option to always use explicit `self` but *only* inside `init`, + by using `--self init-only`: + + ```diff + init(foo: Int, bar: Int) { + self.foo = foo + self.bar = bar + - baz = 42 + } + + init(foo: Int, bar: Int) { + self.foo = foo + self.bar = bar + + self.baz = 42 + } + ``` + """, options: ["self", "selfrequired"] ) { formatter in _ = formatter.options.selfRequired diff --git a/Sources/Rules/RedundantType.swift b/Sources/Rules/RedundantType.swift index 7f1fcc3e6..5d2273ba2 100644 --- a/Sources/Rules/RedundantType.swift +++ b/Sources/Rules/RedundantType.swift @@ -12,6 +12,45 @@ public extension FormatRule { /// Removes explicit type declarations from initialization declarations static let redundantType = FormatRule( help: "Remove redundant type from variable declarations.", + examples: """ + ```diff + // inferred + - let view: UIView = UIView() + + let view = UIView() + + // explicit + - let view: UIView = UIView() + + let view: UIView = .init() + + // infer-locals-only + class Foo { + - let view: UIView = UIView() + + let view: UIView = .init() + + func method() { + - let view: UIView = UIView() + + let view = UIView() + } + } + + // Swift 5.9+, inferred (SE-0380) + - let foo: Foo = if condition { + + let foo = if condition { + Foo("foo") + } else { + Foo("bar") + } + + // Swift 5.9+, explicit (SE-0380) + let foo: Foo = if condition { + - Foo("foo") + + .init("foo") + } else { + - Foo("bar") + + .init("foo") + } + ``` + """, options: ["redundanttype"] ) { formatter in formatter.forEach(.operator("=", .infix)) { i, _ in diff --git a/Sources/Rules/RedundantTypedThrows.swift b/Sources/Rules/RedundantTypedThrows.swift index f40a0d7eb..4f9d8fbb1 100644 --- a/Sources/Rules/RedundantTypedThrows.swift +++ b/Sources/Rules/RedundantTypedThrows.swift @@ -10,8 +10,23 @@ import Foundation public extension FormatRule { static let redundantTypedThrows = FormatRule( - help: "Converts `throws(any Error)` to `throws`, and converts `throws(Never)` to non-throwing.") - { formatter in + help: """ + Converts `throws(any Error)` to `throws`, and converts `throws(Never)` to non-throwing. + """, + examples: """ + ```diff + - func foo() throws(Never) -> Int { + + func foo() -> Int { + return 0 + } + + - func foo() throws(any Error) -> Int { + + func foo() throws -> Int { + throw MyError.foo + } + ``` + """ + ) { formatter in formatter.forEach(.keyword("throws")) { throwsIndex, _ in guard // Typed throws was added in Swift 6.0: https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md formatter.options.swiftVersion >= "6.0", diff --git a/Sources/Rules/RedundantVoidReturnType.swift b/Sources/Rules/RedundantVoidReturnType.swift index fabfe77af..f15b3ea58 100644 --- a/Sources/Rules/RedundantVoidReturnType.swift +++ b/Sources/Rules/RedundantVoidReturnType.swift @@ -12,6 +12,17 @@ public extension FormatRule { /// Remove redundant void return values for function and closure declarations static let redundantVoidReturnType = FormatRule( help: "Remove explicit `Void` return type.", + examples: """ + ```diff + - func foo() -> Void { + // returns nothing + } + + + func foo() { + // returns nothing + } + ``` + """, options: ["closurevoid"] ) { formatter in formatter.forEach(.operator("->", .infix)) { i, _ in diff --git a/Sources/Rules/Semicolons.swift b/Sources/Rules/Semicolons.swift index 215317019..e7a55ac0b 100644 --- a/Sources/Rules/Semicolons.swift +++ b/Sources/Rules/Semicolons.swift @@ -12,6 +12,24 @@ public extension FormatRule { /// Remove semicolons, except where doing so would change the meaning of the code static let semicolons = FormatRule( help: "Remove semicolons.", + examples: """ + ```diff + - let foo = 5; + + let foo = 5 + ``` + + ```diff + - let foo = 5; let bar = 6 + + let foo = 5 + + let bar = 6 + ``` + + ```diff + // semicolon is not removed if it would affect the behavior of the code + return; + goto(fail) + ``` + """, options: ["semicolons"], sharedOptions: ["linebreaks"] ) { formatter in diff --git a/Sources/Rules/SortDeclarations.swift b/Sources/Rules/SortDeclarations.swift index 29f93597a..94c320b1b 100644 --- a/Sources/Rules/SortDeclarations.swift +++ b/Sources/Rules/SortDeclarations.swift @@ -15,6 +15,66 @@ public extension FormatRule { and declarations between // swiftformat:sort:begin and // swiftformat:sort:end comments. """, + examples: """ + ```diff + // swiftformat:sort + enum FeatureFlags { + - case upsellB + - case fooFeature + - case barFeature + - case upsellA( + - fooConfiguration: Foo, + - barConfiguration: Bar) + + case barFeature + + case fooFeature + + case upsellA( + + fooConfiguration: Foo, + + barConfiguration: Bar) + + case upsellB + } + + config: + ``` + sortedpatterns: 'Feature' + ``` + + enum FeatureFlags { + - case upsellB + - case fooFeature + - case barFeature + - case upsellA( + - fooConfiguration: Foo, + - barConfiguration: Bar) + + case barFeature + + case fooFeature + + case upsellA( + + fooConfiguration: Foo, + + barConfiguration: Bar) + + case upsellB + } + + enum FeatureFlags { + // swiftformat:sort:begin + - case upsellB + - case fooFeature + - case barFeature + - case upsellA( + - fooConfiguration: Foo, + - barConfiguration: Bar) + + case barFeature + + case fooFeature + + case upsellA( + + fooConfiguration: Foo, + + barConfiguration: Bar) + + case upsellB + // swiftformat:sort:end + + var anUnsortedProperty: Foo { + Foo() + } + } + ``` + """, options: ["sortedpatterns"], sharedOptions: ["organizetypes"] ) { formatter in diff --git a/Sources/Rules/SortImports.swift b/Sources/Rules/SortImports.swift index a1634fe10..e25b6ed7f 100644 --- a/Sources/Rules/SortImports.swift +++ b/Sources/Rules/SortImports.swift @@ -12,6 +12,29 @@ public extension FormatRule { /// Sort import statements static let sortImports = FormatRule( help: "Sort import statements alphabetically.", + examples: """ + ```diff + - import Foo + - import Bar + + import Bar + + import Foo + ``` + + ```diff + - import B + - import A + - #if os(iOS) + - import Foo-iOS + - import Bar-iOS + - #endif + + import A + + import B + + #if os(iOS) + + import Bar-iOS + + import Foo-iOS + + #endif + ``` + """, options: ["importgrouping"], sharedOptions: ["linebreaks"] ) { formatter in diff --git a/Sources/Rules/SortTypealiases.swift b/Sources/Rules/SortTypealiases.swift index 85630ee7b..0963409c2 100644 --- a/Sources/Rules/SortTypealiases.swift +++ b/Sources/Rules/SortTypealiases.swift @@ -10,7 +10,21 @@ import Foundation public extension FormatRule { static let sortTypealiases = FormatRule( - help: "Sort protocol composition typealiases alphabetically." + help: "Sort protocol composition typealiases alphabetically.", + examples: """ + ```diff + - typealias Placeholders = Foo & Bar & Baaz & Quux + + typealias Placeholders = Baaz & Bar & Foo & Quux + + typealias Dependencies + - = FooProviding + + = BaazProviding + & BarProviding + - & BaazProviding + + & FooProviding + & QuuxProviding + ``` + """ ) { formatter in formatter.forEach(.keyword("typealias")) { typealiasIndex, _ in guard let (equalsIndex, andTokenIndices, endIndex) = formatter.parseProtocolCompositionTypealias(at: typealiasIndex), diff --git a/Sources/Rules/SpaceAroundBraces.swift b/Sources/Rules/SpaceAroundBraces.swift index 405c76346..273f2d7ac 100644 --- a/Sources/Rules/SpaceAroundBraces.swift +++ b/Sources/Rules/SpaceAroundBraces.swift @@ -12,7 +12,18 @@ public extension FormatRule { /// Ensure that there is space between an opening brace and the preceding /// identifier, and between a closing brace and the following identifier. static let spaceAroundBraces = FormatRule( - help: "Add or remove space around curly braces." + help: "Add or remove space around curly braces.", + examples: """ + ```diff + - foo.filter{ return true }.map{ $0 } + + foo.filter { return true }.map { $0 } + ``` + + ```diff + - foo( {} ) + + foo({}) + ``` + """ ) { formatter in formatter.forEach(.startOfScope("{")) { i, _ in if let prevToken = formatter.token(at: i - 1) { diff --git a/Sources/Rules/SpaceAroundBrackets.swift b/Sources/Rules/SpaceAroundBrackets.swift index ed00b288c..e68efae6b 100644 --- a/Sources/Rules/SpaceAroundBrackets.swift +++ b/Sources/Rules/SpaceAroundBrackets.swift @@ -17,7 +17,18 @@ public extension FormatRule { /// * There is space between a closing bracket and following identifier /// * There is space between a closing bracket and following opening brace static let spaceAroundBrackets = FormatRule( - help: "Add or remove space around square brackets." + help: "Add or remove space around square brackets.", + examples: """ + ```diff + - foo as[String] + + foo as [String] + ``` + + ```diff + - foo = bar [5] + + foo = bar[5] + ``` + """ ) { formatter in formatter.forEach(.startOfScope("[")) { i, _ in let index = i - 1 diff --git a/Sources/Rules/SpaceAroundComments.swift b/Sources/Rules/SpaceAroundComments.swift index 1fb3a6ad0..09f9c392d 100644 --- a/Sources/Rules/SpaceAroundComments.swift +++ b/Sources/Rules/SpaceAroundComments.swift @@ -11,7 +11,18 @@ import Foundation public extension FormatRule { /// Add space around comments, except at the start or end of a line static let spaceAroundComments = FormatRule( - help: "Add space before and/or after comments." + help: "Add space before and/or after comments.", + examples: """ + ```diff + - let a = 5// assignment + + let a = 5 // assignment + ``` + + ```diff + - func foo() {/* ... */} + + func foo() { /* ... */ } + ``` + """ ) { formatter in formatter.forEach(.startOfScope("//")) { i, _ in if let prevToken = formatter.token(at: i - 1), !prevToken.isSpaceOrLinebreak { diff --git a/Sources/Rules/SpaceAroundGenerics.swift b/Sources/Rules/SpaceAroundGenerics.swift index 1c029fe62..eeb3c6af9 100644 --- a/Sources/Rules/SpaceAroundGenerics.swift +++ b/Sources/Rules/SpaceAroundGenerics.swift @@ -11,7 +11,13 @@ import Foundation public extension FormatRule { /// Ensure there is no space between an opening chevron and the preceding identifier static let spaceAroundGenerics = FormatRule( - help: "Remove space around angle brackets." + help: "Remove space around angle brackets.", + examples: """ + ```diff + - Foo () + + Foo() + ``` + """ ) { formatter in formatter.forEach(.startOfScope("<")) { i, _ in if formatter.token(at: i - 1)?.isSpace == true, diff --git a/Sources/Rules/SpaceAroundOperators.swift b/Sources/Rules/SpaceAroundOperators.swift index 101474227..eba01ee41 100644 --- a/Sources/Rules/SpaceAroundOperators.swift +++ b/Sources/Rules/SpaceAroundOperators.swift @@ -17,6 +17,22 @@ public extension FormatRule { /// preceded by a space, unless it appears at the beginning of a line. static let spaceAroundOperators = FormatRule( help: "Add or remove space around operators or delimiters.", + examples: """ + ```diff + - foo . bar() + + foo.bar() + ``` + + ```diff + - a+b+c + + a + b + c + ``` + + ```diff + - func ==(lhs: Int, rhs: Int) -> Bool + + func == (lhs: Int, rhs: Int) -> Bool + ``` + """, options: ["operatorfunc", "nospaceoperators", "ranges", "typedelimiter"] ) { formatter in formatter.forEachToken { i, token in diff --git a/Sources/Rules/SpaceAroundParens.swift b/Sources/Rules/SpaceAroundParens.swift index e396ccd12..ed798fff4 100644 --- a/Sources/Rules/SpaceAroundParens.swift +++ b/Sources/Rules/SpaceAroundParens.swift @@ -18,7 +18,18 @@ public extension FormatRule { /// * There is space between a closing paren and following opening brace /// * There is no space between a closing paren and following opening square bracket static let spaceAroundParens = FormatRule( - help: "Add or remove space around parentheses." + help: "Add or remove space around parentheses.", + examples: """ + ```diff + - init (foo) + + init(foo) + ``` + + ```diff + - switch(x){ + + switch (x) { + ``` + """ ) { formatter in formatter.forEach(.startOfScope("(")) { i, _ in let index = i - 1 diff --git a/Sources/Rules/SpaceInsideBraces.swift b/Sources/Rules/SpaceInsideBraces.swift index 1b60e87ab..d667bb6df 100644 --- a/Sources/Rules/SpaceInsideBraces.swift +++ b/Sources/Rules/SpaceInsideBraces.swift @@ -11,7 +11,13 @@ import Foundation public extension FormatRule { /// Ensure that there is space immediately inside braces static let spaceInsideBraces = FormatRule( - help: "Add space inside curly braces." + help: "Add space inside curly braces.", + examples: """ + ```diff + - foo.filter {return true} + + foo.filter { return true } + ``` + """ ) { formatter in formatter.forEach(.startOfScope("{")) { i, _ in if let nextToken = formatter.token(at: i + 1) { diff --git a/Sources/Rules/SpaceInsideBrackets.swift b/Sources/Rules/SpaceInsideBrackets.swift index f374842b8..6f23c493f 100644 --- a/Sources/Rules/SpaceInsideBrackets.swift +++ b/Sources/Rules/SpaceInsideBrackets.swift @@ -11,7 +11,13 @@ import Foundation public extension FormatRule { /// Remove space immediately inside square brackets static let spaceInsideBrackets = FormatRule( - help: "Remove space inside square brackets." + help: "Remove space inside square brackets.", + examples: """ + ```diff + - [ 1, 2, 3 ] + + [1, 2, 3] + ``` + """ ) { formatter in formatter.forEach(.startOfScope("[")) { i, _ in if formatter.token(at: i + 1)?.isSpace == true, diff --git a/Sources/Rules/SpaceInsideComments.swift b/Sources/Rules/SpaceInsideComments.swift index e1dfa10d7..9aab39d10 100644 --- a/Sources/Rules/SpaceInsideComments.swift +++ b/Sources/Rules/SpaceInsideComments.swift @@ -12,7 +12,18 @@ public extension FormatRule { /// Add space inside comments, taking care not to mangle headerdoc or /// carefully preformatted comments, such as star boxes, etc. static let spaceInsideComments = FormatRule( - help: "Add leading and/or trailing space inside comments." + help: "Add leading and/or trailing space inside comments.", + examples: """ + ```diff + - let a = 5 //assignment + + let a = 5 // assignment + ``` + + ```diff + - func foo() { /*...*/ } + + func foo() { /* ... */ } + ``` + """ ) { formatter in formatter.forEach(.startOfScope("//")) { i, _ in guard case let .commentBody(string)? = formatter.token(at: i + 1), diff --git a/Sources/Rules/SpaceInsideGenerics.swift b/Sources/Rules/SpaceInsideGenerics.swift index 8a496e562..960fd0a35 100644 --- a/Sources/Rules/SpaceInsideGenerics.swift +++ b/Sources/Rules/SpaceInsideGenerics.swift @@ -11,7 +11,13 @@ import Foundation public extension FormatRule { /// Remove space immediately inside chevrons static let spaceInsideGenerics = FormatRule( - help: "Remove space inside angle brackets." + help: "Remove space inside angle brackets.", + examples: """ + ```diff + - Foo< Bar, Baz > + + Foo + ``` + """ ) { formatter in formatter.forEach(.startOfScope("<")) { i, _ in if formatter.token(at: i + 1)?.isSpace == true { diff --git a/Sources/Rules/SpaceInsideParens.swift b/Sources/Rules/SpaceInsideParens.swift index 7a3ebc452..6349a49b0 100644 --- a/Sources/Rules/SpaceInsideParens.swift +++ b/Sources/Rules/SpaceInsideParens.swift @@ -11,7 +11,13 @@ import Foundation public extension FormatRule { /// Remove space immediately inside parens static let spaceInsideParens = FormatRule( - help: "Remove space inside parentheses." + help: "Remove space inside parentheses.", + examples: """ + ```diff + - ( a, b) + + (a, b) + ``` + """ ) { formatter in formatter.forEach(.startOfScope("(")) { i, _ in if formatter.token(at: i + 1)?.isSpace == true, diff --git a/Sources/Rules/SpacingGuards.swift b/Sources/Rules/SpacingGuards.swift index 8362bbd6b..9c35c0978 100644 --- a/Sources/Rules/SpacingGuards.swift +++ b/Sources/Rules/SpacingGuards.swift @@ -4,9 +4,23 @@ import Foundation public extension FormatRule { - static let spacingGuards = FormatRule(help: "Remove space between guard and add spaces after last guard.", - disabledByDefault: true) - { formatter in + static let spacingGuards = FormatRule( + help: "Remove space between guard statements, and add spaces after last guard.", + examples: """ + ```diff + guard let spicy = self.makeSpicy() else { + return + } + - + guard let soap = self.clean() else { + return + } + + + let doTheJob = nikekov() + ``` + """, + disabledByDefault: true + ) { formatter in formatter.forEach(.keyword("guard")) { guardIndex, _ in guard let startOfScopeOfGuard = formatter.index(of: .startOfScope("{"), after: guardIndex), let endOfScopeOfGuard = formatter.endOfScope(at: startOfScopeOfGuard) diff --git a/Sources/Rules/StrongOutlets.swift b/Sources/Rules/StrongOutlets.swift index 7f7210d5c..a31ffd166 100644 --- a/Sources/Rules/StrongOutlets.swift +++ b/Sources/Rules/StrongOutlets.swift @@ -11,7 +11,16 @@ import Foundation public extension FormatRule { /// Strip unnecessary `weak` from @IBOutlet properties (except delegates and datasources) static let strongOutlets = FormatRule( - help: "Remove `weak` modifier from `@IBOutlet` properties." + help: "Remove `weak` modifier from `@IBOutlet` properties.", + examples: """ + As per Apple's recommendation + (https://developer.apple.com/videos/play/wwdc2015/407/ @ 32:30). + + ```diff + - @IBOutlet weak var label: UILabel! + + @IBOutlet var label: UILabel! + ``` + """ ) { formatter in formatter.forEach(.keyword("@IBOutlet")) { i, _ in guard let varIndex = formatter.index(of: .keyword("var"), after: i), diff --git a/Sources/Rules/StrongifiedSelf.swift b/Sources/Rules/StrongifiedSelf.swift index 99296e656..81e4e9b7a 100644 --- a/Sources/Rules/StrongifiedSelf.swift +++ b/Sources/Rules/StrongifiedSelf.swift @@ -11,7 +11,17 @@ import Foundation public extension FormatRule { /// Removed backticks from `self` when strongifying static let strongifiedSelf = FormatRule( - help: "Remove backticks around `self` in Optional unwrap expressions." + help: "Remove backticks around `self` in Optional unwrap expressions.", + examples: """ + ```diff + - guard let `self` = self else { return } + + guard let self = self else { return } + ``` + + **NOTE:** assignment to un-escaped `self` is only supported in Swift 4.2 and + above, so the `strongifiedSelf` rule is disabled unless the Swift version is + set to 4.2 or above. + """ ) { formatter in formatter.forEach(.identifier("`self`")) { i, _ in guard formatter.options.swiftVersion >= "4.2", diff --git a/Sources/Rules/Todos.swift b/Sources/Rules/Todos.swift index 45a74a20c..1be596f2b 100644 --- a/Sources/Rules/Todos.swift +++ b/Sources/Rules/Todos.swift @@ -11,7 +11,18 @@ import Foundation public extension FormatRule { /// Ensure that TODO, MARK and FIXME comments are followed by a : as required static let todos = FormatRule( - help: "Use correct formatting for `TODO:`, `MARK:` or `FIXME:` comments." + help: "Use correct formatting for `TODO:`, `MARK:` or `FIXME:` comments.", + examples: """ + ```diff + - /* TODO fix this properly */ + + /* TODO: fix this properly */ + ``` + + ```diff + - // MARK - UIScrollViewDelegate + + // MARK: - UIScrollViewDelegate + ``` + """ ) { formatter in formatter.forEachToken { i, token in guard case var .commentBody(string) = token else { diff --git a/Sources/Rules/TrailingClosures.swift b/Sources/Rules/TrailingClosures.swift index d94a753b8..d191fa2ba 100644 --- a/Sources/Rules/TrailingClosures.swift +++ b/Sources/Rules/TrailingClosures.swift @@ -12,6 +12,17 @@ public extension FormatRule { /// Convert closure arguments to trailing closure syntax where possible static let trailingClosures = FormatRule( help: "Use trailing closure syntax where applicable.", + examples: """ + ```diff + - DispatchQueue.main.async(execute: { ... }) + + DispatchQueue.main.async { + ``` + + ```diff + - let foo = bar.map({ ... }).joined() + + let foo = bar.map { ... }.joined() + ``` + """, options: ["trailingclosures", "nevertrailing"] ) { formatter in let useTrailing = Set([ diff --git a/Sources/Rules/TrailingCommas.swift b/Sources/Rules/TrailingCommas.swift index 5197c2259..1f1a2005f 100644 --- a/Sources/Rules/TrailingCommas.swift +++ b/Sources/Rules/TrailingCommas.swift @@ -13,6 +13,21 @@ public extension FormatRule { /// This is useful for preventing noise in commits when items are added to end of array. static let trailingCommas = FormatRule( help: "Add or remove trailing comma from the last item in a collection literal.", + examples: """ + ```diff + let array = [ + foo, + bar, + - baz + ] + + let array = [ + foo, + bar, + + baz, + ] + ``` + """, options: ["commas"] ) { formatter in formatter.forEach(.endOfScope("]")) { i, _ in diff --git a/Sources/Rules/TypeSugar.swift b/Sources/Rules/TypeSugar.swift index aa7d6687d..90d84cc88 100644 --- a/Sources/Rules/TypeSugar.swift +++ b/Sources/Rules/TypeSugar.swift @@ -12,6 +12,22 @@ public extension FormatRule { /// Replace Array, Dictionary and Optional with [T], [T: U] and T? static let typeSugar = FormatRule( help: "Prefer shorthand syntax for Arrays, Dictionaries and Optionals.", + examples: """ + ```diff + - var foo: Array + + var foo: [String] + ``` + + ```diff + - var foo: Dictionary + + var foo: [String: Int] + ``` + + ```diff + - var foo: Optional<(Int) -> Void> + + var foo: ((Int) -> Void)? + ``` + """, options: ["shortoptionals"] ) { formatter in formatter.forEach(.startOfScope("<")) { i, _ in diff --git a/Sources/Rules/UnusedArguments.swift b/Sources/Rules/UnusedArguments.swift index 1ffb48931..a33b0052a 100644 --- a/Sources/Rules/UnusedArguments.swift +++ b/Sources/Rules/UnusedArguments.swift @@ -12,6 +12,37 @@ public extension FormatRule { /// Replace unused arguments with an underscore static let unusedArguments = FormatRule( help: "Mark unused function arguments with `_`.", + examples: """ + ```diff + - func foo(bar: Int, baz: String) { + print("Hello \\(baz)") + } + + + func foo(bar _: Int, baz: String) { + print("Hello \\(baz)") + } + ``` + + ```diff + - func foo(_ bar: Int) { + ... + } + + + func foo(_: Int) { + ... + } + ``` + + ```diff + - request { response, data in + self.data += data + } + + + request { _, data in + self.data += data + } + ``` + """, options: ["stripunusedargs"] ) { formatter in guard !formatter.options.fragment else { return } diff --git a/Sources/Rules/UnusedPrivateDeclaration.swift b/Sources/Rules/UnusedPrivateDeclaration.swift index 748586367..bcde6957e 100644 --- a/Sources/Rules/UnusedPrivateDeclaration.swift +++ b/Sources/Rules/UnusedPrivateDeclaration.swift @@ -12,6 +12,15 @@ public extension FormatRule { /// Remove unused private and fileprivate declarations static let unusedPrivateDeclaration = FormatRule( help: "Remove unused private and fileprivate declarations.", + examples: """ + ```diff + struct Foo { + - fileprivate var foo = "foo" + - fileprivate var baz = "baz" + var bar = "bar" + } + ``` + """, disabledByDefault: true, options: ["preservedecls"] ) { formatter in diff --git a/Sources/Rules/Void.swift b/Sources/Rules/Void.swift index 033ff80d9..62000373b 100644 --- a/Sources/Rules/Void.swift +++ b/Sources/Rules/Void.swift @@ -12,6 +12,32 @@ public extension FormatRule { /// Normalize the use of void in closure arguments and return values static let void = FormatRule( help: "Use `Void` for type declarations and `()` for values.", + examples: """ + ```diff + - let foo: () -> () + + let foo: () -> Void + ``` + + ```diff + - let bar: Void -> Void + + let bar: () -> Void + ``` + + ```diff + - let baz: (Void) -> Void + + let baz: () -> Void + ``` + + ```diff + - func quux() -> (Void) + + func quux() -> Void + ``` + + ```diff + - callback = { _ in Void() } + + callback = { _ in () } + ``` + """, options: ["voidtype"] ) { formatter in let hasLocalVoid = formatter.hasLocalVoid() diff --git a/Sources/Rules/WrapArguments.swift b/Sources/Rules/WrapArguments.swift index 05bfafb37..913a8c511 100644 --- a/Sources/Rules/WrapArguments.swift +++ b/Sources/Rules/WrapArguments.swift @@ -12,6 +12,74 @@ public extension FormatRule { /// Normalize argument wrapping style static let wrapArguments = FormatRule( help: "Align wrapped function arguments or collection elements.", + examples: """ + **NOTE:** For backwards compatibility with previous versions, if no value is + provided for `--wrapparameters`, the value for `--wraparguments` will be used. + + `--wraparguments before-first` + + ```diff + - foo(bar: Int, + - baz: String) + + + foo( + + bar: Int, + + baz: String + + ) + ``` + + ```diff + - class Foo + + + class Foo< + + Bar, + + Baz + + > + ``` + + `--wrapparameters after-first` + + ```diff + - func foo( + - bar: Int, + - baz: String + - ) { + ... + } + + + func foo(bar: Int, + + baz: String) + + { + ... + } + ``` + + `--wrapcollections before-first`: + + ```diff + - let foo = [bar, + baz, + - quuz] + + + let foo = [ + + bar, + baz, + + quuz + + ] + ``` + + `--conditionswrap auto`: + + ```diff + - guard let foo = foo, let bar = bar, let third = third + + guard let foo = foo, + + let bar = bar, + + let third = third + else {} + ``` + + """, orderAfter: [.wrap], options: ["wraparguments", "wrapparameters", "wrapcollections", "closingparen", "callsiteparen", "wrapreturntype", "wrapconditions", "wraptypealiases", "wrapeffects", "conditionswrap"], diff --git a/Sources/Rules/WrapAttributes.swift b/Sources/Rules/WrapAttributes.swift index 3584614ae..ee8004c86 100644 --- a/Sources/Rules/WrapAttributes.swift +++ b/Sources/Rules/WrapAttributes.swift @@ -11,6 +11,43 @@ import Foundation public extension FormatRule { static let wrapAttributes = FormatRule( help: "Wrap @attributes onto a separate line, or keep them on the same line.", + examples: """ + `--funcattributes prev-line` + + ```diff + - @objc func foo() {} + + + @objc + + func foo() { } + ``` + + `--funcattributes same-line` + + ```diff + - @objc + - func foo() { } + + + @objc func foo() {} + ``` + + `--typeattributes prev-line` + + ```diff + - @objc class Foo {} + + + @objc + + class Foo { } + ``` + + `--typeattributes same-line` + + ```diff + - @objc + - enum Foo { } + + + @objc enum Foo {} + ``` + """, options: ["funcattributes", "typeattributes", "varattributes", "storedvarattrs", "computedvarattrs", "complexattrs", "noncomplexattrs"], sharedOptions: ["linebreaks", "maxwidth"] ) { formatter in diff --git a/Sources/Rules/WrapConditionalBodies.swift b/Sources/Rules/WrapConditionalBodies.swift index d69fcb6b1..2b00e82f9 100644 --- a/Sources/Rules/WrapConditionalBodies.swift +++ b/Sources/Rules/WrapConditionalBodies.swift @@ -11,6 +11,21 @@ import Foundation public extension FormatRule { static let wrapConditionalBodies = FormatRule( help: "Wrap the bodies of inline conditional statements onto a new line.", + examples: """ + ```diff + - guard let foo = bar else { return baz } + + guard let foo = bar else { + + return baz + + } + ``` + + ```diff + - if foo { return bar } + + if foo { + + return bar + + } + ``` + """, disabledByDefault: true, sharedOptions: ["linebreaks", "indent"] ) { formatter in diff --git a/Sources/Rules/WrapEnumCases.swift b/Sources/Rules/WrapEnumCases.swift index 39ef101b2..354dbda30 100644 --- a/Sources/Rules/WrapEnumCases.swift +++ b/Sources/Rules/WrapEnumCases.swift @@ -12,6 +12,18 @@ public extension FormatRule { /// Formats enum cases declaration into one case per line static let wrapEnumCases = FormatRule( help: "Rewrite comma-delimited enum cases to one case per line.", + examples: """ + ```diff + enum Foo { + - case bar, baz + } + + enum Foo { + + case bar + + case baz + } + ``` + """, disabledByDefault: true, options: ["wrapenumcases"], sharedOptions: ["linebreaks"] diff --git a/Sources/Rules/WrapLoopBodies.swift b/Sources/Rules/WrapLoopBodies.swift index 42119dbc7..c32f5c5bc 100644 --- a/Sources/Rules/WrapLoopBodies.swift +++ b/Sources/Rules/WrapLoopBodies.swift @@ -11,6 +11,21 @@ import Foundation public extension FormatRule { static let wrapLoopBodies = FormatRule( help: "Wrap the bodies of inline loop statements onto a new line.", + examples: """ + ```diff + - for foo in array { print(foo) } + + for foo in array { + + print(foo) + + } + ``` + + ```diff + - while let foo = bar.next() { print(foo) } + + while let foo = bar.next() { + + print(foo) + + } + ``` + """, orderAfter: [.preferForLoop], sharedOptions: ["linebreaks", "indent"] ) { formatter in diff --git a/Sources/Rules/WrapMultilineConditionalAssignment.swift b/Sources/Rules/WrapMultilineConditionalAssignment.swift index 6459462c9..400746769 100644 --- a/Sources/Rules/WrapMultilineConditionalAssignment.swift +++ b/Sources/Rules/WrapMultilineConditionalAssignment.swift @@ -11,6 +11,21 @@ import Foundation public extension FormatRule { static let wrapMultilineConditionalAssignment = FormatRule( help: "Wrap multiline conditional assignment expressions after the assignment operator.", + examples: #""" + ```diff + - let planetLocation = if let star = planet.star { + - "The \(star.name) system" + - } else { + - "Rogue planet" + - } + + let planetLocation = + + if let star = planet.star { + + "The \(star.name) system" + + } else { + + "Rogue planet" + + } + ``` + """#, disabledByDefault: true, orderAfter: [.conditionalAssignment], sharedOptions: ["linebreaks"] diff --git a/Sources/Rules/WrapMultilineStatementBraces.swift b/Sources/Rules/WrapMultilineStatementBraces.swift index 928ced2bd..109ff044a 100644 --- a/Sources/Rules/WrapMultilineStatementBraces.swift +++ b/Sources/Rules/WrapMultilineStatementBraces.swift @@ -11,6 +11,61 @@ import Foundation public extension FormatRule { static let wrapMultilineStatementBraces = FormatRule( help: "Wrap the opening brace of multiline statements.", + examples: """ + ```diff + if foo, + - bar { + // ... + } + + if foo, + + bar + + { + // ... + } + ``` + + ```diff + guard foo, + - bar else { + // ... + } + + guard foo, + + bar else + + { + // ... + } + ``` + + ```diff + func foo( + bar: Int, + - baz: Int) { + // ... + } + + func foo( + bar: Int, + + baz: Int) + + { + // ... + } + ``` + + ```diff + class Foo: NSObject, + - BarProtocol { + // ... + } + + class Foo: NSObject, + + BarProtocol + + { + // ... + } + ``` + """, orderAfter: [.braces, .indent, .wrapArguments], sharedOptions: ["linebreaks"] ) { formatter in diff --git a/Sources/Rules/WrapSwitchCases.swift b/Sources/Rules/WrapSwitchCases.swift index f9703dfc9..f7041c6e9 100644 --- a/Sources/Rules/WrapSwitchCases.swift +++ b/Sources/Rules/WrapSwitchCases.swift @@ -12,6 +12,20 @@ public extension FormatRule { /// Writes one switch case per line static let wrapSwitchCases = FormatRule( help: "Wrap comma-delimited switch cases onto multiple lines.", + examples: """ + ```diff + switch foo { + - case .bar, .baz: + break + } + + switch foo { + + case .foo, + + .bar: + break + } + ``` + """, disabledByDefault: true, sharedOptions: ["linebreaks", "tabwidth", "indent", "smarttabs"] ) { formatter in diff --git a/SwiftFormat.xcodeproj/project.pbxproj b/SwiftFormat.xcodeproj/project.pbxproj index 93cab7bce..e308e61fe 100644 --- a/SwiftFormat.xcodeproj/project.pbxproj +++ b/SwiftFormat.xcodeproj/project.pbxproj @@ -47,10 +47,6 @@ 01A8320924EC7F7800A9D0EB /* FormattingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */; }; 01A95BD2225BEDE300744931 /* ParsingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */; }; 01A95BD3225BEDE400744931 /* ParsingHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */; }; - 01ACAE05220CD90F003F3CCF /* Examples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ACAE04220CD90F003F3CCF /* Examples.swift */; }; - 01ACAE06220CD914003F3CCF /* Examples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ACAE04220CD90F003F3CCF /* Examples.swift */; }; - 01ACAE07220CD915003F3CCF /* Examples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ACAE04220CD90F003F3CCF /* Examples.swift */; }; - 01ACAE08220CD916003F3CCF /* Examples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01ACAE04220CD90F003F3CCF /* Examples.swift */; }; 01B3987B1D763424009ADE61 /* FormatterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3987A1D763424009ADE61 /* FormatterTests.swift */; }; 01B3987D1D763493009ADE61 /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3987C1D763493009ADE61 /* Formatter.swift */; }; 01B3987F1D7634A0009ADE61 /* Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01B3987C1D763493009ADE61 /* Formatter.swift */; }; @@ -766,7 +762,6 @@ 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftFormat.swift; sourceTree = ""; }; 01A0EACA1D5DB5F500A0A8E3 /* swiftformat */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = swiftformat; sourceTree = BUILT_PRODUCTS_DIR; }; 01A0EACC1D5DB5F500A0A8E3 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; - 01ACAE04220CD90F003F3CCF /* Examples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Examples.swift; sourceTree = ""; }; 01B3987A1D763424009ADE61 /* FormatterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormatterTests.swift; sourceTree = ""; }; 01B3987C1D763493009ADE61 /* Formatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Formatter.swift; sourceTree = ""; }; 01BBD85821DAA2A000457380 /* Globs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globs.swift; sourceTree = ""; }; @@ -1152,7 +1147,6 @@ 01567D2E225B2BFD00B22D41 /* ParsingHelpers.swift */, 01D3B28524E9C9C700888DE0 /* FormattingHelpers.swift */, 2E230CA12C4C1C0700A16E2E /* DeclarationHelpers.swift */, - 01ACAE04220CD90F003F3CCF /* Examples.swift */, 2E7D30A32A7940C500C32174 /* Singularize.swift */, 01A0EAA71D5DB4CF00A0A8E3 /* SwiftFormat.h */, 01A0EAC41D5DB54A00A0A8E3 /* SwiftFormat.swift */, @@ -1846,7 +1840,6 @@ 2E2BAC172C57F6DD00590239 /* ConsistentSwitchCaseSpacing.swift in Sources */, 2E2BACE32C57F6DD00590239 /* GenericExtensions.swift in Sources */, 01BBD85921DAA2A000457380 /* Globs.swift in Sources */, - 01ACAE05220CD90F003F3CCF /* Examples.swift in Sources */, 01D3B28624E9C9C700888DE0 /* FormattingHelpers.swift in Sources */, 2E2BAD532C57F6DD00590239 /* TrailingCommas.swift in Sources */, 2E2BAD5F2C57F6DD00590239 /* RedundantBackticks.swift in Sources */, @@ -2160,7 +2153,6 @@ EBA6E7032C5B7D4800CBD360 /* SpacingGuards.swift in Sources */, 2E2BAC602C57F6DD00590239 /* BlockComments.swift in Sources */, 2E2BAD0C2C57F6DD00590239 /* ApplicationMain.swift in Sources */, - 01ACAE06220CD914003F3CCF /* Examples.swift in Sources */, 2E2BAD102C57F6DD00590239 /* RedundantProperty.swift in Sources */, 2E2BAC9C2C57F6DD00590239 /* RedundantInit.swift in Sources */, C2FFD1832BD13C9E00774F55 /* XMLReporter.swift in Sources */, @@ -2280,7 +2272,6 @@ E4962DE0203F3CD500A02013 /* OptionsStore.swift in Sources */, 2E2BAD2D2C57F6DD00590239 /* WrapEnumCases.swift in Sources */, 2E2BACDD2C57F6DD00590239 /* SpaceAroundBraces.swift in Sources */, - 01ACAE07220CD915003F3CCF /* Examples.swift in Sources */, 2E2BAD692C57F6DD00590239 /* HoistAwait.swift in Sources */, 2E2BAD192C57F6DD00590239 /* BlankLineAfterImports.swift in Sources */, 2E2BAC052C57F6DD00590239 /* RedundantBreak.swift in Sources */, @@ -2481,7 +2472,6 @@ 90F16AF81DA5EB4600EB4EA1 /* FormatFileCommand.swift in Sources */, 2E2BACCA2C57F6DD00590239 /* HeaderFileName.swift in Sources */, 2E2BAD262C57F6DD00590239 /* RedundantSelf.swift in Sources */, - 01ACAE08220CD916003F3CCF /* Examples.swift in Sources */, 2E2BACE22C57F6DD00590239 /* RedundantReturn.swift in Sources */, D52F6A672A82E04600FE1448 /* GitFileInfo.swift in Sources */, 2E2BAD962C57F6DD00590239 /* NumberFormatting.swift in Sources */, diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index d57b1955f..3a63e630d 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -300,14 +300,6 @@ class MetadataTests: XCTestCase { } } - // MARK: examples - - func testAllExamplesMatchRule() { - for key in FormatRules.examplesByName.keys { - XCTAssertNotNil(FormatRules.byName[key], "Examples includes entry for unknown rule '\(key)'") - } - } - // MARK: releases func testLatestVersionInChangelog() { From 5df3377875a379682e28d57fbd28b13d23b5575e Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sat, 24 Aug 2024 22:07:24 -0700 Subject: [PATCH 48/52] Fix missing lint output for organizeDeclarations rule (#1826) --- .../Extension/FormatFileCommand.swift | 2 +- .../Extension/FormatSelectionCommand.swift | 2 +- Sources/DeclarationHelpers.swift | 6 +- Sources/Formatter.swift | 139 +++++++++++++++++- .../Rules/DocCommentsBeforeAttributes.swift | 6 +- Sources/SwiftFormat.swift | 13 +- Tests/CommandLineTests.swift | 39 +++++ Tests/FormatterTests.swift | 96 ++++++++---- Tests/ReporterTests.swift | 4 +- Tests/Rules/ConsecutiveBlankLinesTests.swift | 2 +- Tests/Rules/IndentTests.swift | 2 +- Tests/Rules/MarkTypesTests.swift | 4 +- Tests/Rules/OrganizeDeclarationsTests.swift | 27 ++++ Tests/Rules/WrapTests.swift | 2 +- Tests/SwiftFormatTests.swift | 24 +-- Tests/XCTestCase+testFormatting.swift | 25 +++- 16 files changed, 326 insertions(+), 67 deletions(-) diff --git a/EditorExtension/Extension/FormatFileCommand.swift b/EditorExtension/Extension/FormatFileCommand.swift index b0a310e3c..ed2592e99 100644 --- a/EditorExtension/Extension/FormatFileCommand.swift +++ b/EditorExtension/Extension/FormatFileCommand.swift @@ -57,7 +57,7 @@ class FormatFileCommand: NSObject, XCSourceEditorCommand { let output: [Token] do { - output = try format(input, rules: rules, options: formatOptions) + output = try format(input, rules: rules, options: formatOptions).tokens } catch { return completionHandler(error) } diff --git a/EditorExtension/Extension/FormatSelectionCommand.swift b/EditorExtension/Extension/FormatSelectionCommand.swift index d25fd973a..3d40617b3 100644 --- a/EditorExtension/Extension/FormatSelectionCommand.swift +++ b/EditorExtension/Extension/FormatSelectionCommand.swift @@ -67,7 +67,7 @@ class FormatSelectionCommand: NSObject, XCSourceEditorCommand { let start = tokenIndex(for: startOffset, in: output, tabWidth: formatOptions.tabWidth) let end = tokenIndex(for: endOffset, in: output, tabWidth: formatOptions.tabWidth) do { - output = try format(output, rules: rules, options: formatOptions, range: start ..< end) + output = try format(output, rules: rules, options: formatOptions, range: start ..< end).tokens } catch { return completionHandler(error) } diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 3df27cfec..018d9a696 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -768,7 +768,11 @@ extension Formatter { } let updatedTokens = updatedDeclarations.flatMap { $0.tokens } - replaceTokens(in: tokens.indices, with: updatedTokens) + + // Only apply the updated tokens if the source representation changes. + if tokens.string != updatedTokens.string { + replaceAllTokens(with: updatedTokens) + } } } diff --git a/Sources/Formatter.swift b/Sources/Formatter.swift index 93f67d318..7bf30a3a2 100644 --- a/Sources/Formatter.swift +++ b/Sources/Formatter.swift @@ -179,6 +179,7 @@ public class Formatter: NSObject { public let line: Int public let rule: FormatRule public let filePath: String? + public let isMove: Bool public var help: String { stripMarkdown(rule.help).replacingOccurrences(of: "\n", with: " ") @@ -195,12 +196,13 @@ public class Formatter: NSObject { /// Should formatter track changes? private let trackChanges: Bool - private func trackChange(at index: Int) { + private func trackChange(at index: Int, isMove: Bool = false) { guard trackChanges else { return } changes.append(Change( line: originalLine(at: index), rule: currentRule ?? .none, - filePath: options.fileInfo.filePath + filePath: options.fileInfo.filePath, + isMove: isMove )) } @@ -247,8 +249,13 @@ public extension Formatter { /// Replaces the token at the specified index with a new token func replaceToken(at index: Int, with token: Token) { + replaceToken(at: index, with: token, isMove: false) + } + + /// Replaces the token at the specified index with a new token + private func replaceToken(at index: Int, with token: Token, isMove: Bool) { if trackChanges, token.string != tokens[index].string { - trackChange(at: index) + trackChange(at: index, isMove: isMove) } tokens[index] = token } @@ -256,14 +263,22 @@ public extension Formatter { /// Replaces the tokens in the specified range with new tokens @discardableResult func replaceTokens(in range: Range, with tokens: ArraySlice) -> Int { + replaceTokens(in: range, with: tokens, isMove: false) + } + + /// Replaces the tokens in the specified range with new tokens + @discardableResult + private func replaceTokens(in range: Range, with tokens: ArraySlice, isMove: Bool) -> Int { let max = min(range.count, tokens.count) for i in 0 ..< max { - replaceToken(at: range.lowerBound + i, with: tokens[tokens.startIndex + i]) + replaceToken(at: range.lowerBound + i, with: tokens[tokens.startIndex + i], isMove: isMove) } if range.count > max { - removeTokens(in: range.dropFirst(max)) + for index in range.dropFirst(max).reversed() { + removeToken(at: index, isMove: isMove) + } } else if tokens.count > max { - insert(tokens.dropFirst(max), at: range.lowerBound + max) + insert(tokens.dropFirst(max), at: range.lowerBound + max, isMove: isMove) } return tokens.count - range.count } @@ -307,9 +322,44 @@ public extension Formatter { replaceTokens(in: range.lowerBound ..< range.upperBound + 1, with: token) } + /// Replaces all of the tokens with the given new tokens, + /// diffing the lines and tracking lines that move without changes. + func replaceAllTokens(with updatedTokens: [Token]) { + guard #available(macOS 10.15, *) else { + // Swift's diffing implementation is only available in macOS 10.15+ + replaceTokens(in: tokens.indices, with: updatedTokens) + return + } + + let originalLines = tokens.lines + let updatedLines = updatedTokens.lines + let difference = updatedLines.difference(from: originalLines).inferringMoves() + + for step in difference { + switch step { + case let .insert(lineIndex, line, movedFromLineIndex): + let lineRanges = tokens.lineRanges + if lineIndex >= lineRanges.count { + insert(line, at: tokens.endIndex, isMove: movedFromLineIndex != nil) + } else { + insert(line, at: lineRanges[lineIndex].lowerBound, isMove: movedFromLineIndex != nil) + } + + case let .remove(lineIndex, _, movedToLineIndex): + for index in tokens.lineRanges[lineIndex].reversed() { + removeToken(at: index, isMove: movedToLineIndex != nil) + } + } + } + } + /// Removes the token at the specified index func removeToken(at index: Int) { - trackChange(at: index) + removeToken(at: index, isMove: false) + } + + private func removeToken(at index: Int, isMove: Bool) { + trackChange(at: index, isMove: isMove) updateRange(at: index, delta: -1) tokens.remove(at: index) if enumerationIndex >= index { @@ -342,6 +392,41 @@ public extension Formatter { } } + /// Moves the tokens in the given range to the new index. + /// Handles additional internal bookkeeping so this change produces + /// `Formatter.Change`s that represent moves and won't be filtered out + /// as redundant. + func moveTokens(in range: ClosedRange, to newIndex: Int) { + let tokensToMove = tokens[range] + var newIndex = newIndex + + for index in range.reversed() { + removeToken(at: index, isMove: true) + + if index < newIndex { + newIndex -= 1 + } + } + + insert(ArraySlice(tokensToMove), at: newIndex, isMove: true) + } + + /// Moves the tokens in the given range to the new index. + /// Handles additional internal bookkeeping so this change produces + /// `Formatter.Change`s that represent moves and won't be filtered out + /// as redundant. + func moveTokens(in range: Range, to index: Int) { + moveTokens(in: ClosedRange(range), to: index) + } + + /// Moves the tokens in the given range to the new index. + /// Handles additional internal bookkeeping so this change produces + /// `Formatter.Change`s that represent moves and won't be filtered out + /// as redundant. + func moveToken(at originalIndex: Int, to newIndex: Int) { + moveTokens(in: originalIndex ... originalIndex, to: newIndex) + } + /// Removes the last token func removeLastToken() { trackChange(at: tokens.endIndex - 1) @@ -351,8 +436,12 @@ public extension Formatter { /// Inserts an array of tokens at the specified index func insert(_ tokens: ArraySlice, at index: Int) { + insert(tokens, at: index, isMove: false) + } + + private func insert(_ tokens: ArraySlice, at index: Int, isMove: Bool) { if tokens.isEmpty { return } - trackChange(at: index) + trackChange(at: index, isMove: isMove) updateRange(at: index, delta: tokens.count) self.tokens.insert(contentsOf: tokens, at: index) if enumerationIndex >= index { @@ -655,3 +744,37 @@ extension String { return result } } + +private extension Array where Element == Token { + /// Ranges of lines within this array of tokens + var lineRanges: [ClosedRange] { + var lineRanges: [ClosedRange] = [] + var currentLine: ClosedRange? + + for (index, token) in enumerated() { + if currentLine == nil { + currentLine = index ... index + } else { + currentLine = currentLine!.lowerBound ... index + } + + if token.isLinebreak { + lineRanges.append(currentLine!) + currentLine = nil + } + } + + if let currentLine = currentLine { + lineRanges.append(currentLine) + } + + return lineRanges + } + + /// All of the lines within this array of tokens + var lines: [ArraySlice] { + lineRanges.map { lineRange in + self[lineRange] + } + } +} diff --git a/Sources/Rules/DocCommentsBeforeAttributes.swift b/Sources/Rules/DocCommentsBeforeAttributes.swift index db641a9ab..423a3f296 100644 --- a/Sources/Rules/DocCommentsBeforeAttributes.swift +++ b/Sources/Rules/DocCommentsBeforeAttributes.swift @@ -43,8 +43,10 @@ public extension FormatRule { let commentRange = indexAfterAttributes ..< restOfDeclaration let comment = formatter.tokens[commentRange] - formatter.removeTokens(in: commentRange) - formatter.insert(comment, at: startOfAttributes) + formatter.moveTokens( + in: indexAfterAttributes ..< restOfDeclaration, + to: startOfAttributes + ) } } } diff --git a/Sources/SwiftFormat.swift b/Sources/SwiftFormat.swift index 046dcc20a..bd4401b75 100644 --- a/Sources/SwiftFormat.swift +++ b/Sources/SwiftFormat.swift @@ -574,7 +574,9 @@ private func applyRules( return false } last = change - if newLines[change.line] == oldLines[change.line] { + // Filter out lines that haven't changed from their corresponding original line + // in the input code, unless the change was explicitly marked as a move. + if !change.isMove, newLines[change.line] == oldLines[change.line] { return false } return true @@ -605,18 +607,19 @@ private func applyRules( public func format( _ tokens: [Token], rules: [FormatRule] = FormatRules.default, options: FormatOptions = .default, range: Range? = nil -) throws -> [Token] { - try applyRules(rules, to: tokens, with: options, trackChanges: false, range: range).tokens +) throws -> (tokens: [Token], changes: [Formatter.Change]) { + try applyRules(rules, to: tokens, with: options, trackChanges: true, range: range) } /// Format code with specified rules and options public func format( _ source: String, rules: [FormatRule] = FormatRules.default, options: FormatOptions = .default, lineRange: ClosedRange? = nil -) throws -> String { +) throws -> (output: String, changes: [Formatter.Change]) { let tokens = tokenize(source) let range = lineRange.map { tokenRange(forLineRange: $0, in: tokens) } - return try sourceCode(for: format(tokens, rules: rules, options: options, range: range)) + let output = try format(tokens, rules: rules, options: options, range: range) + return (sourceCode(for: output.tokens), output.changes) } /// Lint a pre-parsed token array diff --git a/Tests/CommandLineTests.swift b/Tests/CommandLineTests.swift index 9a9dd53ce..b018eaa9e 100644 --- a/Tests/CommandLineTests.swift +++ b/Tests/CommandLineTests.swift @@ -658,4 +658,43 @@ class CommandLineTests: XCTestCase { let ouput = try String(contentsOf: outputURL) XCTAssert(ouput.contains(" Date: Sun, 25 Aug 2024 18:18:27 +0200 Subject: [PATCH 49/52] Fix typos discovered by codespell (#1835) --- Snapshots/Euclid/Sources/CSG.swift | 4 ++-- Snapshots/Euclid/Sources/Polygon.swift | 2 +- Snapshots/Expression/README.md | 2 +- Snapshots/Expression/Sources/AnyExpression.swift | 2 +- Snapshots/Expression/Sources/Expression.swift | 2 +- Snapshots/Issues/1644.swift | 4 ++-- Snapshots/Layout/Layout/LayoutNode+XML.swift | 2 +- Snapshots/Layout/Layout/UICollectionView+Layout.swift | 2 +- Snapshots/Layout/README.md | 2 +- Snapshots/Sprinter/README.md | 2 +- Sources/DeclarationHelpers.swift | 4 ++-- Sources/Rules/PreferForLoop.swift | 2 +- Sources/Rules/SortTypealiases.swift | 2 +- Tests/CommandLineTests.swift | 8 ++++---- Tests/TokenizerTests.swift | 2 +- 15 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Snapshots/Euclid/Sources/CSG.swift b/Snapshots/Euclid/Sources/CSG.swift index 3ca2d372c..ad74bde5d 100644 --- a/Snapshots/Euclid/Sources/CSG.swift +++ b/Snapshots/Euclid/Sources/CSG.swift @@ -88,7 +88,7 @@ public extension Mesh { return reduce(meshes, using: { $0.subtract($1) }) } - /// Returns a new mesh reprenting only the volume exclusively occupied by + /// Returns a new mesh representing only the volume exclusively occupied by /// one shape or the other, but not both. /// /// +-------+ +-------+ @@ -124,7 +124,7 @@ public extension Mesh { return multimerge(meshes, using: { $0.xor($1) }) } - /// Returns a new mesh reprenting the volume shared by both the mesh + /// Returns a new mesh representing the volume shared by both the mesh /// parameter and the receiver. If these do not intersect, an empty /// mesh will be returned. /// diff --git a/Snapshots/Euclid/Sources/Polygon.swift b/Snapshots/Euclid/Sources/Polygon.swift index b6d53cb88..ce3e00131 100644 --- a/Snapshots/Euclid/Sources/Polygon.swift +++ b/Snapshots/Euclid/Sources/Polygon.swift @@ -128,7 +128,7 @@ public extension Polygon { return polygons } - /// Tesselates polygon into triangles using the "ear clipping" method + /// Tessellates polygon into triangles using the "ear clipping" method func triangulate() -> [Polygon] { var vertices = self.vertices guard vertices.count > 3 else { diff --git a/Snapshots/Expression/README.md b/Snapshots/Expression/README.md index c4c60b630..d026ea6c6 100755 --- a/Snapshots/Expression/README.md +++ b/Snapshots/Expression/README.md @@ -228,7 +228,7 @@ This is an alphanumeric identifier representing a constant or variable in an exp Like Swift, Expression allows unicode characters in identifiers, such as emoji and scientific symbols. Unlike Swift, Expression's identifiers may also contain periods (.) as separators, which is useful for name-spacing (as demonstrated in the Layout example app). -The parser also accepts quoted strings as identifiers. Single quotes (') , double quotes (") , or backticks (`) may be used. Since `Expression` only deals with numeric values, it's up to your application to map these string indentifiers to numbers (if you are using [AnyExpression](#anyexpression) then this is handled automatically). +The parser also accepts quoted strings as identifiers. Single quotes (') , double quotes (") , or backticks (`) may be used. Since `Expression` only deals with numeric values, it's up to your application to map these string identifiers to numbers (if you are using [AnyExpression](#anyexpression) then this is handled automatically). Unlike regular identifiers, quoted identifiers can contain any unicode character, including spaces. Newlines, quotes and other special characters can be escaped using a backslash (\). Escape sequences are decoded for you, but the outer quotes are retained so you can distinguish strings from other identifiers. diff --git a/Snapshots/Expression/Sources/AnyExpression.swift b/Snapshots/Expression/Sources/AnyExpression.swift index da4294d03..dbf4d235c 100755 --- a/Snapshots/Expression/Sources/AnyExpression.swift +++ b/Snapshots/Expression/Sources/AnyExpression.swift @@ -579,7 +579,7 @@ public struct AnyExpression: CustomStringConvertible { /// All symbols used in the expression public var symbols: Set { return expression.symbols } - /// Returns the optmized, pretty-printed expression if it was valid + /// Returns the optimized, pretty-printed expression if it was valid /// Otherwise, returns the original (invalid) expression string public var description: String { return describer() } } diff --git a/Snapshots/Expression/Sources/Expression.swift b/Snapshots/Expression/Sources/Expression.swift index 8f9b2b6b8..23fb0d65e 100755 --- a/Snapshots/Expression/Sources/Expression.swift +++ b/Snapshots/Expression/Sources/Expression.swift @@ -508,7 +508,7 @@ public final class Expression: CustomStringConvertible { } } - /// Returns the optmized, pretty-printed expression if it was valid + /// Returns the optimized, pretty-printed expression if it was valid /// Otherwise, returns the original (invalid) expression string public var description: String { return root.description } diff --git a/Snapshots/Issues/1644.swift b/Snapshots/Issues/1644.swift index 21a36035d..cc6780b03 100644 --- a/Snapshots/Issues/1644.swift +++ b/Snapshots/Issues/1644.swift @@ -245,7 +245,7 @@ public extension Playdate { /// Adds a new menu item that can be checked or unchecked by the player. /// - Parameters: /// - title: The title displayed by the menu item. - /// - checked: Wether or not the menu item is checked. + /// - checked: Whether or not the menu item is checked. /// - callback: The callback invoked when the menu item is selected by the user. /// - userdata: The userdata to associate with the menu item. /// - Returns: The menu item @@ -262,7 +262,7 @@ public extension Playdate { /// Adds a new menu item that can be checked or unchecked by the player. /// - Parameters: /// - title: The title displayed by the menu item. - /// - checked: Wether or not the menu item is checked. + /// - checked: Whether or not the menu item is checked. /// - callback: The callback invoked when the menu item is selected by the user. /// - userdata: The userdata to associate with the menu item. /// - Returns: The menu item diff --git a/Snapshots/Layout/Layout/LayoutNode+XML.swift b/Snapshots/Layout/Layout/LayoutNode+XML.swift index 6eafa3934..a17b0dbb3 100755 --- a/Snapshots/Layout/Layout/LayoutNode+XML.swift +++ b/Snapshots/Layout/Layout/LayoutNode+XML.swift @@ -11,7 +11,7 @@ public extension LayoutNode { } /// Creates a LayoutNode from a parse XML file - /// The optional `url` parameter tells Layout where the node was loded from + /// The optional `url` parameter tells Layout where the node was loaded from /// The optional` relativeTo` parameter helps to locate the original source file convenience init(xmlData: Data, url: URL? = nil, relativeTo: String? = #file) throws { try self.init(layout: Layout(xmlData: xmlData, url: url, relativeTo: relativeTo)) diff --git a/Snapshots/Layout/Layout/UICollectionView+Layout.swift b/Snapshots/Layout/Layout/UICollectionView+Layout.swift index aa97cf240..c172cebad 100755 --- a/Snapshots/Layout/Layout/UICollectionView+Layout.swift +++ b/Snapshots/Layout/Layout/UICollectionView+Layout.swift @@ -356,7 +356,7 @@ public extension UICollectionView { } let cell = dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) if let node = cell.layoutNode { - node.update() // Ensure frame is updated before re-use + node.update() // Ensure frame is updated before reuse return node } switch layoutData { diff --git a/Snapshots/Layout/README.md b/Snapshots/Layout/README.md index 2a43203d5..6afa92044 100755 --- a/Snapshots/Layout/README.md +++ b/Snapshots/Layout/README.md @@ -1315,7 +1315,7 @@ loadLayout( ``` -You can use the same array literal syntax inside [macros](#macros), if you need to re-use the values: +You can use the same array literal syntax inside [macros](#macros), if you need to reuse the values: ```xml diff --git a/Snapshots/Sprinter/README.md b/Snapshots/Sprinter/README.md index e009292f4..b04ab6596 100755 --- a/Snapshots/Sprinter/README.md +++ b/Snapshots/Sprinter/README.md @@ -136,7 +136,7 @@ It may seem cumbersome to have to create a `StringFormat` object before printing 2. The expensive string parsing and `NumberFormatter` initialization steps can be performed once and then stored, not repeated each time the string is displayed. -For these reasons, it's recommended that you store and re-use your `FormatString` objects. You can either do this up-front for all strings, or lazily the first time each string is displayed - whichever makes more sense for your app. +For these reasons, it's recommended that you store and reuse your `FormatString` objects. You can either do this up-front for all strings, or lazily the first time each string is displayed - whichever makes more sense for your app. A good approach would be to create a wrapper function that encapsulates your app-specific string requirements. For example, you might want to ignore string format errors in production (since it's too late to fix by that point), and just display a blank string instead. Here is an example wrapper that you might use in your app: diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 018d9a696..87c527bcb 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -13,7 +13,7 @@ import Foundation /// A declaration, like a property, function, or type. /// https://docs.swift.org/swift-book/documentation/the-swift-programming-language/declarations/ /// -/// Forms a tree of declaratons, since `type` declarations have a body +/// Forms a tree of declarations, since `type` declarations have a body /// that contains child declarations. enum Declaration: Hashable { /// A type-like declaration with body of additional declarations (`class`, `struct`, etc) @@ -345,7 +345,7 @@ extension Formatter { // Prefer keeping linebreaks at the end of a declaration's tokens, // instead of the start of the next delaration's tokens. - // - This inclues any spaces on blank lines, but doesn't include the + // - This includes any spaces on blank lines, but doesn't include the // indentation associated with the next declaration. while let linebreakSearchIndex = endOfDeclaration, token(at: linebreakSearchIndex + 1)?.isSpaceOrLinebreak == true diff --git a/Sources/Rules/PreferForLoop.swift b/Sources/Rules/PreferForLoop.swift index 38d9288b1..02339cce9 100644 --- a/Sources/Rules/PreferForLoop.swift +++ b/Sources/Rules/PreferForLoop.swift @@ -272,7 +272,7 @@ extension Formatter { // 4. a trailing closure like `map { ... }` // 5. Some other combination of parens / subscript like `(foo).` // or even `foo["bar"]()()`. - // And any of these can be preceeded by one of the others + // And any of these can be preceded by one of the others switch tokens[index] { case let .identifier(identifierName): // Allowlist certain dot chain elements that should be ignored. diff --git a/Sources/Rules/SortTypealiases.swift b/Sources/Rules/SortTypealiases.swift index 0963409c2..1e0a530b1 100644 --- a/Sources/Rules/SortTypealiases.swift +++ b/Sources/Rules/SortTypealiases.swift @@ -111,7 +111,7 @@ public extension FormatRule { } // Make sure there's always a linebreak after any comments, to prevent - // them from accidentially commenting out following elements of the typealias + // them from accidentally commenting out following elements of the typealias if elementIndex != sortedElements.indices.last, sortedElements[elementIndex].allTokens.last?.isComment == true, let nextToken = formatter.nextToken(after: parsedElements[elementIndex].endIndex), diff --git a/Tests/CommandLineTests.swift b/Tests/CommandLineTests.swift index b018eaa9e..68908219b 100644 --- a/Tests/CommandLineTests.swift +++ b/Tests/CommandLineTests.swift @@ -559,8 +559,8 @@ class CommandLineTests: XCTestCase { url.path, ], in: "") } - let ouput = try String(contentsOf: outputURL) - XCTAssert(ouput.contains("\"rule_id\" : \"emptyBraces\"")) + let output = try String(contentsOf: outputURL) + XCTAssert(output.contains("\"rule_id\" : \"emptyBraces\"")) } func testGithubActionsLogReporterEndToEnd() throws { @@ -655,8 +655,8 @@ class CommandLineTests: XCTestCase { url.path, ], in: "") } - let ouput = try String(contentsOf: outputURL) - XCTAssert(ouput.contains(" Date: Sun, 25 Aug 2024 18:55:58 -0700 Subject: [PATCH 50/52] Update minimum supported Swift version to Swift 5.7 --- .github/workflows/build.yml | 4 ++-- .swiftformat | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bd5cb797..e17785f52 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,8 +42,8 @@ jobs: fail-fast: false matrix: swiftver: - - swift:5.2 - - swift:5.6 + - swift:5.7 + - swift:5.10 swiftos: - focal runs-on: ubuntu-latest diff --git a/.swiftformat b/.swiftformat index 97a9a01e7..8800667c5 100644 --- a/.swiftformat +++ b/.swiftformat @@ -29,7 +29,7 @@ --self remove --semicolons inline --stripunusedargs always ---swiftversion 5.1 +--swiftversion 5.7 --trimwhitespace always --wraparguments preserve --wrapcollections preserve From 0202f5eff1f48951293c1acbdaa21b6865fecbf4 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sun, 25 Aug 2024 18:58:04 -0700 Subject: [PATCH 51/52] Reformat code with --swiftversion 5.7 --- .../Application/Source/AppDelegate.swift | 8 ++-- EditorExtension/Shared/RulesStore.swift | 2 +- Sources/Arguments.swift | 10 ++--- Sources/CommandLine.swift | 29 ++++++------ Sources/DeclarationHelpers.swift | 34 +++++++------- Sources/FormatRule.swift | 4 +- Sources/Formatter.swift | 12 ++--- Sources/FormattingHelpers.swift | 44 +++++++++---------- Sources/GitFileInfo.swift | 4 +- Sources/GithubActionsLogReporter.swift | 2 +- Sources/Inference.swift | 10 ++--- Sources/OptionDescriptor.swift | 4 +- Sources/ParsingHelpers.swift | 10 ++--- Sources/Rules/AndOperator.swift | 2 +- Sources/Rules/BlankLinesAtEndOfScope.swift | 4 +- Sources/Rules/Braces.swift | 2 +- Sources/Rules/ConditionalAssignment.swift | 2 +- Sources/Rules/DocComments.swift | 2 +- Sources/Rules/ExtensionAccessControl.swift | 4 +- Sources/Rules/FileHeader.swift | 4 +- Sources/Rules/GenericExtensions.swift | 4 +- Sources/Rules/Indent.swift | 12 ++--- Sources/Rules/MarkTypes.swift | 6 +-- Sources/Rules/OpaqueGenericParameters.swift | 8 ++-- Sources/Rules/OrganizeDeclarations.swift | 26 +++++------ Sources/Rules/PreferForLoop.swift | 4 +- Sources/Rules/PreferKeyPath.swift | 2 +- Sources/Rules/RedundantClosure.swift | 4 +- Sources/Rules/RedundantNilInit.swift | 2 +- Sources/Rules/RedundantParens.swift | 2 +- Sources/Rules/RedundantPattern.swift | 6 +-- Sources/Rules/RedundantReturn.swift | 2 +- Sources/Rules/RedundantType.swift | 2 +- Sources/Rules/RedundantTypedThrows.swift | 2 +- Sources/Rules/SortDeclarations.swift | 4 +- Sources/Rules/SortTypealiases.swift | 2 +- Sources/Rules/TypeSugar.swift | 2 +- Sources/Rules/UnusedArguments.swift | 4 +- Sources/Rules/WrapAttributes.swift | 6 +-- Sources/Rules/WrapEnumCases.swift | 2 +- Sources/Rules/YodaConditions.swift | 4 +- Sources/SwiftFormat.swift | 8 ++-- Sources/Tokenizer.swift | 8 ++-- Tests/ArgumentsTests.swift | 4 +- Tests/CodeOrganizationTests.swift | 2 +- Tests/FormatterTests.swift | 2 +- Tests/MetadataTests.swift | 4 +- Tests/OptionDescriptorTests.swift | 4 +- Tests/ParsingHelpersTests.swift | 6 +-- Tests/XCTestCase+testFormatting.swift | 2 +- 50 files changed, 167 insertions(+), 172 deletions(-) diff --git a/EditorExtension/Application/Source/AppDelegate.swift b/EditorExtension/Application/Source/AppDelegate.swift index 0e2f0b57f..172a872d9 100644 --- a/EditorExtension/Application/Source/AppDelegate.swift +++ b/EditorExtension/Application/Source/AppDelegate.swift @@ -31,7 +31,7 @@ import Cocoa -@NSApplicationMain +@main class AppDelegate: NSObject, NSApplicationDelegate { var window: NSWindow? { NSApp.mainWindow @@ -87,7 +87,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } @IBAction func openConfiguration(_: NSMenuItem) { - guard let window = window else { + guard let window else { return } @@ -108,7 +108,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } @IBAction func saveConfiguration(_: NSMenuItem) { - guard let window = window else { + guard let window else { return } @@ -137,7 +137,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { } private func showError(_ error: Error) { - guard let window = window else { + guard let window else { return } diff --git a/EditorExtension/Shared/RulesStore.swift b/EditorExtension/Shared/RulesStore.swift index a77486a72..776014c87 100644 --- a/EditorExtension/Shared/RulesStore.swift +++ b/EditorExtension/Shared/RulesStore.swift @@ -60,7 +60,7 @@ extension Rule: Comparable { /// Space-separated, lowercased text terms that this rule might by found by. var searchableText: String { var items = [name] - if let formatRule = formatRule { + if let formatRule { items.append(formatRule.help.keywords.joined(separator: " ")) items.append(formatRule.options.joined(separator: " ")) items.append(formatRule.sharedOptions.joined(separator: " ")) diff --git a/Sources/Arguments.swift b/Sources/Arguments.swift index e03c33776..2edd0ebda 100644 --- a/Sources/Arguments.swift +++ b/Sources/Arguments.swift @@ -76,7 +76,7 @@ extension String { } return $0.distance < $1.distance } - .map { $0.0 } + .map(\.0) } /// The Damerau-Levenshtein edit-distance between two strings @@ -439,14 +439,14 @@ func argumentsFor(_ options: Options, excludingDefaults: Bool = false) -> [Strin do { if !fileOptions.excludedGlobs.isEmpty { // TODO: find a better alternative to stringifying url - args["exclude"] = fileOptions.excludedGlobs.map { $0.description }.sorted().joined(separator: ",") + args["exclude"] = fileOptions.excludedGlobs.map(\.description).sorted().joined(separator: ",") } arguments.remove("exclude") } do { if !fileOptions.unexcludedGlobs.isEmpty { // TODO: find a better alternative to stringifying url - args["unexclude"] = fileOptions.unexcludedGlobs.map { $0.description }.sorted().joined(separator: ",") + args["unexclude"] = fileOptions.unexcludedGlobs.map(\.description).sorted().joined(separator: ",") } arguments.remove("unexclude") } @@ -645,8 +645,8 @@ let rulesArguments = [ "rules", ] -let formattingArguments = Descriptors.formatting.map { $0.argumentName } -let internalArguments = Descriptors.internal.map { $0.argumentName } +let formattingArguments = Descriptors.formatting.map(\.argumentName) +let internalArguments = Descriptors.internal.map(\.argumentName) let optionsArguments = fileArguments + rulesArguments + formattingArguments + internalArguments let commandLineArguments = [ diff --git a/Sources/CommandLine.swift b/Sources/CommandLine.swift index f6e658d3b..7e76dcc2b 100644 --- a/Sources/CommandLine.swift +++ b/Sources/CommandLine.swift @@ -241,7 +241,7 @@ private func formatTime(_ time: TimeInterval) -> String { } private func serializeOptions(_ options: Options, to outputURL: URL?) throws { - if let outputURL = outputURL { + if let outputURL { let file = serialize(options: options) + "\n" do { try file.write(to: outputURL, atomically: true, encoding: .utf8) @@ -286,7 +286,7 @@ private func readConfigArg( print("warning: --exclude value '\(exclude)' did not match any files in \(directory).", as: .warning) config["exclude"] = nil } else { - config["exclude"] = excluded.map { $0.description }.sorted().joined(separator: ",") + config["exclude"] = excluded.map(\.description).sorted().joined(separator: ",") } } if let unexclude = config["unexclude"] { @@ -295,7 +295,7 @@ private func readConfigArg( print("warning: --unexclude value '\(unexclude)' did not match any files in \(directory).", as: .warning) config["unexclude"] = nil } else { - config["unexclude"] = unexcluded.map { $0.description }.sorted().joined(separator: ",") + config["unexclude"] = unexcluded.map(\.description).sorted().joined(separator: ",") } } args = try mergeArguments(args, into: config) @@ -369,6 +369,7 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in environment: environment ) else { var message = "'\(identifier)' is not a valid reporter" + // swiftformat:disable:next preferKeyPath let names = Reporters.all.map { $0.name } if let match = identifier.bestMatches(in: names).first { message += " (did you mean '\(match)'?)" @@ -641,7 +642,7 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in break case "clear": setDefaultCacheURL() - if let cacheURL = cacheURL, manager.fileExists(atPath: cacheURL.path) { + if let cacheURL, manager.fileExists(atPath: cacheURL.path) { do { try manager.removeItem(at: cacheURL) } catch { @@ -682,7 +683,7 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in while let line = CLI.readLine() { input = (input ?? "") + line } - guard let input = input else { + guard let input else { status = .finished(.ok) return } @@ -708,7 +709,7 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in verbose: verbose, lint: lint, reporter: reporter ) let output = sourceCode(for: outputTokens) - if let outputURL = outputURL, !useStdout { + if let outputURL, !useStdout { if !dryrun, (try? String(contentsOf: outputURL)) != output { try write(output, to: outputURL) } @@ -721,7 +722,7 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in print(dryrun ? input : output, as: .raw) } } else if let reporterOutput = try reporter.write() { - if let reportURL = reportURL { + if let reportURL { print("Writing report file to \(reportURL.path)", as: .info) try reporterOutput.write(to: reportURL, options: .atomic) } else { @@ -806,11 +807,11 @@ func processArguments(_ args: [String], environment: [String: String] = [:], in return .error } if outputFlags.filesChecked == 0, outputFlags.filesSkipped == 0 { - let inputPaths = inputURLs.map { $0.path }.joined(separator: ", ") + let inputPaths = inputURLs.map(\.path).joined(separator: ", ") print("warning: No eligible files found at \(inputPaths).", as: .warning) } if let reporterOutput = try reporter.write() { - if let reportURL = reportURL { + if let reportURL { print("Writing report file to \(reportURL.path)", as: .info) try reporterOutput.write(to: reportURL, options: .atomic) } else { @@ -981,7 +982,7 @@ func processInput(_ inputURLs: [URL], // Load cache let cacheDirectory = cacheURL?.deletingLastPathComponent().absoluteURL var cache: [String: String]? - if let cacheURL = cacheURL { + if let cacheURL { if let data = try? Data(contentsOf: cacheURL) { cache = try? JSONDecoder().decode([String: String].self, from: data) } @@ -1043,7 +1044,7 @@ func processInput(_ inputURLs: [URL], let cachePrefix = "\(version);\(configHash);" let cacheKey: String = { var path = inputURL.absoluteURL.path - if let cacheDirectory = cacheDirectory { + if let cacheDirectory { let commonPrefix = path.commonPrefix(with: cacheDirectory.path) path = String(path[commonPrefix.endIndex ..< path.endIndex]) } @@ -1057,7 +1058,7 @@ func processInput(_ inputURLs: [URL], sourceHash = computeHash(input) } let output: String - if let cacheHash = cacheHash, cacheHash == sourceHash { + if let cacheHash, cacheHash == sourceHash { output = input if verbose { print("\(lint ? "Linting" : "Formatting") \(inputURL.path)", as: .info) @@ -1166,8 +1167,8 @@ func processInput(_ inputURLs: [URL], } } // Save cache - if outputFlags.filesChecked > 0, let cache = cache, let cacheURL = cacheURL, - let cacheDirectory = cacheDirectory + if outputFlags.filesChecked > 0, let cache, let cacheURL, + let cacheDirectory { do { let data = try JSONEncoder().encode(cache) diff --git a/Sources/DeclarationHelpers.swift b/Sources/DeclarationHelpers.swift index 87c527bcb..d94642b87 100644 --- a/Sources/DeclarationHelpers.swift +++ b/Sources/DeclarationHelpers.swift @@ -47,7 +47,7 @@ enum Declaration: Hashable { return tokens case let .type(_, openTokens, bodyDeclarations, closeTokens, _), let .conditionalCompilation(openTokens, bodyDeclarations, closeTokens, _): - return openTokens + bodyDeclarations.flatMap { $0.tokens } + closeTokens + return openTokens + bodyDeclarations.flatMap(\.tokens) + closeTokens } } @@ -362,7 +362,7 @@ extension Formatter { // If there was another declaration after this one in the same scope, // then we know this declaration ends before that one starts - if let endOfDeclaration = endOfDeclaration { + if let endOfDeclaration { return endOfDeclaration } @@ -551,28 +551,28 @@ extension Declaration { case .keyword("let"), .keyword("var"), .keyword("operator"), .keyword("precedencegroup"): - if isOverriddenDeclaration && availableTypes.contains(.overriddenProperty) { + if isOverriddenDeclaration, availableTypes.contains(.overriddenProperty) { return .overriddenProperty } - if isStaticDeclaration && isDeclarationWithBody && availableTypes.contains(.staticPropertyWithBody) { + if isStaticDeclaration, isDeclarationWithBody, availableTypes.contains(.staticPropertyWithBody) { return .staticPropertyWithBody } - if isStaticDeclaration && availableTypes.contains(.staticProperty) { + if isStaticDeclaration, availableTypes.contains(.staticProperty) { return .staticProperty } - if isClassDeclaration && availableTypes.contains(.classPropertyWithBody) { + if isClassDeclaration, availableTypes.contains(.classPropertyWithBody) { // Interestingly, Swift does not support stored class properties // so there's no such thing as a class property without a body. // https://forums.swift.org/t/class-properties/16539/11 return .classPropertyWithBody } - if isViewDeclaration && availableTypes.contains(.swiftUIProperty) { + if isViewDeclaration, availableTypes.contains(.swiftUIProperty) { return .swiftUIProperty } - if !isDeclarationWithBody && isSwiftUIPropertyWrapper && availableTypes.contains(.swiftUIPropertyWrapper) { + if !isDeclarationWithBody, isSwiftUIPropertyWrapper, availableTypes.contains(.swiftUIPropertyWrapper) { return .swiftUIPropertyWrapper } - if isDeclarationWithBody && availableTypes.contains(.instancePropertyWithBody) { + if isDeclarationWithBody, availableTypes.contains(.instancePropertyWithBody) { return .instancePropertyWithBody } @@ -585,19 +585,19 @@ extension Declaration { // immediately follows the `func` keyword: // https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_function-name let methodName = declarationParser.next(.nonSpaceOrCommentOrLinebreak, after: declarationTypeTokenIndex) - if let methodName = methodName, lifecycleMethods.contains(methodName.string) { + if let methodName, lifecycleMethods.contains(methodName.string) { return .instanceLifecycle } - if isOverriddenDeclaration && availableTypes.contains(.overriddenMethod) { + if isOverriddenDeclaration, availableTypes.contains(.overriddenMethod) { return .overriddenMethod } - if isStaticDeclaration && availableTypes.contains(.staticMethod) { + if isStaticDeclaration, availableTypes.contains(.staticMethod) { return .staticMethod } - if isClassDeclaration && availableTypes.contains(.classMethod) { + if isClassDeclaration, availableTypes.contains(.classMethod) { return .classMethod } - if isViewDeclaration && availableTypes.contains(.swiftUIMethod) { + if isViewDeclaration, availableTypes.contains(.swiftUIMethod) { return .swiftUIMethod } @@ -767,7 +767,7 @@ extension Formatter { transform(declaration) } - let updatedTokens = updatedDeclarations.flatMap { $0.tokens } + let updatedTokens = updatedDeclarations.flatMap(\.tokens) // Only apply the updated tokens if the source representation changes. if tokens.string != updatedTokens.string { @@ -776,7 +776,7 @@ extension Formatter { } } -extension Array where Element == Declaration { +extension [Declaration] { /// Applies `operation` to every recursive declaration of this array of declarations func forEachRecursiveDeclaration(_ operation: (Declaration) -> Void) { for declaration in self { @@ -917,7 +917,7 @@ extension Declaration { } } -extension Array where Element == Token { +extension [Token] { /// Updates the given declaration tokens so it ends with at least one blank like /// (e.g. so it ends with at least two newlines) func endingWithBlankLine() -> [Token] { diff --git a/Sources/FormatRule.swift b/Sources/FormatRule.swift index 297628f03..aeec72fcd 100644 --- a/Sources/FormatRule.swift +++ b/Sources/FormatRule.swift @@ -129,8 +129,8 @@ private func allRules(except rules: [String]) -> [FormatRule] { } private let _allRules = allRules(except: []) -private let _deprecatedRules = _allRules.filter { $0.isDeprecated }.map { $0.name } -private let _disabledByDefault = _allRules.filter { $0.disabledByDefault }.map { $0.name } +private let _deprecatedRules = _allRules.filter(\.isDeprecated).map(\.name) +private let _disabledByDefault = _allRules.filter(\.disabledByDefault).map(\.name) private let _defaultRules = allRules(except: _disabledByDefault) public extension _FormatRules { diff --git a/Sources/Formatter.swift b/Sources/Formatter.swift index 7bf30a3a2..c3cd3c544 100644 --- a/Sources/Formatter.swift +++ b/Sources/Formatter.swift @@ -120,7 +120,7 @@ public class Formatter: NSObject { if let arg = args["1"] { throw FormatError.options("Unknown option \(arg)") } - var options = Options(formatOptions: self.options) + var options = Options(formatOptions: options) try options.addArguments(args, in: "") self.options = options.formatOptions ?? self.options } catch { @@ -207,7 +207,7 @@ public class Formatter: NSObject { } private func updateRange(at index: Int, delta: Int) { - guard let range = range, range.contains(index) else { + guard let range, range.contains(index) else { return } self.range = range.lowerBound ..< range.upperBound + delta @@ -733,9 +733,9 @@ public extension Formatter { extension String { /// https://stackoverflow.com/a/32306142 - func ranges(of string: S, options: String.CompareOptions = []) -> [Range] { + func ranges(of string: some StringProtocol, options: String.CompareOptions = []) -> [Range] { var result: [Range] = [] - var startIndex = self.startIndex + var startIndex = startIndex while startIndex < endIndex, let range = self[startIndex...].range(of: string, options: options) { result.append(range) startIndex = range.lowerBound < range.upperBound ? range.upperBound : @@ -745,7 +745,7 @@ extension String { } } -private extension Array where Element == Token { +private extension [Token] { /// Ranges of lines within this array of tokens var lineRanges: [ClosedRange] { var lineRanges: [ClosedRange] = [] @@ -764,7 +764,7 @@ private extension Array where Element == Token { } } - if let currentLine = currentLine { + if let currentLine { lineRanges.append(currentLine) } diff --git a/Sources/FormattingHelpers.swift b/Sources/FormattingHelpers.swift index b045264e3..f510bfca1 100644 --- a/Sources/FormattingHelpers.swift +++ b/Sources/FormattingHelpers.swift @@ -15,7 +15,7 @@ extension Formatter { func shouldWrapMultilineStatementBrace(at index: Int) -> Bool { assert(tokens[index] == .startOfScope("{")) guard let endIndex = endOfScope(at: index), - tokens[index + 1 ..< endIndex].contains(where: { $0.isLinebreak }), + tokens[index + 1 ..< endIndex].contains(where: \.isLinebreak), let prevIndex = self.index(of: .nonSpaceOrCommentOrLinebreak, before: index), let prevToken = token(at: prevIndex), !prevToken.isStartOfScope, !prevToken.isDelimiter @@ -163,7 +163,7 @@ extension Formatter { guard let endIndex = endOfScope(at: nextIndex) else { return fatalError("Expected end of scope", at: nextIndex) } - if let removeSelfKeyword = removeSelfKeyword { + if let removeSelfKeyword { var i = endIndex - 1 while i > nextIndex { switch tokens[i] { @@ -423,7 +423,7 @@ extension Formatter { } // Insert linebreak after each comma - var index = self.index(of: .nonSpaceOrCommentOrLinebreak, before: endOfScope)! + var index = index(of: .nonSpaceOrCommentOrLinebreak, before: endOfScope)! if tokens[index] != .delimiter(",") { index += 1 } @@ -851,7 +851,7 @@ extension Formatter { ) -> Bool { // ** Decide whether or not this statement needs to be wrapped / re-wrapped let range = startOfLine(at: startIndex) ... endIndex - let length = tokens[range].map { $0.string }.joined().count + let length = tokens[range].map(\.string).joined().count // Only wrap if this line if longer than the max width... let overMaximumWidth = maxWidth > 0 && length > maxWidth @@ -999,7 +999,7 @@ extension Formatter { } func indexWhereLineShouldWrap(from index: Int) -> Int? { - var lineLength = self.lineLength(upTo: index) + var lineLength = lineLength(upTo: index) var stringLiteralDepth = 0 var currentPriority = 0 var lastBreakPoint: Int? @@ -1338,7 +1338,7 @@ extension Formatter { includingReturnInConditionalStatements: Bool? = nil ) -> Bool { guard let endOfScopeIndex = endOfScope(at: startOfScopeIndex) else { return false } - let startOfBody = self.startOfBody(atStartOfScope: startOfScopeIndex) + let startOfBody = startOfBody(atStartOfScope: startOfScopeIndex) // The body should contain exactly one expression. // We can confirm this by parsing the body with `parseExpressionRange`, @@ -1594,7 +1594,7 @@ extension Formatter { /// Inserts a blank line at the end of the switch case func insertTrailingBlankLine(using formatter: Formatter) { - guard let linebreakBeforeEndOfScope = linebreakBeforeEndOfScope else { + guard let linebreakBeforeEndOfScope else { return } @@ -1603,8 +1603,8 @@ extension Formatter { /// Removes the trailing blank line from the switch case if present func removeTrailingBlankLine(using formatter: Formatter) { - guard let linebreakBeforeEndOfScope = linebreakBeforeEndOfScope, - let linebreakBeforeBlankLine = linebreakBeforeBlankLine + guard let linebreakBeforeEndOfScope, + let linebreakBeforeBlankLine else { return } formatter.removeTokens(in: (linebreakBeforeBlankLine + 1) ... linebreakBeforeEndOfScope) @@ -1661,7 +1661,7 @@ extension Formatter { linebreakBeforeEndOfScope = tokenBeforeEndOfScope } - if let linebreakBeforeEndOfScope = linebreakBeforeEndOfScope, + if let linebreakBeforeEndOfScope, let tokenBeforeBlankLine = index(of: .nonSpace, before: linebreakBeforeEndOfScope), tokens[tokenBeforeBlankLine].isLinebreak { @@ -1846,7 +1846,7 @@ extension Formatter { return knownProtocol?.primaryAssociatedType == associatedTypeName }) - if let matchingProtocolWithAssociatedType = matchingProtocolWithAssociatedType { + if let matchingProtocolWithAssociatedType { primaryAssociatedTypes[matchingProtocolWithAssociatedType] = conformance } else { // If this isn't the primary associated type of a protocol constraint, then we can't use it @@ -1884,7 +1884,7 @@ extension Formatter { let typeEndIndex: Int let nextCommaIndex = index(of: .delimiter(","), after: genericTypeNameIndex) - if let nextCommaIndex = nextCommaIndex, nextCommaIndex < genericSignatureEndIndex { + if let nextCommaIndex, nextCommaIndex < genericSignatureEndIndex { typeEndIndex = nextCommaIndex } else { typeEndIndex = genericSignatureEndIndex - 1 @@ -1937,14 +1937,14 @@ extension Formatter { conformanceType = .concreteType } - if let delineatorIndex = delineatorIndex, let conformanceType = conformanceType { + if let delineatorIndex, let conformanceType { let constrainedTypeName = tokens[genericTypeNameIndex ..< delineatorIndex] - .map { $0.string } + .map(\.string) .joined() .trimmingCharacters(in: .init(charactersIn: " \n\r,{}")) let conformanceName = tokens[(delineatorIndex + 1) ... typeEndIndex] - .map { $0.string } + .map(\.string) .joined() .trimmingCharacters(in: .init(charactersIn: " \n\r,{}")) @@ -2125,7 +2125,7 @@ extension Formatter { i += 1 } } - if let type = type { + if let type { membersByType[type.name] = members classMembersByType[type.name] = classMembers } @@ -2374,7 +2374,7 @@ extension Formatter { let classOrStatic = modifiersForDeclaration(at: lastKeywordIndex, contains: { _, string in ["static", "class"].contains(string) }) - if let name = name, classOrStatic || !staticSelf { + if let name, classOrStatic || !staticSelf { processAccessors(["get", "set", "willSet", "didSet", "init", "_modify"], for: name, at: &index, localNames: localNames, members: members, typeStack: &typeStack, closureStack: &closureStack, @@ -2398,7 +2398,7 @@ extension Formatter { // Handle a capture list followed by an optional parameter list: // `{ [self, foo] bar in` or `{ [self, foo] in` etc. - if let inIndex = inIndex, + if let inIndex, let captureListStartIndex = self.index(in: (index + 1) ..< inIndex, where: { !$0.isSpaceOrCommentOrLinebreak && !$0.isAttribute }), @@ -2411,7 +2411,7 @@ extension Formatter { // Handle a parameter list if present without a capture list // e.g. `{ foo, bar in` - else if let inIndex = inIndex, + else if let inIndex, let firstTokenInClosure = self.index(of: .nonSpaceOrCommentOrLinebreak, after: index), isInClosureArguments(at: firstTokenInClosure) { @@ -2431,7 +2431,7 @@ extension Formatter { let captureEntryStrings = captureListEntries.map { captureListEntry in captureListEntry - .map { $0.string } + .map(\.string) .joined() .trimmingCharacters(in: .whitespacesAndNewlines) } @@ -2442,7 +2442,7 @@ extension Formatter { captureListEntries.removeAll(where: { captureListEntry in let text = captureListEntry - .map { $0.string } + .map(\.string) .joined() .trimmingCharacters(in: .whitespacesAndNewlines) @@ -2493,7 +2493,7 @@ extension Formatter { return true } - guard let selfCapture = selfCapture else { + guard let selfCapture else { return false } diff --git a/Sources/GitFileInfo.swift b/Sources/GitFileInfo.swift index cd8291496..b1b1e5d25 100644 --- a/Sources/GitFileInfo.swift +++ b/Sources/GitFileInfo.swift @@ -50,8 +50,8 @@ extension GitFileInfo { } var author: String? { - if let authorName = authorName { - if let authorEmail = authorEmail { + if let authorName { + if let authorEmail { return "\(authorName) <\(authorEmail)>" } return authorName diff --git a/Sources/GithubActionsLogReporter.swift b/Sources/GithubActionsLogReporter.swift index 2c3aad88d..08ce094bd 100644 --- a/Sources/GithubActionsLogReporter.swift +++ b/Sources/GithubActionsLogReporter.swift @@ -62,7 +62,7 @@ final class GithubActionsLogReporter: Reporter { private extension GithubActionsLogReporter { func workspaceRelativePath(filePath: String) -> String { - if let workspaceRoot = workspaceRoot, filePath.hasPrefix(workspaceRoot) { + if let workspaceRoot, filePath.hasPrefix(workspaceRoot) { return filePath.replacingOccurrences(of: workspaceRoot + "/", with: "", options: [.anchored]) } else { return filePath diff --git a/Sources/Inference.swift b/Sources/Inference.swift index 6571e2682..285350d3d 100644 --- a/Sources/Inference.swift +++ b/Sources/Inference.swift @@ -106,7 +106,7 @@ private struct Inference { } if let indent = indents.sorted(by: { $0.count > $1.count - }).first.map({ $0.indent }) { + }).first.map(\.indent) { options.indent = indent } } @@ -408,7 +408,7 @@ private struct Inference { } // Decide on callSiteClosingParenPosition - if functionCallSameLine > functionCallBalanced && functionDeclarationBalanced > functionDeclarationSameLine { + if functionCallSameLine > functionCallBalanced, functionDeclarationBalanced > functionDeclarationSameLine { options.callSiteClosingParenPosition = .sameLine } else { options.callSiteClosingParenPosition = .balanced @@ -563,7 +563,7 @@ private struct Inference { var functionArgsRemoved = 0, functionArgsKept = 0 var unnamedFunctionArgsRemoved = 0, unnamedFunctionArgsKept = 0 - func removeUsed(from argNames: inout [String], with associatedData: inout [T], in range: CountableRange) { + func removeUsed(from argNames: inout [String], with associatedData: inout [some Any], in range: CountableRange) { for i in range { let token = formatter.tokens[i] if case .identifier = token, let index = argNames.firstIndex(of: token.unescaped()), @@ -758,7 +758,7 @@ private struct Inference { i += 1 } } - if let type = type { + if let type { membersByType[type] = members classMembersByType[type] = classMembers } @@ -997,7 +997,7 @@ private struct Inference { } prevIndex -= 1 } - if let name = name { + if let name { processAccessors(["get", "set", "willSet", "didSet", "init", "_modify"], for: name, at: &index, localNames: localNames, members: members, typeStack: &typeStack, membersByType: &membersByType, diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index 5efd3a338..96f36fd53 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -244,7 +244,7 @@ class OptionDescriptor { help: help, deprecationMessage: deprecationMessage, keyPath: keyPath, - type: .enum(T.allCases.map { $0.rawValue }), + type: .enum(T.allCases.map(\.rawValue)), altOptions: altOptions ) } @@ -397,7 +397,7 @@ private var _allDescriptors: [OptionDescriptor] = { private var _descriptorsByName: [String: OptionDescriptor] = Dictionary(uniqueKeysWithValues: _allDescriptors.map { ($0.argumentName, $0) }) private let _formattingDescriptors: [OptionDescriptor] = { - let internalDescriptors = Descriptors.internal.map { $0.argumentName } + let internalDescriptors = Descriptors.internal.map(\.argumentName) return _allDescriptors.filter { !internalDescriptors.contains($0.argumentName) } }() diff --git a/Sources/ParsingHelpers.swift b/Sources/ParsingHelpers.swift index a366b0fa5..5f280eca3 100644 --- a/Sources/ParsingHelpers.swift +++ b/Sources/ParsingHelpers.swift @@ -331,7 +331,7 @@ extension Formatter { case .identifier, .endOfScope(")"), .endOfScope("]"), .operator("?", _), .operator("!", _), .endOfScope where token.isStringDelimiter: - if tokens[prevIndex + 1 ..< index].contains(where: { $0.isLinebreak }) { + if tokens[prevIndex + 1 ..< index].contains(where: \.isLinebreak) { break } return .subscript @@ -837,7 +837,7 @@ extension Formatter { return i } switch tokens[startIndex] { - case .startOfScope("(") where !tokens[i + 1 ..< startIndex].contains(where: { $0.isLinebreak }): + case .startOfScope("(") where !tokens[i + 1 ..< startIndex].contains(where: \.isLinebreak): guard let closeParenIndex = index(of: .endOfScope(")"), after: startIndex) else { return nil } @@ -1038,7 +1038,7 @@ extension Formatter { } if [.endOfScope(")"), .endOfScope("]")].contains(prevToken), let startIndex = index(of: .startOfScope, before: prevIndex), - !tokens[startIndex ..< prevIndex].contains(where: { $0.isLinebreak }) + !tokens[startIndex ..< prevIndex].contains(where: \.isLinebreak) || currentIndentForLine(at: startIndex) == currentIndentForLine(at: prevIndex) { return false @@ -1676,9 +1676,9 @@ extension Formatter { let value: (assignmentIndex: Int, expressionRange: ClosedRange)? var range: ClosedRange { - if let value = value { + if let value { return introducerIndex ... value.expressionRange.upperBound - } else if let type = type { + } else if let type { return introducerIndex ... type.range.upperBound } else { return introducerIndex ... identifierIndex diff --git a/Sources/Rules/AndOperator.swift b/Sources/Rules/AndOperator.swift index 9f15adb47..639db8c44 100644 --- a/Sources/Rules/AndOperator.swift +++ b/Sources/Rules/AndOperator.swift @@ -62,7 +62,7 @@ public extension FormatRule { } nextOpIndex = next } - if let chevronIndex = chevronIndex, + if let chevronIndex, formatter.index(of: .operator(">", .infix), in: index ..< endIndex) != nil { // Check if this would cause ambiguity for chevrons diff --git a/Sources/Rules/BlankLinesAtEndOfScope.swift b/Sources/Rules/BlankLinesAtEndOfScope.swift index f292e6d94..2cb590d1f 100644 --- a/Sources/Rules/BlankLinesAtEndOfScope.swift +++ b/Sources/Rules/BlankLinesAtEndOfScope.swift @@ -49,7 +49,7 @@ public extension FormatRule { guard ["}", ")", "]", ">"].contains(endOfScope.string), // If there is extra code after the closing scope on the same line, ignore it - (formatter.next(.nonSpaceOrComment, after: endOfScopeIndex).map { $0.isLinebreak }) ?? true + (formatter.next(.nonSpaceOrComment, after: endOfScopeIndex).map(\.isLinebreak)) ?? true else { return } // Consumers can choose whether or not this rule should apply to type bodies @@ -79,7 +79,7 @@ public extension FormatRule { index -= 1 } if formatter.options.removeBlankLines, - let indexOfFirstLineBreak = indexOfFirstLineBreak, + let indexOfFirstLineBreak, indexOfFirstLineBreak != indexOfLastLineBreak { formatter.removeTokens(in: indexOfFirstLineBreak ..< indexOfLastLineBreak!) diff --git a/Sources/Rules/Braces.swift b/Sources/Rules/Braces.swift index b2685ebc9..417f51a34 100644 --- a/Sources/Rules/Braces.swift +++ b/Sources/Rules/Braces.swift @@ -74,7 +74,7 @@ public extension FormatRule { } else { // Implement K&R-style braces, where opening brace appears on the same line guard let prevIndex = formatter.index(of: .nonSpaceOrLinebreak, before: i), - formatter.tokens[prevIndex ..< i].contains(where: { $0.isLinebreak }), + formatter.tokens[prevIndex ..< i].contains(where: \.isLinebreak), !formatter.tokens[prevIndex].isComment else { return diff --git a/Sources/Rules/ConditionalAssignment.swift b/Sources/Rules/ConditionalAssignment.swift index 5b32830cf..dd8efbe68 100644 --- a/Sources/Rules/ConditionalAssignment.swift +++ b/Sources/Rules/ConditionalAssignment.swift @@ -142,7 +142,7 @@ public extension FormatRule { startOfParentScope = formatter.startOfScope(at: caseToken) } - if let startOfParentScope = startOfParentScope, + if let startOfParentScope, let mostRecentIfOrSwitch = formatter.index(of: .keyword, before: startOfParentScope, if: { ["if", "switch"].contains($0.string) }), let conditionalBranches = formatter.conditionalBranches(at: mostRecentIfOrSwitch), let startOfFirstParentBranch = conditionalBranches.first?.startOfBranch, diff --git a/Sources/Rules/DocComments.swift b/Sources/Rules/DocComments.swift index 5e7d0e50f..790b5ba60 100644 --- a/Sources/Rules/DocComments.swift +++ b/Sources/Rules/DocComments.swift @@ -77,7 +77,7 @@ public extension FormatRule { let nextDeclarationKeyword = formatter.index(after: endOfDeclaration, where: \.isDeclarationTypeKeyword) { let linebreaksBetweenDeclarations = formatter.tokens[declarationKeyword ... nextDeclarationKeyword] - .filter { $0.isLinebreak }.count + .filter(\.isLinebreak).count // If there is only a single line break between the start of this declaration and the subsequent declaration, // then they are written sequentially in a block. In this case, don't convert regular comments to doc comments. diff --git a/Sources/Rules/ExtensionAccessControl.swift b/Sources/Rules/ExtensionAccessControl.swift index bfa76e2ee..5b4ad16b0 100644 --- a/Sources/Rules/ExtensionAccessControl.swift +++ b/Sources/Rules/ExtensionAccessControl.swift @@ -124,7 +124,7 @@ public extension FormatRule { // Move the extension's visibility keyword to each individual declaration case .onDeclarations: // If the extension visibility is unspecified then there isn't any work to do - guard let extensionVisibility = extensionVisibility else { + guard let extensionVisibility else { return declaration } @@ -146,7 +146,7 @@ public extension FormatRule { } } - let updatedTokens = updatedDeclarations.flatMap { $0.tokens } + let updatedTokens = updatedDeclarations.flatMap(\.tokens) formatter.replaceTokens(in: formatter.tokens.indices, with: updatedTokens) } } diff --git a/Sources/Rules/FileHeader.swift b/Sources/Rules/FileHeader.swift index 822d0dd13..2cc9e3933 100644 --- a/Sources/Rules/FileHeader.swift +++ b/Sources/Rules/FileHeader.swift @@ -152,9 +152,7 @@ public extension FormatRule { if lastHeaderTokenIndex < formatter.tokens.count - 1 { headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 1)) if lastHeaderTokenIndex < formatter.tokens.count - 2, - !formatter.tokens[lastHeaderTokenIndex + 1 ... lastHeaderTokenIndex + 2].allSatisfy({ - $0.isLinebreak - }) + !formatter.tokens[lastHeaderTokenIndex + 1 ... lastHeaderTokenIndex + 2].allSatisfy(\.isLinebreak) { headerTokens.append(.linebreak(formatter.options.linebreak, headerLinebreaks + 2)) } diff --git a/Sources/Rules/GenericExtensions.swift b/Sources/Rules/GenericExtensions.swift index 818f8701a..007007ac8 100644 --- a/Sources/Rules/GenericExtensions.swift +++ b/Sources/Rules/GenericExtensions.swift @@ -137,7 +137,7 @@ public extension FormatRule { } // Remove the now-unnecessary generic constraints from the where clause - let sourceRangesToRemove = providedGenericTypes.map { $0.sourceRange } + let sourceRangesToRemove = providedGenericTypes.map(\.sourceRange) formatter.removeTokens(in: sourceRangesToRemove) // if the where clause is completely empty now, we need to the where token as well @@ -148,7 +148,7 @@ public extension FormatRule { } // Replace the extension typename with the fully-qualified generic angle bracket syntax - let genericSubtypes = providedGenericTypes.map { $0.name }.joined(separator: ", ") + let genericSubtypes = providedGenericTypes.map(\.name).joined(separator: ", ") let fullGenericType = "\(extendedType)<\(genericSubtypes)>" formatter.replaceToken(at: typeNameIndex, with: tokenize(fullGenericType)) } diff --git a/Sources/Rules/Indent.swift b/Sources/Rules/Indent.swift index 4adeda8ce..c30457530 100644 --- a/Sources/Rules/Indent.swift +++ b/Sources/Rules/Indent.swift @@ -271,11 +271,7 @@ public extension FormatRule { if let startIndex = formatter.index(of: .startOfScope("{"), before: i), formatter.index(of: .keyword("for"), in: startIndex + 1 ..< i) == nil, let paramsIndex = formatter.index(of: .startOfScope, in: startIndex + 1 ..< i), - !formatter.tokens[startIndex + 1 ..< paramsIndex].contains(where: { - $0.isLinebreak - }), formatter.tokens[paramsIndex + 1 ..< i].contains(where: { - $0.isLinebreak - }) + !formatter.tokens[startIndex + 1 ..< paramsIndex].contains(where: \.isLinebreak), formatter.tokens[paramsIndex + 1 ..< i].contains(where: \.isLinebreak) { indentStack[indentStack.count - 1] += formatter.options.indent } @@ -310,7 +306,7 @@ public extension FormatRule { // Make sure the `=` actually created a new scope if scopeStack.last == .operator("=", .infix), // Parse the conditional branches following the `=` assignment operator - let previousAssignmentIndex = previousAssignmentIndex, + let previousAssignmentIndex, let nextTokenAfterAssignment = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: previousAssignmentIndex), let conditionalBranches = formatter.conditionalBranches(at: nextTokenAfterAssignment), // If this is the very end of the conditional assignment following the `=`, @@ -579,7 +575,7 @@ public extension FormatRule { { var lineStart = formatter.startOfLine(at: lastNonSpaceOrLinebreakIndex, excludingIndent: true) let startToken = formatter.token(at: lineStart) - if let startToken = startToken, [ + if let startToken, [ .startOfScope("#if"), .keyword("#else"), .keyword("#elseif"), .endOfScope("#endif") ].contains(startToken) { if let index = formatter.index(of: .nonSpaceOrCommentOrLinebreak, before: lineStart) { @@ -784,7 +780,7 @@ extension Formatter { // If there is a linebreak after certain symbols, we should add // an additional indentation to the lines at the same indention scope // after this line. - let endOfLine = self.endOfLine(at: i) + let endOfLine = endOfLine(at: i) switch token(at: endOfLine - 1) { case .keyword("return")?, .operator("=", .infix)?: let endOfNextLine = self.endOfLine(at: endOfLine + 1) diff --git a/Sources/Rules/MarkTypes.swift b/Sources/Rules/MarkTypes.swift index b5494acce..dbafe97c5 100644 --- a/Sources/Rules/MarkTypes.swift +++ b/Sources/Rules/MarkTypes.swift @@ -148,7 +148,7 @@ public extension FormatRule { if !commentTemplate.contains("%c") { markForType = commentTemplate.replacingOccurrences(of: "%t", with: typeName) - } else if commentTemplate.contains("%c"), let conformanceNames = conformanceNames { + } else if commentTemplate.contains("%c"), let conformanceNames { markForType = commentTemplate .replacingOccurrences(of: "%t", with: typeName) .replacingOccurrences(of: "%c", with: conformanceNames) @@ -173,7 +173,7 @@ public extension FormatRule { } let innermostExtension = extensions.last! - let extensionNames = extensions.compactMap { $0.name }.joined(separator: ".") + let extensionNames = extensions.compactMap(\.name).joined(separator: ".") if let extensionBody = innermostExtension.body, extensionBody.count == 1, @@ -261,7 +261,7 @@ public extension FormatRule { } } - let updatedTokens = declarations.flatMap { $0.tokens } + let updatedTokens = declarations.flatMap(\.tokens) formatter.replaceTokens(in: 0 ..< formatter.tokens.count, with: updatedTokens) } } diff --git a/Sources/Rules/OpaqueGenericParameters.swift b/Sources/Rules/OpaqueGenericParameters.swift index fadd1a445..996a4e450 100644 --- a/Sources/Rules/OpaqueGenericParameters.swift +++ b/Sources/Rules/OpaqueGenericParameters.swift @@ -139,7 +139,7 @@ public extension FormatRule { // If the generic type is used in a constraint of any other generic type, then the type // can't be removed without breaking that other type let otherGenericTypes = genericTypes.filter { $0.name != genericType.name } - let otherTypeConformances = otherGenericTypes.flatMap { $0.conformances } + let otherTypeConformances = otherGenericTypes.flatMap(\.conformances) for otherTypeConformance in otherTypeConformances { let conformanceTokens = formatter.tokens[otherTypeConformance.sourceRange] if conformanceTokens.contains(where: { $0.string == genericType.name }) { @@ -161,7 +161,7 @@ public extension FormatRule { // but with `-> some Fooable` the generic type is specified by the function implementation. // Because those represent different concepts, we can't convert between them, // so have to mark the generic type as ineligible if it appears in the return type. - if let returnTypeTokens = returnTypeTokens, + if let returnTypeTokens, returnTypeTokens.contains(where: { $0.string == genericType.name }) { genericType.eligibleToRemove = false @@ -221,9 +221,9 @@ public extension FormatRule { } } - let genericsEligibleToRemove = genericTypes.filter { $0.eligibleToRemove } + let genericsEligibleToRemove = genericTypes.filter(\.eligibleToRemove) let sourceRangesToRemove = Set(genericsEligibleToRemove.flatMap { type in - [type.definitionSourceRange] + type.conformances.map { $0.sourceRange } + [type.definitionSourceRange] + type.conformances.map(\.sourceRange) }) // We perform modifications to the function signature in reverse order diff --git a/Sources/Rules/OrganizeDeclarations.swift b/Sources/Rules/OrganizeDeclarations.swift index e2374a1aa..190efb826 100644 --- a/Sources/Rules/OrganizeDeclarations.swift +++ b/Sources/Rules/OrganizeDeclarations.swift @@ -13,22 +13,22 @@ public extension FormatRule { help: "Organize declarations within class, struct, enum, actor, and extension bodies.", examples: """ Default value for `--visibilityorder` when using `--organizationmode visibility`: - `\(VisibilityCategory.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` + `\(VisibilityCategory.defaultOrdering(for: .visibility).map(\.rawValue).joined(separator: ", "))` Default value for `--visibilityorder` when using `--organizationmode type`: - `\(VisibilityCategory.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` + `\(VisibilityCategory.defaultOrdering(for: .type).map(\.rawValue).joined(separator: ", "))` **NOTE:** When providing custom arguments for `--visibilityorder` the following entries must be included: - `\(VisibilityCategory.essentialCases.map { $0.rawValue }.joined(separator: ", "))` + `\(VisibilityCategory.essentialCases.map(\.rawValue).joined(separator: ", "))` Default value for `--typeorder` when using `--organizationmode visibility`: - `\(DeclarationType.defaultOrdering(for: .visibility).map { $0.rawValue }.joined(separator: ", "))` + `\(DeclarationType.defaultOrdering(for: .visibility).map(\.rawValue).joined(separator: ", "))` Default value for `--typeorder` when using `--organizationmode type`: - `\(DeclarationType.defaultOrdering(for: .type).map { $0.rawValue }.joined(separator: ", "))` + `\(DeclarationType.defaultOrdering(for: .type).map(\.rawValue).joined(separator: ", "))` **NOTE:** The follow declaration types must be included in either `--typeorder` or `--visibilityorder`: - `\(DeclarationType.essentialCases.map { $0.rawValue }.joined(separator: ", "))` + `\(DeclarationType.essentialCases.map(\.rawValue).joined(separator: ", "))` `--organizationmode visibility` (default) @@ -163,7 +163,7 @@ extension Formatter { else { return typeDeclaration } // Parse category order from options - let categoryOrder = self.categoryOrder(for: options.organizationMode) + let categoryOrder = categoryOrder(for: options.organizationMode) // Remove all of the existing category separators, so they can be re-added // at the correct location after sorting the declarations. @@ -256,8 +256,8 @@ extension Formatter { } let lineCount = typeDeclaration.body - .flatMap { $0.tokens } - .filter { $0.isLinebreak } + .flatMap(\.tokens) + .filter(\.isLinebreak) .count return lineCount >= organizationThreshold @@ -340,7 +340,7 @@ extension Formatter { } }) - .map { $0.element } + .map(\.element) } func customDeclarationSortOrderList(from categorizedDeclarations: [CategorizedDeclaration]) -> [String] { @@ -421,11 +421,11 @@ extension Formatter { ) -> Bool { let lhsPropertiesOrder = lhs .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } - .map { $0.declaration } + .map(\.declaration) let rhsPropertiesOrder = rhs .filter { affectsSynthesizedMemberwiseInitializer($0.declaration, $0.category) } - .map { $0.declaration } + .map(\.declaration) return lhsPropertiesOrder == rhsPropertiesOrder } @@ -438,7 +438,7 @@ extension Formatter { /// Ends the current group, ensuring that groups are only recorded /// when they contain two or more declarations. func endCurrentGroup(addingToExistingGroup declarationToAdd: Declaration? = nil) { - if let declarationToAdd = declarationToAdd { + if let declarationToAdd { currentGroup.append(declarationToAdd) } diff --git a/Sources/Rules/PreferForLoop.swift b/Sources/Rules/PreferForLoop.swift index 02339cce9..4f0b9cb20 100644 --- a/Sources/Rules/PreferForLoop.swift +++ b/Sources/Rules/PreferForLoop.swift @@ -82,7 +82,7 @@ public extension FormatRule { // Abort early for single-line loops guard !formatter.options.preserveSingleLineForEach || formatter - .tokens[closureOpenBraceIndex ..< closureCloseBraceIndex].contains(where: { $0.isLinebreak }) + .tokens[closureOpenBraceIndex ..< closureCloseBraceIndex].contains(where: \.isLinebreak) else { return } // Ignore closures with capture lists for now since they're rare @@ -224,7 +224,7 @@ public extension FormatRule { } } - if let forEachCallCloseParenIndex = forEachCallCloseParenIndex { + if let forEachCallCloseParenIndex { formatter.removeToken(at: forEachCallCloseParenIndex) } diff --git a/Sources/Rules/PreferKeyPath.swift b/Sources/Rules/PreferKeyPath.swift index 3405bf63a..349ce12d9 100644 --- a/Sources/Rules/PreferKeyPath.swift +++ b/Sources/Rules/PreferKeyPath.swift @@ -76,7 +76,7 @@ public extension FormatRule { } replacementTokens = [.operator("\\", .prefix)] + tokens } - if let label = label { + if let label { replacementTokens = [.identifier(label), .delimiter(":"), .space(" ")] + replacementTokens } if !parenthesized { diff --git a/Sources/Rules/RedundantClosure.swift b/Sources/Rules/RedundantClosure.swift index 797900fad..dce97be0d 100644 --- a/Sources/Rules/RedundantClosure.swift +++ b/Sources/Rules/RedundantClosure.swift @@ -116,7 +116,7 @@ public extension FormatRule { var startOfScopeContainingClosure = formatter.startOfScope(at: startIndex) var assignmentBeforeClosure = formatter.index(of: .operator("=", .infix), before: startIndex) - if let assignmentBeforeClosure = assignmentBeforeClosure, formatter.isConditionalStatement(at: assignmentBeforeClosure) { + if let assignmentBeforeClosure, formatter.isConditionalStatement(at: assignmentBeforeClosure) { // Not valid to use conditional expression directly in condition body return } @@ -134,7 +134,7 @@ public extension FormatRule { potentialStartOfExpressionContainingClosure = max(startOfScope, assignmentBeforeClosure) } - if let potentialStartOfExpressionContainingClosure = potentialStartOfExpressionContainingClosure { + if let potentialStartOfExpressionContainingClosure { guard var startOfExpressionIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: potentialStartOfExpressionContainingClosure) else { return } diff --git a/Sources/Rules/RedundantNilInit.swift b/Sources/Rules/RedundantNilInit.swift index 63dec0e84..e0e7332f7 100644 --- a/Sources/Rules/RedundantNilInit.swift +++ b/Sources/Rules/RedundantNilInit.swift @@ -85,7 +85,7 @@ extension Formatter { }) switch options.nilInit { case .remove: - if let equalsIndex = equalsIndex, let nilIndex = self.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { + if let equalsIndex, let nilIndex = self.index(of: .nonSpaceOrLinebreak, after: equalsIndex, if: { $0 == .identifier("nil") }) { removeTokens(in: optionalIndex + 1 ... nilIndex) diff --git a/Sources/Rules/RedundantParens.swift b/Sources/Rules/RedundantParens.swift index bc4573c39..66f879ae1 100644 --- a/Sources/Rules/RedundantParens.swift +++ b/Sources/Rules/RedundantParens.swift @@ -111,7 +111,7 @@ public extension FormatRule { case let .keyword(name) where !conditionals.contains(name) && !["let", "var", "return"].contains(name): return case .endOfScope("}"), .endOfScope(")"), .endOfScope("]"), .endOfScope(">"): - if formatter.tokens[previousIndex + 1 ..< i].contains(where: { $0.isLinebreak }) { + if formatter.tokens[previousIndex + 1 ..< i].contains(where: \.isLinebreak) { fallthrough } return // Probably a method invocation diff --git a/Sources/Rules/RedundantPattern.swift b/Sources/Rules/RedundantPattern.swift index 1f9af587e..e4abca836 100644 --- a/Sources/Rules/RedundantPattern.swift +++ b/Sources/Rules/RedundantPattern.swift @@ -26,7 +26,7 @@ public extension FormatRule { ) { formatter in formatter.forEach(.startOfScope("(")) { i, _ in let prevIndex = formatter.index(of: .nonSpaceOrComment, before: i) - if let prevIndex = prevIndex, let prevToken = formatter.token(at: prevIndex), + if let prevIndex, let prevToken = formatter.token(at: prevIndex), [.keyword("case"), .endOfScope("case")].contains(prevToken) { // Not safe to remove @@ -40,7 +40,7 @@ public extension FormatRule { return } formatter.removeTokens(in: i ... endIndex) - if let prevIndex = prevIndex, formatter.tokens[prevIndex].isIdentifier, + if let prevIndex, formatter.tokens[prevIndex].isIdentifier, formatter.last(.nonSpaceOrComment, before: prevIndex)?.string == "." { if let endOfScopeIndex = formatter.index( @@ -60,7 +60,7 @@ public extension FormatRule { // Was an assignment formatter.insert(.identifier("_"), at: i) - if formatter.token(at: i - 1).map({ $0.isSpaceOrLinebreak }) != true { + if formatter.token(at: i - 1).map(\.isSpaceOrLinebreak) != true { formatter.insert(.space(" "), at: i) } } diff --git a/Sources/Rules/RedundantReturn.swift b/Sources/Rules/RedundantReturn.swift index 885605569..45ce1d377 100644 --- a/Sources/Rules/RedundantReturn.swift +++ b/Sources/Rules/RedundantReturn.swift @@ -190,7 +190,7 @@ extension Formatter { // If this scope is a single-statement if or switch statement then we have to recursively // remove the return from each branch of the if statement - let startOfBody = self.startOfBody(atStartOfScope: startOfScopeIndex) + let startOfBody = startOfBody(atStartOfScope: startOfScopeIndex) if let firstTokenInBody = index(of: .nonSpaceOrCommentOrLinebreak, after: startOfBody), let conditionalBranches = conditionalBranches(at: firstTokenInBody) diff --git a/Sources/Rules/RedundantType.swift b/Sources/Rules/RedundantType.swift index 5d2273ba2..656cc9a23 100644 --- a/Sources/Rules/RedundantType.swift +++ b/Sources/Rules/RedundantType.swift @@ -93,7 +93,7 @@ public extension FormatRule { // Explicit type can't be safely removed from @Model classes // https://github.com/nicklockwood/SwiftFormat/issues/1649 if !isInferred, - let declarationKeywordIndex = declarationKeywordIndex, + let declarationKeywordIndex, formatter.modifiersForDeclaration(at: declarationKeywordIndex, contains: "@Model") { return diff --git a/Sources/Rules/RedundantTypedThrows.swift b/Sources/Rules/RedundantTypedThrows.swift index 4f9d8fbb1..3c1f2639a 100644 --- a/Sources/Rules/RedundantTypedThrows.swift +++ b/Sources/Rules/RedundantTypedThrows.swift @@ -36,7 +36,7 @@ public extension FormatRule { else { return } let throwsTypeRange = (startOfScope + 1) ..< endOfScope - let throwsType: String = formatter.tokens[throwsTypeRange].map { $0.string }.joined() + let throwsType: String = formatter.tokens[throwsTypeRange].map(\.string).joined() if throwsType == "Never" { if formatter.tokens[endOfScope + 1].isSpace { diff --git a/Sources/Rules/SortDeclarations.swift b/Sources/Rules/SortDeclarations.swift index 94c320b1b..22b3e6a70 100644 --- a/Sources/Rules/SortDeclarations.swift +++ b/Sources/Rules/SortDeclarations.swift @@ -159,7 +159,7 @@ public extension FormatRule { } }) - .map { $0.element } + .map(\.element) // Make sure there's at least one newline between each declaration for i in 0 ..< max(0, declarations.count - 1) { @@ -177,7 +177,7 @@ public extension FormatRule { } } - var sortedFormatter = Formatter(declarations.flatMap { $0.tokens }) + var sortedFormatter = Formatter(declarations.flatMap(\.tokens)) // Make sure the type has the same number of leading line breaks // as it did before sorting diff --git a/Sources/Rules/SortTypealiases.swift b/Sources/Rules/SortTypealiases.swift index 1e0a530b1..b0a1b82b4 100644 --- a/Sources/Rules/SortTypealiases.swift +++ b/Sources/Rules/SortTypealiases.swift @@ -69,7 +69,7 @@ public extension FormatRule { let tokens = Array(formatter.tokens[elementStartIndex ... elementEndIndex]) let typeName = tokens .filter { !$0.isSpaceOrCommentOrLinebreak && !$0.isOperator } - .map { $0.string }.joined() + .map(\.string).joined() // While we're here, also filter out any duplicates. // Since we're sorting, duplicates would sit right next to each other diff --git a/Sources/Rules/TypeSugar.swift b/Sources/Rules/TypeSugar.swift index 90d84cc88..bb1f3a191 100644 --- a/Sources/Rules/TypeSugar.swift +++ b/Sources/Rules/TypeSugar.swift @@ -42,7 +42,7 @@ public extension FormatRule { let dotIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex, if: { $0.isOperator(".") }) - if let dotIndex = dotIndex, formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: dotIndex, if: { + if let dotIndex, formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: dotIndex, if: { ![.identifier("self"), .identifier("Type")].contains($0) }) != nil, identifier != "Optional" { return diff --git a/Sources/Rules/UnusedArguments.swift b/Sources/Rules/UnusedArguments.swift index a33b0052a..28756b323 100644 --- a/Sources/Rules/UnusedArguments.swift +++ b/Sources/Rules/UnusedArguments.swift @@ -205,8 +205,8 @@ public extension FormatRule { } extension Formatter { - func removeUsed(from argNames: inout [String], with associatedData: inout [T], - locals: Set = [], in range: CountableRange) + func removeUsed(from argNames: inout [String], with associatedData: inout [some Any], + locals: Set = [], in range: CountableRange) { var isDeclaration = false var wasDeclaration = false diff --git a/Sources/Rules/WrapAttributes.swift b/Sources/Rules/WrapAttributes.swift index ee8004c86..4ad914af5 100644 --- a/Sources/Rules/WrapAttributes.swift +++ b/Sources/Rules/WrapAttributes.swift @@ -124,7 +124,7 @@ public extension FormatRule { case .sameLine: // Make sure there isn't a newline immediately following the attribute if let nextIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: endIndex), - formatter.tokens[(endIndex + 1) ..< nextIndex].contains(where: { $0.isLinebreak }) + formatter.tokens[(endIndex + 1) ..< nextIndex].contains(where: \.isLinebreak) { // If unwrapping the attribute causes the line to exceed the max width, // leave it as-is. The existing formatting is likely better than how @@ -133,8 +133,8 @@ public extension FormatRule { let endOfLine = formatter.endOfLine(at: i) let startOfNextLine = formatter.startOfLine(at: nextIndex, excludingIndent: true) let endOfNextLine = formatter.endOfLine(at: nextIndex) - let combinedLine = formatter.tokens[startOfLine ... endOfLine].map { $0.string }.joined() - + formatter.tokens[startOfNextLine ..< endOfNextLine].map { $0.string }.joined() + let combinedLine = formatter.tokens[startOfLine ... endOfLine].map(\.string).joined() + + formatter.tokens[startOfNextLine ..< endOfNextLine].map(\.string).joined() if formatter.options.maxWidth > 0, combinedLine.count > formatter.options.maxWidth { return diff --git a/Sources/Rules/WrapEnumCases.swift b/Sources/Rules/WrapEnumCases.swift index 354dbda30..2767f4a0e 100644 --- a/Sources/Rules/WrapEnumCases.swift +++ b/Sources/Rules/WrapEnumCases.swift @@ -113,7 +113,7 @@ extension Formatter { func shouldWrapCaseRangeGroup(_ caseRangeGroup: [Formatter.EnumCaseRange]) -> Bool { guard let firstIndex = caseRangeGroup.first?.value.lowerBound, let scopeStart = startOfScope(at: firstIndex), - tokens[scopeStart ..< firstIndex].contains(where: { $0.isLinebreak }) + tokens[scopeStart ..< firstIndex].contains(where: \.isLinebreak) else { // Don't wrap if first case is on same line as opening `{` return false diff --git a/Sources/Rules/YodaConditions.swift b/Sources/Rules/YodaConditions.swift index 10629e5e3..e66552415 100644 --- a/Sources/Rules/YodaConditions.swift +++ b/Sources/Rules/YodaConditions.swift @@ -41,7 +41,7 @@ public extension FormatRule { extension Formatter { func valuesInRangeAreConstant(_ range: CountableRange) -> Bool { - var index = self.index(of: .nonSpaceOrCommentOrLinebreak, in: range) + var index = index(of: .nonSpaceOrCommentOrLinebreak, in: range) while var i = index { switch tokens[i] { case .startOfScope where isConstant(at: i): @@ -109,7 +109,7 @@ extension Formatter { } func isOperator(at index: Int?) -> Bool { - guard let index = index else { + guard let index else { return false } switch tokens[index] { diff --git a/Sources/SwiftFormat.swift b/Sources/SwiftFormat.swift index bd4401b75..474d78a82 100644 --- a/Sources/SwiftFormat.swift +++ b/Sources/SwiftFormat.swift @@ -446,7 +446,7 @@ public func parsingError(for tokens: [Token], options: FormatOptions) -> FormatE /// Convert a token array back into a string public func sourceCode(for tokens: [Token]?) -> String { - (tokens ?? []).map { $0.string }.joined() + (tokens ?? []).map(\.string).joined() } /// Apply specified rules to a token array and optionally capture list of changes @@ -489,12 +489,12 @@ private func applyRules( // Infer shared options var options = options - options.enabledRules = Set(rules.map { $0.name }) + options.enabledRules = Set(rules.map(\.name)) let sharedOptions = FormatRules .sharedOptionsForRules(rules) .compactMap { Descriptors.byName[$0] } .filter { $0.defaultArgument == $0.fromOptions(options) } - .map { $0.propertyName } + .map(\.propertyName) inferFormatOptions(sharedOptions, from: tokens, into: &options) @@ -588,7 +588,7 @@ private func applyRules( } let formatter = Formatter(tokens, options: options, trackChanges: true, range: range) rules.sorted().forEach { $0.apply(with: formatter) } - let rulesApplied = Set(formatter.changes.map { $0.rule.name }).sorted() + let rulesApplied = Set(formatter.changes.map(\.rule.name)).sorted() if rulesApplied.isEmpty { throw FormatError.writing("Failed to terminate") } diff --git a/Sources/Tokenizer.swift b/Sources/Tokenizer.swift index 828f8cb77..895499b5e 100644 --- a/Sources/Tokenizer.swift +++ b/Sources/Tokenizer.swift @@ -555,9 +555,9 @@ extension Token { } } -extension Collection where Element == Token { +extension Collection { var string: String { - map { $0.string }.joined() + map(\.string).joined() } } @@ -712,7 +712,7 @@ private extension UnicodeScalarView { mutating func read(head: (UnicodeScalar) -> Bool, tail: (UnicodeScalar) -> Bool) -> String? { if let c = first, head(c) { - var index = self.index(after: startIndex) + var index = index(after: startIndex) while index < endIndex { if !tail(self[index]) { break @@ -1907,7 +1907,7 @@ public func tokenize(_ source: String) -> [Token] { token = tokens[count - 1] switch token { case .startOfScope("/"): - if characters.first.map({ $0.isSpaceOrLinebreak }) ?? true { + if characters.first.map(\.isSpaceOrLinebreak) ?? true { // Misidentified as regex token = .operator("/", .none) tokens[count - 1] = token diff --git a/Tests/ArgumentsTests.swift b/Tests/ArgumentsTests.swift index a13bb54ab..bc25589c8 100644 --- a/Tests/ArgumentsTests.swift +++ b/Tests/ArgumentsTests.swift @@ -690,13 +690,13 @@ class ArgumentsTests: XCTestCase { func testParseExcludedURLsFileOption() throws { let options = try Options(["exclude": "foo bar, baz"], in: "/dir") - let paths = options.fileOptions?.excludedGlobs.map { $0.description } ?? [] + let paths = options.fileOptions?.excludedGlobs.map(\.description) ?? [] XCTAssertEqual(paths, ["/dir/foo bar", "/dir/baz"]) } func testParseUnexcludedURLsFileOption() throws { let options = try Options(["unexclude": "foo bar, baz"], in: "/dir") - let paths = options.fileOptions?.unexcludedGlobs.map { $0.description } ?? [] + let paths = options.fileOptions?.unexcludedGlobs.map(\.description) ?? [] XCTAssertEqual(paths, ["/dir/foo bar", "/dir/baz"]) } diff --git a/Tests/CodeOrganizationTests.swift b/Tests/CodeOrganizationTests.swift index 13e399243..b349951db 100644 --- a/Tests/CodeOrganizationTests.swift +++ b/Tests/CodeOrganizationTests.swift @@ -116,7 +116,7 @@ class CodeOrganizationTests: XCTestCase { let fullHelperName: String if let argumentLabels = matchingHelper.funcArgLabels { let argumentLabelStrings = argumentLabels.map { label -> String in - if let label = label { + if let label { return label + ":" } else { return "_:" diff --git a/Tests/FormatterTests.swift b/Tests/FormatterTests.swift index 925e6d64a..4e5f4e1b2 100644 --- a/Tests/FormatterTests.swift +++ b/Tests/FormatterTests.swift @@ -559,7 +559,7 @@ class FormatterTests: XCTestCase { XCTAssertEqual(sourceCode(for: formatter.tokens), sourceCode(for: output)) // The changes should include both moves and non-moves - XCTAssert(formatter.changes.contains(where: { $0.isMove })) + XCTAssert(formatter.changes.contains(where: \.isMove)) XCTAssert(formatter.changes.contains(where: { !$0.isMove })) } } diff --git a/Tests/MetadataTests.swift b/Tests/MetadataTests.swift index 3a63e630d..81d371a63 100644 --- a/Tests/MetadataTests.swift +++ b/Tests/MetadataTests.swift @@ -49,7 +49,7 @@ class MetadataTests: XCTestCase { result += "\n* [\(rule.name)](#\(rule.name))" } - let deprecatedRules = FormatRules.all.filter { $0.isDeprecated } + let deprecatedRules = FormatRules.all.filter(\.isDeprecated) if !deprecatedRules.isEmpty { result += "\n\n# Deprecated Rules (do not use)\n" for rule in deprecatedRules { @@ -329,7 +329,7 @@ class MetadataTests: XCTestCase { let dateRange = try XCTUnwrap(title.range(of: " \\([^)]+\\)$", options: .regularExpression)) let dateString = String(title[dateRange]) let date = try XCTUnwrap(dateParser.date(from: dateString)) - if let lastDate = lastDate, date > lastDate { + if let lastDate, date > lastDate { XCTFail("\(title) has newer date than subsequent version (\(date) vs \(lastDate))") return } diff --git a/Tests/OptionDescriptorTests.swift b/Tests/OptionDescriptorTests.swift index 595d74617..a5a1b7c55 100644 --- a/Tests/OptionDescriptorTests.swift +++ b/Tests/OptionDescriptorTests.swift @@ -94,7 +94,7 @@ class OptionDescriptorTests: XCTestCase { XCTAssertEqual(descriptor.fromOptions(options), item.argumentValue, "\(testName): Option is transformed to argument") } - if let invalid = invalid { + if let invalid { options[keyPath: keyPath] = invalid XCTAssertEqual(descriptor.fromOptions(options), descriptor.defaultArgument, "\(testName): invalid input return the default value") } @@ -150,7 +150,7 @@ class OptionDescriptorTests: XCTestCase { } func testAllPropertiesHaveDescriptor() { - let allDescriptors = Set(Descriptors.all.map { $0.propertyName }) + let allDescriptors = Set(Descriptors.all.map(\.propertyName)) for property in FormatOptions.default.allOptions.keys { XCTAssert( allDescriptors.contains(property), diff --git a/Tests/ParsingHelpersTests.swift b/Tests/ParsingHelpersTests.swift index 7ffcd066f..9b292ace0 100644 --- a/Tests/ParsingHelpersTests.swift +++ b/Tests/ParsingHelpersTests.swift @@ -1160,7 +1160,7 @@ class ParsingHelpersTests: XCTestCase { let declarations = Formatter(originalTokens).parseDeclarations() // Verify we didn't lose any tokens - XCTAssertEqual(originalTokens, declarations.flatMap { $0.tokens }) + XCTAssertEqual(originalTokens, declarations.flatMap(\.tokens)) XCTAssertEqual( sourceCode(for: declarations[0].tokens), @@ -2328,7 +2328,7 @@ class ParsingHelpersTests: XCTestCase { var parseIndex = 0 while let expressionRange = formatter.parseExpressionRange(startingAt: parseIndex) { - let expression = formatter.tokens[expressionRange].map { $0.string }.joined() + let expression = formatter.tokens[expressionRange].map(\.string).joined() expressions.append(expression) if let nextExpressionIndex = formatter.index(of: .nonSpaceOrCommentOrLinebreak, after: expressionRange.upperBound) { @@ -2344,7 +2344,7 @@ class ParsingHelpersTests: XCTestCase { func parseExpression(in input: String, at index: Int) -> String { let formatter = Formatter(tokenize(input)) guard let expressionRange = formatter.parseExpressionRange(startingAt: index) else { return "" } - return formatter.tokens[expressionRange].map { $0.string }.joined() + return formatter.tokens[expressionRange].map(\.string).joined() } // MARK: isStoredProperty diff --git a/Tests/XCTestCase+testFormatting.swift b/Tests/XCTestCase+testFormatting.swift index ce9f89e43..e72f06f99 100644 --- a/Tests/XCTestCase+testFormatting.swift +++ b/Tests/XCTestCase+testFormatting.swift @@ -59,7 +59,7 @@ extension XCTestCase { } // The `name` property on individual rules is not populated until the first call into `rulesByName`, // so we have to make sure to trigger this before checking the names of the given rules. - if rules.contains(where: { $0.name.isEmpty }) { + if rules.contains(where: \.name.isEmpty) { _ = FormatRules.all } From dbd22c86319f98731f803e25305efda9ca1e1e1d Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Sun, 25 Aug 2024 19:00:20 -0700 Subject: [PATCH 52/52] We only need one Package.swift now --- Package.swift | 29 +++++++++++++++++++++++++---- Package@swift-5.6.swift | 36 ------------------------------------ 2 files changed, 25 insertions(+), 40 deletions(-) delete mode 100644 Package@swift-5.6.swift diff --git a/Package.swift b/Package.swift index e3c2a3bdd..05f6dd0a8 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.2 +// swift-tools-version:5.6 import PackageDescription let package = Package( @@ -6,10 +6,31 @@ let package = Package( products: [ .executable(name: "swiftformat", targets: ["CommandLineTool"]), .library(name: "SwiftFormat", targets: ["SwiftFormat"]), + .plugin(name: "SwiftFormatPlugin", targets: ["SwiftFormatPlugin"]), ], targets: [ - .target(name: "CommandLineTool", dependencies: ["SwiftFormat"], path: "CommandLineTool"), - .target(name: "SwiftFormat", path: "Sources"), - .testTarget(name: "SwiftFormatTests", dependencies: ["SwiftFormat"], path: "Tests"), + .executableTarget( + name: "CommandLineTool", dependencies: ["SwiftFormat"], path: "CommandLineTool", + exclude: ["swiftformat"] + ), + .target(name: "SwiftFormat", path: "Sources", exclude: ["Info.plist"]), + .testTarget( + name: "SwiftFormatTests", + dependencies: ["SwiftFormat"], + path: "Tests", + exclude: ["Info.plist", "GlobTest[5].txt"] + ), + .plugin( + name: "SwiftFormatPlugin", + capability: .command( + intent: .custom( + verb: "swiftformat", description: "Formats Swift source files using SwiftFormat" + ), + permissions: [ + .writeToPackageDirectory(reason: "This command reformats source files"), + ] + ), + dependencies: [.target(name: "CommandLineTool")] + ), ] ) diff --git a/Package@swift-5.6.swift b/Package@swift-5.6.swift deleted file mode 100644 index 05f6dd0a8..000000000 --- a/Package@swift-5.6.swift +++ /dev/null @@ -1,36 +0,0 @@ -// swift-tools-version:5.6 -import PackageDescription - -let package = Package( - name: "SwiftFormat", - products: [ - .executable(name: "swiftformat", targets: ["CommandLineTool"]), - .library(name: "SwiftFormat", targets: ["SwiftFormat"]), - .plugin(name: "SwiftFormatPlugin", targets: ["SwiftFormatPlugin"]), - ], - targets: [ - .executableTarget( - name: "CommandLineTool", dependencies: ["SwiftFormat"], path: "CommandLineTool", - exclude: ["swiftformat"] - ), - .target(name: "SwiftFormat", path: "Sources", exclude: ["Info.plist"]), - .testTarget( - name: "SwiftFormatTests", - dependencies: ["SwiftFormat"], - path: "Tests", - exclude: ["Info.plist", "GlobTest[5].txt"] - ), - .plugin( - name: "SwiftFormatPlugin", - capability: .command( - intent: .custom( - verb: "swiftformat", description: "Formats Swift source files using SwiftFormat" - ), - permissions: [ - .writeToPackageDirectory(reason: "This command reformats source files"), - ] - ), - dependencies: [.target(name: "CommandLineTool")] - ), - ] -)