SwiftFormatとSwiftLintで綺麗なソースコードを維持する

コードフォーマッターの`SwiftFormat`と、コード検査の`SwiftLint`を使って、手軽に綺麗なSwiftのソースコードを維持しよう:relaxed:、という記事です。`SwiftFormat`も`SwiftLint`も、共に簡単に使えるツールなのですが、デフォルトの設定のまま使うと色々困る:persevere:点があるので、それらの問題を解消する設定について説明します。:v:

# 登場するツール
* SwiftFormat: ソースコードを自動整形(フォーマット)してくれるツール
* SwiftLint: ソースコードを検査してくれるツール

この二つのツールを活用して、ソースコードのスタイルを効率的に統一していこう、という試みです。

## それぞれが良しとするフォーマット/スタイルが違う問題:fearful:

ただし、この二つのツールは独立してAppleではなくサードパーティによって開発されているものなので、デフォルトの設定だと下記のポイントが微妙に揃っていません。

* Xcodeが生成するソースコードのフォーマット
* SwiftFormatによって整形されるフォーマット
* SwiftLintが警告を出す条件

ソースコードのフォーマット、スタイルに関しては、どれが正解・あるいは優れていると簡単に言えるものではありませんが、プロジェクトを進める上ではどれか一つに統一しておくべきです。ここでは、**Xcodeが生成するコードのスタイルは絶対神**という方針で、なるべくXcodeのフォーマット/スタイルに合わせる形で調整・設定を行っていきます。

