JavaScriptで3 > a > 1と書いてしまった話

はじめに

仕事でチームメンバーみんなでTypeScriptを書いているのだが、たまにmarkdownの中のJavaScriptをいじることがある。

ある日、ある数字aが3より小さく1より大きいという条件を書く必要があった。

そのときに書いたコードがこれ

if (3 > a > 1) {
  // 省略
}

プログラミングをやっていると、こういうときは 論理演算子で2つの式を結合して3 > a && a > 1 と書くように手癖がつくものだが、小学生のときから習ってきた数学的な記法はふとしたときに顔を出す。

特に、現代のエディターによる便利な補完に慣れきってしまっている状態で一切の補完が効かない状態で油断してJSを書くと上記のようなコードが爆誕する。

今回はこれのお話。

3 > a > 1の返り値

ところで、3 > a > 1の式、実はJSのSyntaxErrorにならないのを知っていただろうか?

私はてっきり、この式は文法的に正しくないとばっかり思っていたので、上記のコードがマージされてバグとして顕在化するまで🤔となっていた。

3 > 2 && 2 > 1;
// > true

3 > 2 > 1;
// > ???

これの答えがパッと出てくるとECMAScriptチョットデキルと言える道が見えてきそう。

答えはこちら

3 > 2 && 2 > 1;
// true

3 > 2 > 1;
// false

解説はこちら

developer.mozilla.org

比較演算子は左結合で評価されるので、 3 > 2trueになって、true > 1が評価される。

trueを数字にすると1なので、1 > 1になって、falseが返り値になる。

https://www.ecma-international.org/ecma-262/#sec-abstract-relational-comparison

この記事を書くにあたってECMA-262を確認したら、こんなのもできるらしい

"hoge" > "hoge"
// false

"hogedayo" > "hoge"
// true

JS、秘孔がたくさんあるので、面白い。

やはり仕様書は正義。ちゃんと読めてるのか不安だが。。。

3 > a > 1を禁止したい

さすがに、3 > a > 1を許容していたらつらいので、linterで止める方向性で解決策を模索した。

とりあえず、markdownの中のJSにlintするのはこいつでいけそうというのをチームメンバーが見つけてくれたので採用。

github.com

そうしたら、「ban-consective-comparison-operator」とか「ban-continous-comparison-operator」とかでeslintのruleがあるだろうと探してみたが、見つからない。

eslint本家のrepositoryのissueも探してみたが話題にすら上がっていない。ついでに、3rd partyのlint ruleも探したがやっぱり見つからない。

世の中の人は3 > a > 1とか書かないらしい。

これは仕方ないので空きを見つけて自作して、チームメンバーの@1natsuが公開しているルール集に突っ込んでおくかという話になった。

github.com

コミュニティに聞いてみた

自作する方向にはなったが、コミュニティに聞いてないなと思い、dicorsdのcommunityのhelpに投げてみた。

eslintのcommunity chat、もともとgitterを使っていたはずだが、いつの間にかdicordに移行していたらしい。

こちらが聞いてみた結果。

gyazo.com

質問を投げて1分でチームメンバーから返答がついてびっくりした。

やはり、調べたとおり直接禁止するルールはないらしいが、no-restricted-syntaxなるruleを使えばいけるらしい。

聞いたことがないruleだったので、ドキュメントを見てみた。

eslint.org

要はASTでqueryができる範囲においてあらゆるruleを代替できる、まさに銀の弾丸

これ見たとき、ASTが分かれば、好きなルール書けるじゃんと結構興奮した。

例では、FunctionExpressionWithStatementin演算子を禁止している。

eslintに限らず、この手のDX系のplugin機構を持つライブラリはuserがなんにでも使える何かを用意せず、そういうことがやりたいならAPIを叩くイメージがあったので目からうろこだった。

もちろん、ASTでqueryができる範囲なのでno-unused-varsとかは代替できない(はず)。

ということでさっそく禁止してみることにした。

禁止してみる

最近のフロントエンドエンジニアは当たり前のようにASTを読んだり書けるらしいので、おもむろにASTExplorerを開く。

知りたいASTはこちら。

astexplorer.net

これで、ASTは分かったので、queryするだけ。

eslintのplugin機構に詳しくなかったり、ASTってなんじゃという人はこちらがおすすめ。日本語も完備されている。

github.com

これをやると完全に「これ進研ゼミでやったやつだ」になれる。

今回はこの辺。

https://github.com/Quramy/eslint-plugin-tutorial/tree/057e4cbc907d0d571996452639a9b5470bf197fa/guide/20_dive_into_ast#build-selector https://eslint.org/docs/developer-guide/selectors

CSSっぽいselectorを書けるらしい。

ここで試せるので、試行錯誤しながら書いていく。

http://estools.github.io/esquery/

出来上がったqueryはこちら

BinaryExpression[operator=/>|>=|<|<=/] > BinaryExpression

連続したBinaryExpressionをすべて禁止すると1 + 1とかまで禁止されてしまうので比較演算子だけに絞っている。

実際に禁止してみたところ🎉

f:id:sasurau4:20200622114438p:plain

ちなみに、eslintのデフォルトがES5であることを知らなくて、しばらくconstでparse errorが発生して🤔になっていた。

いつもは雑にeslint-plugin-standardを入れているので、最新のECMAScriptが使えるということを知った。

github.com

おまけ

TypeScriptではどうなるかというとこうなる。

twitter.com

f:id:sasurau4:20200622115237p:plain

www.typescriptlang.org

結論

やっぱりTypeScriptは最高