ESLint 0.1.0

ESLint 0.1.0がリリースされました。

ESLintは、JSLintJSHintのようにJavaScriptをLintするツールで、以下のような特徴を持っています。

追記:

この記事は0.1.0の情報のためいくつか古い情報が混じっています。

  • プラグインの実装とテストのやり方の変更
  • 速度の開演

などが、変わっている部分があります。

独自のLintルールの実装については以下が参考になります。

特徴

ESLint の大きな特徴として、EsprimaでパースしたASTを元に、
それぞれのLintするルールがプラグインとして実装されている点です。

それぞれのプラグインは本体とは切り離れて実装されているので、
JSHint Optionsのように設定で無効化することや、そもそもプラグインファイルを読み込まないということも出来ます。

現在用意されているRulesのプラグインは、JSLintJSHint互換のものがプラグインとして作られています。

後述しますが、公式のプラグインの他に自分でプラグインを追加して利用する方法も用意されているので、独自のルールをプラグインとして追加することも可能です。

JSHintとの違い

JSLintJSHint の違いは Why I forked JSLint to JSHint {JSLint から JSHint をフォークした理由(翻訳)} を読んでもらうとして、

ESLintJSHint の違いについて簡単にまとめてみます。

ESLint

  • プラグイン機構を持っているので、Lintするルールを追加できる
    • ESLint 本体のコードがシンプルに保たれる
  • Mozilla JavaScript AST をベースとしているため、他のものと連携しやすい
  • EsprimaでパースしたASTをLintする two-pass のツール
    • つまりEsprimaでパースできないとLintはできない
    • また、現段階だと巨大なjsファイルではJSHintより3倍程度遅い
    • Rule performance などにまとめられている、速度については今後改善していくとのこと
  • JSLint/JSHint互換のようなルールが作られている – ESLint Rules List
  • Node.js で書かれているツール
    • 将来的には Browserified されたバージョンも出す予定
  • ドキュメントがよく書かれている

JSHint

  • 広く使われてるので安定している
  • 内部にJavaScriptパーサーを持っている
    • ES6の先行的な実装やMozilla特有のJSなどにも対応したLintも行える
  • パースしながらLintを行える single-pass のツール
    • 構文エラーとWarningを両方出せる
    • 編集中にLintをガンガン出す場合は、今のところはJSHintの方がよく働くはず
    • そのかわりJSHintコード内部は複雑
  • Rhinoやブラウザなど色々なruntimeで動かすことができる

どちらとも、共通している点としてオープンソースでありGithubで活発に開発されています。

また、reporterをカスタマイズすることができ、
sindresorhus/eslint-stylishのように出力を見やすくしたり、tap形式で出力する等ができます。

ロードマップ

ESLint の ロードマップは Release goals · nzakas/eslint Wiki に書かれています。

また、Now available: ESLint v0.1.0 | NCZOnlineにも少し書かれています。

0.1.0はまだα版の位置づけのため、設定の方法やプラグインのためのAPIなどに変更があるかもしれませんが、フィードバックはIssues · nzakas/eslintESLint – Google グループから行えます。

プラグインを書いてみよう


追記(2014-07-10): このプラグインの作り方は現在のバージョンと異なる場合があります。

以下なども参考にしてください。


ESLinteslint / lib / rules / にある公式のプラグインと
$ eslint --rulesdir /path/to/dir/ のCLIオプションで自分の書いたプラグインを追加し読み込む機能があります。

今回は、 --rulesdir で追加するシンプルなプラグインを書いてみたいと思います。

ソースコードは azu/eslint-plugin-example においてあります。

今回は、公式にPullRequestで追加するようなプラグインではなく、オレオレプラグインを作る環境の話なので、多少やりかたは違いますが基本的にはDeveloper Guideと同じなので、こちらを読んでおくといいでしょう。

taboo-name

azu/eslint-plugin-example で作るプラグインは、指定した名前に一致する変数名等があったらWarningを出すものです。

ESLintはJSHint Optionsと同じように、configファイルでルールの有効化の有無やルールに対してオプションとなる値を渡せるようになっています。

今回の taboo-name では、以下のように、 "taboo-name": [1, "taboo"] という値が指定しています。

{
    "env": {
        "browser": false,
        "node": false,
        "amd": false
    },
    "rules": {
        "taboo-name": [1, "taboo"],

        "no-alert": 0,
        "……" :  1
    }
}

これで、1 有効化(Warning、2ならError) で、"taboo" という値を taboo-name ルールに渡すという意味になります。

プロジェクトの構造は以下のようになっています。

├── eslint(submodule)
├── example
│   ├── config.json
│   └── squalid.js
├── plugin
│   └── taboo-name.js
└── test
    └── taboo-nameSpec.js

taboo-name.js というプラグインのファイル名そのものが、Configで指定するルールの名前(RULE_ID)になっていることに注意しておいて下さい。

eslintのリポジトリをsubmoduleとして配置して、git submodule foreach 'npm install || :のような感じで、eslint内で npm install してeslintが依存してるモジュールをインストールしておくといいでしょう。

公式のプラグインはVowsでテストを書きますが、今回はMocha + Chaiで書いています。

テストを書こう

まずはプラグインのテストを書いていきます。

