Closed31

ts-morphについて学ぶ

tkrytkry

https://ts-morph.com/

気になっていたけど使ったことがないのでドキュメントを眺める

tkrytkry

getXXX()で色々あるとのこと

  • .getChildren():すべての子ノード
  • .forEachChild(child => {}) - ノードのプロパティであるすべての子ノードを反復する
  • .forEachDescendant(child => {}):すべての子孫ノードを反復処理。ビジターパターンで便利
    • 第二引数のtraversalで走査を操作出来て便利らしい
tkrytkry

Getting Source Files

プロジェクト内の特定のファイルを参照したり、操作する

  • .getSourceFiles():すべてのファイル、引数指定でフィルタしたりできる
  • .getSourceFile():ファイルパスに一致する最初のソースファイルを返す
tkrytkry

Directories

ソースファイルに対してディレクトリオブジェクトを作成する。
ディレクトリの概念は無視してもいいらしい(何に使うんだろうこれ)

tkrytkry

Example - Navigating Within Source Files

例?なぜここに...
わからない関数はchatgptに聞いた方が早いことに気がついた
(cloneして検索かけたけど、メソッド自体の説明が意外と見当たらない)

  • .addSourceFilesAtPaths():複数のソースファイルをパス指定で一括追加するメソッド
  • .getSourceFileOrThrow().getSourceFile()は存在しない場合、例外ではなくundefinedを返すからこの関数があるらしい
tkrytkry

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";
tkrytkry

Finding References

参照を探す。初期knipで使われてたやつだ。

  • .findReferences():identifier or named/nameable declarationを呼び出して、ノードのすべての参照を検索します。
  • .findReferencesAsNodes():named/nameable declarationだけ欲しい場合
  • .getDefinitions():定義にジャンプ
  • getDefinitionNodes():定義のノードを取得
tkrytkry

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
tkrytkry

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],
  },
}
tkrytkry

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],
  },
}
tkrytkry

Type Checker

これも同じで使わないapiっぽい

tkrytkry

Ambient Modules

@types or node_modulesみたいなアンビエントモジュールを取得する

tkrytkry

Manipulating Source Files

https://ts-morph.com/manipulation/

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()
tkrytkry

https://ts-morph.com/manipulation/renaming

  • .rename():すべてのファイルのすべての使用箇所をrenameする
    • オプションを渡すと、コメントや文字列も該当箇所があったらrenameする
    • usePrefixAndSuffixTextオプションを使うと、単純置換ではなくエイリアスへの変換になる
const b = 5;
const x = { a: b };

export { b as a }; // as とか使ってくれる

ファイル名やディレクトリ名は別の方法でrenameする

tkrytkry

https://ts-morph.com/manipulation/structures#structures

structuresってのが何かわからないけど、Nodeを構造化したオブジェクト的なやつっぽい?

  • node.getStructure()

Traversing structures

  • Nodeと同じでStructureで型ガードできる
  • forEachStructureChild:forEachChild的なことができる

Finding a child structure

ここよくわからなかったけど、gpt曰く挙動の違いっぽい?

  • forEachChild(ts-morph):すべてを走査する
  • forEachChild(typescript api):真値を返す値が見つかった時点で処理が終わる
  • forEachStructureChild(ts-morph):真値を返す値が見つかった時点で処理が終わる
    • だから効率が良い、みたいな感じらしい
tkrytkry

Details

これがリファレンスとして詳細
ざっと読んでみて、気になったところだけメモっていくか。。。
https://ts-morph.com/details/index

tkrytkry

取りたい情報のDetailを読めば、操作方法がわかる気がする

tkrytkry

使い方まとめ

何となくイメージついたのでまとめておく

  1. projectを作成する
  2. sourceFileを作る
    • sourceFileは操作したりsave()したりcopy()したり出来るが、実体化するにはemitしてファイルに書き出したりメモリにダンプする必要がある
  3. 個別のNodeに対する操作はDetailに記載されている
    • ただし、パフォーマンスを考えるならNodeではなくStructureで操作するのが良い
tkrytkry

ファイル間の依存関係や関数の依存関係を視覚化したい

OSSのコードを読みたいんだけど、全体感が把握できなくてつらいので視覚化できるツールがあると嬉しい
ts-morphを使ったら簡単に作れたりしないかな。。。

このスクラップは3ヶ月前にクローズされました