import SwiftSyntax @SwiftSyntaxRule(explicitRewriter: true) struct UnusedControlFlowLabelRule: Rule { var configuration = SeverityConfiguration(.warning) static let description = RuleDescription( identifier: "unused_control_flow_label", name: "Unused Control Flow Label", description: "Unused control flow label should be removed", kind: .lint, nonTriggeringExamples: [ Example("loop: while true { break loop }"), Example("loop: while true { continue loop }"), Example("loop:\n while true { break loop }"), Example("while true { break }"), Example("loop: for x in array { break loop }"), Example(""" label: switch number { case 1: print("1") case 2: print("2") default: break label } """), Example(""" loop: repeat { if x == 10 { break loop } } while true """), ], triggeringExamples: [ Example("↓loop: while true { break }"), Example("↓loop: while true { break loop1 }"), Example("↓loop: while true { break outerLoop }"), Example("↓loop: for x in array { break }"), Example(""" ↓label: switch number { case 1: print("1") case 2: print("2") default: break } """), Example(""" ↓loop: repeat { if x == 10 { break } } while true """), ], corrections: [ Example("↓loop: while true { break }"): Example("while true { break }"), Example("↓loop: while true { break loop1 }"): Example("while true { break loop1 }"), Example("↓loop: while true { break outerLoop }"): Example("while true { break outerLoop }"), Example("↓loop: for x in array { break }"): Example("for x in array { break }"), Example(""" ↓label: switch number { case 1: print("1") case 2: print("2") default: break } """): Example(""" switch number { case 1: print("1") case 2: print("2") default: break } """), Example(""" ↓loop: repeat { if x == 10 { break } } while true """): Example(""" repeat { if x == 10 { break } } while true """), ] ) } private extension UnusedControlFlowLabelRule { final class Visitor: ViolationsSyntaxVisitor { override func visitPost(_ node: LabeledStmtSyntax) { if let position = node.violationPosition { violations.append(position) } } } final class Rewriter: ViolationsSyntaxRewriter { override func visit(_ node: LabeledStmtSyntax) -> StmtSyntax { guard node.violationPosition != nil else { return super.visit(node) } numberOfCorrections += 1 return visit(node.statement.with(\.leadingTrivia, node.leadingTrivia)) } } } private extension LabeledStmtSyntax { var violationPosition: AbsolutePosition? { let visitor = BreakAndContinueLabelCollector(viewMode: .sourceAccurate) let labels = visitor.walk(tree: self, handler: \.labels) guard !labels.contains(label.text) else { return nil } return label.positionAfterSkippingLeadingTrivia } } private class BreakAndContinueLabelCollector: SyntaxVisitor { private(set) var labels: Set = [] override func visitPost(_ node: BreakStmtSyntax) { if let label = node.label?.text { labels.insert(label) } } override func visitPost(_ node: ContinueStmtSyntax) { if let label = node.label?.text { labels.insert(label) } } }