最近は ESLint をいじって遊んでるので紹介します
2022年の sosukesuzuki 1人アドベントカレンダー 3 日目です。
学校の課題や仕事をどうしてもやりたくないけど、数時間だけ余裕があって、プログラムは書きたい!というとき、みなさんはどうしていますか?
最近の自分は ESLint をやっています。ということで自分が実装した変更を紹介していきます。
基本的に、ESLint の Rules ドキュメントを眺めてみて「バグがありそうだな〜」とか、「このルール足りてなさそう!」とか思ったときに Issue を作成して修正する方針でやりました。
ESLint の Contributor Pool プログラム
ESLint には Contributor Pool プログラムというものがあります。OSS プロジェクトとしてとても良い取り組みだと思うのでここで紹介します。
Contributor Pool プログラム は、ESLint チームが外部の貢献者に対してその貢献に応じて報奨金を支払うという取り組みです。
私は ESLint チームのメンバーではありませんが、この記事で紹介している変更に対して合わせて $1000 ほどの報奨金をいただきました。具体的な金額については ESLint の TSC ミーティングで決定されているようです。
新しいルール
現在の ESLint は、新しい ECMAScript の機能に関連するルールのみ追加する方針になっています。
空の Static Block を禁止するルールを追加
https://github.com/eslint/eslint/pull/16325
空の Static Block は基本的に無意味なので、それを禁止するルールを新しく作りました。
class Foo {
static {}
}
余談だけど Static Block って使ったことない。
この変更は 8.27.0 でリリースされました。
BigInt や Symbol を new で呼び出すのを禁止するルールを追加
https://github.com/eslint/eslint/pull/16368
ESLint には no-new-symbol というルールがあります。これは Symbol を new で呼び出すのを禁止するルールです。
// だめ
const symbol = new Symbol("description");
これを実行すると実行時エラーが起こります。なので ESLint の段階でエラーにしておきたいわけです。
最近 ECMAScript に追加されたプリミティブにはこういう、new で呼び出すと実行時エラーが起こるという特徴がありがちです。
で、最近追加されたプリミティブとして Symbol の他に BigInt があります。なので no-new-symbol と同じように no-new-bigint という新しいルールが必要だと考えました。
しかしこうすると、今後同じようなプリミティブが追加されたときに新しいルールを追加する必要があります(たとえば Tuple とか Record が検討されている)。
これでは面倒なので、そういう new で呼び出すことができないもの全般を new で呼び出すのを禁止する汎用的なルールを追加することになりました
そこで no-new-native-nonconstructor を実装しました。このルールの命名には色々と議論がありました。結果として少しわかりにくいルール名になってしまったかもしれません。
この変更は 8.27.0 でリリースされました。
既存ルールの変更
array-callback-return の findLast と findLastIndex サポートの追加
https://github.com/eslint/eslint/pull/16314
array-callback-return というルールがあります。これは Array.prototype.map とか Array.prototype.filter みたいなメソッドの引数に渡すコールバック関数が、必ずなにかを return するように強制するルールです。
このルールは現在 Stage 4 の findLast、findLastIndex をサポートしていなかったので、サポートするように修正しました。
この変更は 8.24.0 でリリースされました。
id-length がコードユニットの数ではなく grapheme (書記素) を数えるようにする
https://github.com/eslint/eslint/pull/16321
id-length というルールがあります。簡単にいえば短すぎる識別子を禁止するためのルールです。
id-length はデフォルトでは 2 文字未満の識別子を禁止します。たとえば
const x = "x";
const y = "y";
みたいなものは禁止されますが、
const _x = "x";
const _y = "y";
みたいなものは許容されます。
で、このルールは文字のカウントに String.prototype.length を使っていました。これは JavaScript あるあるなんですが String.prototype.length というのは UTF-8 のコードユニットの数を数えるので、いわゆる我々が思う「文字数」とは違う結果が返ってきます。
以前 Prettier で似たような問題があったときに https://zenn.dev/sosukesuzuki/articles/d21d69a5914a03 という記事を書いたので興味がある人はそちらも参照してください。
つまり id-length ルールは(デフォルトで)識別子 叱(U+53F1) は禁止するものの、𠮟(U+D842, U+DF9F) は length が 2 なので 許容するのです。
// invalid (U+53F1)
const 叱 = 2;
// valid (U+D842, U+DF9F)
const 𠮟 = 2;
なので String.prototype.length の代わりに graphemse-splitter というすでに ESLint の依存に含まれているライブラリを使って grapheme を数えるようにしました。
よく考えると ASCII に含まれない範囲の文字を識別子に使うことは現実的にはほとんどないので、別に直さなくてもよかったかもしれません。
この変更は 8.25.0 でリリースされました。
no-implicit-globals の exported ブロックコメントサポートの追加
https://github.com/eslint/eslint/pull/16343
まず no-unused-vars というルールがあります。このルールは名前のとおり未使用の変数を禁止するルールです。このルールには exported という機能があります。
こんなふうにブロックコメントで変数の名前を指定すると、未使用でも許容されるようになりました。
/* exported global_var */
var global_var = 42;
そして no-implicit-globals というルールがあります。このルールは暗黙的にグローバル変数を作ってしまうことを禁止します。
スクリプトで、こんなふうに変数を宣言すると暗黙にグローバルになってしまうので、no-implicit-globals はこれを禁止します。
var foo = 1;
この no-implicit-globals も、no-unused-vars と同じように exported ブロックコメントをサポートするべきという Issue があったので、実装しました。
/* exported foo */
var foo = 1;
この変更は 8.26.0 でリリースされました。
no-obj-calls の Intl サポートを追加
https://github.com/eslint/eslint/pull/16543
no-obj-calls というルールがあります。このルールは Math、JSON、Reflect、Atomics を関数として呼び出すことを禁止するルールです。
これらはグローバルに生えていて、名前の先頭が大文字で始まっていうのでコンストラクタっぽく見えますが、実際にはコンストラクタではありません。なのでルールで禁止したいということです。
こういう関数呼び出しを禁止します。
const math = Math();
const newMath = new Math();
で、このルールは ECMA 402 にある Intl をサポートしていません。Intl も Math などと同じようにコンストラクタではないもののグローバルに生えています。
ちなみに、ECMA 262 ではなく ECMA 402 に定義されている Intl を ESLint コアルールがサポートするべきかどうか、という論点はあります。とはいえ、ほぼすべての JavaScript 実行環境が Intl をサポートしていること、ESLint の他の一部のルールも Intl の存在を考慮していることから、サポートすることになりました。
この変更は 8.28.0 でリリースされました。