ラベル EditorConfig の投稿を表示しています。 すべての投稿を表示
ラベル EditorConfig の投稿を表示しています。 すべての投稿を表示

2019年3月5日火曜日

EditorConfig の設定に従っているかをテストしてみた

どうも、先日 .editorconfig の記述に typo をずっと放置していて驚愕した僕です。
ブログズミ: .editorconfig ファイル自体の Lint を CI で回す

.editorconfig "自体"の Lint ができたので、今回は .editorconfig に従っているかチェックしたいと思います。
こんな方にオススメ
.editorconfig を開発途中から導入した場合
iutest もそうですが、仕事のプロジェクトでも最初から .editorconfig が用意されているケースがなく、あとから .editorconfig を追加するケースをよく目にしています。
.editorconfig を導入したあとは、対応しているエディタで作業していれば、.editorconfig に従っていない記述が混入することはないはずですが、開発途中に導入された場合は変更が加わらない限り補完されません。

従ってないことでクリティカルな問題が出ることは少ないと思いますが、作業者が変更したところ以外に差分が出るとレビューの際にじゃまになったりするので、やる意味はあるかなと思います。

.editorconfig に対応していないエディタからのコミッターがいる
このケースは存在するのだろうか?という疑問は置いておいて…
git の pre-commit とかに仕込んでおくと、対応している人と対応してない人が無駄に差分を出し合うことがなくなるかなーと思います。

使った Lint ツール
今回の検証では、2 つの Lint ツールを使いました。
使ってみた結果、一長一短あったため 2 つとも採用しています。
Editorconfig Checker
最初に見つけたツールが、Editorconfig Checker です。
https://github.com/editorconfig-checker
複数の言語に対応しているようで、JavaScript/Go/PHP/Java のリポジトリがありました。
今回は最初に見つけたのが npm package だったのでそちらを使うことにしました。

eclint
もう1つは eclint です。
https://github.com/jedmao/eclint
こちらも npm package があり、Editorconfig checker を npm で取ってきたこともあり、すぐに使えそうだったので試してみました。


Editorconfig Checker の使い方と注意
npm install などセットアップについては省略します。

使い方は単純で、解析したいパスを渡すだけです。
オプションで除外パスの指定もできるので、エディタで編集しないようなファイルを除外しつつチェックができます。
また、デフォルトで .git ディレクトリ以下や画像ファイルの拡張子は除外してくれるのと、--dotfiles or -d オプションで dotfiles を除外できるので、単純にルートディレクトリを解析するでも問題ないかもしれません。

以下は、iutest でのオプションの抜粋です。
Visual Studio のソリューション・プロジェクトファイルだけ除外しました。
CI サービス上で実行する想定なので、git 管理外のファイル除外には対応してません。

"scripts": {
    "eccheck": "editorconfig-checker --exclude-regexp \"\\.sln|\\..*proj\" ."
  },

これで npm run eccheck を実行すれば検証してくれます。
このような形でレポートされます。


注意点
Editorconfig Checker は、文字コード(charset)の検出ができないです。
BOM のつけ忘れを検出したいなーと思ったのですが、これができなかったのがもう1つツールを使うことにした理由です。

eclint の使い方と注意
続いて eclint です。
こちらも npm install などのセットアップについては省略します。
eclint も基本的には検証したいパスを引数に渡すだけで使用できます。
Editorconfig Checker とは違い除外オプションはないので、ワイルドカードでの指定や、find コマンドなどを使ってファイルのリストを渡すなどの工夫が必要です。

以下は、iutest でのオプションの抜粋です。
"scripts": {
    "eclint": "npm-run-all eclint:*",
    "eclint:src": "eclint check ./src",
    "eclint:include": "find ./include -type f -not -name iutest_ver.hpp | xargs eclint check -s 2",
    "eclint:root": "find . -maxdepth 1 -type f | xargs eclint check"
  },

これで npm run eclint を実行すれば検証されます。
(除外指定をしつつワンライナーで書くのが手間だったので npm-run-all を使いました)
このような形でレポートされます。


Editorconfig Checker ではできなかった charset の検出ができました。

