Open10

ts-morphについてまとめる

uttkuttk

Projectを作成する&SourceFileの作成

ts-morphの基本は、Projectを作って、そのProject内でSourceFileをファイルから読み込んだり、直接作成したりする。

import { Project } from "ts-morph";

const project = new Project();

// SourceFileを作成する
const sourceFile = project.createSourceFile("./hoge");

// ファイルからSourceFileを作成する
const sourceFile = project.getSourceFile("./hoge.ts")

// Project内の全てのファイルからSourceFileを作成する
const sourceFiles = project.getSourceFiles() 

// 引数にglobパターン文字列を渡すと、それにマッチしたファイルのみロードする
const sourceFiles = project.getSourceFiles("*.test.ts") 
uttkuttk

Projectの設定

import { Project } from "ts-morph";

new Project({
  // tsconfig.jsonの設定
  compilerOptions: CompilerOptions, 

  // tsconfig.jsonへのパス
  tsConfigFilePath: string,

  // 指定したtsconfig.jsonからのファイルの追加をスキップするかどうか
  skipAddingFilesFromTsConfig: boolean,

  // ファイルが追加された時などに、依存関係の解決をスキップするかどうか
  skipFileDependencyResolution: boolean,

  // lib ファイルのロードをスキップします。 
  // コンパイラ API とは異なり、ts-morph はこれらを node_modules フォルダーからロードしませんが、
  // 代わりに他の JS コードからロードし、存在するために偽のパスを使用します。 
  // カスタム lib フォルダーパスを使用する場合は、libFolderPath オプションを使用してパスを指定します。
  skipLoadingLibFiles: boolean,

  // libファイルのロードに使用するフォルダーへのパス
  libFolderPath: string, 

  // ファイルを保存する時の設定?
  manipulationSettings: {
    indentationText: IndentationText, // インデントに使う文字列      
    newLineKind: NewLineKind,         // 改行コード
    quoteKind: QuoteKind,              // クォーテーションの種類
    useTrailingCommas: boolean,       // 末尾のコンマを使用するかどうか。
    // プロパティ省略代入、バインディング要素、importやexport名の名前変更を有効にするかどうか。
    usePrefixAndSuffixTextForRename: boolean,
  },

  // インメモリのファイルシステムを使用するかどうか
  useInMemoryFileSystem: true, 

  // オプションのファイル システム ホスト。
  // ファイル システムへのアクセスをモックするのに役立ちます。
  // @remarks: 代わりに「useInMemoryFileSystem」の使用を検討してください。
  fileSystem: FileSystemHost,

  // カスタム モジュールや型参照ディレクティブの解決する関数を指定。
  // 見た感じ、denoとかに対応するためのやつッポイ。
  resolutionHost: ResolutionHostFactory,  
})
uttkuttk

ソースコードの読み込み

index.ts
import * as path from 'path'
import { Project } from 'ts-morph'

// プロジェクトを作成、この時点でファイルのロードが発生する
const project = new Project({
  tsConfigFilePath: path.join(process.cwd(), 'tsconfig.json'),
})

// index.tsのsourceオブジェクトを取得する
const source = project.getSourceFileOrThrow('index.ts')

// index.tsファイルの中身を出力
console.log(source.getText()) 

// 出力結果
/*
import * as path from 'path'
import { Project, Node } from 'ts-morph'

// プロジェクトを作成、この時点でファイルのロードが発生する
const project = new Project({
  tsConfigFilePath: path.join(process.cwd(), 'tsconfig.json'),
})

// index.tsのsourceオブジェクトを取得する
const source = project.getSourceFileOrThrow('index.ts')

// index.tsファイルの中身を出力
console.log(source.getText())
*/
uttkuttk

Type Guardを使ってNodeの判定に型を付けたい

Nodeをインポートして、そこからType Guard関数を使うことができる。

import { Node } from "ts-morph"

// 変数のnodeを取得する
const varDec = source.getVariableDeclarationOrThrow("hoge")

