【ESLint】eslint-plugin-lodash-templateがJavaScriptテンプレートをサポートしたよ


かなり前に、【ESLint】eslint-plugin-lodash-template作ってみたよ という記事を書きましたが、今回、この eslint-plugin-lodash-template を拡張したのでその思い出を残します。

eslint-plugin-lodash-templateとは

Lodashの_.templateに渡して使えるテンプレートタグ内のJavaScriptに対してESLintで検証できるようにするESLintのプラグインです。

つまり、次のようなテンプレートでエラー検出ができるようになるESLintのプラグインです。

WEBで試す

JavaScriptテンプレートをサポートしました

(実験的機能という体で)JavaScriptを生成するテンプレートをサポートしました。
何を行っているかと言いますと、次のようなテンプレートでESLintかけられるようにしました。

const obj   = <%= JSON.stringify(options) %>

<% for (const key of Object.  keys(additionals)) { %>
    obj[ <%= key %>] =<%= additionals[key] %>
<% } %>

export default obj

具体的には、以前まではテンプレートタグ(<%...%>)の内側のJavaScriptだけ検証していましたが、今回のアップデートでテンプレートタグ(<%...%>)をひっぺがした外側のJavaScriptを検証できるようになりました

WEBで試す

使用方法

事前準備

ESLintを利用できる環境を整えてください。ここではESLintがすでに利用できる状態からの利用方法を書きます。

インストール

eslint-plugin-lodash-templateをインストールしましょう。

npm install --save-dev eslint-plugin-lodash-template

いないと思いますが、すでにインストールされている場合は最新版に更新してください。(ただし破壊的変更があるので注意してください)

設定

.eslintrc.*の設定ファイルに下記のいずれかを追記します。

  • 簡単設定版
    プラグインが持っているいくつかのルールも有効になります。
{
    // ...
    "overrides": [
        {
            "files": ["**/your/templates/*.js"],  // あなたのテンプレートファイルを指すように設定します
            "extends": [
                "plugin:lodash-template/recommended-with-script"  // 簡単設定
            ],
        }
    ]
    // ...
}
  • カスタマイズ設定版
    プラグインが持っているルールを有効にしたい場合は自分で"rules"に追加してください。
{
    // ...
    "overrides": [
        {
            "files": ["**/your/templates/*.js"],  // あなたのテンプレートファイルを指すように設定します
            "extends": [
                "plugin:lodash-template/base"  // eslint-plugin-lodash-templateが有効になります。
            ],
            "processor": "lodash-template/script", // JavaScriptテンプレートモードを有効にします。
            "rules": {
                // ... 好きなルールを追加してください。
            }
        }
    ]
    // ...
}

で動くと思います。

JavaScriptを生成するテンプレートの拡張子が.jsではない場合は、実行時にその拡張子を含めるようにしたり、エディタの設定が必要な場合があります。
また、以前からeslint-plugin-lodash-templateがサポートしている普通のHTMLテンプレートで使いたい場合は、別途、設定が必要です。

詳しくはドキュメントを参照してください。

JavaScriptテンプレートを検証する仕組み

僕がこの記事で一番書きたかった内容です。

複数のJavaScriptに分解して検証する

eslint-plugin-lodash-templateではテンプレートタグ(<%...%>)の内側のJavaScriptと外側のJavaScriptをESLintにかけるために、別々に複数のJavaScriptを作ってESLintで検証して、エラー結果をマージして出力するということをしています。

つまり次の例では、

const obj   = <%= JSON.stringify(options) %>

<% for (const key of Object.  keys(additionals)) { %>
    obj[ <%= key %>] =<%= additionals[key] %>
<% } %>

export default obj

次のようなJavaScriptを作り出します。

  • テンプレートタグ(<%...%>)の内側のJavaScript
                 JSON.stringify(options);  

   for (const key of Object.  keys(additionals)) {   
             key;         additionals[key];  
   }   


  • テンプレートタグ(<%...%>)の外側のJavaScript
