SwiftLintのAnalyzeを使って高度な解析をする方法

　SwiftLintの記事はシリーズになっています。
記事を順番に読み進めると、SwiftLintを使いこなせるようになります。

- [Swiftの静的解析ツール「SwiftLint」のセットアップ方法](https://qiita.com/uhooi/items/bf888b53b4b8210108aa)
- [SwiftLintの全ルール一覧(Swift 4.2版)](https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed)
- [SwiftLintで追加・変更・廃止されたルールまとめ(Swift 4.2→5.1.2版)](https://qiita.com/uhooi/items/8e9767c2e746f4171ded)
- [SwiftLintで追加・廃止されたルールまとめ(Swift 5.1.2→5.3.1版)](https://qiita.com/uhooi/items/10b9e49d3bd6f1344cad)
- [SwiftLintのオレオレカスタムルール一覧](https://qiita.com/uhooi/items/9deb56416aaa81709f69)
- SwiftLintのAnalyzeを使って高度な解析をする方法　__←イマココ__
- [SwiftLintの設定ファイルをサーバーに配置して共有する方法](https://qiita.com/uhooi/items/b5b26caeeefd8dbe1afd)
- [SwiftLintの「Currently running SwiftLint x.y.z but configuration specified version a.b.c.」エラーの解決法](https://qiita.com/uhooi/items/b1f2f0df2822f04c2161)

## はじめに

本記事は [Swift/Kotlin愛好会 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/love_swift_kotlin) の8日目の記事です。
SwiftLintのAnalyze機能を紹介します。

## 環境

- OS：macOS Big Sur 11.0.1
- Swift：5.3.1
- Xcode：12.2 (12B45b)
- SwiftLint：0.41.0

## 本記事で説明しないこと

- SwiftLintの概要やセットアップ方法  
私が以前書いた記事を参考にしてください。  
[Swiftの静的解析ツール「SwiftLint」のセットアップ方法 - Qiita](https://qiita.com/uhooi/items/bf888b53b4b8210108aa)
- SwiftのASTについて  
そもそも私が理解できていないので説明できません。  
詳しく学びたい人は以下の動画が参考になると思います。  
[try! Swift Tokyo 2018 - AST Meta-programming - YouTube](https://www.youtube.com/watch?v=XG3yxw8lRJc)

## SwiftLintの「Analyze」とは？

かんたんにいうと「ビルドログのASTを使って解析する」機能です。

Analyzeは実験的な機能であり、いつでも変更される可能性があるとのことです。

## Analyzeの使い方

Analyzeの使い方を紹介します。

### 設定ファイルの作成

Analyze用のルールを設定ファイルの `analyzer_rules` に記述します。

```yaml:.swiftlint.yml
analyzer_rules:
  #- explicit_self # 関数は `self.` を付けずに呼び出したいため
  - unused_declaration
  - unused_import
```

公式ドキュメントで「Analyzer rule: Yes」となっているルールが対象で、私が見た限りでは以下の3つのみでした。

|ルール|説明|参考リンク|
|:--|:--|:--|
|Explicit Self|`self.` を明示的に書くべき|https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#explicit-self|
|Unused Declaration|宣言した変数やクラスなどは使われるべき|https://qiita.com/uhooi/items/8e9767c2e746f4171ded#unused-declaration|
|Unused Import|インポートされたモジュールは使われるべき|https://qiita.com/uhooi/items/7f5d6cf2b240f60ba1ed#unused-import|

私は同一クラス内の関数を `self.` なしで呼び出したいため、下の2つのみ有効にしています。

### Analyzeの実行

READMEに記載されている通り `xcodebuild` コマンドでログをファイルに出力し、それを `--compiler-log-path` オプションで渡します。

コマンドが長くなるので、私は `Makefile` に定義して `make analyze` で実行できるようにしました。
`make build-debug` はCIでビルドが通るかの確認にも使っているため、 `swiftlint analyze` に対して必要最小限にはなっていません。

```make:Makefile
PRODUCT_NAME := UhooiPicBook # 製品名を適宜変更する
PROJECT_NAME := ${PRODUCT_NAME}.xcodeproj
SCHEME_NAME := ${PRODUCT_NAME}

TEST_SDK := iphonesimulator
TEST_CONFIGURATION := Debug
TEST_PLATFORM := iOS Simulator
TEST_DEVICE ?= iPhone 12 Pro Max
TEST_OS ?= 14.2
TEST_DESTINATION := 'platform=${TEST_PLATFORM},name=${TEST_DEVICE},OS=${TEST_OS}'

XCODEBUILD_BUILD_LOG_NAME := xcodebuild_build.log

.PHONY: analyze
analyze: # Analyze with SwiftLint
	$(MAKE) build-debug
	mint run swiftlint swiftlint analyze --autocorrect --compiler-log-path ./${XCODEBUILD_BUILD_LOG_NAME}

.PHONY: build-debug
build-debug: # Xcode build for debug
	set -o pipefail \
&& xcodebuild \
-sdk ${TEST_SDK} \
-configuration ${TEST_CONFIGURATION} \
-project ${PROJECT_NAME} \
-scheme ${SCHEME_NAME} \
-destination ${TEST_DESTINATION} \
build \
| tee ./${XCODEBUILD_BUILD_LOG_NAME} \
| bundle exec xcpretty --color
```

`--autocorrect` オプションを付けると自動で修正されるのでオススメです。

`make analyze` を実行します。

```bash
$ make analyze
# ...
# `make build-debug` のログは省略
# ...
mint run swiftlint swiftlint analyze --autocorrect --compiler-log-path ./xcodebuild_build.log
Loading configuration from '.swiftlint.yml'
Correcting Swift files at paths 
Collecting 'Debug.swift' (1/23)
Collecting 'ActivityRouter.swift' (2/23)
# ...
Correcting 'MonsterListInteractor.swift' (15/23)
/Users/uhooi/Documents/Repos/GitHub/uhooi/UhooiPicBook/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift:10:1 Corrected Unused Import
Correcting 'MonsterListViewController.swift' (16/23)
# ...
Correcting 'MonsterDetailViewController.swift' (22/23)
Correcting 'SceneDelegate.swift' (23/23)
Done correcting 23 files!
```

`.../SpotlightRepository.swift:10:1 Corrected Unused Import` のログからわかる通り、 `SpotlightRepository.swift` の10行1列目に未使用のモジュールがインポートされているので自動で削除されました。

`git diff` で確認します。

```diff
$ git diff
diff --git a/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift b/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift
index e435a9b..ba45ffb 100644
--- a/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift
+++ b/UhooiPicBook/Repository/Spotlight/SpotlightRepository.swift
@@ -7,7 +7,6 @@
 
 import CoreGraphics.CGGeometry
 import CoreSpotlight
-import MobileCoreServices
 
 /// @mockable
 protocol SpotlightRepository: AnyObject { // swiftlint:disable:this file_types_order
```

確かにインポート文が削除されています。

今回は一度実行済みなので1箇所のみ削除されましたが、初回は大量の `import Foundation` と `import UIKit` が削除されて気持ちいいです。

## おまけ①: 使用しているインポートが削除される場合がある

先ほど削除された `import MobileCoreServices` ですが、実はないとビルドエラーになります。

理由としては、iOS 13以前で `kUTTypeData` の呼び出しに使っているためです。

```swift:SpotlightRepository.swift
    private func createAttributeSet(title: String, contentDescription: String, thumbnailData: Data?) -> CSSearchableItemAttributeSet {
        let attributeSet: CSSearchableItemAttributeSet
        if #available(iOS 14.0, *) {
            attributeSet = .init(contentType: .data)
        } else {
            attributeSet = .init(itemContentType: kUTTypeData as String) // !!!: ここでビルドエラーになる
        }
        // ...
        // 中略
        // ...
        return attributeSet
    }
```

`xcodebuild` の `-destination` オプションでiOS 14.2を指定しているためだと思われます。
仕方ないので手動でインポート文を戻しました。

このようなケースはまれですが、自動修正する場合はコミット前にビルドが通るか確認しましょう。

## おまけ②: Analyzeの実行タイミング

READMEに記載されている通り、Analyzeには時間がかかります。
そのため、Xcodeのビルドフェイズで毎回実行するのを避けています。

現在は手動実行しているのですが、それだと漏れる可能性があるため、いい方法を考え中です。

## おわりに

これで未使用のインポート文を大量に削除できます！

以上、 [Swift/Kotlin愛好会 Advent Calendar 2020](https://qiita.com/advent-calendar/2020/love_swift_kotlin) の8日目の記事でした。
~~明日はまだ埋まっていません。ぜひ [こちら](https://qiita.com/advent-calendar/2020/love_swift_kotlin/day/new?day=9) から参加しましょう！~~
@Sho-heikun さんが参加してくださりました！
ありがとうございます :relaxed:
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ウホーイさんを1人にさせるわけにはいかないので明日分入れときました！　1人で１週間埋めたいとかあったらキャンセルしますので教えてください！w</p>&mdash; ShoHeiKun (@ShoHeiKun3) <a href="https://twitter.com/ShoHeiKun3/status/1336278758488039425?ref_src=twsrc%5Etfw">December 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 参考リンク

- [realm/SwiftLint: A tool to enforce Swift style and conventions.](https://github.com/realm/SwiftLint#analyze-experimental)
- [Feature/bump swiftlint to 0.41.0 by uhooi · Pull Request #141 · uhooi/UhooiPicBook](https://github.com/uhooi/UhooiPicBook/pull/141)
- [Swiftlintのunused_importを使う - shimastripe - g.o.a.t](https://shimastripe.goat.me/kRNLYAxyiA)