Lodashの_.template
に渡して使えるテンプレート構文1内のJavaScriptに対してESLintで静的解析できるようにするプラグインeslint-plugin-lodash-templateを作ってみました。
npm: eslint-plugin-lodash-template
これを使うとこんな感じでエラー出ます。(--fix
にも対応してるはず。)
この記事はそんなプラグインを作ってみた話を書きます。
作れそうだと思った
最近eslint-plugin-vueのソースコードをまじまじ見る機会があって、この仕組み色々パクったら参考にしたら簡単に出来るんじゃないかと思ったのでやってみました。
基本的な仕組み
parser
ESLintにはparserをねじこむ仕組みがあってそれを使って、JavaScriptのASTを返せば、後はうまいこと各lintルールさんたちが解析してくれます。
今回はテンプレートからJavaScriptの部分を抽出して、普通のJavaScriptが食べられるparserに渡せば上手いことやってくれるんじゃないかと思いついたのでこれを実施してみました。
つまり、次のテンプレートで例を示すと、
<ul>
<% _.forEach(users, (user) => { %>
<li><%- user %></li>
<% }); %>
</ul>
<div>
<h1>My name is <%- name %></h1>
<p>I like <%- hobbies %> but hate being a <%- occupation %></p>
</div>
このテンプレートから以下のJavaScriptを抽出して
_.forEach(users, (user) => {
user ;
});
name ;
hobbies ; occupation ;
(各ASTの出現index等揃えるためscript以外の部分は空白で埋める)
この文字列をperserに食べさせた結果ASTを各ESLintのルールに食わせるだけ。
processor
上のparseだけでそこそこの素敵な結果になると思いきやそんなに甘くなく、、、
上に書いたJavaScript見れば当然わかると思うのですが、インデントが酷いものですね。ESLintは漏れなくクソインデントを報告してくれます。が今回の特殊な状況に限ってはインデントエラーは無視して欲しいです。
というようなルールindent
とかmax-statements-per-line
とかstrict
やら今回不要なエラー報告をprocessorをつかって除外します。
と基本的な仕組みはこんな感じです。
ルールを追加してみる
テンプレートタグの空白スタイル揃えてみる
<li><%- user %></li>
は、以下のようにスタイルにブレが発生するので、
<li><%-user%></li>
<li><%- user %></li>
<li><%- user%></li>
これを統一したいです。
eslint-plugin-vueにもmustacheの空白スタイルを揃えるルールがあるのでこれをパクりまリスペクトしてオマージュ致しました。
成果物:template-tag-spacing.md / template-tag-spacing.js
parserServices
ルール処理のjsで、テンプレートタグ(<%- user %>
)の出現位置を知りたいです。
ESLintにはparserServices
というparserで処理した結果とかを各ルールで使うことができる仕組みがあります。
parserでjs部分を抽出するときにテンプレートタグの出現位置を取っておき、parserServices
経由でルール処理側に渡すことで処理することができました。
後はmustache-interpolation-spacingをパクるオマージュするだけです。
しっかり--fix
にも対応しています。
HTML形式のコメントアウトを禁止してみる
この形式のテンプレートってHTML形式のコメントアウトって不要だと思うんです。
<!-- comment -->
って書くと当然ページのHTMLにコメント情報が残りますが、
<% /* comment */ %>
って書くと実行時にjsのコメントとして扱われるのでページのHTMLからは消えます。
なので、HTML形式のコメントアウトを禁止たほうが美しいと思うのでルールを実装してみます。
成果物:no-html-comments.md / no-html-comments.js
parse5
HTMLのコメントの場所を知りたいのでHTMLのparseをします。
HTMLのparserはparse5を使うと概ね良さそうです。
(ガチでHTMLのスタイル検出とかやり始めると問題ある様子ですが今回はそこまでじゃないので大丈夫でしょう)
htmlparser2も悪くないですが、出現箇所の情報が少ない様子です。
const parse5 = require("parse5")
const document = parse5.parse(html, {
locationInfo: true,
})
という感じでparseして、後はHTMLコメント出現箇所をエラー報告します。
インデント修正してみる
基本はeslint-plugin-vueのscript-indentとhtml-indentとをパクりまsリスペクトしてオマージュ致しました。
成果物:
script-indent.md / script-indent.js
html-indent.md / html-indent.js
HTMLインデント
HTMLのインデントが意外と難しくて、
<div>
<% if (a === 1) { %>
<div class="case-1">
<% } else { %>
<div class="case-2">
<% } %>
</div>
</div>
とかなってた場合に、単純にHTMLで見ると最後の</div>
の開始タグは<div class="case-1">
なのです。
これに対応するために、終了タグのインデント位置がおかしそうだな?と思ったらelse
の中を剥がして再度HTMLを構築して、もう一度、最後の</div>
のインデント位置を計算するとかやってます。(自信ないのでバグってるかも)
scriptインデント
eslint-plugin-vueのscript-indentの処理が僕にはとっても難しくて、自分の理解が追い付いてないので、
ほとんどコピって参考にして作ったeslint-plugin-lodash-templateのインデント処理は劣化しているかもしれません。。。
--
他にもいくつか遊び半分でルールを追加しています。
カスタマイズさせたい
parserOptions
Lodashの_.template
には_.templateSettings
なんて機能がありまして、テンプレートタグの開始・終了の区切文字をカスタマイズできるんですね。。。
せっかくなのでこれも対応してみます。
parserのオプションとして渡せればいいので、そのまんまのparserOptionsという設定を使うことで対応できそうです。
このOptionはparserの
exports.parseForESLint = function(code, options) {
の第2引数に渡されるのでこれをつかってテンプレートのparser処理をすることで対応できました。
※誰もやらないと思いますが、個人的にハマったので一応書くと、parserOptions
には正規表現オブジェクトを渡すことはできないようです。JSONを考慮してるから?
processorの設定
processorの処理の記事を書いたところで、「不要なエラー除外」したと書きましたが、これカスタマイズできるようにしたいです。
どうprocessorに設定を受け入れればいいかわからず、設定を受け入れて、シングルトンインスタンスに保持させるだけのルールを作ってしまいました。。。
(processorでそのシングルトンインスタンスからデータを取得して処理する)
ESLintの設定項目にsettings
なんていうそれっぽい設定はありましたが、プラグイン側のprocessorではどうやって受け入れていいのかわからず。。。
ちなみにエディタでこのプラグインを使う場合。
SublimeTextでこのプラグインを使う場合
SublimeLinter-eslintで使う場合、vue用の設定をちょっと変えて、
"linters": {
"eslint": {
"selector": "text.html.vue, text.html, source.js - meta.attribute-with-value"
}
}
とするといいです。
VSCodeでこのプラグインを使う場合
VS Code ESLint extensionで使う場合こんな感じ。
{
"eslint.validate": [ "javascript", "javascriptreact", { "language": "html", "autoFix": true } ]
}
そもそも同じようなプラグインが既存でないのか?
作ってる人はいます。
https://www.npmjs.com/package/eslint-plugin-microtemplates
ただ、試してみると色々と問題があり使い難い印象だったとの、追加ルールも特に無いし、最終更新が2年前なのもあり、んー。。。
作ってみて
参考になるプラグインeslint-plugin-vueがあったおかげで基本的な仕組みは簡単にできました 感謝。
ただやはりインデント処理とかは難しくて、標準のESLintとeslint-plugin-vueでインデント処理作った人はすごいなーと改めて思いました。感謝。
-
このテンプレートの正式名称って何? Micro-Template?Lodash template?Underscore template? Micro-Templating → https://johnresig.com/blog/javascript-micro-templating/ ↩