const obj   = _hogehoge_hash1

/* コメントにする                              */
    obj[ _hogehoge_hash2] =_hogehoge_hash3
/* コメントにする */

export default obj

_hogehoge_hash*って書いた部分は実際はテンプレートタグのハッシュ値です。

と、このように複数のJavaScriptを作っています。

条件分岐で壊れるJavaScriptをサポートする

ところで、先ほどからずっと「複数」のJavaScriptと表現していますが、これまでの説明だとテンプレートタグの内側と外側のJavaScriptで2つだけですね。
なぜ「複数」と表現しているかといいますと、条件分岐で壊れるJavaScriptをサポートするために外側のJavaScriptはテンプレートタグ(<%...%>)の条件分によって複数作成します。

例えば、次のようなテンプレートがあった場合、

<% if (flag) { %>
    const obj = <%= JSON.stringify(optionsA) %>
<% } else { %>
    const obj = <%= JSON.stringify(optionsB) %>
<% } %>

export default obj

単純にテンプレートタグ(<%...%>)の外側のJavaScriptを作ると、

/* ... */
    const obj = _hash1
/* ... */
    const obj = _hash2
/* ... */

export default obj

となりますが、このJavaScriptはパースエラーです。const obj二つ目があるよと怒られるだけで終わってしまします。
なので、この問題を解決するために、テンプレートタグに条件分岐が含まれる場合は、全てのPathを通るようにJavaScriptを複数作成しています。

上の例だと、

/* ... */
    const obj = _hash1
/* ... */

/* ... */

export default obj

/* ... */

/* ... */
    const obj = _hash2
/* ... */

export default obj

の2つのJavaScriptを作ります。

ESLintプラグインでこの仕組みを作るには

ESLintにはProcessorっていうプラグインのための仕組みがありまして、例えば.html.mdで複数のJavaScriptを分解して、あとでエラーメッセージをマージするというのができるような仕組みです。バッチリ今回の用途にも合いそうな仕組みですね。
しかしこれも一筋縄ではいかず、ちょっと変わった使い方をしました。

分解したJavaScriptを渡して、エラー結果をマージするには、エラー結果の出現箇所を復元する必要があります。
しかし、これがなかなか難しく、例えば、行の最大文字数の検出は、ソースコード自体が違う場合にマージするなんてできませんね。

この問題を無理やり解決するために、Processorの前処理では、幾つのJavaScriptの処理が必要かどうかだけ計算して、同じ元のソースコードを複数個返し、Parserでパースするタイミングで、書き換えたJavaScriptでASTを構築して、エラー結果をもらうという対応をしました。

おかげでマージは楽になったので、Processorの後処理で、メッセージ重複を排除したり、適当に作ったハッシュが気に入らないと怒られる部分を除外したりするだけです。

残る不安

と、このような仕組みで作ってはみたもののいくつかの不安が残っています。

  1. テンプレートタグ(<%...%>)の外側JavaScript生成の時に、補間(<%=...%>)の部分にハッシュ値入れてるのですが、これで色々なテンプレートで問題なくパースできるJavaScriptが作れるのかどうかはかなり不安です。。
  2. 条件分岐があった場合に分岐をパス網羅するようにJavaScriptを作っているのですが、これで色々なテンプレートで問題なくパースできるJavaScriptが作れるのかどうかはかなり不安です。。

なので、実験的機能ということにしています。

あとがき

僕、実は、JavaScriptを生成するテンプレートってプロダクションで書いたことないんですけどね。
なんでこれ対応したかというとnuxt-communityで使いたいみたいなissueが来てちょっと面白そうだったからやってみたという感じです。実際、使えるかはまだわからないですけど。

もし、JavaScriptを生成するテンプレートを Micro-Template とか Lodash template とか Underscore template とか EJS とかで書いている方はぜひ使ってみていただけると嬉しいです。