# SwiftFormatでコードの自動整形をする
[nicklockwood/SwiftFormat](https://github.com/nicklockwood/SwiftFormat)

## インストール
`CocoaPods`を使ってインストールするのがおすすめです。

```
pod 'SwiftFormat/CLI'
```

HomeBrew:beers:でインストールすることもできますし、サクッと試すだけならそのほうが楽なのですが、後述の方法で「ビルド時/テスト時に自動でフォーマットをかける」ようにするには`CocoaPods`でインストールした方が便利です。(`pod install`をするだけで、どのビルド環境でも使えるようになるため)

`CocoaPods`でインストールをすると、`${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat`に実行用のバイナリがインストールされます。インストールされて使える状態になっているか、`swiftformat`を実行して試してみましょう。

```
cd プロジェクトのルートディレクトリ
Pods/SwiftFormat/CommandLineTool/swiftformat --version
```


## 基本的な使い方(コマンドラインから使ってみる)

`SwiftFormat`では、引数にソースコードがあるディレクトリやファイルを指定すると、そのディレクトリ/ファイルをルールに従ってフォーマットしてくれます。

```
Pods/SwiftFormat/CommandLineTool/swiftformat SwiftFormatExp/AppDelegate.swift
```

```Swift:AppDelegate.swift(フォーマット前)
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    
    private var someVar:String?=nil


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.someVar="abc"
        
        return true
    }
}
```

```Swift:AppDelegate.swift(フォーマット後)
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    private var someVar: String?

    func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        someVar = "abc"

        return true
    }
}
```

この場合では、不要な`self.`が削除されたり、`=`の前後にスペースが挿入されたりしています。このように`SwiftFormat`をかけると、表記のブレを無くすことができます。

ただし、デフォルトの設定(オプション指定なし)でフォーマットを行うと、上の例だと`application:didFinishLaunchingWithOptions:`の引数のように、`Xcode`が生成・サジェストしたコードもフォーマットされてしまいます。`Xcode`が生成するコードと、`SwiftFormat`のフォーマット結果に差があるのは様々な点でちょっとした不都合をもたらすので、**Xcodeが生成するコードのスタイルは絶対神**という方針で、なるべく`Xcode`が生成するスタイルにあうようにオプションを指定します。

```Shell
Pods/SwiftFormat/CommandLineTool/swiftformat SwiftFormatExp/AppDelegate.swift --trimwhitespace nonblank-lines --stripunusedargs closure-only --disable strongOutlets,trailingCommas
```

僕の場合はこのようなオプションを指定するようにしています。

|オプション|説明|
|--------|----|
|--trimwhitespace nonblank-lines|空白行のインデントが消されてしまわないようにする|
|--stripunusedargs closure-only|使われていない引数名の省略はクロージャに限る|
|--disable strongOutlets,trailingCommas|Outletの`weak`を取り除かないようにする, 配列の要素の最後にカンマをつけないようにする|

これらのオプションを指定してフォーマットを実行すると下記のようになり、ちゃんとメソッドの引数名が保持されるようになります。

```Swift:AppDelegate.swift(オプション指定版)
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    
    private var someVar: String?
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        someVar = "abc"
        
        return true
    }
}
```

プロジェクト全体に適応したい場合は、`exclude`オプションで`Carthage`や`CocoaPods`などの外部ライブラリーのソースコードを適用外にしておきましょう。

```
Pods/SwiftFormat/CommandLineTool/swiftformat . --exclude Carthage,Pods --trimwhitespace nonblank-lines --stripunusedargs closure-only --disable strongOutlets,trailingCommas
```

## ビルド時/テスト時に自動でフォーマットをかける

コードフォーマットを毎回手動で走らせても良いのですが、うっかりフォーマットを忘れてレポジトリにコミットしてしまうことを防ぐ仕組みを作りましょう。

自動的にコードフォーマットを走らせるタイミングとしては、下記のようなタイミングが考えられます。

|タイミング|頻度|処理時間|エディタの快適性(※)|適用漏れ防止|備考|
|--------|---|-------|----------------|----------|--------|
|ファイルを保存した時|◎|×|×|◎|設定が大変|
|ソースコードをビルドした時|○|△|×|○|-|
|テストを実行した時|△|○|△|×|毎回テストを実行する、という前提がないとワークしない|
|コミットする直前(gitのpre-commitフックなど)|×|◎|○|◎|フォーマットした結果(ソースコード,ビルド結果共に)を確認する前にコミットされてしまう|

※`SwiftFormat`でフォーマットした結果が、`Xcode`のエディタに反映されるまでには若干の時差があります。その間の時間に`Xcode`上でソースコードを編集してしまうと、`SwiftFormat`でのフォーマット結果とコンフリクトが起きる、という問題があります。

それぞれ一長一短がありますが、上記エディタの快適性が失われるのは結構ストレスなので、テストを実行する前提で開発を進めている場合はテスト実行時にフォーマットを行う形で設定をするのがオススメです。

プロジェクトのテスト用ターゲットの`Build Phases`に`New Run Script Phase`を追加します。

<img width="871" alt="スクリーンショット 2018-04-29 17.50.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/14097/c9639275-b354-d723-9d6f-b14df52d7f2a.png">

追加したScript Phaseに、`SwiftFormat`のコマンドを記述します。

```
"${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat" . --exclude Carthage,Pods --trimwhitespace nonblank-lines --stripunusedargs closure-only --disable strongOutlets,trailingCommas
```

CocoaPodsでインストールした場合は、`${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat`で`swiftformat`のパスを指定できます。作成したPhaseが`Compile Sources`より前に実行されるように移動します。

<img width="871" alt="スクリーンショット 2018-04-29 17.49.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/14097/248bf473-ad1d-30a5-2cec-3e0e75fb95ef.png">

ここまでできたら、適当なソースコードに無駄なインデントやスペースなどを入れた状態でテストを実行してみましょう。そのタイミングでソースコードがフォーマットされた状態になっていたら`SwiftFormat`の設定は完了です。


# SwiftLintで問題点をチェックする

[realm/SwiftLint](https://github.com/realm/SwiftLint)

## インストール

こちらも、HomeBrewとCocoaPodsどちらでもインストールができますが、`SwiftFormat`とあわせて`CocoaPods`でインストールしましょう。

```
pod 'SwiftLint'
```

こちらも`CocoaPods`でインストールをすると、`${PODS_ROOT}/SwiftLint/swiftlint`に実行用のバイナリがインストールされます。インストールされて使える状態になっているか、実行して試してみましょう。

```
cd プロジェクトのルートディレクトリ
Pods/SwiftLint/swiftlint version
```

## 基本的な使い方(コマンドラインから使ってみる)

`SwiftLint`の場合は、基本的に実行時のディレクトリ以下にあるファイルを対象にチェックを行うため、対象ファイル/ディレクトリを引数で指定する必要はありません。

```
cd プロジェクトのルートディレクトリ
Pods/SwiftLint/swiftlint
```

実行すると、問題箇所が出力されます。

```Shell
Linting Swift files in current working directory
Linting 'AppDelegate.swift' (1/3)
Linting 'ViewController.swift' (2/3)
Linting 'SwiftFormatExpTests.swift' (3/3)
AppDelegate.swift:6: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)
AppDelegate.swift:8: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)
AppDelegate.swift:11: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)
AppDelegate.swift:9: warning: Line Length Violation: Line should be 120 characters or less: currently 144 characters (line_length)
ViewController.swift:16: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)
SwiftFormatExpTests.swift:16: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)
SwiftFormatExpTests.swift:21: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)
SwiftFormatExpTests.swift:26: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace)
Done linting! Found 8 violations, 0 serious in 3 files.
```

## ビルド時にチェックをする

このチェックを`Xcode`上でビルド時に自動的に実行されるようにしましょう。

プロジェクトの**アプリ用ターゲット**(今回はテスト用ターゲットではないので注意)のBuild Phasesに`New Run Script Phase`を追加します。

<img width="872" alt="スクリーンショット 2018-04-29 18.12.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/14097/fdb403f4-68fb-a8a3-1d0e-6ddf150e547e.png">

`CocoaPods`でインストールした`swiftlint`コマンドが実行されるようにします。

```
"${PODS_ROOT}/SwiftLint/swiftlint"
```

<img width="667" alt="スクリーンショット 2018-04-29 18.16.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/14097/e5b9226e-8390-b513-09e6-d5e8caa8051f.png">

もし、`SwiftFormat`をビルド毎、保存毎に行うようにしている場合は、フォーマット後のコードに対してチェックを行うように`SwiftLint`は`SwiftFormat`の後に実行されるように並べておきます。

この状態でXcodeでビルドを行うと、SwiftLintの結果が警告として表示されるようになります。

<img width="270" alt="スクリーンショット 2018-04-29 18.18.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/14097/3efc9ad3-4c34-75a3-5bcf-f33eb05fcd34.png">

## 設定の調整

ただし、こちらもデフォルトの設定だと`Xcode`が生成したコードのスタイルに対しても警告が行われてしまいます。Xcodeが生成するコードが標準で警告されるような設定では、Xcodeがコードを生成する度に毎回スタイルを直すのも面倒ですし、かといって生成の度に警告が生まれ、他の警告が埋もれてしまうのも困ります。

ここでも**Xcodeが生成するコードのスタイルは絶対神**という方針で、いくつかルールを設定して不要な警告を緩和してやります。同時に、警告が出たところでソースコードをいじりたくない外部ライブラリが含まれる`Carthage`や`Pods`ディレクトリをチェックの対象外とします。

`SwiftLint`では、プロジェクトルートディレクトリに`.swiftlint.yml`ファイルを設置し、そこにルールを記述すると、その内容に従ってチェックを行ってくれます。

```YAML:.swiftlint.yml
excluded:
- Carthage
- Pods
disabled_rules:
- line_length
- trailing_whitespace
```

上の設定では、空白行のインデントの許容のために`trailing_whitespace`を、また`line_length`を無効化して一行あたりの長さに対する警告を無効化しています。一行あたりの文字数に関しては、無効化するのではなく、長さを長めに指定する形でも良いかもしれません。

```YAML:.swiftlint.yml
excluded:
- Carthage
- Pods
disabled_rules:
- trailing_whitespace
line_length: 150
```

# まとめ
`SwiftFormat`と`SwiftLint`の力を借りて、Happy Swift Coding:ok_woman: