emacs flycheckでSwiftの構文チェックを行う


# はじめに
以前こんな記事を書きました。
[emacs flymakeでSwiftの構文チェック](http://qiita.com/ponpoko1968/items/1604a435e3fc4db22efe)

この記事で紹介した方法では、プロジェクトに含まれる構文チェック対象でない他のソースファイルの情報（クラス名その他、共有する名前空間に存在するシンボル群）をコンパイラが認識できないため、エラーが大量に出てしまいます。

つまり構文チェックのswiftコマンドラインにはプロジェクトの全ソースを記述する必要があります、

（あと、flymakeではMakefileを書かないと行けなくて面倒なので、ついでにflycheckを使うようにしたいです。）

そこで、プロジェクトファイル(.pbxproj)の情報を用いてxcodeと同等のコマンドラインを生成することを目指しました。

# xctool

xcodeと同等のコマンドラインの情報を得るため、facebookが公開しているxctoolを使用することにしました。
xctoolは、homebrewなら

```shell-session
$ brew instal xctool
```

でインストール出来ます。

xctoolはビルド記録を出力形式を選択したり、カスタマイズできるreporterプラグインという仕組みを持っていて、このプラグインのうち、`json-compilation-database`を使うと、純粋なSwiftソースファイルのコンパイルコマンドラインが得られます。

Xcodeのプロジェクトの直下で、

```shell-session

$ xctool -workspace ./${PROJECT}.xcworkspace -scheme ${PROJECT} -reporter json-compilation-database -sdk iphonesimulator build 
```

を実行すると、標準出力にビルド記録が出力されます。結構な量になるので注意してください。
JSONの内容は、

```json
[
  {
    "command" : "<コンパイルコマンドライン>",
    "file" : "<対象ファイル>",
    "directory" : "<ディレクトリ>"
...
  }
]
```

となっているので、このうち、配列名の任意の辞書のcommandキーの値を取り出して、構文解析用のコマンドになるよう修正します。directoryにはビルドディレクトリが記されているので、これを参照すれば、ユニットテストプロジェクトや、cocoapodsのビルドに用いているコマンドラインを除外できます。

ここで実行されるコンパイルコマンドラインはこのような形式になっています。

 > /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -c _プロジェクトソースファイル群_  -primary-file _コンパイル対象_ -target _他のコマンドラインオプション_


改造するポイントとしては、

- `-frontend`の後ろに構文解析のみを行う`-parse`オプションを付ける
- `-primary-file`の後ろ、対象ファイルを書き換える

です。注意すべきは`-primary-file`の位置が`-c`と`-target`の間のどこに置かれるかが不定な点です。

※文末にJSONファイルから構文解析用のswiftコマンドラインを生成するpythonスクリプトを貼っているので適宜改造してご使用ください。

このスクリプトを実行して作ったシェルスクリプトを実行可能パーミッションを付けて、flycheckに設定すればOKです。

#flycheck設定

```el
(flycheck-define-checker swift
  "Flycheck plugin for for Apple's Swift programming language."
  :command ("<your-bin-directory>/compile_swift"
            source-original)
  :error-patterns
  ((error line-start (file-name) ":" line ":" column ": "
          "error: " (message) line-end)
   (warning line-start (file-name) ":" line ":" column ": "
            "warning: " (message) line-end))
  :modes swift-mode)

...

(add-hook 'swift-mode-hook
  '(lambda()
     (add-to-list 'flycheck-checkers 'swift)
     ...
     )
)

```

# 課題


- 複数のプロジェクトで同時並行で作業する場合に、構文チェックコマンドを切り替えるのが面倒
- プロジェクトにソースファイルが追加されたら毎回構文チェックコマンドを手動で生成し直すのが面倒
  -  これは、.pbxprojと構文チェックコマンドのタイムスタンプを比較して自動で実行するとか、
  -  gitのhookをつかって.pbxprojがコミットされた時点で再生成するとかで解決できそう

# JSONから構文チェックスクリプトを生成するPythonスクリプト(python3)

```python

#!/usr/bin/env python
import sys
import json
import re
import os.path
def main():
    sources=[]
    with  open( sys.argv[1] ) as f:
        result = json.load(f)
        for l in result:
            line = l["command"]
            dir = l["directory"]
            if os.path.basename(dir) == 'Pods':
                continue
            primary_file = re.search("-primary-file ([^ ]+) ",line)
            if primary_file:
                srcs = re.search("-c (.+) -target",line)
                list = []
                for src in srcs.group(1).split(' '):
                    if src == '-primary-file':
                        continue
                    sources.append(src)
                print(primary_file.group(1),file=sys.stderr)
                prefix = (os.path.basename(primary_file.group(1)).split('.'))[0]
                replaced = re.sub("-frontend", '-frontend -parse',line)
                replaced = re.sub("-c (.+) -target", '-c ${SRCS} -primary-file $1 -target',replaced)
                replaced = re.sub(prefix, '${PRIMARY_PREFIX}',replaced)

                cmd =  """\
#!/bin/bash
SRCS="%s"
PRIMARY=`basename $1`
PRIMARY_PREFIX=${PRIMARY%%.swift}
%s""" % (' '.join(sources), replaced)
                print(cmd)
                break

if __name__ == '__main__':
    main()

```