@@ -16,73 +16,95 @@ import SwiftSyntax
1616
1717/// Early exits should be used whenever possible.
1818///
19- /// Practically, this means that `if ... else return/throw/break` constructs should be replaced by
20- /// `guard ... else { return/throw/break }` constructs in order to keep indentation levels low.
19+ /// This means that `if ... else { return/throw/break/continue }` constructs should be replaced by
20+ /// `guard ... else { return/throw/break/continue }` constructs in order to keep indentation levels
21+ /// low. Specifically, code of the following form:
2122///
22- /// Lint: `if ... else return/throw/break` constructs will yield a lint error.
23+ /// ```swift
24+ /// if condition {
25+ /// trueBlock
26+ /// } else {
27+ /// falseBlock
28+ /// return/throw/break/continue
29+ /// }
30+ /// ```
2331///
24- /// Format: `if ... else return/throw/break` constructs will be replaced with equivalent
25- /// `guard ... else { return/throw/break }` constructs.
26- /// TODO(abl): replace implicit guards as well?
32+ /// will be transformed into:
33+ ///
34+ /// ```swift
35+ /// guard condition else {
36+ /// falseBlock
37+ /// return/throw/break/continue
38+ /// }
39+ /// trueBlock
40+ /// ```
41+ ///
42+ /// Lint: `if ... else { return/throw/break/continue }` constructs will yield a lint error.
43+ ///
44+ /// Format: `if ... else { return/throw/break/continue }` constructs will be replaced with
45+ /// equivalent `guard ... else { return/throw/break/continue }` constructs.
2746///
2847/// - SeeAlso: https://google.github.io/swift#guards-for-early-exits
2948public final class UseEarlyExits : SyntaxFormatRule {
3049
31- public override func visit( _ node: CodeBlockSyntax ) -> Syntax {
32-
33- var newItems : [ CodeBlockItemSyntax ] = [ ]
34- for statement in node. statements {
35- guard let ifStmt = statement. item as? IfStmtSyntax else { return node }
36- guard let elseStmt = ifStmt. elseBody else { return node }
37- guard let elseBody = elseStmt as? CodeBlockSyntax else { return node }
50+ public override func visit( _ node: CodeBlockItemListSyntax ) -> Syntax {
51+ // Continue recursing down the tree first, so that any nested/child nodes get transformed first.
52+ let nodeAfterTransformingChildren = super. visit ( node)
53+ guard let codeBlockItems = nodeAfterTransformingChildren as? CodeBlockItemListSyntax else {
54+ return nodeAfterTransformingChildren
55+ }
56+
57+ return SyntaxFactory . makeCodeBlockItemList (
58+ codeBlockItems. flatMap { ( codeBlockItem: CodeBlockItemSyntax ) -> [ CodeBlockItemSyntax ] in
59+ // The `elseBody` of an `IfStmtSyntax` will be a `CodeBlockSyntax` if it's an `else` block,
60+ // or another `IfStmtSyntax` if it's an `else if` block. We only want to handle the former.
61+ guard let ifStatement = codeBlockItem. item as? IfStmtSyntax ,
62+ let elseBody = ifStatement. elseBody as? CodeBlockSyntax ,
63+ codeBlockEndsWithEarlyExit ( elseBody)
64+ else {
65+ return [ codeBlockItem]
66+ }
3867
39- if elseContainsControlStmt ( elseStmt: elseStmt) {
40- diagnose ( . useGuardStmt, on: ifStmt)
41- guard let moveDeletedIfCode = visit (
42- ifStmt. body. withLeftBrace ( nil ) . withRightBrace ( nil ) ) as? CodeBlockSyntax else { continue }
43- guard let moveElseBody = visit ( elseBody) as? CodeBlockSyntax else { continue }
44-
45- let ifConditions = ifStmt. conditions
46- let formattedGuardKeyword = SyntaxFactory . makeGuardKeyword (
47- leadingTrivia: ifStmt. ifKeyword. leadingTrivia,
68+ diagnose ( . useGuardStatement, on: ifStatement. elseKeyword)
69+
70+ let trueBlock = ifStatement. body. withLeftBrace ( nil ) . withRightBrace ( nil )
71+
72+ let guardKeyword = SyntaxFactory . makeGuardKeyword (
73+ leadingTrivia: ifStatement. ifKeyword. leadingTrivia,
4874 trailingTrivia: . spaces( 1 ) )
49- let newGuardStmt = SyntaxFactory . makeGuardStmt (
50- guardKeyword: formattedGuardKeyword ,
51- conditions: ifConditions ,
75+ let guardStatement = SyntaxFactory . makeGuardStmt (
76+ guardKeyword: guardKeyword ,
77+ conditions: ifStatement . conditions ,
5278 elseKeyword: SyntaxFactory . makeElseKeyword ( trailingTrivia: . spaces( 1 ) ) ,
53- body: moveElseBody)
54- newItems. append (
55- SyntaxFactory . makeCodeBlockItem ( item: newGuardStmt,
56- semicolon: nil )
57- )
58- newItems. append (
59- SyntaxFactory . makeCodeBlockItem ( item: moveDeletedIfCode,
60- semicolon: nil )
61- )
62- }
63- }
79+ body: elseBody)
6480
65- let newNode = node. withStatements ( SyntaxFactory . makeCodeBlockItemList ( newItems) )
66- return super. visit ( newNode)
81+ return [
82+ SyntaxFactory . makeCodeBlockItem ( item: guardStatement, semicolon: nil ) ,
83+ SyntaxFactory . makeCodeBlockItem ( item: trueBlock, semicolon: nil ) ,
84+ ]
85+ } )
6786 }
6887
69- func elseContainsControlStmt( elseStmt: Syntax ) -> Bool {
70- for child in elseStmt. children {
71- guard let codeBlockList = child as? CodeBlockItemListSyntax else { continue }
72- guard let last = codeBlockList. child ( at: codeBlockList. count - 1 ) as?
73- CodeBlockItemSyntax else { continue }
88+ /// Returns true if the last statement in the given code block is one that will cause an early
89+ /// exit from the control flow construct or function.
90+ private func codeBlockEndsWithEarlyExit( _ codeBlock: CodeBlockSyntax ) -> Bool {
91+ let statements = codeBlock. statements
92+ guard statements. count > 0 else { return false }
93+ let lastStatement = statements [ statements. count - 1 ]
7494
75- switch last. item {
76- case is ReturnStmtSyntax , is ThrowStmtSyntax , is BreakStmtSyntax , is ContinueStmtSyntax :
77- return true
78- default :
79- continue
80- }
95+ switch lastStatement. item {
96+ case is ReturnStmtSyntax , is ThrowStmtSyntax , is BreakStmtSyntax , is ContinueStmtSyntax :
97+ return true
98+ default :
99+ return false
81100 }
82- return false
83101 }
84102}
85103
86104extension Diagnostic . Message {
87- static let useGuardStmt = Diagnostic . Message ( . warning, " replace with guard statement " )
105+
106+ static let useGuardStatement = Diagnostic . Message (
107+ . warning,
108+ " replace the `if/else` block with a `guard` statement containing the early exit "
109+ )
88110}
0 commit comments