はじめに
JavaScript の Linter である ESLint のルールを自作しそれを動かす方法を作りながら調べました。
この記事はその備忘録の第一回です。最初なのでまずは単純な ESLint ルールを作ります。そしてそれをローカルルールとして動かしてみます。
Linter とは
プログラムコードのクオリティを担保するため、開発チーム内でコーディングルールが決められます。
しかし、チームレビューなどの目視による確認方法では、見落としが発生することが容易に想像できます。そこでモダンな開発チームでは、静的コード解析ツール (Linter) を用いて自動的に機械にチェックさせています。そうすることで見落としを防ぐことができます。便利ですね。
例えるなら...
あなたのプロジェクトはガガゼト山です。ここに召喚士一行が通りかかりますが、あなたは次のルールを課しています:
召喚士は通す。 ガードも通す。 キマリは通さない。
しかし、24 時間 365 日、キマリが通るかどうかを一人で監視することは難しいです。立て看板を置いておきますか? キマリは無視して通ってしまうかもしれません。他のロンゾ族に伝えておきますか? もしかしたらそのロンゾ族は、反逆者である召喚士が己の身を捧げる覚悟を聞いてキマリを通してしまうかもしれません。
そこでビランとエンケに頼みます。ビランとエンケは絶対にキマリを通しません。召喚士を通しても、ガードを通しても、キマリは通しません。いつでもガガゼト山の道を一族の誇りをかけて監視しており、通りがかったキマリを見つけては過去の因縁を持ち出して弄ったり二対一で戦ったりして通せんぼします。二人はキマリを通さないことにかけてはエキスパートです。
しかし、二人はキマリの成長した強さに感服してキマリを通してしまいます。
というわけで
キマリを通さない ESLint ルールを作ります。
要件
すでに汎用的な ESLint のルールは広く公開されており、大抵の場合はそれらを組み合わせて適用すれば事足ります。ですがその中にキマリを通さない ESLint ルールはありません。なので自作しましょう。
ここではコード中の文字列リテラルに「キマリ」が含まれている場合にエラーにします。
ちなみに、文字列リテラル内で「キマリ」と連続していなければならないことにします。仮に「キ」と「マ」と「リ」を別個に宣言して後から結合しても、ガガゼト山を通ろうとしているのは「キマリ」ではなく「キ」と「マ」と「リ」に分割されたものだからです。
実装
まずは eslint
を install しましょう。
yarn add eslint
コードは AST (抽象構文木)と呼ばれる、コードをパースした JSON で ESLint に入ってきます(AST のわかりやすい解説)。コードは抽象化され、例えば ""
と ''
のような書き方の差異は構文木の構造自体には現れなくなります。それに JSON 形式なのでプログラムで簡単に扱うことができます。
今回の要件では Literal
ノードを見て、その value
が文字列かつ「キマリ」が含まれているかチェックするだけで OK です。エラー地点は Literal
ノード、メッセージは「キマリは通さない」にします。
"use strict";
module.exports = {
create: function(context) {
return {
Literal: function(node) {
if (
typeof node.value == "string" &&
node.value.indexOf("キマリ") !== -1
) {
context.report({ node: node, message: "キマリは通さない" });
}
}
};
}
};
テスト
正しく動くかどうかテストを書きます。テストには mocha
を利用し、次のような形式で簡単に書くことができます。
yarn add mocha
通すルールは valid
に書きます。まずはvar a = 1;
という簡単な式が通ることを書いておきましょう。後は召喚士は通す、ガードも通すことも書いておきます。
通さないルールである invalid
でキマリを通さないことを記述します。
"use strict";
const RuleTester = require("eslint").RuleTester;
const rule = require("../rules/kimahri-not-pass");
const tester = new RuleTester();
tester.run("kiomahri-not-pass", rule, {
valid: [{ code: "var a = 1;" }, { code: '"召喚士"' }, { code: '"ガード"' }],
invalid: [{ code: '"キマリ"', errors: ["キマリは通さない"] }]
});
テストスクリプトは mocha "tests/**/*.js" --reporter dot
です。無事通過すれば作成完了です。
ルールを動かす
実際に開発しているような環境でルールを動かして確かめないと納得がいきません。実際に src ファイルを書いて、それに対して lint をかけてみます。
キマリを通さないのはガガゼト山だけでいいので、パッケージとしてではなくプロジェクト内のローカルルールとして動かします。ローカルルールを .eslintrc.js
に書くために eslint-plugin-rulesdir
を add します(--rulesdir
オプションでもいいんですが後述の VSCode で動かすために plugin で書きます)。
yarn add eslint-plugin-rulesdir
.eslintrc.js
は次のように書きます:
const rulesDirPlugin = require("eslint-plugin-rulesdir");
rulesDirPlugin.RULES_DIR = "rules";
module.exports = {
parserOptions: {
ecmaVersion: 6
},
plugins: ["rulesdir"],
rules: {
"rulesdir/kimahri-not-pass": "error"
}
};
src ファイルは次の通りです。
"use strict";
const a = "キマリは通さない";
eslint ./src
を打てばちゃんとエラーとして検出されました。
VSCode でルールを動かす
さらに VSCode の ESLint 拡張を使って動作するようにします。
上で書いた通りコマンドオプションでなく eslint-plugin-rulesdir
でルールの場所を教えてあげているので ESLint 拡張もローカルルールを読み込むことができます。
これが次のように表示されれば成功です。動かない時は VSCode を再起動するといいかと思います。
終わりに
今回、簡単な ESLint のルールを作って、それをローカルルールとして動作させる方法を学びました。
実際のプロダクトにおいてはキマリを通さないルールを課すことはないでしょうし、キマリは実力を示すことでガガゼト山を通過することを認められます。
これを足がかりに、次回はきちんとプロジェクトで使えそうなルールを書く予定です。
参考にしたサイト
・ルールの書き方
https://qiita.com/mysticatea/items/cc3f648e11368799e66c
https://techblog.yahoo.co.jp/javascript/how-to-create-eslint-rules
・AST のわかりやすい解説
https://efcl.info/2016/03/06/ast-first-step/
・実際にコードを書いて AST を見られるツール
https://astexplorer.net
・公式
https://eslint.org/docs/developer-guide/working-with-rules
あと実際に公開されているルールのコードを見ると勝てます。