SwiftPMでのSwift製Danger導入

# はじめに
前回書いたRuby版のDanger導入方法に続き、今回はSwift製のDangerであるDanger Swiftでの導入を試しためましたので、その方法やハマりポイントについて記事にしようと思います。

# 環境
- Xcode16
- Swift6.0
- macOS 15.0
- CI: GitHub Actions

# 導入方法

基本的に公式の導入方法を参考に進めていきます。
https://danger.systems/swift/guides/getting_started

## 1. SPMでDanger Swiftの依存を持ったダイナミックライブラリを作成する

```swift
dependencies: [
    .package(url: "https://github.com/danger/swift.git", exact: "3.21.1")
],
targets: [
        .target(
            name: "DangerDependencies",
            dependencies: ["Danger"]
        )
    ]
```

- dependencies に Danger を追加する
- Danger に依存したターゲットを追加

ただ、公式の方法でtargetにDangerへの依存を追加しようとしてもビルドエラーが出てしまい、以下のように`name`と`package`を指定してやる必要がありました。
```diff_swift
targets: [
    .target(
        name: "DangerDependencies",
-       dependencies: ["Danger"]
+       dependencies: [
+           .product(name: "Danger", package: "swift")
+       ]
    )
]
```

最終的な Package.swift の内容が以下になります。

```swift
// swift-tools-version: 6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Dangerfile",
    platforms: [.macOS(.v14)],
    products: [
        .library(
            name: "Dangerfile",
            type: .dynamic,
            targets: ["Dangerfile"]
        ),
    ],
    dependencies: [
        .package(url: "https://github.com/danger/swift.git", exact: "3.21.1")
    ],
    targets: [
        .target(
            name: "Dangerfile",
            dependencies: [
                .product(name: "Danger", package: "swift")
            ]
        )
    ]
)
```


