Slide 1

Slide 1 text

ts-morph実践: 型を利用するcodemodの技巧 テクニック 2025/05/23:TSKaigi 2025 @ 東京・神田 ypresto (@yuya_presto)

Slide 2

Slide 2 text

ypresto © LayerX Inc. LayerX バクラク事業部 (2024-01〜) プロダクト開発部 債権債務チーム Software Engineer TypeScriptへのコントリビュート歴あり 宣伝の時間なんてないので採用サイトを 2

Slide 3

Slide 3 text

CfP応募後に tsgoが発表された! Compiler APIが塞がる可能性・・・ © LayerX Inc. 3

Slide 4

Slide 4 text

終 制作・著作 LayerX ったかと思ったけど大丈夫かも!? 中の人の回答 © LayerX Inc. 4

Slide 5

Slide 5 text

codemod © LayerX Inc. AST (抽象構文木) を通して、機械的にコードを書き換える DOMツリーとHTMLの関係 = ASTとコードの関係、と考えると楽。 ASTのNodeを探して書き換えると、コードが書き換わる jscodeshift:Meta製 例えばMUIのアップグレードのための @mui/codemod で使われている ts-morph: 型が使える← 今日 TypeScript Compiler API をゴリゴリとwrapしたライブラリ 5

Slide 6

Slide 6 text

ts-morphを使う利点 jscodeshiftや、ラップされていないCompiler APIと比べて・・・ © LayerX Inc. node.findReferencesAsNodes() でエディタのように参照元を探せる node.getType() で型情報をチェックできる getTypeNode() で型注釈もすぐ取れる node.replaceWithText("const foo = 1") のように、 AST Nodeを組み立てず文字列で書き込める => 楽で安全な書き換え 6

Slide 7

Slide 7 text

具体例1: consoleの呼び出しをloggerに書き換え あちこちにはびこる「とりあえずconsole.error(e)」を退治したい © LayerX Inc. 7

Slide 8

Slide 8 text

Find And Replaceだと不安がある理由 © LayerX Inc. console.error(e) → logger.caught(e) 8

Slide 9

Slide 9 text

© LayerX Inc. console.error(e) → logger.caught(e) / 実装 9

Slide 10

Slide 10 text

© LayerX Inc. console.error(e) → logger.caught(e) / 実装 10

Slide 11

Slide 11 text

© LayerX Inc. console.error(e) → logger.caught(e) / 実装 11

Slide 12

Slide 12 text

Cannot find name 'logger' sourceFile.organizeImports() せずにimportを足すのがむずい。 そもそもimport { A } from B のAとBをASTで扱うのがめんどい。 © LayerX Inc. console.error(e) → logger.caught(e) 12

Slide 13

Slide 13 text

しゃーなしでヘルパー作りました しばらくしたら公開されます。 © LayerX Inc. 13

Slide 14

Slide 14 text

具体例2: noImplicitAny: false を倒したい © LayerX Inc. 14

Slide 15

Slide 15 text

具体例2: noImplicitAny: false を倒したい 理論上は可能 (たぶん) © LayerX Inc. 15

Slide 16

Slide 16 text

型注釈を自動でつけるには © LayerX Inc. noImplicitAny: false を倒したい 16

Slide 17

Slide 17 text

1. 引数に使われた型を集める © LayerX Inc. noImplicitAny: false を倒したい 17

Slide 18

Slide 18 text

2. 集めた型をこねて丸める Type Wideningがないと、 let foo = 1 に 2 が入らなくなります。 © LayerX Inc. noImplicitAny: false を倒したい 18

Slide 19

Slide 19 text

ts-morphもCompiler APIも、型を作る方法がない 全くないからしゃーなしでchecker.ts読んで拾ってきた。 © LayerX Inc. noImplicitAny: false を倒したい 19

Slide 20

Slide 20 text

実装 © LayerX Inc. noImplicitAny: false を倒したい 20

Slide 21

Slide 21 text

意外とできる © LayerX Inc. 21

Slide 22

Slide 22 text

おわり © LayerX Inc. ts-morphで型を使うと無限の可能性がある おもしろい悪事ができそう ASTの知識が必要 https://astexplorer.net/ を片手にやりましょう ヘルパーを用意して楽にしましょう 今日のコードはここに上げています https://github.com/ypresto/ts-morph-toybox あとでnpmへ 22

Slide 23

Slide 23 text

ここから先は喋らないやつ © LayerX Inc. 23

Slide 24

Slide 24 text

あと:AI来るともcodemod死せず codemodの弱点:ASTの知識が必要、書くのが大変 © LayerX Inc. AIは、プロジェクト全体をまとめて読めるわけじゃない (Context Window) codemodは、プロジェクト全体に決定論的に同じ内容を高速に当てら れる なので、codemodは引き続き有効。できればAIに書かせたい。 24

Slide 25

Slide 25 text

例えばenabled: trueを全部の呼び出し元に追加したいとき © LayerX Inc. 25

Slide 26

Slide 26 text

メソッドチェーンの呼び出しを探す © LayerX Inc. { baz: () => { ... } } のbazの呼び出しが、foo.bar.baz(1) になっていると き 愚直に祖先のcallを取ると、boo(foo.bar.baz) の boo() が取れてしまう ASTが読めると baz (Identifier) → foo.bar.baz (Property Access Expression) → foo.bar.baz(...) (Call Expression) とできる https://astexplorer.net/ を片手にやる 26

Slide 27

Slide 27 text

理解は容易、 実装は困難。 © LayerX Inc. 27