8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【ESLint】eslint-plugin-lodash-template作ってみたよ

Last updated at Posted at 2018-05-24

Lodashの_.templateに渡して使えるテンプレート構文1内のJavaScriptに対してESLintで静的解析できるようにするプラグインeslint-plugin-lodash-templateを作ってみました。

npm: eslint-plugin-lodash-template

これを使うとこんな感じでエラー出ます。(--fixにも対応してるはず。)

sample-sublime-text

この記事はそんなプラグインを作ってみた話を書きます。

作れそうだと思った

最近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-vuescript-indenthtml-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-vuescript-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で使う場合こんな感じ。

settings.json
{
    "eslint.validate": [ "javascript", "javascriptreact", { "language": "html", "autoFix": true } ]
}

そもそも同じようなプラグインが既存でないのか?

作ってる人はいます。
https://www.npmjs.com/package/eslint-plugin-microtemplates
ただ、試してみると色々と問題があり使い難い印象だったとの、追加ルールも特に無いし、最終更新が2年前なのもあり、んー。。。

作ってみて

参考になるプラグインeslint-plugin-vueがあったおかげで基本的な仕組みは簡単にできました:smiley: 感謝。
ただやはりインデント処理とかは難しくて、標準のESLinteslint-plugin-vueでインデント処理作った人はすごいなーと改めて思いました。感謝。


  1. このテンプレートの正式名称って何? Micro-TemplateLodash templateUnderscore template? Micro-Templating → https://johnresig.com/blog/javascript-micro-templating/

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?