Skip to content

Commit

Permalink
Make unneeded_break_in_switch auto correctable (#5188)
Browse files Browse the repository at this point in the history
  • Loading branch information
KS1019 authored Oct 3, 2023
1 parent 5911540 commit 5d95b0a
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
[gibachan](https://github.com/gibachan)
[#5233](https://github.com/realm/SwiftLint/pull/5233)

* Make `unneeded_break_in_switch` auto correctable.
[KS1019](https://github.com/KS1019/)

#### Bug Fixes

* Fix false positive in `unused_import` rule that triggered on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ private func embedInSwitch(
""", file: file, line: line)
}

struct UnneededBreakInSwitchRule: SwiftSyntaxRule, ConfigurationProviderRule {
struct UnneededBreakInSwitchRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)

static let description = RuleDescription(
Expand Down Expand Up @@ -45,23 +45,121 @@ struct UnneededBreakInSwitchRule: SwiftSyntaxRule, ConfigurationProviderRule {
embedInSwitch("something()\n ↓break // comment"),
embedInSwitch("something()\n ↓break", case: "default"),
embedInSwitch("something()\n ↓break", case: "case .foo, .foo2 where condition")
],
corrections: [
embedInSwitch("something()\n ↓break")
: embedInSwitch("something()"),
embedInSwitch("something()\n ↓break // line comment")
: embedInSwitch("something()\n // line comment"),
embedInSwitch("""
something()
↓break
/*
block comment
*/
""")
: embedInSwitch("""
something()
/*
block comment
*/
"""),
embedInSwitch("something()\n ↓break /// doc line comment")
: embedInSwitch("something()\n /// doc line comment"),
embedInSwitch("""
something()
↓break
///
/// doc block comment
///
""")
: embedInSwitch("""
something()
///
/// doc block comment
///
"""),
embedInSwitch("something()\n ↓break", case: "default")
: embedInSwitch("something()", case: "default"),
embedInSwitch("something()\n ↓break", case: "case .foo, .foo2 where condition")
: embedInSwitch("something()", case: "case .foo, .foo2 where condition")
]
)

func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
UnneededBreakInSwitchRuleVisitor(viewMode: .sourceAccurate)
}

func makeRewriter(file: SwiftLintCore.SwiftLintFile) -> SwiftLintCore.ViolationsSyntaxRewriter? {
UnneededBreakInSwitchRewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}

private final class UnneededBreakInSwitchRuleVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: SwitchCaseSyntax) {
guard node.statements.count > 1,
let statement = node.statements.last,
let breakStatement = statement.item.as(BreakStmtSyntax.self),
breakStatement.label == nil else {
guard let statement = node.unneededBreak else {
return
}

violations.append(statement.item.positionAfterSkippingLeadingTrivia)
}
}

private final class UnneededBreakInSwitchRewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [SwiftSyntax.AbsolutePosition] = []
let locationConverter: SourceLocationConverter
let disabledRegions: [SourceRange]

init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}

override func visit(_ node: SwitchCaseSyntax) -> SwitchCaseSyntax {
let stmts = CodeBlockItemListSyntax(node.statements.dropLast())

guard let breakStatement = node.unneededBreak, let secondLast = stmts.last,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return super.visit(node)
}

correctionPositions.append(breakStatement.item.positionAfterSkippingLeadingTrivia)

let trivia = breakStatement.item.leadingTrivia + breakStatement.item.trailingTrivia

let newNode = node
.with(\.statements, stmts)
.with(\.statements.trailingTrivia, secondLast.item.trailingTrivia + trivia)
.trimmed { !$0.isComment }
.formatted()
.as(SwitchCaseSyntax.self)!

return super.visit(newNode)
}
}

private extension SwitchCaseSyntax {
var unneededBreak: CodeBlockItemSyntax? {
guard statements.count > 1,
let breakStatement = statements.last?.item.as(BreakStmtSyntax.self),
breakStatement.label == nil else {
return nil
}

return statements.last
}
}

private extension TriviaPiece {
var isComment: Bool {
switch self {
case .lineComment, .blockComment, .docLineComment, .docBlockComment:
return true
default:
return false
}
}
}

0 comments on commit 5d95b0a

Please sign in to comment.