Files
2025-12-07 08:11:08 -05:00

272 lines
9.8 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftSyntax
@SwiftSyntaxRule
struct SwitchCaseAlignmentRule: Rule {
var configuration = SwitchCaseAlignmentConfiguration()
static let description = RuleDescription(
identifier: "switch_case_alignment",
name: "Switch and Case Statement Alignment",
description: """
Case statements should vertically align with their closing brace, or indented if configured \
otherwise.
""",
kind: .style,
nonTriggeringExamples: Examples(indentedCases: false).nonTriggeringExamples + [
Example("""
extension OSLogFloatFormatting {
/// Returns a fprintf-compatible length modifier for a given argument type
@_semantics("constant_evaluable")
@inlinable
@_optimize(none)
internal static func _formatStringLengthModifier<I: FloatingPoint>(
_ type: I.Type
) -> String? {
switch type {
// fprintf formatters promote Float to Double
case is Float.Type: return ""
case is Double.Type: return ""
#if !os(Windows) && (arch(i386) || arch(x86_64))
// fprintf formatters use L for Float80
case is Float80.Type: return "L"
#endif
default: return nil
}
}
}
""", excludeFromDocumentation: true),
],
triggeringExamples: Examples(indentedCases: false).triggeringExamples
)
}
extension SwitchCaseAlignmentRule {
final class Visitor: ViolationsSyntaxVisitor<ConfigurationType> {
override func visitPost(_ node: SwitchExprSyntax) {
guard node.cases.isNotEmpty,
let firstCasePosition = node.cases.first?.positionAfterSkippingLeadingTrivia
else {
return
}
let closingBracePosition = node.rightBrace.positionAfterSkippingLeadingTrivia
let closingBraceLocation = locationConverter.location(for: closingBracePosition)
let switchKeywordPosition = node.switchKeyword.positionAfterSkippingLeadingTrivia
let switchKeywordLocation = locationConverter.location(for: switchKeywordPosition)
if configuration.ignoreOneLiners, switchKeywordLocation.line == closingBraceLocation.line {
return
}
let closingBraceColumn = closingBraceLocation.column
let firstCaseColumn = locationConverter.location(for: firstCasePosition).column
for `case` in node.cases where `case`.is(SwitchCaseSyntax.self) {
let casePosition = `case`.positionAfterSkippingLeadingTrivia
let caseColumn = locationConverter.location(for: casePosition).column
let hasViolation = (configuration.indentedCases && caseColumn <= closingBraceColumn) ||
(!configuration.indentedCases && caseColumn != closingBraceColumn) ||
(configuration.indentedCases && caseColumn != firstCaseColumn)
guard hasViolation else {
continue
}
let reason = """
Case statements should \
\(configuration.indentedCases ? "be indented within" : "vertically aligned with") \
their closing brace
"""
violations.append(ReasonedRuleViolation(position: casePosition, reason: reason))
}
}
}
struct Examples {
private let indentedCasesOption: Bool
private let violationMarker = ""
init(indentedCases: Bool) {
self.indentedCasesOption = indentedCases
}
var triggeringExamples: [Example] {
(indentedCasesOption ? nonIndentedCases : indentedCases)
+ invalidCases
+ invalidOneLiners
}
var nonTriggeringExamples: [Example] {
indentedCasesOption ? indentedCases : nonIndentedCases + validOneLiners
}
private var indentedCases: [Example] {
let violationMarker = indentedCasesOption ? "" : violationMarker
return [
Example("""
switch someBool {
\(violationMarker)case true:
print("red")
\(violationMarker)case false:
print("blue")
}
"""),
Example("""
if aBool {
switch someBool {
\(violationMarker)case true:
print('red')
\(violationMarker)case false:
print('blue')
}
}
"""),
Example("""
switch someInt {
\(violationMarker)case 0:
print('Zero')
\(violationMarker)case 1:
print('One')
\(violationMarker)default:
print('Some other number')
}
"""),
Example("""
let a = switch i {
\(violationMarker)case 1: 1
\(violationMarker)default: 2
}
"""),
]
}
private var nonIndentedCases: [Example] {
let violationMarker = indentedCasesOption ? violationMarker : ""
return [
Example("""
switch someBool {
\(violationMarker)case true: // case 1
print('red')
\(violationMarker)case false:
/*
case 2
*/
if case let .someEnum(val) = someFunc() {
print('blue')
}
}
enum SomeEnum {
case innocent
}
"""),
Example("""
if aBool {
switch someBool {
\(violationMarker)case true:
print('red')
\(violationMarker)case false:
print('blue')
}
}
"""),
Example("""
switch someInt {
// comments ignored
\(violationMarker)case 0:
// zero case
print('Zero')
\(violationMarker)case 1:
print('One')
\(violationMarker)default:
print('Some other number')
}
"""),
Example("""
func f() -> Int {
return switch i {
\(violationMarker)case 1: 1
\(violationMarker)default: 2
}
}
"""),
]
}
private var invalidCases: [Example] {
let indentation = indentedCasesOption ? " " : ""
return [
Example("""
switch someBool {
\(indentation)case true:
\(indentation)print('red')
\(indentation)\(violationMarker)case false:
\(indentation)print('blue')
}
"""),
Example("""
if aBool {
switch someBool {
\(indentation)\(indentedCasesOption ? "" : violationMarker)case true:
\(indentation)print('red')
\(indentation)\(indentedCasesOption ? violationMarker : "")case false:
\(indentation)print('blue')
}
}
"""),
Example("""
let a = switch i {
\(indentation)case 1: 1
\(indentation)\(indentedCasesOption ? "" : violationMarker)default: 2
}
"""),
]
}
private var validOneLiners: [Example] = [
Example(
"switch i { case .x: 1 default: 0 }",
configuration: ["ignore_one_liners": true]
),
Example(
"let a = switch i { case .x: 1 default: 0 }",
configuration: ["ignore_one_liners": true]
),
]
private var invalidOneLiners: [Example] {
[
// Default configuration should not ignore one liners
Example(
"switch i { \(violationMarker)case .x: 1 \(violationMarker)default: 0 }"
),
Example("""
switch i {
\(violationMarker)case .x: 1 \(violationMarker)default: 0 }
""", configuration: ["ignore_one_liners": true]),
Example("""
switch i { \(violationMarker)case .x: 1 \(violationMarker)default: 0
}
""", configuration: ["ignore_one_liners": true]),
Example("""
switch i
{ \(violationMarker)case .x: 1 \(violationMarker)default: 0 }
""", configuration: ["ignore_one_liners": true]),
Example("""
let a = switch i {
case .x: 1 \(violationMarker)default: 0
}
""", configuration: ["ignore_one_liners": true]),
Example("""
let a = switch i {
\(violationMarker)case .x: 1 \(violationMarker)default: 0 }
""", configuration: ["ignore_one_liners": true]),
]
}
}
}