[記事](https://qiita.com/lovee/items/42aa886605a65938b60e#danger-%E7%94%A8%E3%81%AE%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%83%BC%E3%82%92%E4%BD%9C%E6%88%90)を参考に、ルートディレクトリに`Dangerfile`フォルダーを作り、
先ほど作成したSPMライブラリを全て突っ込みます。

ここまでで、Danger Swiftをコマンドラインで実行するためのダイナミックライブラリの作成準備までできました。

## 2. Dangerfileを作る

次に`Dangerfile`というものを作ります。
[前回の記事](https://qiita.com/stotic-dev/items/1e3ae73b9fc7704f6b82)でDangerfileがどういうものかは説明していますので、よければこちらもご覧ください。
ここでは何をどのように自動レビューするのかを定義するファイルという説明に留めます。

編集方法は[公式ドキュメント](https://danger.systems/swift/guides/about_the_dangerfile#:~:text=Dangerfile.swift%E3%80%82-,%23,-Dangerfile%20%E3%81%AE%E7%B7%A8%E9%9B%86)を参照したところ、以下コマンドを打てば良さそうです。

```
swift build
swift run danger-swift edit
```

ここで、以下エラーにつまづきました。

```
Building for debugging...
[1/1] Write swift-version-7ECE0B649383FE20.txt
Build of product 'danger-swift' complete! (0.28s)
ERROR: Could not find a libDanger to link against at any of: [".build/debug", ".build/x86_64-unknown-linux/debug", ".build/release", "/usr/local/lib/danger", "/opt/homebrew/lib/danger", "/Users/satoutaichi/.local/share/mise/installs/danger-swift/3.21.1/lib/danger"]
Or via Homebrew, or Marathon, or Mise
```

`libDanger`がないと言っており、`.build/debug`には`libDangerfile`というファイルがあったので、Package.swiftのlibrary名に関係していると思い、library名を`Danger`に修正したところ、`libDanger`が作成されるようになりました。

```diff swift
products: [
        .library(
-           name: "Dangerfile",
+           name: "Danger",
            type: .dynamic,
            targets: ["MachoDanger"]
        ),
    ],
```

上記に修正したら、再度以下コマンドを実行します。
```
swift build
swift run danger-swift edit
```

上記コマンドを打つと、xcode が開いて Dangerfile を編集できるようになりました。
以下のような画面が開きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/0859e798-a8d0-4733-89f0-0b3a532d2429.png)

ここで、`Sources/Dangerfile/main.swift`を編集して以下のようにしてみます。

```swift
import Danger

let danger = Danger()
if let github = danger.github {
    // PRのタイトルが "WIP" を含んでいる場合、警告を出す
    if github.pullRequest.title.contains("WIP") {
        warn("PRが 'WIP' のままです。完了したらタイトルを変更してください。")
    }
    
    // PRの説明が短すぎる場合に警告を出す
    if let body = github.pullRequest.body, body.count < 100 {
        warn("PRの説明が短すぎます。詳細を追加してください。")
    }
    
    // 変更行が500行を超える場合に警告を出す
    let changeFilesCount = github.pullRequest.additions ?? 0
    if changeFilesCount > 500 {
        warn("変更行数が500行を超えています(修正行: \(changeFilesCount))。PRを小さく分けられるか検討してください。")
    }
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/1b354504-0d1d-40aa-9178-f51cff7fdc98.png)

上記XcodeでDangerfile修正後、`swift run danger-swift edit`を実行したコンソールで、Enterを押下するとDangerfileの編集が完了します。(逆にいうとEnterを押して正しくeditモードを終了しないと保存されないので注意が必要です！)

```console
ℹ️  Danger will keep running, in order to commit any changes you make in Xcode back to the original script file
   Press the return key once you're done
```

## 3. DangerJSをインストールする
一旦ローカル確認ようにDangerJSをインストールします。

インストールは以下コマンドを実行すれば良いです。
```
npm install -g danger
```

https://github.com/danger/swift#:~:text=and%20your%20plugins%3A-,Install,-Danger%20JS

上記インストールするには`npm`が必要になるので、もし入っていなければ以下あたりを参考にすれば良いかなと思います。

https://fromscratch-y.work/blog/programming/mac-nodejs-install/

## 4. ローカルでDangerの動作確認をする

公式のREADMEに倣って、以下コマンドを実行してみます。
```console
swift run danger-swift local --base main
```

```
% swift run danger-swift local --base BR_develop-1.0 --verbose
Building for debugging...
[1/1] Write swift-version-7ECE0B649383FE20.txt
Build of product 'danger-swift' complete! (0.21s)
Launching Danger Swift local (v3.20.2)
Finding out where the danger executable is
Running: /Users/satoutaichi/.nvm/versions/node/v18.16.0/bin/danger local --process .build/debug/danger-swift --passURLForDSL --base BR_develop-1.0 --verbose
/var/folders/dz/_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger/12C0154A-9691-458D-94AB-EE1F0A8D8FF0/_tmp_dangerfile.swift:3:14: error: cannot call value of non-function type 'module<Danger>'
1 | import Danger
2 | import Foundation
3 | let danger = Danger()
  |              `- error: cannot call value of non-function type 'module<Danger>'
4 |

Launching Danger Swift runner (v3.20.2)
Got URL for JSON: /var/folders/dz/_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger-dsl-c1899073.json
Created a temporary file for the Dangerfile DSL at: /var/folders/dz/_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger-dsl-c1899073.json
Running Dangerfile at: Dangerfile.swift
Preparing to compile
Running: /Applications/Xcode16.2.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift --driver-mode=swift -L /Users/satoutaichi/work/product/AkiraDiary/Danger/Dangerfile/.build/debug -I /Users/satoutaichi/work/product/AkiraDiary/Danger/Dangerfile/.build/debug -lDanger /var/folders/dz/_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger/12C0154A-9691-458D-94AB-EE1F0A8D8FF0/_tmp_dangerfile.swift runner /Users/satoutaichi/.nvm/versions/node/v18.16.0/lib/node_modules/danger/distribution/commands/danger-local.js --process .build/debug/danger-swift --passURLForDSL --base BR_develop-1.0 --verbose /var/folders/dz/_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger-dsl-c1899073.json /var/folders/dz/_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger/12C0154A-9691-458D-94AB-EE1F0A8D8FF0/danger-response.json
Completed evaluation
ERROR: Dangerfile eval failed at Dangerfile.swift
ERROR: Could not get the results JSON file at /var/folders/dz/_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger/12C0154A-9691-458D-94AB-EE1F0A8D8FF0/danger-response.json

Danger: ⅹ Failing the build, there is 1 fail.
## Failures
`danger-swift` failed.
## Markdowns
### Log

<details>



/var/folders/dz/\_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger/12C0154A-9691-458D-94AB-EE1F0A8D8FF0/\_tmp_dangerfile.swift:3:14: error: cannot call value of non-function type 'module<Danger>'
1 | import Danger
2 | import Foundation
3 | let danger = Danger()
| `- error: cannot call value of non-function type 'module<Danger>'
4 |
Launching Danger Swift runner (v3.20.2)
Got URL for JSON: /var/folders/dz/\_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger-dsl-c1899073.json
Created a temporary file for the Dangerfile DSL at: /var/folders/dz/\_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger-dsl-c1899073.json
Running Dangerfile at: Dangerfile.swift
Preparing to compile
Running: /Applications/Xcode16.2.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift --driver-mode=swift -L /Users/satoutaichi/work/product/AkiraDiary/Danger/Dangerfile/.build/debug -I /Users/satoutaichi/work/product/AkiraDiary/Danger/Dangerfile/.build/debug -lDanger /var/folders/dz/\_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger/12C0154A-9691-458D-94AB-EE1F0A8D8FF0/\_tmp_dangerfile.swift runner /Users/satoutaichi/.nvm/versions/node/v18.16.0/lib/node_modules/danger/distribution/commands/danger-local.js --process .build/debug/danger-swift --passURLForDSL --base BR_develop-1.0 --verbose /var/folders/dz/\_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger-dsl-c1899073.json /var/folders/dz/\_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger/12C0154A-9691-458D-94AB-EE1F0A8D8FF0/danger-response.json
Completed evaluation
ERROR: Dangerfile eval failed at Dangerfile.swift
ERROR: Could not get the results JSON file at /var/folders/dz/\_vzvz7yn6hg_by51jhrw1tz80000gn/T/danger/12C0154A-9691-458D-94AB-EE1F0A8D8FF0/danger-response.json


</details>
```

この問題は、Package.swiftのlibrary名に原因があったようで、
名前を`DangerDeps`にすると 動作するようになりました。

```diff_swift
products: [
        .library(
-           name: "Danger",
+           name: "DangerDeps",
            type: .dynamic,
            targets: ["Dangerfile"]
        ),
    ],
```

Danger Swift のコードを確認してみたところ、libraly 名の頭に`DangerDeps`が付いていないと`SPMDanger`を初期化せず実行内容が少し変わっているため、そこに原因がありそうでした。
(この問題はXcode16系の環境でしか起きず、Xcode15系の場合では発生しなかったです！罠すぎる、、)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/1015d177-a03c-4a7b-a514-1eac6a6fd70f.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/6616bf02-d4e2-44ce-8bb6-b0161a62fb56.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/a3694053-4745-47a1-9559-3cfbd9aacbfd.png)


ただ、[公式の README](https://github.com/danger/swift) でも library 名は`DangerDeps`が付いていたため公式の方法に準拠するのが良さそうですね。

改めて、ローカル確認をしてみると、うまくいきました。

```console
% swift run danger-swift local --base main
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/.build/checkouts/octokit.swift/OctoKit/Info.plist
Building for debugging...
[1/1] Write swift-version-7ECE0B649383FE20.txt
Build of product 'danger-swift' complete! (0.61s)
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/.build/checkouts/octokit.swift/OctoKit/Info.plist



Danger: ✓ passed review, received no feedback.
```

以下を実行すると、実際のPRに対してローカルでDangerの自動レビュー結果を見ることができますのでこれも試してみます。

```console
swift run danger-swift pr <PRのURL>
```

ただこのコマンドでは実際のPRを参照する都合上、GitHub APIを使用することになるので、GitHub API実行用のGitHubトークンが必要になります。
GitHubトークンを`DANGER_GITHUB_API_TOKEN`という名前の環境変数に設定することで動こかせるようになるはずです。

```console
export DANGER_GITHUB_API_TOKEN=<GitHubトークン>
```

```
% swift run danger-swift pr https://github.com/stotic-dev/DangerSwiftModule/pull/2
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/.build/checkouts/octokit.swift/OctoKit/Info.plist
Building for debugging...
[1/1] Write swift-version-7ECE0B649383FE20.txt
Build of product 'danger-swift' complete! (0.66s)
Starting Danger PR on stotic-dev/DangerSwiftModule#2
You don't have a DANGER_GITHUB_API_TOKEN set up, this is optional, but TBH, you want to do this
Check out: http://danger.systems/js/guides/the_dangerfile.html#working-on-your-dangerfile
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/.build/checkouts/octokit.swift/OctoKit/Info.plist



Danger: ✓ found only warnings, not failing the build
## Warnings
PRの説明が短すぎます。詳細を追加してください。
-
変更行数が500行を超えています(修正行: 16239)。PRを小さく分けられるか検討してください。
```

一部 Warning が出ているが、ちゃんと動いてくれていそうです！

## 5. CIでDanger Swiftを動かせるようにする

今回は GitHub Actions で動かす方法で試します。
以下は GitHub Actions のワークフローファイルです。

```yaml
name: danger-ci_xcode_16

on:
  pull_request:
    branches: "*"

jobs:
  build:
    # 実行環境はmacosに設定
    runs-on: macos-latest

    steps:
      # fetch-depth の計算
      - run: echo "fetch_depth=$(( commits + 1 ))" >> $GITHUB_ENV
        env:
          commits: ${{ github.event.pull_request.commits }}

      # チェックアウト(リポジトリからソースコードを取得)
      - uses: actions/checkout@v4.2.2
        with:
          fetch-depth: ${{ env.fetch_depth }}

      # Xcodeのバージョン一覧を出力
      - name: Show Xcode list
        run: ls /Applications | grep 'Xcode'

      # Xcodeのバージョンを指定
      - name: Set Xcode Path
        run: sudo xcode-select --switch /Applications/Xcode_16.2.app/Contents/Developer

      # 現在のXcodeバージョンを確認
      - name: Show Xcode version
        run: xcodebuild -version

      # danger jsのインストール
      - name: Install Danger JS
        run: npm install -g danger

      # dangerを実行
      - name: Analytics with danger
        run: |
          swift run --disable-sandbox danger-swift ci
        env:
          # Dangerはプルリクエストを読み取ってコメントをするので、必要な権限を持ったGithubトークンを渡してあげる必要がある
          DANGER_GITHUB_API_TOKEN: ${{ secrets.DANGER_BOT_GITHUB_TOKEN }}
```

### 補足
#### リポジトリのチェックアウト
PRのベースブランチと比較して何かしらのレビューをしたいケースがあると思うので、[こちらの記事](https://zenn.dev/nownabe/articles/fetch-minimum-depth-in-github-workflows)で、ベースブランチ - 現在のPRの情報までをチェックアウトするようにします。

#### `.build`をリポジトリにPushする
SPMでDangerを導入すると、どうしてもSPMのビルド時間がかかってしまうので、`swift build`の成果物である`.build`自体もリポジトリにPushすることで、GitHub ActionsのステップではSPMのビルドは省略し、いきなり`swift run`によるDangerの実行を行うようにしています。
これは、[公式](https://github.com/danger/swift#:~:text=(-,Recommended,-)%20cache%20the%20.build)でも推奨する方法です。

# Danger で SwiftLint の警告やエラーが起きていないかも自動レビューしてもらう

Danger Swift は Ruby 版と違い、SwiftLint の Plugin はデフォルトで入っています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/4588d13c-0ab0-41f6-bf4f-f13861944151.png)


ただ、Danger で SwiftLint を使用するときに内部的に swiftlint コマンドを使用する都合上、swiftlint のバイナリを容易する必要があります。
今回は[こちらの記事](https://qiita.com/lovee/items/42aa886605a65938b60e#fn-package-path)を参考に、SPM に SwiftLint の依存を追加して SwiftLint のバイナリを使えるようにします。

具体的には Package.swift を以下のようにします。

```diff_swift
import PackageDescription

let package = Package(
    name: "Dangerfile",
    platforms: [.macOS(.v14)],
    products: [
        .library(name: "DangerDeps",
                 type: .dynamic,
                 targets: ["Dangerfile"]),
    ],
    dependencies: [
        .package(url: "https://github.com/danger/swift.git", exact: "3.21.1"),
+       .package(url: "https://github.com/SimplyDanny/SwiftLintPlugins", exact: "0.58.2"),
    ],
    targets: [
        .target(
            name: "Dangerfile",
            dependencies: [
                .product(name: "Danger", package: "swift")
            ]
        )
    ]
)
```

SwiftLint には以下のリポジトリのものもあるが、あくまで swiftlint のバイナリを使いたいだけで以下は不要な依存が含まれておりサイズが大きすぎるので、`SwiftLintPlugins`の方を使用しています。

```
https://github.com/realm/SwiftLint
```

Dangerfile には以下を追記します。

```diff_swift
+let lintPath = SwiftLint.SwiftlintPath.bin(".build/artifacts/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-0.58.2-macos/bin/swiftlint")
+
+SwiftLint.lint(.modifiedAndCreatedFiles(directory: "<Lint対象のディレクトリ>"),
+               inline: true,
+               configFile: "<リポジトリのルートディレクトリから見た.swiftlint.ymlのファイルパス>",
+               swiftlintPath: lintPath)
```

`lint`関数の一つ目の引数には、`SwiftLint.LintStyle`を設定する。
定義は以下のような感じになっています。

```swift
public enum LintStyle {
    /// Lints all the files instead of only the modified and created files.
    /// - Parameters:
    ///   - directory: Optional property to set the --path to execute at.
    case all(directory: String?)
    /// Only lints the modified and created files with `.swift` extension.
    /// - Parameters:
    ///   - directory: Optional property to set the --path to execute at.
    case modifiedAndCreatedFiles(directory: String?)
    /// Lints only the given files. This can be useful to do some manual filtering.
    /// The files will be filtered on `.swift` only.
    case files([File])
}
```

今回設定した以下は、PR の変更ファイルの内、`directory`に指定したディレクトリに含まれるファイルを対象に Lint することを表します。
`.modifiedAndCreatedFiles(directory: "<Lint対象のディレクトリ>")`

`inline`の引数では、Lint で検知した警告やエラーをインラインにコメント(つまり問題のあるコードの行に対してレビューコメントをする)するかどうかを表します。
SwiftLint の静的解析によるレビューに関しては、具体的にどの部分が問題なのかを知りたいので、大抵の場合はインラインでのコメントにした方が、幸せになるケースが多いと思います。

`configFile`の引数には、どのようなルールで静的解析するのかを定義する`.swiftlint.yml`のパスを指定します。
もしこの引数を設定しない場合は、SwiftLint のデフォルトのルールで静的解析することになるので必須の引数ではありません。
(swiftlint コマンドの`--config`オプションを指定しないのと同じ)

`swiftlintPath`の引数では、swiftlint を実行するためのバイナリを指定するための情報を設定します。
この引数の型は`SwiftLint.SwiftlintPath`で内容は以下のようになっています。

```swift
public enum SwiftlintPath {
    case swiftPackage(String)
    case bin(String)
    var command: String {
        switch self {
        case let .bin(path):
            return path
        case let .swiftPackage(path):
            return "swift run --package-path \(path) swiftlint"
        }
    }
}
```

`swiftPackage(String)`は`swift run`コマンドで swiftlint を実行できる Package.swift のディレクトリを指定することで、SwiftlintPath を正しく設定することができます。

`bin(String)`は swiftlint のバイナリの相対パス(Dangerfile から見た)を指定することで SwiftlintPath を正しく設定することができます。

今回だと用意した SwiftLint の依存が`SwiftLintPlugins`でこのライブラリの Package.swift には`executable`な product がなく`swift run`から実行することができないので、
`bin(String)`で swiftlint のバイナリを直接指定します。

```swift
let lintPath = SwiftLint.SwiftlintPath.bin(".build/artifacts/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-0.58.2-macos/bin/swiftlint")
```

Package.swift と Dangerfile の修正は一旦完了したので、ローカルで動作するか確認してみます。

```console
swift run danger-swift local --cwd .. --base main
```

ただこれだと以下エラーが発生します。

```
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/Dangerfile/.build/checkouts/octokit.swift/OctoKit/Info.plist
Building for debugging...
[49/49] Applying danger-swift
Build of product 'danger-swift' complete! (3.37s)
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/Dangerfile/.build/checkouts/octokit.swift/OctoKit/Info.plist

The operation couldn’t be completed. (SwiftLintCore.Issue error 15.)
Could not read configuration: file Configuration.swift, line 289

/bin/sh: line 1: 68032 Abort trap: 6           .build/artifacts/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-0.58.2-macos/bin/swiftlint lint --reporter json --quiet --config "DangerSample/.swiftlint.yml" --use-script-input-files --force-exclude > /var/folders/dz/_vzvz7yn6hg_by51jhrw1tz80000gn/T/swiftlintReport.json



Danger: ✓ passed review, received no feedback.
```

これは Dangerfile で指定した`.swiftlint.yml`のパスを指定が誤っており、見つからなかったためでした。

`.swiftlint.yml`のパス指定を以下のように修正したら、この問題は解消されました。

```diff_swift
let lintPath = SwiftLint.SwiftlintPath.bin(".build/artifacts/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-0.58.2-macos/bin/swiftlint")

SwiftLint.lint(.modifiedAndCreatedFiles(directory: "<Lint対象のディレクトリ>"),
                inline: true,
-               configFile: "<リポジトリのルートディレクトリから見た.swiftlint.ymlのファイルパス>",
+               configFile: "<Dangerfileの場所から相対的.swiftlint.ymlのファイルパス>",
                swiftlintPath: lintPath)
```

これで再度`danger-swift local`を実行します。

先ほどの事象は解消されたが、Lint 対象のファイルが読み込めず静的解析ができない問題にぶち当たりました。

```console
% swift run danger-swift local --cwd ../ --base main
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/Dangerfile/.build/checkouts/octokit.swift/OctoKit/Info.plist
Building for debugging...
[1/1] Write swift-version-7ECE0B649383FE20.txt
Build of product 'danger-swift' complete! (0.52s)
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/Dangerfile/.build/checkouts/octokit.swift/OctoKit/Info.plist

Could not read contents of `/Users/satoutaichi/work/swift/DangerSwiftModule/Dangerfile/DangerSample/DangerSample/ContentView.swift`



Danger: ✓ passed review, received no feedback.
```

以下ログから読み取れるのは、存在しないパスを参照しに行っているということ。

```
Could not read contents of `/Users/satoutaichi/work/swift/DangerSwiftModule/Dangerfile/DangerSample/DangerSample/ContentView.swift`
```

danger-swift の`--cwd`でワーキングディレクトリを指定できるらしいのですが、これがなぜか効かないため、swiftlint もリポジトリの`ルートディレクトリ/Dangerfile`から実行されており Lint 対象のファイルをうまく指定できていないっぽいです。(この問題わかる人いたら教えて欲しいです、、)

https://github.com/danger/swift#current-working-directory

これの根本的な解決法は現状思いついてないですが、Dangerfile にある SPM のファイルたちをリポジトリのルートディレクトリに配置するとうまく動くようになりました。

修正前のディレクトリ構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/84c293b6-c62f-48c4-a197-35839e96b1d1.png)

修正後のディレクトリ構成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3359094/2b614ee1-732f-4ec9-aaf9-bf5d09f41f50.png)


Dangerfile をリポジトリのルートディレクトリに移動したので、`.swiftlint.yml`の指定パスも以下のように変更します。

```diff_swift
let lintPath = SwiftLint.SwiftlintPath.bin(".build/artifacts/swiftlintplugins/SwiftLintBinary/SwiftLintBinary.artifactbundle/swiftlint-0.58.2-macos/bin/swiftlint")

SwiftLint.lint(.modifiedAndCreatedFiles(directory: "<Lint対象のディレクトリ>"),
                inline: true,
-               configFile: "<Dangerfileの場所から相対的.swiftlint.ymlのファイルパス>",
+               configFile: "<リポジトリのルートディレクトリから見た.swiftlint.ymlのファイルパス>",
                swiftlintPath: lintPath)
```

これで再度 local での danger-swift の動作確認を行いました。

```
 % swift run danger-swift local --base main
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/.build/checkouts/octokit.swift/OctoKit/Info.plist
Building for debugging...
[49/49] Applying danger-swift
Build of product 'danger-swift' complete! (4.18s)
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/.build/checkouts/octokit.swift/OctoKit/Info.plist



Danger: ✓ found only warnings, not failing the build
## Warnings
Line should be 110 characters or less; currently it has 130 characters (`line_length`)
-
Lines should not have trailing whitespace (`trailing_whitespace`)
-
Lines should not have trailing whitespace (`trailing_whitespace`)
-
Lines should not have trailing whitespace (`trailing_whitespace`)
-
Lines should not have trailing whitespace (`trailing_whitespace`)
-
Limit vertical whitespace to a single empty line; currently 4 (`vertical_whitespace`)
```

SwiftLint がきちんと動作してくれた！

これでリポジトリを Push すると CI でも SwiftLint の静的解析を実行して、警告やエラーがあればレビューしてくれるようになります。

## はまりポイント

SwiftLint を Danger Swift の Dangerfile で実行するようにして、danger-swift を実行すると以下のようなエラーが発生するようになる場合があります。

```
% swift run danger-swift local --base main
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/.build/checkouts/octokit.swift/OctoKit/Info.plist
Building for debugging...
[1/1] Write swift-version-7ECE0B649383FE20.txt
Build of product 'danger-swift' complete! (0.62s)
warning: 'octokit.swift': found 1 file(s) which are unhandled; explicitly declare them as resources or exclude from the target
    /Users/satoutaichi/work/swift/DangerSwiftModule/.build/checkouts/octokit.swift/OctoKit/Info.plist



## Failures
Error deserializing SwiftLint JSON response ([
  {
    "character" : null,
    "file" : "/Users/satoutaichi/work/swift/DangerSwiftModule/DangerSample/DangerSample/ContentView.swift",
    "line" : 13,
    "reason" : "Line should be 110 characters or less; currently it has 130 characters",
    "rule_id" : "line_length",
    "severity" : "Warning",
    "type" : "Line Length"
  },
  {
    "character" : null,
    "file" : "/Users/satoutaichi/work/swift/DangerSwiftModule/DangerSample/DangerSample/ContentView.swift",
    "line" : 14,
    "reason" : "Lines should not have trailing whitespace",
    "rule_id" : "trailing_whitespace",
    "severity" : "Warning",
    "type" : "Trailing Whitespace"
  },
  {
    "character" : null,
    "file" : "/Users/satoutaichi/work/swift/DangerSwiftModule/DangerSample/DangerSample/ContentView.swift",
    "line" : 15,
    "reason" : "Lines should not have trailing whitespace",
    "rule_id" : "trailing_whitespace",
    "severity" : "Warning",
    "type" : "Trailing Whitespace"
  },
  {
    "character" : null,
    "file" : "/Users/satoutaichi/work/swift/DangerSwiftModule/DangerSample/DangerSample/ContentView.swift",
    "line" : 16,
    "reason" : "Lines should not have trailing whitespace",
    "rule_id" : "trailing_whitespace",
    "severity" : "Warning",
    "type" : "Trailing Whitespace"
  },
  {
    "character" : null,
    "file" : "/Users/satoutaichi/work/swift/DangerSwiftModule/DangerSample/DangerSample/ContentView.swift",
    "line" : 17,
    "reason" : "Lines should not have trailing whitespace",
    "rule_id" : "trailing_whitespace",
    "severity" : "Warning",
    "type" : "Trailing Whitespace"
  },
  {
    "character" : null,
    "file" : "/Users/satoutaichi/work/swift/DangerSwiftModule/DangerSample/DangerSample/ContentView.swift",
    "line" : 17,
    "reason" : "Limit vertical whitespace to a single empty line; currently 4",
    "rule_id" : "vertical_whitespace",
    "severity" : "Warning",
    "type" : "Vertical Whitespace"
  }
]
Your version of SwiftLint is up to date.
): dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Unexpected character 'Y' after top-level value around line 57, column 1." UserInfo={NSJSONSerializationErrorIndex=1956, NSDebugDescription=Unexpected character 'Y' after top-level value around line 57, column 1.})))

Danger: ⅹ Failing the build, there is 1 fail.
```

このエラーは SwiftLint の実行結果の json を Danger Swift がパースするときに予期せぬ文字が入り込んでしまっているためにパースに失敗することで発生するエラーです。

具体的には、以下 json の`Your version...`の文字が入っており純粋な json 形式以外の文字が SwiftLint によって出力されているのが問題。

```json
[
  ・・・省略
]
Your version of SwiftLint is up to date. # ⇦この文字が悪い
```

これは指定している`.swiftlint.yml`の以下設定を`false`もしくは削除することで解消できます。

```yml
# If true, SwiftLint will check for updates after linting or analyzing.
check_for_updates: true
```

# おわり
以上が、SPMでのDangerの導入方法でした。
ちょくちょくハマりポイントがあり、初見の導入には苦労した印象ですが、
わかってしまえば簡単かもしれないですね。
ただ、結局未解決の問題(--cwdオプションが効かない問題)があったり、DangerJSに依存していたりと面倒な部分があるのも事実でした。
とはいえ、DangerfileをSwiftでかけるのは、個人的にはメンテしやすくてかなり良いと感じたので事情がない限りはSPMでの導入が良いかなと思いました。

SPMでのDangerの導入を試すためのサンプルプロジェクトを作っているので、よければこちらも参考にしていただければと思います。

https://github.com/stotic-dev/DangerSwiftModule