From 0e6282849ce15363bf5087f3adfbfbeff99fe787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20Prud=E2=80=99homme?= Date: Fri, 18 Jul 2025 15:50:41 +0200 Subject: [PATCH] Add `--blank-line-after-switch-case always` option to `blankLineAfterSwitchCase` rule (#2150) --- Rules.md | 60 ++++++++++++++++- Sources/OptionDescriptor.swift | 6 ++ Sources/Options.swift | 11 +++- Sources/Rules/BlankLineAfterSwitchCase.swift | 65 +++++++++++++++++-- .../Rules/BlankLineAfterSwitchCaseTests.swift | 33 ++++++++++ 5 files changed, 169 insertions(+), 6 deletions(-) diff --git a/Rules.md b/Rules.md index c2c77b96..d775d593 100644 --- a/Rules.md +++ b/Rules.md @@ -277,12 +277,18 @@ Insert blank line after import statements. ## blankLineAfterSwitchCase -Insert a blank line after multiline switch cases (excluding the last case, +Insert a blank line after switch cases (excluding the last case, which is followed by a closing brace). +Option | Description +--- | --- +`--blank-line-after-switch-case` | Insert line After switch cases: "always" or "multiline-only" (default) +
Examples +`--blank-line-after-switch-case multiline-only` (default) + ```diff func handle(_ action: SpaceshipAction) { switch action { @@ -304,6 +310,58 @@ which is followed by a closing brace). } ``` +```diff + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + warpDrive.activate() + + case let .scanPlanet(planet): + scanner.scanForArticialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + } +``` +`--blank-line-after-switch-case always` + +```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() + } + } +``` + +```diff + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + warpDrive.activate() ++ + case let .scanPlanet(planet): + scanner.scanForArticialLife() ++ + case .handleIncomingEnergyBlast: + energyShields.engage() + } + } +``` +

diff --git a/Sources/OptionDescriptor.swift b/Sources/OptionDescriptor.swift index 3e8ce989..6d2f92ce 100644 --- a/Sources/OptionDescriptor.swift +++ b/Sources/OptionDescriptor.swift @@ -1347,6 +1347,12 @@ struct _Descriptors { trueValues: ["true"], falseValues: ["false"] ) + let blankLineAfterSwitchCase = OptionDescriptor( + argumentName: "blank-line-after-switch-case", + displayName: "Blank Line After Switch Cases", + help: "Insert line After switch cases: \"always\" or \"multiline-only\" (default)", + keyPath: \.blankLineAfterSwitchCase + ) // MARK: - Internal diff --git a/Sources/Options.swift b/Sources/Options.swift index fe0a9d71..6002a327 100644 --- a/Sources/Options.swift +++ b/Sources/Options.swift @@ -647,6 +647,13 @@ public enum EquatableMacro: Equatable, RawRepresentable, CustomStringConvertible } } +public enum BlankLineAfterSwitchCase: String, CaseIterable { + /// Add blank lines after multiline switch cases only + case multilineOnly = "multiline-only" + /// Always add blank lines after switch cases + case always +} + public enum URLMacro: Equatable, RawRepresentable, CustomStringConvertible { /// No URL macro case none @@ -795,6 +802,7 @@ public struct FormatOptions: CustomStringConvertible { public var urlMacro: URLMacro public var preferFileMacro: Bool public var lineBetweenConsecutiveGuards: Bool + public var blankLineAfterSwitchCase: BlankLineAfterSwitchCase /// Deprecated public var indentComments: Bool @@ -928,6 +936,7 @@ public struct FormatOptions: CustomStringConvertible { urlMacro: URLMacro = .none, preferFileMacro: Bool = true, lineBetweenConsecutiveGuards: Bool = false, + blankLineAfterSwitchCase: BlankLineAfterSwitchCase = .multilineOnly, // Doesn't really belong here, but hard to put elsewhere fragment: Bool = false, ignoreConflictMarkers: Bool = false, @@ -1050,7 +1059,7 @@ public struct FormatOptions: CustomStringConvertible { self.urlMacro = urlMacro self.preferFileMacro = preferFileMacro self.lineBetweenConsecutiveGuards = lineBetweenConsecutiveGuards - // Doesn't really belong here, but hard to put elsewhere + self.blankLineAfterSwitchCase = blankLineAfterSwitchCase self.indentComments = indentComments self.fragment = fragment self.ignoreConflictMarkers = ignoreConflictMarkers diff --git a/Sources/Rules/BlankLineAfterSwitchCase.swift b/Sources/Rules/BlankLineAfterSwitchCase.swift index 78a34ed7..908f6605 100644 --- a/Sources/Rules/BlankLineAfterSwitchCase.swift +++ b/Sources/Rules/BlankLineAfterSwitchCase.swift @@ -11,19 +11,22 @@ import Foundation public extension FormatRule { static let blankLineAfterSwitchCase = FormatRule( help: """ - Insert a blank line after multiline switch cases (excluding the last case, + Insert a blank line after switch cases (excluding the last case, which is followed by a closing brace). """, disabledByDefault: true, - orderAfter: [.redundantBreak] + orderAfter: [.redundantBreak], + options: ["blank-line-after-switch-case"] ) { formatter in formatter.forEach(.keyword("switch")) { switchIndex, _ in guard let switchCases = formatter.switchStatementBranchesWithSpacingInfo(at: switchIndex) else { return } + let shouldAlwaysInsertBlankLineAfterSwitchCase = formatter.options.blankLineAfterSwitchCase == .always for switchCase in switchCases.reversed() { - // Any switch statement that spans multiple lines should be followed by a blank line + // Any switch statement should be followed by a blank line, depending on the + // `blankLineAfterSwitchCase` option. // (excluding the last case, which is followed by a closing brace). - if switchCase.spansMultipleLines, + if shouldAlwaysInsertBlankLineAfterSwitchCase || switchCase.spansMultipleLines, !switchCase.isLastCase, !switchCase.isFollowedByBlankLine { @@ -41,6 +44,8 @@ public extension FormatRule { } } examples: { #""" + `--blank-line-after-switch-case multiline-only` (default) + ```diff func handle(_ action: SpaceshipAction) { switch action { @@ -61,6 +66,58 @@ public extension FormatRule { } } ``` + + ```diff + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + warpDrive.activate() + + case let .scanPlanet(planet): + scanner.scanForArticialLife() + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + } + ``` + `--blank-line-after-switch-case always` + + ```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() + } + } + ``` + + ```diff + func handle(_ action: SpaceshipAction) { + switch action { + case .engageWarpDrive: + warpDrive.activate() + + + case let .scanPlanet(planet): + scanner.scanForArticialLife() + + + case .handleIncomingEnergyBlast: + energyShields.engage() + } + } + ``` """# } } diff --git a/Tests/Rules/BlankLineAfterSwitchCaseTests.swift b/Tests/Rules/BlankLineAfterSwitchCaseTests.swift index 4d851051..b9be130f 100644 --- a/Tests/Rules/BlankLineAfterSwitchCaseTests.swift +++ b/Tests/Rules/BlankLineAfterSwitchCaseTests.swift @@ -46,6 +46,39 @@ class BlankLineAfterSwitchCaseTests: XCTestCase { testFormatting(for: input, output, rule: .blankLineAfterSwitchCase) } + func testAddsBlankLineAfterSingleSwitchCasesWhenBlankLineAroundSingleLineCases() { + let input = """ + func handle(_ action: SpaceshipAction) { + switch action { + // The warp drive can be engaged by pressing a button on the control panel + case .engageWarpDrive: + warpDrive.activate() + // Triggered automatically whenever we detect an energy blast was fired in our direction + case .handleIncomingEnergyBlast: + 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: + warpDrive.activate() + + // Triggered automatically whenever we detect an energy blast was fired in our direction + case .handleIncomingEnergyBlast: + energyShields.engage() + } + } + """ + testFormatting(for: input, + output, + rule: .blankLineAfterSwitchCase, + options: FormatOptions(blankLineAfterSwitchCase: .always)) + } + func testRemovesBlankLineAfterLastSwitchCase() { let input = """ func handle(_ action: SpaceshipAction) {