ドリコムでサーバーサイドエンジニアをしているおーはらです。

カンファレンスから大分時間が経ってしまいましたが、こちらの記事はElixir Conf Japan 2017のレポート第2回目となります。

幸運にも私はセッションで登壇させていただいたのですが、発表内容はTech的な内容がかなり薄めで、主にチーミングや運用についてのお話でした。
セッションの内容はこちらを見ていただくとして、今回はカンファレンスでは話せなかった、プロジェクトで利用しているスタイルチェッカツールのお話をさせて頂こうと思います。

Dogmaとは

dogmaはElixirのソースコードのスタイルチェッカ(Lint)ツールです。
また、同様のツールとしてcredoというツールもあります。
dogmaを使うことでプロジェクト内のコーディングスタイルを統一したり、CIに組み込んで規約違反のコードを検出する事ができます。

Dogmaの導入方法

dogmaを使うには、mix.exsdepsに以下の様に指定すればOKです。
開発時のみ利用したいのでオプションとして、only: :devを指定しています。

defmodule DogmaTest.Mixfile do
  use Mix.Project

  # 〜略〜

  defp deps do
    [
      {:dogma, "~> 0.1", only: :dev}, # これを追加
    ]
  end
end

Dogmaの実行方法

depsにdogmaの依存関係を指定した状態で、mix deps.getでライブラリの取得、mix deps.compileでライブラリのコンパイルを行うと、mixのタスクとしてmix dogmaを利用できるようになります。

$ mix deps.get
Running d</code>ependency resolution...
* Getting dogma (Hex package)
  Checking package (https://repo.hex.pm/tarballs/dogma-0.1.15.tar)
  Fetched package
* Getting poison (Hex package)
  Checking package (https://repo.hex.pm/tarballs/poison-3.1.0.tar)
  Fetched package
$ mix deps.compile
==> poison
Compiling 4 files (.ex)
Generated poison app
==> dogma
Compiling 59 files (.ex)
Generated dogma app
$ mix help | grep dogma
mix dogma             # Check Elixir source files for style violations
$ mix dogma
Inspecting 5 files.

.....

5 files, no errors!

$

Dogmaのオプション指定

dogmaのスタイルチェックにオプションを渡したい時は、config以下にdogma.exsというファイルを以下の様に作成すると、dogma実行時に自動でロードされて記述された設定が適用されるようになります。
例えば以下の例は、コードの1行の長さをチェックするルールLineLengthの最大長を変更します。

use Mix.Config

config :dogma,
 # ここに各スタイルチェックルールのパラメータを設定する
  override: [
    %Dogma.Rule.LineLength{ max_length: 100 }, # 1行の長さをデフォルトの120文字から100文字に変更
  ]

デフォルトで適用されるルールは、現在33種類あります。
オプションの指定方法はこちらのドキュメントを参照してください。

Dogmaはどうやってスタイルをチェックしているのか

さて、ここからはdogmaの内部の話になります。
dogmaはスタイルをチェックする際に、以下のステップを踏んで実行されます。

  • Step.1 対象となるソースファイルの選択
  • Step.2 ソースファイルからソースコードを文字列として取得
  • Step.3 ソースコードをASTに変換して保存
  • Step.4 ソースコードをトークンに変換して保存
  • Step.5 ソースコードからコメントだけを抜き出して保存
  • Step.6 Step.2〜5の情報を元にスタイルチェックルールを適用して解析

今回はStep.3のASTを使ったシンタックスチェックについての紹介をしようと思います。

AST

Step.3で、チェック対象のソースコードをASTに変換するのですが、ASTとは一体何でしょうか?

ASTはAbstract Syntax Tree(抽象構文木)の略で、コードの意味を木構造で表した内部表現です。
ElixirのASTは以下の様な3要素のタプルの入れ子構造で表現されます。

{ 関数名, メタデータ, 引数のリスト }

ElixirでASTを取得するには、quoteに対象のコードを渡すか、Code.string_to_quotedにコードの文字列を渡す事で取得できます。

iex(1)> quote do: is_atom(:a)
{:is_atom, [context: Elixir, import: Kernel], [:a]}
iex(2)> Code.string_to_quoted("is_atom(:a)")
{:ok, {:is_atom, [line: 1], [:a]}}
iex(3)>

上の例の通り、is_atom(:a)のASTは{:is_atom, メタデータ, [:a]}となります。

ASTによるチェック

ソースコードをASTにすると何が嬉しいのかというと、プログラムを構造を持ったデータとして扱う事ができるので、プログラムをElixirの関数を使って操作する事ができるようになる事につきます。

例えばDogmaで関数の引数数をチェックするFunctionArityのルールは、ASTを使って関数の引数の数をチェックしています。

関数定義defのASTは以下の通りです。

iex(1)> Code.string_to_quoted "def func(arg1, arg2, arg3), do: :hello"
{:ok,
 {:def, [line: 1],
  [{:func, [line: 1],
    [{:arg1, [line: 1], nil}, {:arg2, [line: 1], nil},
     {:arg3, [line: 1], nil}]}, [do: :hello]]}}
iex(2)>

AST部分を見やすく整形すると以下の様な構造になります。

{
  :def,
  <defのメタデータ>,
  [ # defの引数リスト
    {
      :func,
      <funcのメタデータ>,
      [ # funcの引数リスト ★ ここをチェックしたい
        {:arg1, [], nil},
        {:arg2, [], nil},
        {:arg3, [], nil}
      ]
    },
    [do: :hello]
  ]
}

このASTを使って、上の★部分のfuncの引数リストの要素数をチェックすれば簡単に引数の数を取得する事ができます。
もしソースコードの文字列から引数の数を取得しようとするとかなり面倒で泥臭い処理が必要になるでしょう。

その他のスタイルチェック

Step.3のAST以外にも、ソースコードをトークンにして構造を解析したり、コメントをメタ情報として取得して解析するなど、Dogmaは様々な方法でソースコードを解析しています。

Dogmaのルールは、それぞれ非常にシンプルな実装で実現されています。
可読性も高くASTやトークンの使い方を知る為の格好の教材コードだと思います。

まとめ

ElixirのシンタックスチェッカーのDogmaの紹介をしました。
Dogmaを利用するのはもちろん、内部の実装について読んで見てはいかがでしょうか?