ts-morphについて学ぶ
気になっていたけど使ったことがないのでドキュメントを眺める
Navigating the AST
getXXX()で色々あるとのこと
-
.getChildren()
:すべての子ノード -
.forEachChild(child => {})
- ノードのプロパティであるすべての子ノードを反復する -
.forEachDescendant(child => {})
:すべての子孫ノードを反復処理。ビジターパターンで便利- 第二引数の
traversal
で走査を操作出来て便利らしい
- 第二引数の
Getting Source Files
プロジェクト内の特定のファイルを参照したり、操作する
-
.getSourceFiles()
:すべてのファイル、引数指定でフィルタしたりできる -
.getSourceFile()
:ファイルパスに一致する最初のソースファイルを返す
Directories
ソースファイルに対してディレクトリオブジェクトを作成する。
ディレクトリの概念は無視してもいいらしい(何に使うんだろうこれ)
Example - Navigating Within Source Files
例?なぜここに...
わからない関数はchatgptに聞いた方が早いことに気がついた
(cloneして検索かけたけど、メソッド自体の説明が意外と見当たらない)
-
.addSourceFilesAtPaths()
:複数のソースファイルをパス指定で一括追加するメソッド -
.getSourceFileOrThrow()
:.getSourceFile()
は存在しない場合、例外ではなくundefined
を返すからこの関数があるらしい
Underlying Compiler Nodes
この節あんまりわからんかった。ts-morphにapiが生えてない場合に使える的なやつかな
.compilerNode
.getNodeProperty(propName)
createWrappedNode
Important: Using both the TypeScript API and ts-morph
TypeScript APIを使いたいときは、ts-morph
のやつを使って欲しいとのこと
// do this
import { ts } from "ts-morph";
// not this
import * as ts from "typescript";
Finding References
参照を探す。初期knipで使われてたやつだ。
-
.findReferences()
:identifier or named/nameable declarationを呼び出して、ノードのすべての参照を検索します。 -
.findReferencesAsNodes()
:named/nameable declarationだけ欲しい場合 -
.getDefinitions()
:定義にジャンプ -
getDefinitionNodes()
:定義のノードを取得
gptに完全なサンプルを書いてもらった(一部メソッド間違ってたから手直しした)
import { Project } from "ts-morph";
// 新しいプロジェクトを作成
const project = new Project();
// ソースファイルを追加
const sourceFile = project.createSourceFile("example.ts", `
const x = 10;
function printX() {
console.log(x);
}
printX();
`);
// "x" という変数を取得
const variableDeclaration = sourceFile.getVariableDeclarationOrThrow("x");
// "x" の参照を検索
const references = variableDeclaration.findReferences();
// 参照を表示
references.forEach(ref => {
const refNodes = ref.getReferences();
refNodes.forEach(refNode => {
console.log("Found reference at:", refNode.getSourceFile().getFilePath());
console.log("Start:", refNode.getTextSpan().getStart(), "Text:", refNode.getNode().getText());
});
});
// Found reference at: .../example.ts
// Start: 11 Text: x
// Found reference at: .../example.ts
// Start: 63 Text: x
Language Service
基本使わなくていいらしい。
よくわからないからconsole.logしてみた。
{
languageService: LanguageService {
compilerObject: [Getter],
_reset: [Function: _reset],
getProgram: [Function: getProgram],
getDefinitions: [Function: getDefinitions],
getDefinitionsAtPosition: [Function: getDefinitionsAtPosition],
getImplementations: [Function: getImplementations],
getImplementationsAtPosition: [Function: getImplementationsAtPosition],
findReferences: [Function: findReferences],
findReferencesAsNodes: [Function: findReferencesAsNodes],
findReferencesAtPosition: [Function: findReferencesAtPosition],
findRenameLocations: [Function: findRenameLocations],
getSuggestionDiagnostics: [Function: getSuggestionDiagnostics],
getFormattingEditsForRange: [Function: getFormattingEditsForRange],
getFormattingEditsForDocument: [Function: getFormattingEditsForDocument],
getFormattedDocumentText: [Function: getFormattedDocumentText],
getEmitOutput: [Function: getEmitOutput],
getIdentationAtPosition: [Function: getIdentationAtPosition],
organizeImports: [Function: organizeImports],
getEditsForRefactor: [Function: getEditsForRefactor],
getCombinedCodeFix: [Function: getCombinedCodeFix],
getCodeFixesAtPosition: [Function: getCodeFixesAtPosition],
},
}
Program
これも基本使わないらしい。
{
program: Program {
compilerObject: [Getter],
_isCompilerProgramCreated: [Function: _isCompilerProgramCreated],
_reset: [Function: _reset],
getTypeChecker: [Function: getTypeChecker],
emit: [Function: emit],
emitSync: [Function: emitSync],
emitToMemory: [Function: emitToMemory],
getSyntacticDiagnostics: [Function: getSyntacticDiagnostics],
getSemanticDiagnostics: [Function: getSemanticDiagnostics],
getDeclarationDiagnostics: [Function: getDeclarationDiagnostics],
getGlobalDiagnostics: [Function: getGlobalDiagnostics],
getConfigFileParsingDiagnostics: [Function: getConfigFileParsingDiagnostics],
getEmitModuleResolutionKind: [Function: getEmitModuleResolutionKind],
isSourceFileFromExternalLibrary: [Function: isSourceFileFromExternalLibrary],
},
}
Type Checker
これも同じで使わないapiっぽい
Ambient Modules
@types or node_modulesみたいなアンビエントモジュールを取得する
Manipulating Source Files
Saving Changes
-
.save()
:移動、コピー、削除はsaveするまでprojectに伝番しない - 即座に反映する関数もある
.deleteImmediately()
.copyImmediately()
.moveImmediately()
Replacing any node with new text
-
.replaceWithText(...)
:ノードの値を置換する- 実行後は元のノードは存在しなくなるから、返り値である置換後のノードを使用すること
Adding, inserting, and removing statements
functions, methods, namespaces, source filesなどに対して、文を追加したり削除したりできる
.addStatements()
.insertStatements
.removeStatements()
.removeStatement()
code writerという方法もある
Inserting, replacing, and removing any text
statementではなく、textというのもあるけど、基本的に使用は避けた方がよいとのこと
Code Fixes
コードの修正機能
- SourceFile#organizeImports()
- SourceFile#fixMissingImports()
- SourceFile#fixUnusedIdentifiers()
projectへの設定方法
-
.rename()
:すべてのファイルのすべての使用箇所をrenameする- オプションを渡すと、コメントや文字列も該当箇所があったらrenameする
-
usePrefixAndSuffixText
オプションを使うと、単純置換ではなくエイリアスへの変換になる
const b = 5;
const x = { a: b };
export { b as a }; // as とか使ってくれる
ファイル名やディレクトリ名は別の方法でrenameする
enumのmemberを削除したりできる
structuresってのが何かわからないけど、Nodeを構造化したオブジェクト的なやつっぽい?
node.getStructure()
Traversing structures
- Nodeと同じでStructureで型ガードできる
-
forEachStructureChild
:forEachChild的なことができる
Finding a child structure
ここよくわからなかったけど、gpt曰く挙動の違いっぽい?
- forEachChild(ts-morph):すべてを走査する
- forEachChild(typescript api):真値を返す値が見つかった時点で処理が終わる
- forEachStructureChild(ts-morph):真値を返す値が見つかった時点で処理が終わる
- だから効率が良い、みたいな感じらしい
コードをformatできるらしい
tscにこんな機能あったっけ?prettierではなく?
-
.setOrder(newIndex: number)
:ノードの順序を変更する
プロジェクトのインデントや改行の設定に基づいてコードを書き出してくれる
Performance
パフォーマンス向上方法
- Structureを使う
- バッチにする
- 分析と操作を分ける
などなど、必要になったら読めば良いかもしれない
一般的なシナリオではない、コンパイラAPIを直接操作するみたいな感じらしい
jsファイル/d.tsファイルを生成したり、オンメモリーにemitしたりする話
Details
これがリファレンスとして詳細
ざっと読んでみて、気になったところだけメモっていくか。。。
使い方まとめ
何となくイメージついたのでまとめておく
- projectを作成する
- sourceFileを作る
- sourceFileは操作したり
save()
したりcopy()
したり出来るが、実体化するにはemitしてファイルに書き出したりメモリにダンプする必要がある
- sourceFileは操作したり
- 個別のNodeに対する操作はDetailに記載されている
- ただし、パフォーマンスを考えるならNodeではなくStructureで操作するのが良い
ファイル間の依存関係や関数の依存関係を視覚化したい
OSSのコードを読みたいんだけど、全体感が把握できなくてつらいので視覚化できるツールがあると嬉しい
ts-morphを使ったら簡単に作れたりしないかな。。。