SwiftRewriter icon indicating copy to clipboard operation
SwiftRewriter copied to clipboard

πŸ“ Swift code formatter using SwiftSyntax.

πŸ“ SwiftRewriter

Swift 5.1 Build Status

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:

Overview

  1. SwiftRewriter: Collection of reusable & composable SyntaxRewriters
  2. 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 extension scope (for "states" readability)
  • [ ] Align multiline = assignments
  • [ ] (Your idea comes here πŸ’‘)

Acknowledgement

License

MIT