環境
- Xcode 14.3.1
- SwiftLint 0.52.4
はじめに
SwiftLintをプロジェクトに導入している場合、プロジェクト構成はルートディレクトリに .swiftlint.yml
を配置し以下のようになっているかと思います。
ProjectRoot
├ .swiftlint.yml
├ SampleProject
├ xxx.swift
└ yyy.swift
└ SampleProjectTests
├ xxxTest.swift
└ yyyTest.swift
ルールの使い分け
なぜルールを使い分けたいのか
プロダクトの不具合を防ぐために force_unwrapping や force_try といったルールを適用しているケースを考えます。
opt_in_rules:
- force_unwrapping
# force_try はデフォルトで有効
テストコードはデコードしたりキャストしてもテストが成功するように記述しているはずなので強制アンラップや try!
を書いても問題ないはずですが、プロダクトコードと同じルールが適用されてしまうと以下のように警告やエラーが表示されてしまいます。

それぞれの警告やエラーに対して // swiftlint:disable:this force_xxx
とコメントを追加すればルールを無視することは可能ですが、テストを増やすたびにいちいちコメントを追加するのもとても面倒です。
プロダクトの安全性を高めるためのLintによってテストコードを書くことを阻害されるのは本末転倒です。
テストコードにのみ別のルールを割り当てる
ルートディレクトリとは別にテストコードのディレクトリに対して、テストコード用に新しく作った .swiftlint.yml
を配置します。
(以下、 SampleProjectTests/.swiftlint.yml
と記載します)
ProjectRoot
├ .swiftlint.yml
├ SampleProject
├ xxx.swift
└ yyy.swift
└ SampleProjectTests
├ .swiftlint.yml // <- 追加
├ xxxTest.swift
└ yyyTest.swift
テストコードでのみ force_unwrapping と force_try を無効にするには、 SampleProjectTests/.swiftlint.yml
内で disabled_rules として指定することで無効にすることができます。
disabled_rules:
- force_unwrapping
- force_try
SampleProjectTests/.swiftlint.yml
は SampleProjectTests
配下の swift ファイル全てに適用されます。
ルートディレクトリに配置した yml ファイルと SampleProjectTests 配下に配置した yml ファイルは
- 親: .swiftlint.yml
- 子: SampleProjectTests/.swiftlint.yml
の親子関係になっており、 SampleProjectTests
配下には親子両方の yml ファイルで設定したルールが適用されます。親と子の設定が競合した場合、子の yml ファイルに設定したルールの設定が優先されます。
ルール | SampleProjectTests で有効か |
---|---|
親子ともに有効 | 有効 |
親子ともに無効 | 無効 |
親: 有効 子: 無効 |
無効 |
親: 無効 子: 有効 |
有効 |
実行方法
Xcode で SwiftLint を実行する場合も、コマンドライン上で SwiftLint を実行する場合、特にオプション等の追加は不要です。
Xcode上 では App Target > Build Phases > Run Script Phase で swiftlint
コマンドを実行するだけ、コマンドライン上では swiftlint
コマンドを実行するだけで自動的にルールが使い分けられます。

$ swiftlint
swiftlint
のサブコマンド lint
のオプションには --config
がありますが、ディレクトリを入れ子構造にしている場合はオプションを使用する必要はなく、むしろ指定すると --config
オプションで指定した yml ファイルの設定で上書きされてしまいます。
マルチモジュール構成でSwiftLintのルールを使い分ける
前述のディレクトリ構成に加え、 ModuleA
と ModuleB
を追加します。
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
にもテストコード用のルールを適用することができます。
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 上でもコマンドライン上でもコードが長くなるのを防ぐために、以下のようにシェルスクリプトとして切り出しそれを呼び出すようにします。
# SampleProjectTests/.swiftlint.yml を各モジュールのテストディレクトリに複製する
TEST_YML_PASS=SampleProjectTests/.swiftlint.yml
cp ${TEST_YML_PASS} ModuleA/Tests
cp ${TEST_YML_PASS} ModuleB/Tests
# SwiftLintを実行する
swiftlint

こうすることで、 SampleProjectTests/.swiftlint.yml
のみを修正することで全てのモジュールのテストコードにテストコード用のルールを適用することができます。
ここで、 ModuleA/Tests
や ModuleB/Tests
内の yml ファイルを修正してしまうと SwiftLint の実行時に上書きされ内容が消えてしまうため、 yml ファイル内に SampleProjectTests 内のものを修正するようにコメントをしておくとわかりやすいです。
# このファイルは各モジュールのテストディレクトリに複製されています。
# もし編集する際には `SampleProjectTests` のものを編集してください。
disabled_rules:
- force_unwrapping
- force_try