注意点
さて、charset の検出はできましたが、いいことだけではありません。
eclint はインデント判定もちゃんとしてくれるのですが、これがちょっとノイズになってしまいました。
「インデント」ではなく「整列」のために空白を入れたものまで警告されるためです。

具体的には以下のようなコードです。
enum Level
    {
          LOG_INFO
        , LOG_WARNING
        , LOG_ERROR
        , LOG_FATAL
        , LOG_LEVEL_NUM
    };
私は列挙や関数引数を改行して書く場合、「,」を行末ではなく行頭に書くようにしています。
そして、視認性の観点から最初の「,」のいらない要素に関しては、名前の先頭が揃うように空白を挿入しています。

これがインデント幅 4 の設定に適合せず警告されるのです。
個人的に、これは許容したいため 「-s 2」 オプションをつけて、インデント幅 2 として検証をしています。

また、コメント中の整形であれば以下の設定(もしくはオプション指定)で無視することができます。


ただ、それでも無視しきれないケースがあったため、iutest_ver.hpp だけ検証対象から除外しています。
(Editorconfig Checker は幸か不幸か、インデントの検証がされないようなので iutest_ver.hpp は Editorconfig Checker で検証するって感じです)

おまけ: GitHub Actions
GitHub Actions の使い方が少しわかってきたので、こちらの検証は GitHub Actions で行うようにしました。今回は、特に自分ではアクションを作らず、既存の npm アクションを使ってます。

action "lint:editorconfig" {
  uses = "actions/npm@master"
  args = ["apt-get update && apt-get install -y git && npm install && npm run lint:editorconfig"]
  runs = "bash -c"
}

最初に git をインストールしているのは npm アクションのコンテナに git がインストールされていないため、git 依存のあるパッケージを取ってこれないためです。
npm の ENTRYPOINT は npm コマンドになっているので、runs に bash を指定して install から npm run までするようにしました。
この点だけ手間取りましたが、あとは簡単に組めました。



まとめ



自動化しましょう!

2019年2月4日月曜日

.editorconfig ファイル自体の Lint を CI で回す



[*.{cpp,hpp,ipp}}
charset = utf-8-bom
単純な typo ですが、今までずーと気づかずに config が仕事してくれてると思い込んでました。。。
直すのは簡単です。簡単ですが、また同じ失敗をする可能性があります。(にんげんだもの)

というわけで、テスト書きました。

テスト方法
.editorconfig の記法は概ね ini ファイルと一致します。
なので、適当な ini ファイルパーサーに読み込ませてエラーが起きないかどうか?で検証できそうです。

iutest は C++ のテスティングフレームワークですが、ツール系には Python も使用しています。
Python には ConfigParser があるのでそちらでテストを書きました。

import sys
import os

try:
    from configparser import ConfigParser
except ImportError:
    from ConfigParser import SafeConfigParser as ConfigParser


class EditorConfig(object):
    def __init__(self, path):
        self.name = os.path.basename(path)
        self.fp = open(path)
        self.first_head = True

    def readline(self):
        if self.first_head:
            self.first_head = False
            return '[global]\n'
        return self.fp.readline()

    def __iter__(self):
        return self

    def __next__(self):
        line = self.readline()
        if not line:
            raise StopIteration
        return line

    next = __next__  # For Python 2 compatibility.


def main():
    path = sys.argv[1]
    ini = ConfigParser()
    if not os.path.exists(path):
        sys.stderr.write('%s not found...' % path)
        sys.exit(2)
    ini.readfp(EditorConfig(path))
    sys.exit(0)

if __name__ == '__main__':
    main()

基本的には ini ファイルをパーサーにかけるだけなんですが、1点だけポイントがあります。
EditorConfig は、ディレクトリを上がって .editorconfig を探索しますが、root = true を最初に書いておくとそこで探索が打ち切られます。
この root = true が ini ファイルとしてはセクションのない要素のためエラーになってしまいます。
そのため、最初に [global] というダミーセクションを挿入して ConfigParser に渡してます

CI に組み込む
Python が実行できる CI サービスであればなんでも問題ありません。
今回 iutest では Codeship で cpplint やインクルードガードのチェックや絶対パスが混入してないかの、雑多なテストを行っているのでそちらに組み込みました。



こんな感じに検出されます!
これでもう安心ですね。