// 右辺のnodeを取得する
const initDec = varDec.getInitializerOrThrow()

// アロー関数だった場合
if( Node.isArrowFunction(initDec) ) {
  console.log( initDec.getBody() ) // 型エラーにならない
}

console.log( initDec.getBody() ) // 型エラーになる 

参照

https://github.com/dsherret/ts-morph/issues/1149

uttkuttk

コメントの取得

コメントの取得方法をまとめます。
詳細は、以下のドキュメントを参照してください👇

https://ts-morph.com/details/comments

uttkuttk

SourceFileからコメントを取得する

import * as path from 'path'
import { Project, Node } from 'ts-morph'

// プロジェクトを作成、この時点でファイルのロードが発生する
const project = new Project({
  tsConfigFilePath: path.join(process.cwd(), 'tsconfig.json'),
})

// index.tsのsourceオブジェクトを取得する
const source = project.getSourceFileOrThrow('test.ts')

// コメントを含めたStatement配列を返す
const statements = source.getStatementsWithComments()

statements.forEach((statement) => {
  if (Node.isCommentNode(statement)) {
    console.log(statement.getText()) // コメントの内容を出力する
  }
})

/*
===== 出力結果 =====

// プロジェクトを作成、この時点でファイルのロードが発生する
// index.tsのsourceオブジェクトを取得する
// コメントを含めたStatement配列を返す

*/
uttkuttk

Interfaceからコメントを取得する

普通のコメント

interface Hoge {

  // このコメントは取得できます

  hello: string // このコメントは取得できません

  obj: {
    // このコメントは取得できません
    hoge: string // このコメントは取得できません
  }

}

// interfaceのNodeを取得する
const interfaceDec = source.getInterfaceOrThrow('Hoge')

// interface内のStatementを配列で受け取る
const membersDec = interfaceDec.getMembersWithComments()

membersDec.forEach((statement) => {
  if (Node.isCommentNode(statement)) {
    console.log( statement.getText() ) // コメントの内容を出力する
  }
})

/*
===== 出力結果 =====

// このコメントは取得できます

*/

JSDoc

/**
 * @description インターフェースのJSDoc
 */
interface Hoge {
  
  /**
   * @description プロパティのJSDoc
   */
  world: string
}

// interfaceのNodeを取得する
const interfaceDec = source.getInterfaceOrThrow("Hoge");

// interfaceについているJSDocを表示
interfaceDec.getJsDocs().forEach((doc) => console.log(doc.getText()));

// プロパティについているJSDocを表示
interfaceDec.getProperties().forEach((prop) => {
  prop.getJsDocs().forEach((doc) => console.log(doc.getText()));
});

// ===== 出力結果 =====
// 
// /**
//  * @description インターフェースのJSDoc
//  */
// 
// /**
//  * @description プロパティのJSDoc
//  */

注意として、コメントの位置によって取得できないコメントがあります。

上記の実装では、interfaceのフィールド内に記述されているコメントは取得できますが、プロパティと同じ行に記述されていたり、別のフィールド(上記の場合だと、objのフィールド)に記述されているコメントは別の実装が必要になってきます。

uttkuttk

特定のライブラリがインポートされているか判定する

test.ts
import * as path from 'path'
import { Project } from 'ts-morph'

// プロジェクトを作成、この時点でファイルのロードが発生する
const project = new Project({
  tsConfigFilePath: path.join(process.cwd(), 'tsconfig.json'),
})

// test.tsのsourceオブジェクトを取得する
const source = project.getSourceFileOrThrow('test.ts')

const imports = source.getImportDeclarations()

// ts-morphがインポートしているか判定する
const isImportedTsMorph = imports.some((node) =>
  node.getModuleSpecifier().getText().match('ts-morph')
)

if (isImportedTsMorph) {
  console.log(`このファイルはts-morphをインポートしています`)
}


/*
===== 出力結果 =====

このファイルはts-morphをインポートしています

*/