hitode909の日記

趣味はマリンスポーツですの日記です

TypeScript Compiler APIとmdn-browser-compat-dataとbrowserslistを使ってサポートされていない呼び出しを見つける

IE11で動かしたいコードでelement.prepend()してたら怒ってほしい

ライブラリを使っていたらブラウザ間の差異を気にする必要があまりないけど、最近開発しているプロダクトではDOMのAPIを直接触っているので、このメソッドIEにもあるんだっけ、呼んでいいんだっけ、というのをレビューで確認したりしていて苦労していた。
過去にうっかり呼んでしまったメソッドについてはpolyfillが入っていたりして、appendのpolyfillは入ってるけどprependのpolyfillは入っていないためにIE11でエラーになる、ということが起き得る。
ブラウザ間の差異をlintしてくれるグッズとして、eslint-plugin-compatというeslint pluginがあって、fetch使えませんよ、とか発見してエラーとして報告してくれる。
便利なのだけど、たとえばelement.prepend();とか、IE11には存在しないメソッドを呼んだときには問題を検知してくれない。それはprependが本当にブラウザのAPIなのか知る必要があり、elementが何なのか知るには静的解析して型を見て、どこで定義されているものか、本当にParentNode.prependであるかどうかを調べる必要がある。
TypeScriptで書いていて、TypeScript Compiler APIを使えば型を得られるけど、eslint-plugin-compatはJavaScriptを対象としていて、また、Flowで型がついていて、TypeScriptと相性が悪い。

作ってみる

最初からTypeScriptを対象とした新しいプラグインを作ればprependの呼び出しを見つけられるのでは、と思ったのでプラグインを作ってみた。やっていることはこういう感じ。

  • eslint pluginとして実装していて、@typescript-eslint/parserをパーサーとして使っているときに動く
  • MemberExpressionとIdentifierが登場するたびに、lib.dom.d.tsで定義されたものか調べる
    • Arrayのメソッドが足りない、とかはcore-jsで定義できるので対象外にした
  • mdn-browser-compat-dataとbrowserslistを見比べて、実行対象のブラウザで定義されているか調べる
  • 定義されていなければエラーとして報告する

github.com

様子

ためしにこういうのを書いてみると

function appendHello(e: Element) {
    e.append('hello!');
}

const container = document.querySelector('#container');
if (container) appendHello(container);

appendはIE11で定義されていませんよというエラーが出る。
f:id:hitode909:20200301171118p:plain

これだけなら最高のプラグインだけど、そんなことは精度は全然だめで、appendを自分で定義するコードもエラーとして発見してくれる。代入していたら検知しないとか、登録済みのpolyfillを定義できるようにするとか、そういうのをこれからやっていく必要がある。

function appendHello(e: Element) {
    e.append('hello!');
}

const container = document.querySelector('#container');

if (container) {
    container.append = (s: string) => container.appendChild(document.createTextNode(s));
    appendHello(container);
}

f:id:hitode909:20200301172047p:plain

とはいえASTの型を見て怒ってくれるプラグインを作れることは分かったので、こつこつやっていけばそのうちそこそこちゃんと動くものができそう。あと、TypeScript Compiler APIに詳しい人と友達になりたい。