- 投稿日:
process.exit()
のラッパーだったり、throw
する関数だったりして、戻り値がnever
を取る関数で、TypeScript上、後続処理がデッドロジックになるものを作る方法。
確認環境
Env | Ver |
---|---|
TypeScript | 5.4.5 |
やりたいこと
process.exit()
を書くと次の行以降がデッドロジック扱いされるが、これをやりたい。
問題点
普通に実装して型推論に任せてもうまくいかない。
例えば次の左図の関数の戻り値型はnever
だが、右図ではデッドロジックとならない。
これは次のように戻り値の型にnever
を直書きしても機能しない。
解決策
次の左図のように関数そのものの型を作ると解決できる。右図を見るとデッドロジックになっていることが確認できる。
何のためにこれをやるか
別の関数から呼んだときに、呼び側の関数の戻り値型を正しくするためだ。下図はデッドロジックになっていない状態のときのものだが、処理が死なずに通り抜けてしまうのでundefined
が帰ることになっており、この関数の呼び下でundefined
だった時の処理を書く必要が出てきてしまう。
しかし、デッドロジック扱いになっていれば下図のように戻り値の型が期待した通りの内容になる。この関数がundefined
を返すことはないので、この状態を実現するために行っている。
関連記事
- TypeScriptでprocess.exit()ラッパーの後をデッドロジック扱いにする
- オブジェクトをラップして、そのオブジェクトに型を付けることで解決している
- 投稿日:
依存ライブラリの関係でESLint v9への移行はできていないが、取り合えずFlat configに移行した。ESLintは7.9.0から8.57.0に更新している。
確認環境
Flat configになってからeslint:recommended
は@eslint/jsに移った模様。
Env | Ver |
---|---|
@eslint/js | 9.2.0 |
@lycolia/eslint-config | 0.9.1 |
@swc/jest | 0.2.31 |
@types/jest | 29.5.11 |
@types/node | 20.11.5 |
eslint | 8.57.0 |
eslint-plugin-jest | 28.5.0 |
jest | 29.7.0 |
prettier | 3.2.5 |
typescript | 5.4.5 |
typescript-eslint | 7.8.0 |
元の設定
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:jest/style",
"plugin:jest/recommended",
"@lycolia/eslint-config",
"prettier"
],
"plugins": [
"@typescript-eslint"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
}
}
やったこと
ESLintなど必要ライブラリのアップデート
npm i -D eslint @eslint/js eslint-plugin-jest jest prettier typescript typescript-eslint @lycolia/eslint-config
新設定の作成
eslint.config.js
を作成
extendsの設定
今までextends
に書いていたものはrequire
でモジュールを参照してmodule.exports = []
の配列の中に書いた。typescript-eslintとeslint-plugin-jestの対応が何とも言えない。なお、この状態だとtypescript-eslintが正常に動かない。
const eslint = require("@eslint/js");
const eslintTypeScript = require("typescript-eslint");
const eslintJest = require("eslint-plugin-jest");
const eslintLycolia = require("@lycolia/eslint-config");
module.exports = [
eslint.configs.recommended,
eslintTypeScript.configs.recommended[0],
eslintJest.configs["flat/style"],
eslintJest.configs["flat/recommended"],
eslintLycolia,
];
設定が競合して都合が悪かったので、この時にPrettierの設定を削除しているが、Flat config化とは特に関係ないので無視してよい。
@lycolia/eslint-configは私が作っているカスタム設定だが、これは特に何もせずとも使えた。
.eslintignore対応
extendsしたオブジェクトの次にオブジェクトを記述し、そこにignores: []
を追加し、その中に同じことを書いた。恐らく根っこの配列はESLintの設定オブジェクトの羅列で、最終的に後勝ちでマージされる様に出来ているのだろう。
const eslint = require("@eslint/js");
const eslintTypeScript = require("typescript-eslint");
const eslintJest = require("eslint-plugin-jest");
const eslintLycolia = require("@lycolia/eslint-config");
module.exports = [
eslint.configs.recommended,
eslintTypeScript.configs.recommended[0],
eslintJest.configs["flat/style"],
eslintJest.configs["flat/recommended"],
eslintLycolia,
{
ignores: [
'dist/*',
'coverage/*',
'**/*.d.ts',
'/src/public/',
'/src/type',
'*.config.js'
],
}
];
TypeScript対応
eslintTypeScript.config()
でESLintの設定配列全体を引数としてラップし、languageOptions
の中身を移植した。旧設定にあったplugins
は公式のドキュメントを見る限り不要になっている模様。
const eslint = require('@eslint/js');
const eslintTypeScript = require('typescript-eslint');
const eslintJest = require('eslint-plugin-jest');
const eslintLycolia = require('@lycolia/eslint-config');
module.exports = eslintTypeScript.config(
eslint.configs.recommended,
...eslintTypeScript.configs.recommended,
eslintJest.configs['flat/style'],
eslintJest.configs['flat/recommended'],
eslintLycolia,
{
ignores: [
'dist/*',
'coverage/*',
'**/*.d.ts',
'/src/public/',
'/src/type',
'*.config.js'
],
languageOptions: {
parser: eslintTypeScript.parser,
parserOptions: {
project: './tsconfig.json'
}
}
}
);
旧設定の削除
.eslintrc
と.eslintignore
を消した。
ESLint v9対応
typescript-eslintが対応していないため、現時点ではできなかった。
変更差分まとめ
-{
- "extends": [
- "eslint:recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:jest/style",
- "plugin:jest/recommended",
- "@lycolia/eslint-config",
- "prettier"
- ],
- "plugins": [
- "@typescript-eslint"
- ],
- "parser": "@typescript-eslint/parser",
- "parserOptions": {
- "project": "./tsconfig.json"
+const eslint = require('@eslint/js');
+const eslintTypeScript = require('typescript-eslint');
+const eslintJest = require('eslint-plugin-jest');
+const eslintLycolia = require('@lycolia/eslint-config');
+
+module.exports = eslintTypeScript.config(
+ eslint.configs.recommended,
+ ...eslintTypeScript.configs.recommended,
+ eslintJest.configs['flat/style'],
+ eslintJest.configs['flat/recommended'],
+ eslintLycolia,
+ {
+ ignores: [
+ 'dist/*',
+ 'coverage/*',
+ '**/*.d.ts',
+ '/src/public/',
+ '/src/type',
+ '*.config.js'
+ ],
+ rules: {
+ semi: ['error', 'always']
+ },
+ languageOptions: {
+ parser: eslintTypeScript.parser,
+ parserOptions: {
+ project: './tsconfig.json'
+ }
+ }
}
-}
+);
- 投稿日:
ここ5年間くらいTypeScriptを書くことが多いが、何故TypeScriptで書いているかについてメリデメを大まかに整理してみる。
メリットに感じている部分
- 静的型付けがある
- IDEで入力保管が出るのは便利だし、型ミスによるバグも見つけやすい
- マルチプラットフォーム
- OSの差を意識せず開発しやすい
- VSCodeで書きやすい
- IntelliJ使いたくないので…
- テストコードを書くのが楽
- Jestは扱いやすい
- 大抵のことができる
- CLIアプリからWebサーバー、Webフロントエンドまでこれ一本でできるのは嬉しい
デメリットに感じている部分
- 依存地獄
- node_modules配下が地獄になりがち
- Node.jsの頻繁なバージョンアップ
- TypeScriptはNode.jsに依存しているため、どうしてもこの部分に引っ張られがち(DenoとかBunとかそういうのは関知しない)
fs
を中心に破壊的変更がよくあり、割合壊れる
- TypeScriptはNode.jsに依存しているため、どうしてもこの部分に引っ張られがち(DenoとかBunとかそういうのは関知しない)
- デバッグがめんどくさい
- C#だとデバッガを上げるのは楽だがNode.jsはめんどくさい
- たまに素直にブレークポイントにはまってくれないこともあり辛い
- 言語エンジンが重い
- 入力補完や型情報の照会などはコードが肥大化するにつれ重くなる
- そのためにキャッシュ機能があるようだが、ブランチを切り替えるとキャッシュされた型情報を見てくるせいでうまく動かないなど面倒