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

かなり前に、[【ESLint】eslint-plugin-lodash-template作ってみたよ](https://qiita.com/ota-meshi/items/6e218768c7483e279898) という記事を書きましたが、今回、この [eslint-plugin-lodash-template] を拡張したのでその思い出を残します。

# [eslint-plugin-lodash-template]とは

[Lodashの`_.template`](https://lodash.com/docs/#template)に渡して使えるテンプレートタグ内のJavaScriptに対して[ESLint]で検証できるようにする[ESLint]のプラグインです。

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84259/e2bad6e3-5482-4985-60c2-cc1aab44bf5e.png)

[WEBで試す](https://ota-meshi.github.io/eslint-plugin-lodash-template/playground/)

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

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

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

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

export default obj
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84259/5cc41dfb-55df-9326-054c-198042010309.png)

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

[WEBで試す](https://ota-meshi.github.io/eslint-plugin-lodash-template/playground/#eJyrVipOLsosKFGyKikqTa0FAC0nBcw=)

# 使用方法

## 事前準備

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

## インストール

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

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

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

## 設定

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

- 簡単設定版  
  プラグインが持っているいくつかのルールも有効になります。

```js
{
    // ...
    "overrides": [
        {
            "files": ["**/your/templates/*.js"],  // あなたのテンプレートファイルを指すように設定します
            "extends": [
                "plugin:lodash-template/recommended-with-script"  // 簡単設定
            ],
        }
    ]
    // ...
}
```

- カスタマイズ設定版  
  プラグインが持っているルールを有効にしたい場合は自分で`"rules"`に追加してください。

```js
{
    // ...
    "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テンプレートで使いたい場合は、別途、設定が必要です。  

詳しくは[ドキュメント](https://ota-meshi.github.io/eslint-plugin-lodash-template/)を参照してください。

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

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

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

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

つまり次の例では、

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

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

export default obj
```

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

- テンプレートタグ(`<%...%>`)の内側のJavaScript

```js
                 JSON.stringify(options);  

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

                  
```

- テンプレートタグ(`<%...%>`)の外側のJavaScript

```js
const obj   = _hogehoge_hash1

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

export default obj
```

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

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

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

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

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

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

export default obj
```

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


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

export default obj
```

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

```js
/* ... */
    const obj = _hash1
/* ... */
    
/* ... */

export default obj
```

と

```js
/* ... */
    
/* ... */
    const obj = _hash2
/* ... */

export default obj
```

の2つのJavaScriptを作ります。

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

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

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

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

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

# 残る不安

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

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

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

# あとがき

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

もし、JavaScriptを生成するテンプレートを [Micro-Template](https://johnresig.com/blog/javascript-micro-templating/) とか [Lodash template]((https://lodash.com/docs/#template)) とか [Underscore template]((http://underscorejs.org/#template)) とか [EJS](http://ejs.co/) とかで書いている方はぜひ使ってみていただけると嬉しいです。

[eslint-plugin-lodash-template]: https://github.com/ota-meshi/eslint-plugin-lodash-template
[`eslint-plugin-lodash-template`]: https://github.com/ota-meshi/eslint-plugin-lodash-template
[ESLint]: https://eslint.org/
[Processor]: https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
[Parser]: https://eslint.org/docs/developer-guide/working-with-custom-parsers
[パースエラー]:https://eslint.org/demo#eyJ0ZXh0IjoiLyogLi4uICovXG4gICAgY29uc3Qgb2JqID0gX2hhc2gxXG4vKiAuLi4gKi9cbiAgICBjb25zdCBvYmogPSBfaGFzaDJcbi8qIC4uLiAqL1xuXG5leHBvcnQgZGVmYXVsdCBvYmoiLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOjksInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnsiY29uc3RydWN0b3Itc3VwZXIiOjIsIm5vLWNhc2UtZGVjbGFyYXRpb25zIjoyLCJuby1jbGFzcy1hc3NpZ24iOjIsIm5vLWNvbXBhcmUtbmVnLXplcm8iOjIsIm5vLWNvbmQtYXNzaWduIjoyLCJuby1jb25zb2xlIjoyLCJuby1jb25zdC1hc3NpZ24iOjIsIm5vLWNvbnN0YW50LWNvbmRpdGlvbiI6Miwibm8tY29udHJvbC1yZWdleCI6Miwibm8tZGVidWdnZXIiOjIsIm5vLWRlbGV0ZS12YXIiOjIsIm5vLWR1cGUtYXJncyI6Miwibm8tZHVwZS1jbGFzcy1tZW1iZXJzIjoyLCJuby1kdXBlLWtleXMiOjIsIm5vLWR1cGxpY2F0ZS1jYXNlIjoyLCJuby1lbXB0eS1jaGFyYWN0ZXItY2xhc3MiOjIsIm5vLWVtcHR5LXBhdHRlcm4iOjIsIm5vLWVtcHR5IjoyLCJuby1leC1hc3NpZ24iOjIsIm5vLWV4dHJhLWJvb2xlYW4tY2FzdCI6Miwibm8tZXh0cmEtc2VtaSI6Miwibm8tZmFsbHRocm91Z2giOjIsIm5vLWZ1bmMtYXNzaWduIjoyLCJuby1nbG9iYWwtYXNzaWduIjoyLCJuby1pbm5lci1kZWNsYXJhdGlvbnMiOjIsIm5vLWludmFsaWQtcmVnZXhwIjoyLCJuby1pcnJlZ3VsYXItd2hpdGVzcGFjZSI6Miwibm8tbWl4ZWQtc3BhY2VzLWFuZC10YWJzIjoyLCJuby1uZXctc3ltYm9sIjoyLCJuby1vYmotY2FsbHMiOjIsIm5vLW9jdGFsIjoyLCJuby1yZWRlY2xhcmUiOjIsIm5vLXJlZ2V4LXNwYWNlcyI6Miwibm8tc2VsZi1hc3NpZ24iOjIsIm5vLXNwYXJzZS1hcnJheXMiOjIsIm5vLXRoaXMtYmVmb3JlLXN1cGVyIjoyLCJuby11bmRlZiI6Miwibm8tdW5leHBlY3RlZC1tdWx0aWxpbmUiOjIsIm5vLXVucmVhY2hhYmxlIjoyLCJuby11bnNhZmUtZmluYWxseSI6Miwibm8tdW5zYWZlLW5lZ2F0aW9uIjoyLCJuby11bnVzZWQtbGFiZWxzIjoyLCJuby11bnVzZWQtdmFycyI6Miwibm8tdXNlbGVzcy1lc2NhcGUiOjIsInJlcXVpcmUteWllbGQiOjIsInVzZS1pc25hbiI6MiwidmFsaWQtdHlwZW9mIjoyfSwiZW52Ijp7fX19