Skip to content

inamiy/SwiftRewriter

Repository files navigation

📝 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).

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