SwiftRewriter
                                
                                 SwiftRewriter copied to clipboard
                                
                                    SwiftRewriter copied to clipboard
                            
                            
                            
                        π Swift code formatter using SwiftSyntax.
π SwiftRewriter
Swift code formatter using SwiftSyntax.
Requirements: Swift 5.1 (Xcode 11.0) + SwiftSyntax 0.50100.0
See also my iOSConf SG 2019 talk for more detail:
- Slide: Making your own Code Formatter in Swift - Speaker Deck
- Video: Make your own code formatter in Swift β iOS Conf SG 2019 β Learn Talks
Overview
- SwiftRewriter: Collection of reusable & composable- SyntaxRewriters
- swift-rewriter: Simple command-line executable
How to use
$ swift build
$ swift run swift-rewriter help
Available commands:
   help        Display general or command-specific help
   print-ast   print AST from file or string
   run         Auto-correct code in the file or directory
# Auto-correct code in the directory
$ swift run swift-rewriter run --path /path/to/file-or-directory
Configuration
In swift-rewriter CLI tool, rewriting rules are configured in rewriter.swift (configuration file e.g. yaml or json is not supported yet).
- rewriter.swift
Please change the configuration as you like (you can make your own rewriter and combine!), and swift build & run.
// rewriter.swift
import SwiftRewriter
var rewriter: Rewriter {
    // Comment
    HeaderCopyrightTrimmer()
        // Move
        >>> ImportSorter()
//        >>> ExtensionIniter() // not useful for everyone
        // Token
        >>> DecimalLiteralUnderscorer()
        >>> SemicolonTrimmer()
        // Newline (whitespace)
//        >>> ExtraNewliner()   // not useful for everyone
        >>> ElseNewliner(newline: false)
        >>> MethodChainNewliner()
        // Indent (whitespace)
        >>> Indenter(.init(
            perIndent: .spaces(4),
            shouldIndentSwitchCase: false,
            shouldIndentIfConfig: false,
            skipsCommentedLine: true
            ))
        // Space (whitespace)
//        >>> ExtraSpaceTrimmer()   // may disturb manually-aligned code
        >>> ColonSpacer(spaceBefore: false, spaceAfter: true)
        >>> TernaryExprSpacer()
        >>> BinaryOperatorSpacer(spacesAround: true)
        // Ignore to not distrub user-aligned multiple assignments
        // TODO: Improve multiple assignment alignment
//        >>> EqualSpacer(spacesAround: true)
        >>> ArrowSpacer(spaceBefore: true, spaceAfter: true)
        >>> LeftBraceSpacer(spaceBefore: true)
        >>> LeftParenSpacer(spaceBefore: true)
        >>> TrailingSpaceTrimmer()
}
Rewriter examples
Indenter
Better right-brace position
@@ β1,6 +1,6 @@
βlets
β    .code {
β    }
β    .format {
-} // this!!!
+    } // this!!!
P.S. This is the primary goal of making SwiftRewriter.
First-item-aware indent
    struct Foo {
                         init(bool: Bool,
              int: Int) {
                              self.bool = bool
                           if true {
                     print()
                  }
                   run { x in
                            print(x,
                                      y,
                                          z)
                }
                        }
            }
will be:
struct Foo {
    init(bool: Bool,
         int: Int) {
        self.bool = bool
        if true {
            print()
        }
        run { x in
            print(x,
                  y,
                  z)
        }
    }
}
HeaderCopyrightTrimmer
@@ β1,10 +1,2 @@
-//
-//  example.swift
-//  SwiftRewriter
-//
-//  Created by Yasuhiro Inami on 2018-12-09.
-//  Copyright Β© 2018 Yasuhiro Inami. All rights reserved.
-//
-
β// All your code are belong to us.
ImportSorter
import C
import B
func foo() {}
import A
import D
will be:
import A
import B
import C
import D
func foo() {}
ExtensionIniter
This rewriter moves the code to enable struct's memberwise initializer.
struct Foo {
    let int: Int
    init(int: Int) {
        self.int = int
    }
    init() {
        self.int = 0
    }
}
@@ β1,9 +1,12 @@
βstruct Foo {
β    let int: Int
+}
+
+extension Foo {
β    init(int: Int) {
β        self.int = int
β    }
β    init() {
β        self.int = 0
β    }
β}
ExtraNewliner (Work in Progress)
This rewriter adds a newline when code is too dense.
import Foundation
var computed1: Int = 1
var computed2: Int = { return 2 }
/// doc
var computed3: Int = { return 3 }
/// doc
var computedBlock: String {
    return ""
}
func send() -> Observable<Void> {
    return apiSession
        .send(request)
        .do(onError: { [weak self] error in
            guard let me = self else { return }
            me.doSomething()
        })
        .do(onError: { [weak self] error in
            guard let me = self else { return }
            me.doSomething()
            me.doSomething()
        })
}
will be:
import Foundation
var computed1: Int = 1
var computed2: Int = { return 2 }
/// doc
var computed3: Int = { return 3 }
/// doc
var computedBlock: String {
    return ""
}
func send() -> Observable<Void> {
    return apiSession
        .send(request)
        .do(onError: { [weak self] error in
            guard let me = self else { return }
            me.doSomething()
        })
        .do(onError: { [weak self] error in
            guard let me = self else { return }
            me.doSomething()
            me.doSomething()
        })
}
Roadmap / TODO
- [ ] Add configuration file support
- [ ] Automatic code folding
- [ ] Move properties above method (for "states" readability)
- [ ] Move inner types to extensionscope (for "states" readability)
- [ ] Align multiline =assignments
- [ ] (Your idea comes here π‘)
Acknowledgement
- Improving Swift Tools with libSyntax by @harlanhaskins
- Creating Refactoring Transformations for Swift by @nkcsgexi
- try! Swift Tokyo 2018 - AST Meta-programming by @kishikawakatsumi
- SwiftSyntax - NSHipster by @mattt
- SwiftLint and SwiftSyntax benchmarking by @jpsim et al.
License
MIT