SwiftFormatとGitフックを用いてコード整形を行ってみる

# Adovent Calendarのご挨拶
Life is Tech Advent Calendar Day13 の記事です！
メンターのはるちろです！よろしくお願いします。

いつもはMinecraftのコースを主にやっていますが、今回は自分の趣味で触っているiosについての記事を紹介をしていきたいと思います。

# 初めに
みなさん、Swiftでプログラミングはされていますでしょうか？
Swiftは便利でiosから、macの開発、ましては、最近話題となったVRゴーグルVisionProにも使われています。
そんな`なうい`言語であるSwiftなのですが、複数人で開発した時にちょっとした改行のブレだったり、if文の表記の揺れなどが起きてしまいます。ですので、この記事を用いて複数人での開発に表記揺れを抑えてみてはいかがでしょうか？
また、今回はSwiftFormatをGitでCommitをした時、走らせるようにしました。その理由としては、XCodeでビルドを走らせないでコミットをしてプルリクを投げられた時に何も気づかずにマージされてしまい、コードを書いていない別の人がSwiftFormatを走らせた時に自分の該当箇所以外の場所が変更されてしまうのを防ぐためです。
色々と、ながなが話してきましたが、ぜひ、この内容を参考にして、より良いチーム開発を行いましょう。

# 流れ
1. SwiftFormatをインストールする
1. SwiftFormatの設定をする
1. GitFookを用いてCommitをした時にSwiftFormatを走らせる


# PodFileにSwiftFormatを追加してPodInstallをする

では、いつものようにPodを用いてSwiftFormatをインストールしましょう。
これはBrewなどでインストールしてもいいのですが、チーム内で環境を統一するために行います。

```rb:Podfile

def common_pods
  pod 'SwiftFormat/CLI', '~> 0.49' # 追加

end

```


下記コマンドを実行してPodをインストールする
```bash
pod install
```


## .swiftformatを作成して、設定を行う

基本的には、コマンドラインの引数の方から設定をすることも可能だが、基本的にはディレクトリごと設定できた方がいいと思うので、.swiftformatを使う。
ディレクトリにあるswiftformatを自動的に読み込んでくれるため、ルートディレクトリにあれば全ての範囲に影響を及ぼす。
しかし、ディレクトリの中の子ディレクトリは、そちらにswiftformatがあれば、そちらが優先される。

![スクリーンショット 2023-12-12 22.05.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1209385/120f6ab8-be52-89b8-c86f-bf71a90eab40.png)


### 設定一覧

基本的には、以下の記事を参考にして書いていきます。

日本語記事

https://qiita.com/yum_fishing/items/179316f59a52f075e0ff

公式

https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md

設定ファイルの参考URL

https://tech.mirrativ.stream/entry/2022/06/27/060850

今回は以下のようになりました。

```yaml
# Podsファイルの中身は書き換えるとかなりめんどくさいので触らないようにする。
--exclude Pods 

# Rule List - https://github.com/nicklockwood/SwiftFormat/blob/master/Rules.md

--swiftversion 5.8

# file options 除外するディレクトリやファイル

# format options

--commas inline # trailingCommas 有効時 複数行の配列や辞書などで、最後のカンマを無効化
--ranges no-space # spaceAroundOperators 有効時 例: "0...9" を "0 ... 9" としない

# Default Rules (enabled by default) デフォルトで有効なルール
# コメントアウト部分が有効

--disable andOperator
--disable anyObjectProtocol
--disable assertionFailures
# --disable blankLinesAroundMark
# --disable blankLinesAtEndOfScope
--disable blankLinesAtStartOfScope
--disable blankLinesBetweenScopes
--disable braces
# --disable consecutiveBlankLines
--disable consecutiveSpaces
--disable duplicateImports
--disable elseOnSameLine
--disable emptyBraces
--disable enumNamespaces
--disable extensionAccessControl
--disable fileHeader
--disable hoistPatternLet
# --disable indent
--disable initCoderUnavailable
--disable leadingDelimiters
--disable linebreakAtEndOfFile
--disable linebreaks
--disable modifierOrder
--disable numberFormatting
--disable preferKeyPath
--disable redundantBackticks
--disable redundantBreak
--disable redundantClosure
--disable redundantExtensionACL
--disable redundantFileprivate
--disable redundantGet
--disable redundantInit
--disable redundantLet
# --disable redundantLetError
--disable redundantNilInit
--disable redundantObjc
--disable redundantParens
--disable redundantPattern
--disable redundantRawValues
# --disable redundantReturn
# --disable redundantSelf
--disable redundantType
--disable redundantVoidReturnType
--disable semicolons
--disable sortDeclarations
--disable sortedImports
# --disable spaceAroundBraces
# --disable spaceAroundBrackets
# --disable spaceAroundComments
# --disable spaceAroundGenerics
# --disable spaceAroundOperators
# --disable spaceAroundParens
# --disable spaceInsideBraces
# --disable spaceInsideBrackets
# --disable spaceInsideComments
# --disable spaceInsideGenerics
# --disable spaceInsideParens
--disable strongOutlets
--disable strongifiedSelf
--disable todos
--disable trailingClosures
--disable trailingCommas
# --disable trailingSpace
--disable typeSugar
--disable unusedArguments
--disable void
--disable wrap
--disable wrapArguments
--disable wrapAttributes
--disable wrapMultilineStatementBraces
--disable yodaConditions

# Opt-in Rules (disabled by default) デフォルトで無効なルール
# コメントアウト部分は無効

#--enable acronyms
#--enable blankLinesBetweenImports
#--enable blockComments
--enable isEmpty
#--enable markTypes
#--enable organizeDeclarations
#--enable preferDouble
#--enable sortedSwitchCases
#--enable wrapConditionalBodies
#--enable wrapEnumCases
#--enable wrapSwitchCases
```

