コードフォーマッターのSwiftFormat
と、コード検査のSwiftLint
を使って、手軽に綺麗なSwiftのソースコードを維持しよう、という記事です。SwiftFormat
もSwiftLint
も、共に簡単に使えるツールなのですが、デフォルトの設定のまま使うと色々困る点があるので、それらの問題を解消する設定について説明します。
登場するツール
- SwiftFormat: ソースコードを自動整形(フォーマット)してくれるツール
- SwiftLint: ソースコードを検査してくれるツール
この二つのツールを活用して、ソースコードのスタイルを効率的に統一していこう、という試みです。
それぞれが良しとするフォーマット/スタイルが違う問題
ただし、この二つのツールは独立してAppleではなくサードパーティによって開発されているものなので、デフォルトの設定だと下記のポイントが微妙に揃っていません。
- Xcodeが生成するソースコードのフォーマット
- SwiftFormatによって整形されるフォーマット
- SwiftLintが警告を出す条件
ソースコードのフォーマット、スタイルに関しては、どれが正解・あるいは優れていると簡単に言えるものではありませんが、プロジェクトを進める上ではどれか一つに統一しておくべきです。ここでは、Xcodeが生成するコードのスタイルは絶対神という方針で、なるべくXcodeのフォーマット/スタイルに合わせる形で調整・設定を行っていきます。
SwiftFormatでコードの自動整形をする
インストール
CocoaPods
を使ってインストールするのがおすすめです。
pod 'SwiftFormat/CLI'
HomeBrewでインストールすることもできますし、サクッと試すだけならそのほうが楽なのですが、後述の方法で「ビルド時/テスト時に自動でフォーマットをかける」ようにするにはCocoaPods
でインストールした方が便利です。(pod install
をするだけで、どのビルド環境でも使えるようになるため)
CocoaPods
でインストールをすると、${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat
に実行用のバイナリがインストールされます。インストールされて使える状態になっているか、swiftformat
を実行して試してみましょう。
cd プロジェクトのルートディレクトリ
Pods/SwiftFormat/CommandLineTool/swiftformat --version
基本的な使い方(コマンドラインから使ってみる)
SwiftFormat
では、引数にソースコードがあるディレクトリやファイルを指定すると、そのディレクトリ/ファイルをルールに従ってフォーマットしてくれます。
Pods/SwiftFormat/CommandLineTool/swiftformat SwiftFormatExp/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
}
}
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
が生成するスタイルにあうようにオプションを指定します。
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 を取り除かないようにする, 配列の要素の最後にカンマをつけないようにする |
これらのオプションを指定してフォーマットを実行すると下記のようになり、ちゃんとメソッドの引数名が保持されるようになります。
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
を追加します。
追加した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
より前に実行されるように移動します。
ここまでできたら、適当なソースコードに無駄なインデントやスペースなどを入れた状態でテストを実行してみましょう。そのタイミングでソースコードがフォーマットされた状態になっていたらSwiftFormat
の設定は完了です。
SwiftLintで問題点をチェックする
インストール
こちらも、HomeBrewとCocoaPodsどちらでもインストールができますが、SwiftFormat
とあわせてCocoaPods
でインストールしましょう。
pod 'SwiftLint'
こちらもCocoaPods
でインストールをすると、${PODS_ROOT}/SwiftLint/swiftlint
に実行用のバイナリがインストールされます。インストールされて使える状態になっているか、実行して試してみましょう。
cd プロジェクトのルートディレクトリ
Pods/SwiftLint/swiftlint version
基本的な使い方(コマンドラインから使ってみる)
SwiftLint
の場合は、基本的に実行時のディレクトリ以下にあるファイルを対象にチェックを行うため、対象ファイル/ディレクトリを引数で指定する必要はありません。
cd プロジェクトのルートディレクトリ
Pods/SwiftLint/swiftlint
実行すると、問題箇所が出力されます。
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
を追加します。
CocoaPods
でインストールしたswiftlint
コマンドが実行されるようにします。
"${PODS_ROOT}/SwiftLint/swiftlint"
もし、SwiftFormat
をビルド毎、保存毎に行うようにしている場合は、フォーマット後のコードに対してチェックを行うようにSwiftLint
はSwiftFormat
の後に実行されるように並べておきます。
この状態でXcodeでビルドを行うと、SwiftLintの結果が警告として表示されるようになります。
設定の調整
ただし、こちらもデフォルトの設定だとXcode
が生成したコードのスタイルに対しても警告が行われてしまいます。Xcodeが生成するコードが標準で警告されるような設定では、Xcodeがコードを生成する度に毎回スタイルを直すのも面倒ですし、かといって生成の度に警告が生まれ、他の警告が埋もれてしまうのも困ります。
ここでもXcodeが生成するコードのスタイルは絶対神という方針で、いくつかルールを設定して不要な警告を緩和してやります。同時に、警告が出たところでソースコードをいじりたくない外部ライブラリが含まれるCarthage
やPods
ディレクトリをチェックの対象外とします。
SwiftLint
では、プロジェクトルートディレクトリに.swiftlint.yml
ファイルを設置し、そこにルールを記述すると、その内容に従ってチェックを行ってくれます。
excluded:
- Carthage
- Pods
disabled_rules:
- line_length
- trailing_whitespace
上の設定では、空白行のインデントの許容のためにtrailing_whitespace
を、またline_length
を無効化して一行あたりの長さに対する警告を無効化しています。一行あたりの文字数に関しては、無効化するのではなく、長さを長めに指定する形でも良いかもしれません。
excluded:
- Carthage
- Pods
disabled_rules:
- trailing_whitespace
line_length: 150
まとめ
SwiftFormat
とSwiftLint
の力を借りて、Happy Swift Coding