Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ts-morph と ast-grep でたくさんの TypeScript コードを書き換えた話
Search
Tomoya Chiba
January 12, 2024
Technology
4
3.9k
ts-morph と ast-grep でたくさんの TypeScript コードを書き換えた話
Tokyoto.js #02 - Kyoto.js in Tokyo (
https://kyotojs.connpass.com/event/302442/
) で発表した資料です。
Tomoya Chiba
January 12, 2024
Tweet
Share
More Decks by Tomoya Chiba
See All by Tomoya Chiba
スポンサーブース用の ruby.wasm くじを vibe coding した話
tomoasleep
0
70
東京Ruby会議12ヘルパー楽しかった✌
tomoasleep
0
110
rbs-inline 生成してみた
tomoasleep
1
300
LiveShare で森羅万象を共同編集する(?)
tomoasleep
1
660
GitHub Actions による RSpec の時間を半分以上短縮した話
tomoasleep
2
1.4k
render 出来るオブジェクトの作り方
tomoasleep
0
240
Rails アプリを10年以上継続していくためのフロントエンドの底上げ
tomoasleep
3
960
Rails のブラウザテストを Playwright で動かすようにしたらデバッグが簡単になって捗った
tomoasleep
3
3k
Sorbetやっていき(たい)宣言
tomoasleep
0
440
Other Decks in Technology
See All in Technology
いかにして命令の入れ替わりについて心配するのをやめ、メモリモデルを愛するようになったか(改)
nullpo_head
7
2.6k
Intro to Software Startups: Spring 2025
arnabdotorg
0
250
はじめての転職講座/The Guide of First Career Change
kwappa
0
600
Nx × AI によるモノレポ活用 〜コードジェネレーター編〜
puku0x
0
560
Amazon Inspector コードセキュリティで手軽に実現するシフトレフト
maimyyym
0
100
LLMをツールからプラットフォームへ〜Ai Workforceの戦略〜 #BetAIDay
layerx
PRO
1
980
生成AI時代におけるAI・機械学習技術を用いたプロダクト開発の深化と進化 #BetAIDay
layerx
PRO
1
1.2k
Google Agentspaceを実際に導入した効果と今後の展望
mixi_engineers
PRO
3
700
形式手法特論:位相空間としての並行プログラミング #kernelvm / Kernel VM Study Tokyo 18th
ytaka23
3
1.3k
Findy Freelance 利用シーン別AI活用例
ness
0
490
React Server ComponentsでAPI不要の開発体験
polidog
PRO
0
230
UDDのススメ - 拡張版 -
maguroalternative
1
520
Featured
See All Featured
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
139
34k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
23
1.4k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
183
54k
[RailsConf 2023] Rails as a piece of cake
palkan
56
5.8k
StorybookのUI Testing Handbookを読んだ
zakiyama
30
6k
Into the Great Unknown - MozCon
thekraken
40
2k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
29
1.8k
Practical Orchestrator
shlominoach
190
11k
A Modern Web Designer's Workflow
chriscoyier
695
190k
It's Worth the Effort
3n
185
28k
Transcript
Tomoya Chiba (@tomoasleep) ts-morph と ast-grep でたくさんの TypeScript コ ードを書き変えた話
1
千葉 知也 (@tomoasleep) Qiita 株式会社 エンジニア Tokyo と Nagoya に拠点があります
自分は Tokyo の民です Ruby と TypeScript と HCL と YAML を書いたり消した りする仕事をしています 自己紹介 2
ライブラリ移行 → たくさん書き換えないと行けない → つらい!!無理!! ts-morph と ast-grep を使って ↑
に立ち向かう話 今日のお題 3
移行したもの: GraphQL のコード生成ライブラリ apollo-tooling (Deprecated) → GraphQL Code Generator type
User implements Node { id: ID! username: String! email: String! } query FindUser($id: ID!) { user(id: $id) { id username email } } 今回移行したライブラリ 4
移行したもの: GraphQL のコード生成ライブラリ apollo-tooling (Deprecated) → GraphQL Code Generator //
こんな感じのコードを生成してくれる export interface User = Node & { __typename?: 'User'; id: ID; username: string; email: string; }; export interface GetUser { user: GetUser_user; } export interface GetUser_user { id: string; username: string; email: string; 今回移行したライブラリ 5
移行したもの: GraphQL のコード生成ライブラリ apollo-tooling (Deprecated) → GraphQL Code Generator 今回移行したライブラリ
6
生成されるものがめちゃくちゃ変わってしまう ( ファイル名, export, ...etc) これをほとんどのコードに対して行う必要がある import GetItemQuery from '~/graphql/generated/someQueryByQueryName.gql'
import { GetItem, GetItemVariable, GetItem_items } from '~/graphql/generated/types/someQueryB const { data, refetch } = useQuery<GetItem, GetItemVariables>(GetItemQuery) const items: GetItem_items[] = data.items ↓ import { GetItem, GetItemDataDocument } from '~/graphql/generated/some-query-by-file-name' type GetItem_items = GetItem['items'][number] const { data, refetch } = useQuery(GetItemDocument) const items: GetItem_items[] = data.items 色々書き換える必要が出てくる 7
(VSCode の機能とか頑張って使えばなんとかなるんじゃね?) 結論: 失敗 人がやるとミスが出る 最初の試行錯誤した部分と、その後で書き換え方がブレて一貫性がない 後から発生する要望、フィードバックに答えられない Conflict への対応が重すぎ レビュアーの負担も重い
Try 1: 手で頑張って置き換えてみる 8
ts-morph, ast-grep 等のツールを使う 全ての書き換えをスクリプト化する 手での書き換えは ( 基本) やらない # 書き換えに使ったスクリプト例
( 後のスライドで再掲します) # コードの状態をリセット BASE_COMMIT_ID=$(git log --grep="XXXXXXX" --author="tomo.asleep" -n 1 --pretty=%H) git restore --source=$BASE_COMMIT_ID --worktree -- src/ # コードを書き換え ast-grep --pattern 'useQuery<$$$>($$$P)' --rewrite 'useQuery($$$P)' \ --lang ts --update-all src/ ts-node ./rewriter-with-ts-morph.ts # Linter, Formatter でコードを整形 yarn run format Try 2: 自動化する 9
https://ts-morph.com/ TypeScript ファイルの書き換えを行える AST 操作だけでなく、様々な書き換え機能を提供 VSCode のリファクタリング機能と出来ることは大体行える ( と思う) 公式ドキュメントが分かりやすい
ts-morph 10
import { Project } from "ts-morph"; const project = new
Project(); const myCode = project.getSourceFile("myCode.ts"); // import MyClass from "./file"; を追加 myCode.addImportDeclaration({ defaultImport: "MyClass", moduleSpecifier: "./file", }): // type TypeAlias = string; を追加 myCode.addTypeAlias({ name: "TypeAlias", type: "string", }); await myCode.save(); ts-morph でコードの追加が簡単に行える 11
定義、import した変数とその利用箇所をまとめて rename 出来る // ts-morph で import を書き換える const
importDeclaration = myCode.getImportDeclaration("package") const namedImports = importDeclaration.getNamedImports() namedImports.forEach(namedImport => { // import 名と、利用箇所をまとめて rename する namedImport.setName("NewName") }) /* ----------------------------------------------------------------------------- */ // Before import { OldName } from "package" someFunction(OldName) // ↓ // After import { NewName } from "package" someFunction(NewName) ts-morph で複雑な書き換えが簡単に行える 12
使ってない import の削除も簡単に出来る sourceFile.fixUnusedIdentifiers(); /* ----------------------------------------------------------------------------- */ // Before import
{ OldName, NewName } from "package" someFunction(NewName) // ↓ // After import { NewName } from "package" someFunction(NewName) ts-morph で複雑な書き換えが簡単に行える 13
https://ast-grep.github.io/ AST を加味した grep, sed, awk みたいなもの 様々な言語 (TypeScript, Ruby,
Python, Go, Rust, ...etc) に対応 直感的に書きやすい && 何をやっているかわかりやすい シンプルな書き換えはこちらを使った ast-grep --pattern 'useQuery<$, $>($$$A)' --rewrite 'useQuery($$$A)' \ --lang ts --update-all src/ // Before const data = useQuery<FetchData, FetchDataVariables>(FetchDataQuery) // ↓ // After const data = useQuery(FetchDataQuery) ast-grep 14
コードをリセット → 書き換え → Linter 等で整形、を順に行うスクリプトを書く 冪等になるようにスクリプト化しておくと便利 書き換えの試行錯誤がしやすくなる Conflict 解消、変更への追従が簡単
# コードの状態をリセット BASE_COMMIT_ID=$(git log --grep="XXXXXXX" --author="tomo.asleep" -n 1 --pretty=%H) git restore --source=$BASE_COMMIT_ID --worktree -- src/ # コードを書き換え ast-grep --pattern 'useQuery<$$$>($$$P)' --rewrite 'useQuery($$$P)' \ --lang ts --update-all src/ ts-node ./rewriter-with-ts-morph.ts # Linter, Formatter でコードを整形 yarn run format 書き換えをスクリプト化した 15
書き換えスクリプトは1 日で作れた (※ 個人差があります) ts-morph, ast-grep 公式ドキュメントが充実してて、調べながらやりやすい スクリプト化しておくと色々良かった レビューでの説明がある程度楽 レビューでの指摘を柔軟に取り入れられる
Conflict 解消、変更への追従が簡単で、他の人の作業をブロックしにくい レビュアーに睨まれたりしない やってみてのまとめ 16