sosukesuzuki.dev

2022年 sosukesuzuki 1人アドベントカレンダー

December, 03 2022

最近は 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 でリリースされました。

BigIntSymbolnew で呼び出すのを禁止するルールを追加

https://github.com/eslint/eslint/pull/16368

ESLint には no-new-symbol というルールがあります。これは Symbolnew で呼び出すのを禁止するルールです。

// だめ
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-returnfindLastfindLastIndex サポートの追加

https://github.com/eslint/eslint/pull/16314

array-callback-return というルールがあります。これは Array.prototype.map とか Array.prototype.filter みたいなメソッドの引数に渡すコールバック関数が、必ずなにかを return するように強制するルールです。

このルールは現在 Stage 4 の findLastfindLastIndex をサポートしていなかったので、サポートするように修正しました。

この変更は 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) は length2 なので 許容するのです。

// 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-globalsexported ブロックコメントサポートの追加

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-callsIntl サポートを追加

https://github.com/eslint/eslint/pull/16543

no-obj-calls というルールがあります。このルールは MathJSONReflectAtomics を関数として呼び出すことを禁止するルールです。

これらはグローバルに生えていて、名前の先頭が大文字で始まっていうのでコンストラクタっぽく見えますが、実際にはコンストラクタではありません。なのでルールで禁止したいということです。

こういう関数呼び出しを禁止します。

const math = Math();
const newMath = new Math();

で、このルールは ECMA 402 にある Intl をサポートしていません。IntlMath などと同じようにコンストラクタではないもののグローバルに生えています。

ちなみに、ECMA 262 ではなく ECMA 402 に定義されている Intl を ESLint コアルールがサポートするべきかどうか、という論点はあります。とはいえ、ほぼすべての JavaScript 実行環境が Intl をサポートしていること、ESLint の他の一部のルールも Intl の存在を考慮していることから、サポートすることになりました。

この変更は 8.28.0 でリリースされました。