Mintでバージョン管理する SwiftLint/SwiftFormat/SwiftGen - 環境構築の手順

# LinterやFormatterもバージョン管理したい！

Xcodeで開発するときに便利なツールたちですが、HomeBrewで入れることも多いですよね？
開発者の端末ごとのバージョン違いで微妙に挙動が違って、PRのたびに無駄な差分が発生してしまします。
そこで、重い腰を上げて、Mintでバージョン管理をすることにしました。
この記事では、導入手順とハマりポイントを紹介したいと思います。

# Mintのインストール

これに関しては、HomeBrewでもいいかも……（え

```zsh
brew install mint
```

ちゃんとやるなら、gitから直接！

```zsh
$ git clone https://github.com/yonaskolb/Mint.git
$ cd Mint
$ swift run mint install yonaskolb/mint
```

# 新規プロジェクトの作成

今回は Storyboard/UIKit App Delegate を選択しました。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/123df833-fec2-8c5f-6005-8e499412cd2d.png" />

## .gitignoreを追加してコミット

[こちら](https://github.com/github/gitignore/blob/master/Swift.gitignore)の`.gitignore`をコピーさせていただいて、いったんコミットします。

```zsh
➜  ToolsSample git init
➜  ToolsSample git:(master) ✗ git add .gitignore 
➜  ToolsSample git:(master) ✗ git add .
➜  ToolsSample git:(master) ✗ git commit -m '[chore] Initial commit with .gitignore'
```

# SwiftLint/SwiftFormatの導入

## Mintfileを作成して、`mint bootstrap`

最新版をインストールするため、いったんバージョン指定なしで作成します。
導入は後ですが、SwiftGenもここで一緒にインストールしてしまいます。

```zsh:Mintfile
realm/SwiftLint
nicklockwood/SwiftFormat
SwiftGen/SwiftGen
```

`mint bootstrap`でリポジトリをクローンして、ビルドします。

```zsh
➜  ToolsSample git:(master) ✗ mint bootstrap
🌱 Finding latest version of SwiftLint
🌱 Cloning SwiftLint 0.40.3
🌱 Resolving package
🌱 Building package
🌱 Installed SwiftLint 0.40.3
🌱 Finding latest version of SwiftFormat
🌱 Cloning SwiftFormat 0.47.0
🌱 Resolving package
🌱 Building package
🌱 Installed SwiftFormat 0.47.0
🌱 Finding latest version of SwiftGen
🌱 Cloning SwiftGen 6.4.0
🌱 Resolving package
🌱 Building package
🌱 Copying resources for SwiftGen: templates, Resources/Stencil-Info.plist, Resources/StencilSwiftKit-Info.plist, Resources/SwiftGen-Info.plist, Resources/SwiftGenKit-Info.plist ...
🌱 Installed SwiftGen 6.4.0
🌱 Installed 3/3 packages
```

インストールされた最新バージョンを確認して、Mintfileをアップデート。プロジェクトで使用するツールのバージョンを固定できます。

```zsh:Mintfile
realm/SwiftLint@0.40.3
nicklockwood/SwiftFormat@0.47.0
SwiftGen/SwiftGen@6.4.0
```

## Run Scriptの追加

Linter/Formatterを手動で毎度走らせるのはつらいので、ビルドのタイミングで実行するようにします。

TARGETSを選択して、Build Phasesのタブの左上のプラスボタンから`New Run Script Phase`を選択して、Run Scriptを追加します。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/d1418829-bcc3-f6e2-c629-ceb38530740b.png" />

## Run Scriptにコマンドを追加

SwiftLintとSwiftFormatは一部のフォーマット機能がかぶるので、SwiftLintのautocorrectをかけたあとに、SwiftFormatでフォーマットします。

```sh:RunScript
mint run swiftlint autocorrect
mint run swiftlint lint
mint run swiftformat --swiftversion 5.3 .
```

SwiftLintやSwiftFormatのREADMEでは、`which`コマンドでツールの存在をチェックしてから実行する方法が記載してありますが、存在チェックを省いておくとツールが利用できない場合はXcodeがビルドエラーで教えてくれます。

## 動作確認

ここまでできたら、ビルド（Command+b）してみて、SwiftLintが警告を出してくれることを確認しましょう。
ViewControllerのソースコードのインデントを適当に変更してみると、SwiftFormatがインデントを整えてくれることも確認できます。
<img width="200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/f25f1343-4b1d-b1e4-d792-72b929852300.png" />

# SwiftGenの導入

ここではLocalizable.stringsからファイル生成をやってみます。

## ローカライズの設定

ProjectのInfoタブから、Localizationsのプラスボタンをクリックして、日本語を選択します。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/c6349ed2-a4b4-f984-c012-37c8ddac5770.png" />

適当な設定を選んでFinish
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/64766843-59a0-f600-b456-8af07b20634e.png" />

## `Localizable.strings`の作成

新しくResoucesグループを作って

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/674fcf9c-8ae3-effc-622e-a50bb4d7afca.png" />

New file...からStrings Fileを選択して、`Localizable.strings`を作成します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/61e5e372-85df-9e0f-d9b2-a97935098805.png" />

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/88ed5689-d3a0-98d1-b1a8-07a471cc9489.png" />

ファイルのインスペクタからLocalize...を押して、Japaneseを選択してLocalize!
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/fc296109-5235-fbc9-adfb-57baedfdb71f.png" />

インスペクタに戻って、上で選択しなかった方の言語（ここではEnglish）にもチェックを入れる。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/1a0ffb83-58d5-000f-78b2-be064a6d9ff4.png" />

それぞれの言語に対応する、lprojディレクトリと`Localizable.strings`が生成されます。

```zsh
➜  ToolsSample git:(master) ✗ ls ToolsSample/Resources/*.lproj  
ToolsSample/Resources/en.lproj:
Localizable.strings

ToolsSample/Resources/ja.lproj:
Localizable.strings
```

ここでは、ひとつだけ文字列を登録してみましょう。

```swift:Locaizable.strings
// Resources/ja.lproj/Localizable.strings

"greetings.hello" = "こんにちは"

// Resources/en.lproj/Localizable.strings

"greetings.hello" = "Hello"


```

## SwiftGenのconfigファイルの生成

フレームワークごとに文字列のファイルを生成する場合を考えて、configファイルはフレームワーク内に作成することにします。

```zsh
➜  ToolsSample git:(master) ✗ mint run swiftgen config init --config ToolsSample/Resources/swiftgen.yml
Example configuration file created: ToolsSample/Resources/swiftgen.yml
```

## SwiftGenの生成用のディレクトリの作成

SwiftGenが生成するファイルを配置するディレクトリを作成します。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/82c3a233-96ae-a09f-c609-e5401af409ac.png" />

## `swiftgen.yml`を編集

今回のディレクトリ構成に合わせて編集します。configファイルのパスを起点に記述するようです。

```zsh:ディレクトリ構成
➜  ToolsSample git:(master) ✗ tree ToolsSample/Resources 
ToolsSample/Resources
├── en.lproj
│   └── Localizable.strings
├── ja.lproj
│   └── Localizable.strings
└── swiftgen.yml
```

```yml:swiftgen.yml
input_dir: ./
output_dir: ../Generated/

strings:
  inputs:
    - en.lproj
  filter:
    - .+\.strings$
  outputs:
    - templateName: structured-swift5
      output: Strings+Generated.swift
```

`config init`で生成したswiftgen.ymlに記載されているコメントにある通り、stringsのinputsにはローカライズのディレクトリをひとつだけを記載します。

`config lint`で記述をチェック

```zsh
➜  ToolsSample git:(master) ✗ mint run swiftgen config lint --config ToolsSample/Resources/swiftgen.yml
Linting ToolsSample/Resources/swiftgen.yml
> Common parent directory used for all input paths:  ./
> Common parent directory used for all output paths: ../Generated/
swiftgen: error: output_dir: Output directory ../Generated/ does not exist.
> 1 entry for command strings:
 $ swiftgen strings --templateName structured-swift5 --output ../Generated/Strings+Generated.swift en.lproj
```

output pathsでエラーが出ていますが、どうやらoutput pathsだけはコマンドを実行したパスからの相対パスでチェックされるようです（バグ？）。実際に生成する際は問題なく動くので、今回は無視します。

## SwiftGenによるファイルの生成

```zsh
➜  ToolsSample git:(master) ✗ mint run swiftgen config run --config ToolsSample/Resources/swiftgen.yml
File written: ../Generated/Strings+Generated.swift
```

初回だけ、生成されたファイルをXcodeに追加します。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/df467c15-56b1-b519-e1f0-d2d6854ab037.png" />


```swift:Strings+Generated.swift
internal enum L10n {
  internal enum Greetings {
    /// Hello
    internal static let hello = L10n.tr("Localizable", "greetings.hello")
  }
}

extension L10n {
  private static func tr(_ table: String, _ key: String, _ args: CVarArg...) -> String {
    let format = BundleToken.bundle.localizedString(forKey: key, value: nil, table: table)
    return String(format: format, locale: Locale.current, arguments: args)
  }
}

private final class BundleToken {
  static let bundle: Bundle = {
    #if SWIFT_PACKAGE
    return Bundle.module
    #else
    return Bundle(for: BundleToken.self)
    #endif
  }()
}
```

## Run Scriptに生成コマンドを追加

Localizable.stringsを編集するたびにコマンドラインから実行するのは面倒なので、ビルドのタイミングで実行するようにします。

```sh:RunScript
mint run swiftgen config run --config ToolsSample/Resources/swiftgen.yml # 追加！
mint run swiftlint autocorrect
mint run swiftlint lint
mint run swiftformat --swiftversion 5.3 .
```

## `.swiftformat`を追加

ここでハマりポイントがあります。
このままの設定では、SwiftFormatがSwiftGenが生成した`BundleToken`クラスを`enum`に変更してしまいます。

そこで、`.swiftformat`ファイルを作成して、Generatedディレクトリをフォーマット対象から除外しておきます。

```zsh:.swiftformat
# file options
--exclude ToolsSample/Generated
```

ちなみに、隠しファイルをXcodeプロジェクトに追加したい場合は、追加画面で`Command+Shift+.`を入力すると、隠しファイルが表示されるようになります。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/25af2826-4f20-6915-b205-c77d2fb49ea8.png" />


## 動作確認

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/76632bfa-d124-7bbe-c0ea-0be9f461cc6f.png" /><img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/499615/18ea33e8-17e0-df15-40f4-71f8d89beab5.png" />

# おわりに

おつかれさまでした。
参考になれば幸いです！

# 参考

- Mint: https://github.com/yonaskolb/Mint
- SwiftLint: https://github.com/realm/SwiftLint
- SwiftFormat: https://github.com/nicklockwood/SwiftFormat
- SwiftGen: https://github.com/SwiftGen/SwiftGen