マルチモジュール構成でSwiftLintのルールを使い分ける

# 環境
- Xcode 14.3.1
- SwiftLint 0.52.4

# はじめに
SwiftLintをプロジェクトに導入している場合、プロジェクト構成はルートディレクトリに `.swiftlint.yml` を配置し以下のようになっているかと思います。

```ディレクトリ構成.txt
ProjectRoot
    ├ .swiftlint.yml
    ├ SampleProject
        ├ xxx.swift
        └ yyy.swift
    └ SampleProjectTests
        ├ xxxTest.swift
        └ yyyTest.swift
```

# ルールの使い分け
## なぜルールを使い分けたいのか 

プロダクトの不具合を防ぐために [force_unwrapping](https://realm.github.io/SwiftLint/force_unwrapping.html) や [force_try](https://realm.github.io/SwiftLint/force_try.html) といったルールを適用しているケースを考えます。

```.swiftlint.yml
opt_in_rules:
- force_unwrapping

# force_try はデフォルトで有効
```

テストコードはデコードしたりキャストしてもテストが成功するように記述しているはずなので強制アンラップや `try!` を書いても問題ないはずですが、プロダクトコードと同じルールが適用されてしまうと以下のように警告やエラーが表示されてしまいます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149055/9e284a68-dff2-1997-8737-8c60f89feaab.png" width="800px">

それぞれの警告やエラーに対して `// swiftlint:disable:this force_xxx` とコメントを追加すればルールを無視することは可能ですが、**テストを増やすたびにいちいちコメントを追加するのもとても面倒**です。
プロダクトの安全性を高めるためのLintによって**テストコードを書くことを阻害されるのは本末転倒**です。

## テストコードにのみ別のルールを割り当てる

ルートディレクトリとは別にテストコードのディレクトリに対して、**テストコード用に新しく作った `.swiftlint.yml` を配置します**。
（以下、 `SampleProjectTests/.swiftlint.yml` と記載します）

```ディレクトリ構成.txt
ProjectRoot
    ├ .swiftlint.yml
    ├ SampleProject
        ├ xxx.swift
        └ yyy.swift
    └ SampleProjectTests
        ├ .swiftlint.yml // <- 追加
        ├ xxxTest.swift
        └ yyyTest.swift
```

テストコードでのみ force_unwrapping と force_try を無効にするには、 `SampleProjectTests/.swiftlint.yml` 内で disabled_rules として指定することで無効にすることができます。

```SampleProjectTests/.swiftlint.yml
disabled_rules:
    - force_unwrapping
    - force_try
```

`SampleProjectTests/.swiftlint.yml` は `SampleProjectTests` 配下の swift ファイル全てに適用されます。
ルートディレクトリに配置した yml ファイルと SampleProjectTests 配下に配置した yml ファイルは

- 親: .swiftlint.yml
- 子: SampleProjectTests/.swiftlint.yml

の親子関係になっており、 `SampleProjectTests` 配下には親子両方の yml ファイルで設定したルールが適用されます。親と子の設定が競合した場合、**子の yml ファイルに設定したルールの設定が優先されます**。

| ルール | SampleProjectTests で有効か |
| :--- | :---: |
| 親子ともに有効 | 有効 |
| 親子ともに無効 | 無効 |
| 親: 有効<br/>子: **無効** | 無効 |
| 親: 無効<br/>子: **有効** | 有効 |

### 実行方法

Xcode で SwiftLint を実行する場合も、コマンドライン上で SwiftLint を実行する場合、特にオプション等の追加は不要です。
Xcode上 では App Target > Build Phases > Run Script Phase で `swiftlint` コマンドを実行するだけ、コマンドライン上では `swiftlint` コマンドを実行するだけで自動的にルールが使い分けられます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149055/f2d119a7-1c6a-3c99-0a8a-bf03c5d358b2.png" width="600px">

```sh
$ swiftlint
```

`swiftlint` のサブコマンド `lint` のオプションには `--config` がありますが、ディレクトリを入れ子構造にしている場合はオプションを使用する必要はなく、むしろ指定すると `--config` オプションで指定した yml ファイルの設定で上書きされてしまいます。

# マルチモジュール構成でSwiftLintのルールを使い分ける

前述のディレクトリ構成に加え、 `ModuleA` と `ModuleB` を追加します。

```ディレクトリ構成.txt
ProjectRoot
    ├ .swiftlint.yml
    ├ SampleProject
        ├ xxx.swift
        └ yyy.swift
    ├ SampleProjectTests
        ├ .swiftlint.yml
        ├ xxxTest.swift
        └ yyyTest.swift
    ├ ModuleA
        ├ Sources
            └ aaa.swift
        └ Tests
            └ aaaTest.swift
    └ ModuleB
        ├ Sources
            └ bbb.swift
        └ Tests
            └ bbbTest.swift
```

この時、テストコードは `SampleProjectTests` ディレクトリの他に `ModuleA/Tests` と `ModuleB/Tests` の中に含まれていますが、前述のテストコード用のルールは `SampleProjectTests` ディレクトリにのみ適用され、各モジュール配下のテストコードのディレクトリには適用されません。 `ModuleA/Tests` と `ModuleB/Tests` の中のテストコードでは force_unwrapping と force_try が有効になってしまいます。

そこで、 `SampleProjectTests/.swiftlint.yml` をコピーしたものを `ModuleA/Tests` と `ModuleB/Tests` にも配置することで、 `ModuleA/Tests` と `ModuleB/Tests` にもテストコード用のルールを適用することができます。

```ディレクトリ構成(一部省略).txt
ProjectRoot
    ├ .swiftlint.yml
    ├ SampleProject
    ├ SampleProjectTests
        └ .swiftlint.yml
    ├ ModuleA
        ├ Sources
        └ Tests
            ├ .swiftlint.yml // <- SampleProjectTests/.swiftlint.yml をコピー&ペースト
            └ aaaTest.swift
    └ ModuleB
        ├ Sources
        └ Tests
            ├ .swiftlint.yml // <- SampleProjectTests/.swiftlint.yml をコピー&ペースト
            └ bbbTest.swift
```

## テストコード用の .swiftlint.yml を一括管理する

上記のように `SampleProjectTests/.swiftlint.yml` を各モジュールにコピーしたあとにテストコード用のルールを変更しようとすると、前述のディレクトリ構成では

- `SampleProjectTests/.swiftlint.yml`
- `ModuleA/Tests/.swiftlint.yml`
- `ModuleB/Tests/.swiftlint.yml`

の**3ファイルに同じ修正をする必要があります**。
テストコード用のルールを変更しようと思った時、毎回3ファイル全ての修正を忘れずにいられるかというとそんなことはないかと思います。
理想としては1つのファイルをアップデートしたら3ファイル全てに反映されるのが理想です。

そこで、 SwiftLint を実行する直前に上記3ファイルの中身が同じになるように1つのファイルを残り2箇所に複製する処理を入れます。

Xcode 上でもコマンドライン上でもコードが長くなるのを防ぐために、以下のようにシェルスクリプトとして切り出しそれを呼び出すようにします。

```run_swiftlint.sh
# SampleProjectTests/.swiftlint.yml を各モジュールのテストディレクトリに複製する
TEST_YML_PASS=SampleProjectTests/.swiftlint.yml
cp ${TEST_YML_PASS} ModuleA/Tests
cp ${TEST_YML_PASS} ModuleB/Tests

# SwiftLintを実行する
swiftlint
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149055/313449dc-794e-5298-8dd8-8bba142c5555.png" width="600px">

こうすることで、 `SampleProjectTests/.swiftlint.yml` のみを修正することで全てのモジュールのテストコードにテストコード用のルールを適用することができます。

ここで、 `ModuleA/Tests` や `ModuleB/Tests` 内の yml ファイルを修正してしまうと SwiftLint の実行時に上書きされ内容が消えてしまうため、 **yml ファイル内に SampleProjectTests 内のものを修正するようにコメントをしておく**とわかりやすいです。

```SampleProjectTests/.swiftlint.yml
# このファイルは各モジュールのテストディレクトリに複製されています。
# もし編集する際には `SampleProjectTests` のものを編集してください。
disabled_rules:
    - force_unwrapping
    - force_try
```



# 参考文献

- https://github.com/realm/SwiftLint/blob/main/README.md