stefafafan の fa は3つです

"すてにゃん" こと id:stefafafan のブログです

TypeScript 5.6 の「Disallowed Nullish and Truthy Checks」

先日TypeScript5.6がリリースされました。その中で「Disallowed Nullish and Truthy Checks」という内容が個人的に目を引いたので、軽く内容について知っておこうと思って関連Pull Requestの様子などを見ていきました。


「Disallowed Nullish and Truthy Checks」の概要

ブログで紹介されている内容を簡単にかいつまんで紹介すると、以下のような感じになります:

  • これまでvalidなJavaScriptとして通っていた一部のケースを、TypeScriptでエラーとして扱うようにした
  • 具体的には、意図せず常に真として判定されてしまうコードや、 ??演算子の優先順を勘違いしたコードが指摘されるようになる
  • 既存のESLintルール no-constant-binary-expression と似ているが、完全に同じというわけではない

これまで存在してきたESLintルール no-constant-binary-expression とは

no-constant-binary-expression では、常にtruthyもしくはfalsyになることが確定しているような無意味なコードを指摘してくれます。

以下の全ての例においてエラーとして指摘してくれます(例は上記ドキュメントより引用)。

/*eslint no-constant-binary-expression: "error"*/

const value1 = +x == null;

const value2 = condition ? x : {} || DEFAULT;

const value3 = !foo == null;

const value4 = new Boolean(foo) === true;

const objIsEmpty = someObj === {};

const arrIsEmpty = someArr === [];

const shortCircuit1 = condition1 && false && condition2;

const shortCircuit2 = condition1 || true || condition2;

const shortCircuit3 = condition1 ?? "non-nullish" ?? condition2;

また、以下のような条件文でも左辺が常に true なため、エラーとして指摘されます。

/*eslint no-constant-binary-expression: "error"*/
if (true || inDebuggingOrDevelopmentEnvironment) {
    // ...
}

ちなみにこのルールは @eslint/jsrecommended を有効にしていれば有効化されているそうなので、普段意識せず使っている人も多いかもしれません。

Using the recommended config from @eslint/js in a configuration file enables this rule

no-constant-binary-expression - ESLint - Pluggable JavaScript Linter

以下の記事ではこのルールによって実際に著名なOSSプロダクトの様々なバグを発見したことや、??演算子の優先順に気づきやすくするためのVS Code拡張を紹介していたりします。

「Disallowed Nullish and Truthy Checks」に対応するPull Requestの詳細

今回の変更は以下のPull Requestによって追加されました。

Pull Requestの説明によると、先述のESLintのルールと重なる部分は多いものの、 falsetrue01は実行結果に変化がなかったとしても、実装者として意図的であることが多いためチェックから除外しているそうです。

The expressions false, true, 0, and 1 are excluded from this check, since code like while (true) {, while(1) {, do { } while (false), if (true || i_am_just_debugging) { are common and unlikely to represent programmer errors.

Disallow truthiness/nullishness checks on syntax that never varies on it by RyanCavanaugh · Pull Request #59217 · microsoft/TypeScript · GitHub

実際にPlaygroundで試したところ、以下のようなコードでは指摘はなかったので、少なくともこの点においてはESLintのルールと差分があるようでした。

if (true || inDebuggingOrDevelopmentEnvironment) {
    // ...
}

面白いのは、実際にこのPull Requestによって様々なOSSのバグが発見されPull Requestコメントで列挙されていました。

Pull Requestを作成したRyanCavanaugh氏曰く、見つけたバグの種類について以下のような分類ができるらしいです(詳しくはGitHubのコメントをみてください)。

  • ??演算子の左辺の処理忘れ、もしくは括弧を忘れている
  • 三項演算子の左辺に対する処理が不足 (常に真になるなど)
  • 括弧の配置ミス
  • a + b ?? c のような文においての ??演算子の優先順に対する認識ミス
  • if (historyMsgLength > modelConfig?.max_tokens ?? 4000) のような文において、modelConfig がnullableな状態で ?? を使おうとしている
  • 三項演算子の優先順に対する認識ミス
  • 不可能な状態に対して処理しようとしている実装(TypeScriptの言語仕様に対する自信のなさからくるもの?)
  • 左辺と右辺の取り違えタイポ (processArray([] || someArray) のケースでの []someArray のような)
  • const opts = { ...someOpts } || { } のような、 { ...someOpts } が falsy になるという認識違い
  • ネストされた三項演算子による実装ミス (const test = a ? b : c ? d : "e" ? f : g;)
  • 上記以外の意図のわからないコード
今回の変更で追加で目にするようになるエラーメッセージ

Pull Requestの差分をみていると、TypeScriptのエラーメッセージはどうやら src/compiler/diagnosticMessages.json というファイルで管理されていることがわかりました。ということで今回のこの機能によって足されたエラーメッセージは以下のリンクの5つです。

具体的には、

  1. Right operand of ?? is unreachable because the left operand is never nullish.
  2. This binary expression is never nullish. Are you missing parentheses?
  3. This expression is always nullish.
  4. This kind of expression is always truthy.
  5. This kind of expression is always falsy.

感想

ESLintのルールとして存在するものが公式の言語仕様として取り入れられるのは嬉しいと思いました。一方で、TypeScriptは最終的にはJavaScriptにトランスパイルされることもあり、JavaScript世界での後方互換性などを意識した作りにする必要があり、大変そうだなとも思ったりしました。
そして、世の中のOSSではこれだけバグが多く存在するんだなとPull Requestを見て若干びっくりしました。確かに、演算子の優先順を完全に理解している自信はありませんですし、不安なときは括弧で囲うなどしていきたい気持ちになってしまいました(まあ、今回の変更やESLintルールがあればそこまで不安はないですが)。
今回の「Disallowed Nullish and Truthy Checks」は確かにTypeScriptを扱ういちプログラマとしてはとても歓迎できる機能だと思うので、なるべく最新バージョンに追従していきたいなと思いました。