TypeScript 4.9からはsatisfies演算子でパターンの網羅性をESLintと共存しつつ簡単にチェックできる
背景
私はAWS CDKを利用する時にデプロイステージをよく以下のように分けています。
export const stages = ["deve", "stag", "prod"] as const;
/**
* 開発用、本番用などを分けるステージ
* {@link https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_core.Stage.html} は一切関係がない
*/
export type Stage = typeof stages[number];
こうするとstages
を反復することで簡単に全てのステージに対してスタックを作ることが可能ですし、その名前にStage
をstring
として埋め込んで区別することが可能です。
引数ではstring
ではなくStage
を受け取ってstage === "deve"
のように比較すれば、リテラル型となっているのでtypoを防ぐことが可能です。
問題
この間switch(stage)
で処理を分けていた時、
ESLintが怒ってくるため、
TypeScriptでas constで作ったリテラル型の値をswitchしてもESLintがdefault-caseつけろって怒ってくる
— エヌユル (@ncaq) 2023年2月17日
JavaScriptでも動くように文字列で型を作るしかないからこういう齟齬が生まれるんだよね
自分が求めるのはswitchがHaskellのパターンマッチみたいにリテラル型が変更したらビルドエラーになること
TypeScriptのexhaustiveness checkをスマートに書くみたいな黒魔術を見てビビってました。
しかしTypeScript 4.9からは割と簡単に書けることを教えてもらいました。
defaultでsatisfies neverするかneverを受け取る関数に渡すかで網羅してるかのビルドエラーは出せると思うんですが、それじゃダメな場合でしょうか?
— Yano (@yuki_ycino) 2023年2月17日
今回の場合は単純に、
switch (stage) {
case "deve":
return deve;
case "stag":
return stag;
case "prod":
return prod;
default:
throw new Error(stage satisfies never);
}
とすれば良かったです。ちゃんとcase
網羅しないとエラーになります。
lspの読み込むバージョンが古くてトラブル
コマンドライン上のlintではTypeScriptとprettierをアップデートすることで問題なくなったのですが、 Emacsのlsp-modeのTypeScript lspがエラーを出し続けて、 language serverを更新してもエラーを出して困惑しました。
しかし*lsp-log*
を見て納得しました。
Using Typescript version (user-setting) 4.8.4 from path "/home/ncaq/.config/yarn/global/node_modules/typescript/lib/tsserver.js"
— エヌユル (@ncaq) 2023年2月17日
はい
何故かグローバルにインストールされたTypeScriptコンパイラを優先的に読み込んでしまっていました。グローバルに使うこともないやと最近グローバルにインストールするスクリプトからもTypeScriptコンパイラは削除したので、
yarn global remove typescript
で解決しました。
雑にts-node
を使いたくなったらどうしようかなと思いましたが、それぐらいならyarn dlx
を使えば良いですかね。
これバグ報告した方が良いのだろうか、しかしもうグローバルにインストールするのはとにかくやめろみたいな風習がありますし悩みどころですね。
as const
が既についている配列にsatisfies
を付けるモチベーションが分からない
記事を漁っていると私のstages
みたいな変数にas const
だけではなくsatisfies
を付けるパターンを見ましたが、
as const
してる時点でリテラル型になっているため、オブジェクト型ならともかく配列型につけるモチベーションは今ひとつ分かりませんでした。