# GitのCommitを受信してSwiftFormatを動かす

Git のカスタマイズ - Git フックというものがあり、その機能を用いることで、Commitを簡単に受信できるようになる。
なお、中身はただのシェルスクリプト。つらたん。

## SwiftFormatの実行結果を取得する

基本的には変数にそのまま出力を代入していきたいが、SwiftFormatはなぜかエラー出力で出ていて、そのまま取得することができなかった。
そのため、SwiftFormatの出力結果をリダイレクションを用いてエラー出力込みで出力させて、尚且つtmpファイルに入れて変数に代入するというちょっと遠回りのやり方でやることにした。

```bash
SWIFTFORMAT_PATH="Pods/SwiftFormat/CommandLineTool/swiftformat"

# SwiftFormatを実行するディレクトリを指定 ここは人によって調整するといいです。
DIRECTORIES_TO_FORMAT=(
    "/API"
)

# SwiftFormatがインストールされているか確認
if ! command -v "$SWIFTFORMAT_PATH" &> /dev/null; then
    echo "$(tput setaf 2) エラー: SwiftFormat が見つかりません。Pod installを実行してください。"
    exit 1
fi

# SwiftFormatを実行
"$SWIFTFORMAT_PATH" "${DIRECTORIES_TO_FORMAT[@]}" --exclude Pods 2>&1| tee tmp_swiftformat_output.txt
SWIFTFORMAT_OUTPUT=$(cat tmp_swiftformat_output.txt)
rm tmp_swiftformat_output.txt
```

あとは、SwiftFormatの中身をGrepと正規表現を用いて、フォーマットを行ったファイル数を取得して、フォーマットを行ったファイル数が０を超えていれば、エラーで落として、ファイルの中身を再度確認してもらうことにした。

```bash
# SwiftFormatが実行されたファイル数を取得
FORMATTED_COUNT=$(echo "$SWIFTFORMAT_OUTPUT" | grep -oE '([0-9]+)/[0-9]+ files formatted' | cut -d'/' -f1)

#　SwiftFormatが実行されたファイル数が0より大きい場合はエラーを出力
if [ "$FORMATTED_COUNT" -gt 0 ]; then
	echo "$(tput setaf 4) お願い: SwiftFormatにより自動で整形されました。整形結果を確認して再度コミットしてください。"
	exit 1
fi

exit 0
```

.git/hooksディレクトリが基本的なHooksを保存する場所だが、そのディレクトリはバージョン管理を行うことができないため、バージョン管理ができるプロジェクトルート直下の.githooksというディレクトリを作成しその場所をhooksが置いてあると認識させる必要があった。

```bash
$ git config --local core.hooksPath .githooks
$ chmod -R +x .githooks/
```

## SwiftFormatの対象ディレクトリの指定方法

今回は、すべてのファイルがいきなり変わって、エラーの大量発生を防ぐためにホワイトリスト制でおこないました。

SwiftFormatの対象ディレクトリは、`.githooks/pre-commit`に記述しています。
下記場所に記述しているので、必要に応じて変更しましょう。
プロジェクトルートからの相対パスで指定してください。

```bash
# SwiftFormatを実行するディレクトリを指定
DIRECTORIES_TO_FORMAT=(
    "/API"
)
```


# まとめ
SwiftFormatを持ちいてプログラムを成形させることに成功させることができました。
成形を行うにあたって、実際にどのように動くのかをルールを確認しながら導入の検討を行いました。
個人で書くプログラムにおいては、そこまで影響範囲などを考えずにこういったプラグインを導入してしまうが、影響範囲を考えないと、自分が思っていた以上に動作の変更が加えられエラーやバグの温床になりやすいため、成形をされたコードを鵜呑みにせず、しっかりと自分自身で確認することが大事だと感じました。
ぜひ、これを参考にして、より便利なSwiftライフを送ってください。

## 参考資料

https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF

https://qiita.com/YusukeHosonuma/items/807dbdbb809eb8feec1d

https://www.server-memo.net/shellscript/bash_redirect.html

https://www.delftstack.com/ja/howto/linux/change-color-of-echo-in-bash/

https://www.farend.co.jp/blog/2020/04/git-hook/

https://qiita.com/ken_sasaki2/items/a52f9dd24e64c57c0db9