公式のプラグインの場合は最初からpluginを置くディレクトリが定義されているので、そこに配置するだけで問題ないですが、今回のは違うディレクトリにpluginを置いています。

そのため、ruleLoader.load(path/to/dir) を使い、pluginを読み込むディレクトリを変更しておきます。

eslintをモジュールとして読み込んでおけば、 eslint.verify(評価するコード文字列, config); でeslintでlintした結果を得ることが出来ます。

lintした結果が予期してる通りがどうかをassertでテストしています。

公式プラグインのtestsの書き方を真似るのが楽でいいと思います。

var assert = require('chai').assert;
var eslint = require("../eslint/lib/eslint");
var ruleLoader = require("../eslint/lib/rules");
// load my plugin directory.
ruleLoader.load(__dirname + "/../plugin");
var RULE_ID = "taboo-name";
describe("taboo-name", function () {
    context("when evaluating 'var taboo = 1;'", function () {
        it("should report a violation", function () {
            var config = { rules: {} };
            config.rules[RULE_ID] = [1, "taboo"];
            // eslintでコードとconfigを渡してLintをかける
            var messages = eslint.verify("var taboo = 1;", config);
            // Lintした結果、1つのWarningが出てることをテストする
            assert.equal(messages.length, 1);
            assert.equal(messages[0].ruleId, RULE_ID);
            assert.equal(messages[0].message, "恥ずかしい'taboo'禁止!");
            assert.include(messages[0].node.type, "Identifier");
        });
    });
});

プラグインを実装しよう

次にテストを通るようにプラグインの taboo-name.js を実装していきます。
(現在は RULE_ID と ファイル名が同一で固定ですが、将来的にこの部分は変更されるかもしれません)

と、その前にESLintはJavaScriptのASTを元にLintを書くツールなので、簡単にJavaScriptのASTとはなにかという話をします。

JavaScript AST

AST(Abstract Syntax Tree) は名前の通り、コードをパースした構文木のことで、
JavaScriptのASTといえば、 Esprimaacorn.js が対応してるMozilla JavaScript AST(Parser API)が一般的です。

実際にどういうものかは、Esprima: Parserでコードを入力して試してみるといいでしょう。

コードをパースした結果JavaScript ASTはJSONで表現されたものが出力されます。

AST Image

AST Image

SpiderMonkey Parser API: A Standard For Structured JS Representations // Speaker Deck より引用

上記のようにコードが、それぞれExpressionなどのオブジェクトがツリーとなった感じに変換されて、
このオブジェクトを見ていきながら何らかのルールに沿ってるかどうかをチェックしていくのがESLintの基本的な機能です。

率直に言えばJSONを見てチェックする感じです。

ESLintのプラグインは次のような形で書くことになっていて、nodeのtypeごとにチェックする機能を実装していく感じです。

module.exports = function(context) {
    "use strict";
    return {
        "Identifier": function(node) {
            // nodeのtypeが"Identifier"ものに対してのチェックを書く
        }
    };

};

node は さきほどの図でいうところのそれぞれ色がついたオブジェクトの部分の事で、
"Identifier" というのはNodeのtypeを示しています。

AST Image

ESLint内部ではEstraverseを使い、このASTを走査しながらnodeオブジェクトをプラグインに渡して、プラグインがチェックした結果を出力する感じになっています。
(nodeから親やコメントの取得するメソッド等もcontextに用意されてる)

taboo-name.js

実際に書いたプラグインを見ていきます。

module.exports = function (context) {

    "use strict";

    var checkForLoops = function (node) {
        var name = node.name;
        context.options.forEach(function (tabooName) {
            if (name.indexOf(tabooName) !== -1) {
                context.report(node, "恥ずかしい''禁止!", {
                    name: name
                });
            }
        });
    };

    return {
        'Identifier': checkForLoops
    };
};

"Identifier" typeのnodeは、var a; でいうところの a にあたる部分が入ってるnodeなので、変数名や関数名と言ったnodeに対して、checkForLoops という関数でチェックしています。

context.options には テストの方で渡していた config.rules[RULE_ID] = [1, "taboo"];["taboo"] という配列が入ってるので、それと変数名等が一致してないかをチェックし、一致してたら context.report で報告するというシンプルなものです。

プラグインを動かしてみる

eslint コマンドでは --rulesdir でプラグインを読み込むディレクトリを追加できるので、今作ったプラグインのディレクトリを指定する事でプラグインを含んだLintを行う事が出来ます。

squalid.js

(function () {
    var taboo = "this is taboo variable.";
})();
$ eslint -c example/config.json --rulesdir plugin/ example/squalid.js
example/squalid.js: line 5, col 8, Warning - 恥ずかしい'taboo'禁止!

1 problem

ソースコード

おわりに

ESLint v0.1.0 がリリースされたので、ESLintの概要とJSHintとの違いなどについて簡単に書いてみました。

gitのpre-commit時により他のLintツールでできない詳細なチェックをするなど、プラグインが作りやすくなってるので、色々使い道がでてくるかもしれません。

最近はJavaScriptのASTがツール的に色々面白いと思ってるので、オフラインJavaScript勉強会 – Online Study TokyoBouldering.js | Doorkeeperあたりで何か話せればいいなと思います。

参考