Rust の SWC で TypeScript のコードの解析と生成をする
Rust の SWC というライブラリで TypeScript のコードの解析と生成をします.
解析
/** アカウント */
export type Account = {
/** アカウントを識別するID */
readonly id: string;
/** アカウント名 */
readonly name: string;
}
この TypeScript のコードに含まれる型のデータを解析し, 次のようなデータを得ることを目標とします.
[
TypeData {
name: "Account",
comment: "アカウント",
members: [
MemberData {
name: "id",
comment: "アカウントを識別するID",
},
MemberData {
name: "name",
comment: "アカウント名",
},
],
},
]
次のコードで取得することができました.
出力
[
TypeData {
name: "Account",
comment: "* アカウント ",
members: [
MemberData {
name: "id",
comment: "* アカウントを識別するID ",
},
MemberData {
name: "name",
comment: "* アカウント名 ",
},
],
},
]
コメントに *
と末尾の空白が入ってる問題はありますが、とりあえずできました. SWC 的には ドキュメンテーションコメントかどうかは区別しないようです.
おそらく特定の部分木を探すみたいな方法がありそうですが, シンプルにパターンマッチングをして取り出しました. そのため, このコードではモジュール内に含まれている型の定義は取得できません.
コメントの部分が, 木構造に含まれていないため別で
このように取得することになります.
また, swc_ecma_ast::TsType::TsTypeLit
と swc_ecma_ast::TsType::TsLitType
の名前が似ているので注意が必要です.
swc_ecma_ast::TsType:: | TypeScript のコード |
---|---|
TsTypeLit |
{ id: string, readonly name: string } |
TsLitType |
"sample" や 28
|
生成
今度は逆に
&TypeData {
name: "Account".to_string(),
comment: "アカウント".to_string(),
members: vec![
MemberData {
name: "id".to_string(),
comment: "* アカウントを識別するID ".to_string(),
},
MemberData {
name: "name".to_string(),
comment: "* アカウント名 ".to_string(),
},
],
}
を入力として, 以下の TypeScript のコードを生成するコードを作成します
/** アカウント */
export type Account = {
/** アカウントを識別するID */
readonly id: string;
/** アカウント名 */
readonly name: string;
};
以下のコードで出力することができました.
出力
/*アカウント*/ export type Account = {
/** アカウントを識別するID */ readonly id: string;
/** アカウント名 */ readonly name: string;
};
コードフォーマットは してくれないようです.
コメントはやはりswcの構文木に含まれていないので, 別で追加しています.
swc_common::BytePos
が位置の指定, swc_common::Spanned::span(&BytePos)
によってコード範囲の swc_common::Span
に変換できます. BytePos
という名前ですが, 出力先のコードのバイト位置にする必要はないみたいです.
swc_common::BytePos(0)
swc_common::BytePos::DUMMY
の指定をしたコメントは出力時に無視されました. 0番目は予約されているみたいです. Option
では だめな理由があるのだろうか...
BytePos のカウント方法
SWC のコード ( https://rustdoc.swc.rs/src/swc_common/syntax_pos.rs.html#581 ) を見ると, GLOBALS というグローバルの構造体?の値を1ずつ増やしてインデックスのカウントを増やしているようです.
自前で足していくよりも 良い方法があるような気がしますが, うまくいきませんでした.
swc_common::Span::dummy_with_cmt();
実行した結果のエラー↓
thread 'type_data_to_code_main' panicked at 'You should perform this operation in the closure passed to `set` of swc_common::syntax_pos::GLOBALS'
また, SWC のドキュメントは https://docs.rs/ ではなく https://rustdoc.swc.rs/swc_common/source_map/struct.Span.html#method.dummy_with_cmt で公開している. ビルドの都合上そうなってしまうのか?
追記
swc_common::GLOBALS.set(&swc_common::Globals::default(), || {
// swc_common::Span::dummy_with_cmt() が使える
}
のようにすることによって, swc_common::Span::dummy_with_cmt()
が使えるようになることが判明しました.
よってコードは次のように変更しました.
この仕様はグローバル変数的な使い方をしたい場合に使う?ライブラリの scoped_tls
によるものでした. この scoped_tls
を エラーメッセージが若干分かりやすいように SWC が改良したのが better_scoped_tls
みたいです
その他, より良い方法を見つけたらコメントをしてくれると嬉しいです.
Discussion