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
解説はこちら
比較演算子は左結合で評価されるので、 3 > 2
がtrue
になって、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するのはこいつでいけそうというのをチームメンバーが見つけてくれたので採用。
そうしたら、「ban-consective-comparison-operator」とか「ban-continous-comparison-operator」とかでeslintのruleがあるだろうと探してみたが、見つからない。
eslint本家のrepositoryのissueも探してみたが話題にすら上がっていない。ついでに、3rd partyのlint ruleも探したがやっぱり見つからない。
世の中の人は3 > a > 1
とか書かないらしい。
これは仕方ないので空きを見つけて自作して、チームメンバーの@1natsuが公開しているルール集に突っ込んでおくかという話になった。
コミュニティに聞いてみた
自作する方向にはなったが、コミュニティに聞いてないなと思い、dicorsdのcommunityのhelpに投げてみた。
eslintのcommunity chat、もともとgitterを使っていたはずだが、いつの間にかdicordに移行していたらしい。
こちらが聞いてみた結果。
質問を投げて1分でチームメンバーから返答がついてびっくりした。
やはり、調べたとおり直接禁止するルールはないらしいが、no-restricted-syntax
なるruleを使えばいけるらしい。
聞いたことがないruleだったので、ドキュメントを見てみた。
要はASTでqueryができる範囲においてあらゆるruleを代替できる、まさに銀の弾丸。
これ見たとき、ASTが分かれば、好きなルール書けるじゃんと結構興奮した。
例では、FunctionExpression
とWithStatement
とin
演算子を禁止している。
eslintに限らず、この手のDX系のplugin機構を持つライブラリはuserがなんにでも使える何かを用意せず、そういうことがやりたいならAPIを叩くイメージがあったので目からうろこだった。
もちろん、ASTでqueryができる範囲なのでno-unused-vars
とかは代替できない(はず)。
ということでさっそく禁止してみることにした。
禁止してみる
最近のフロントエンドエンジニアは当たり前のようにASTを読んだり書けるらしいので、おもむろにASTExplorerを開く。
知りたいASTはこちら。
これで、ASTは分かったので、queryするだけ。
eslintのplugin機構に詳しくなかったり、ASTってなんじゃという人はこちらがおすすめ。日本語も完備されている。
これをやると完全に「これ進研ゼミでやったやつだ」になれる。
今回はこの辺。
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
とかまで禁止されてしまうので比較演算子だけに絞っている。
実際に禁止してみたところ🎉
ちなみに、eslintのデフォルトがES5であることを知らなくて、しばらくconst
でparse errorが発生して🤔になっていた。
いつもは雑にeslint-plugin-standardを入れているので、最新のECMAScriptが使えるということを知った。
おまけ
TypeScriptではどうなるかというとこうなる。
twitter.com両辺の型が違うのでTSが怒ってくれるはずです
— nullptrあざらし✌️ (@webseals) 2020年6月17日
結論
やっぱりTypeScriptは最高