はじめに
以前、jscodeshitという JavaScript
およびTypeScript
コードのリファクタリングツールの紹介記事を書きました。
JavaScriptのリファクタリングツール「jscodeshift」の使い方
このツールは、ASTベースでコードを自在に変換でき、とても便利です。jscodeshift
は、Transform File(変換処理を記述するファイル)として変換処理を書くのですが、これまでJavaScript
で変換処理を書くことが多かったです。
しかし、Transform FileはTypeScript
で書くことが可能です。
最近、TypeScript
で変換処理を書いた際、その開発体験がとても良かったので紹介します。ASTベースのコーディングにTypeScript
の型推論や補完があると、開発が快適になることは想像しやすいでしょう。
この記事で出てくるコードやファイルは以下のリポジトリで確認できます。
セットアップ
まずは、jscodeshift
のTransform FileをTypeScript
で書く準備をします。
ライブラリのインストール
必要なライブラリをインストールします。typescript
の他に、@types/jscodeshift
をインストールします。
$ npm i -D jscodeshift typescript @types/jscodeshift
tsconfig.json
tsconfig.json
を作成します。設定はお好みで調整してください。
$ npx tsc --init
TypeScriptで変換処理を書く
セットアップが終わったら、TypeScript
でTransform Fileを作成します。
jscodeshift + TypeScriptの土台となる記述
jscodeshift
+ TypeScript
でのコーディングの土台となる記述を書きます。
型推論により、型注釈を消すことはできますが、説明の便宜上書いています。
import { Transform, FileInfo, API } from "jscodeshift";
const transform: Transform = ({ source }: FileInfo, { jscodeshift }: API) => {
const j = jscodeshift;
return j(source).toSource();
};
export default transform;
あとは、補完が効くので、快適に開発を進めていくことができます。
変換処理 foo.bar()をfoo.baz()に変換
前回の記事でもシンプルな例として出した、foo.bar()をfoo.baz()に変換
処理を書いていきます。
といっても、主に利用する関数や値は型推論があるので、JavaScriptの時と比べても記述量は変わりません。規模が大きくなり、変換処理を関数として切り出す際などに型注釈を付与することになるでしょう。
対象のNodeを見つける
// ...
const j = jscodeshift;
return j(source)
.find(j.CallExpression, {
callee: {
object: { name: "foo" },
property: { name: "bar" },
},
})
// ...
};
export default transform;
find
関数にわたすAST NodeのTypeを選択する際も、エディターでの補完の恩恵を得ることができ、非常に便利です。
Nodeを置き換える
// ...
.find(j.CallExpression, {
callee: {
object: { name: "foo" },
property: { name: "bar" },
},
})
.replaceWith((path) => {
// TODO
})
// ....
};
別のAST Nodeへ置き換えるreplaceWith
関数に渡ってくるpath
オブジェクトの型も推論されます。
新しいNodeの作成
const j = jscodeshift;
// ...
.replaceWith((path) => {
return j.callExpression(
j.memberExpression(j.identifier("foo"), j.identifier("baz")),
path.value.arguments
);
})
// ...
};
export default transform;
置き換えるAST Nodeを作成する際に、利用する便利なBuilder
メソッド(e.g. j.memberExpression
)へ渡すことが可能な値も検査してくれます。
JavaScript
のときは、このあたりが特に苦労したので、とても助かります。
成果物
最終的には以下のようなコードになりました。
transform.ts
import { Transform, FileInfo, API } from "jscodeshift";
const transform: Transform = ({ source }: FileInfo, { jscodeshift }: API) => {
const j = jscodeshift;
return j(source)
.find(j.CallExpression, {
callee: {
object: { name: "foo" },
property: { name: "bar" },
},
})
.replaceWith((path) => {
return j.callExpression(
j.memberExpression(j.identifier("foo"), j.identifier("baz")),
path.value.arguments
);
})
.toSource();
};
export default transform;
動作確認
jscodeshift
コマンドにTransform Fileと対象ファイルを渡して動作確認しましょう。
-p
はprintです。-d
はdry runです。
また、対象ファイルとしてTypeScript
ファイルを指定する場合は、--parser
オプションでts
を指定します。(tsx
も指定可能です)
$ npx jscodeshift -t transform.ts index.ts --parser ts -p -d
Processing 1 files...
Spawning 1 workers...
Running in dry mode, no files will be written!
Sending 1 files to free worker...
const foo: any = {};
foo.baz();
All done.
Results:
0 errors
0 unmodified
0 skipped
1 ok
Time elapsed: 0.855seconds
テスト
最後に、テストについても触れておきます。
jscodeshift
のテストについては以下の記事を見ると良いです。
今回はjscodeshift
のテストおよびテストのfixture fileにTypeScript
ファイルを使う方法を紹介します。
ライブラリのインストール
jest
とts-jest
をインストールします。
$ npm i -D jest ts-jest
jestのセットアップ
jest.config.js
のpreset
でts-jest
を指定します。
module.exports = {
preset: "ts-jest",
};
fixture fileとテストファイルの作成
jscodesfhit
のドキュメントで指定されているようなディレクトリ構造でfixture fileとテストファイルを作成します。
今回は、JavaScript
ファイルとTypeScript
ファイルのfixture fileを配置します。
/transform.ts
/__tests__/transform.test.js
/__testfixtures__/foobar.input.ts
/__testfixtures__/foobar.output.ts
/__testfixtures__/foobar.input.js
/__testfixtures__/foobar.output.js
__tests__/transform.test.js
を以下のように記述しました。
TypeScript
ファイルを変換する場合は、parser
オプションを指定する必要があるので、defineTest
関数で指定しています。
// @ts-ignore
import { defineTest } from "jscodeshift/dist/testUtils";
describe("test with .js file", () => {
defineTest(__dirname, "transform", null, "foobar");
});
describe("test with .ts file", () => {
defineTest(__dirname, "transform", null, "foobar", { parser: "ts" });
});
$ npx jest
> jest
PASS __tests__/transform.ts
test with .js file
transform
✓ transforms correctly using "foobar" data (129 ms)
test with .ts file
transform
✓ transforms correctly using "foobar" data (10 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.14 s
まとめ
jscodeshift
のTransform FileをTypeScript
で書く方法と、TypeScript
ファイルでのテストの方法を紹介しました。
ASTベースのプログラミングは、型の恩恵を強く受けることができ、TypeScript
との相性が非常に良いです。
今後は、jscodeshift
を使う際は、TypeScript
で書くのが良さそうです。