Slide 1

Slide 1 text

型情報を手繰り寄せる技術 ~TypeScript Compiler APIによる型解析実践~ TSKaigi Hokuriku 小島大基 (@jiko21) / エムスリー株式会社

Slide 2

Slide 2 text

自己紹介 • 名前: 小島大基(こじまだいき) / @jiko21 • 所属: エムスリー株式会社 • 北陸の思い出 • 2年前、大阪から金沢までサンダーバードを使って観光旅行に行きました • 金箔入りソフトクリームがおすすめ

Slide 3

Slide 3 text

突然ですが…

Slide 4

Slide 4 text

型から何かを生成したくないですか?

Slide 5

Slide 5 text

型から生成できるとうれしいもの • APIのSchema • IF定義(open-api)からの生成はあるが実装からIFを作りたいetc • テスト用ダミーデータの生成 • いつも自前で実装しているが型からfaker等利用して生成できると楽 • その他いっぱい…

Slide 6

Slide 6 text

ワイ「型からHTML生成したい!」

Slide 7

Slide 7 text

型からHTMLを生成したいモチベーション • HTMLのセマンティクスやルールに沿ったHTMLだけを書ける状態にしたい • NGパターン1 inline要素の中に block要素は置けない

Slide 8

Slide 8 text

型からHTMLを生成したいモチベーション • HTMLのセマンティクスやルールに沿ったHTMLだけを書ける状態にしたい • NGパターン2 ブロック要素があると pタグは閉じられる ブロック要素があると pタグは閉じられる

Slide 9

Slide 9 text

こういうものを作りました!

Slide 10

Slide 10 text

html-typeについて • TypeScriptの型からHTMLを生成するライブラリ

Slide 11

Slide 11 text

html-typeについて • Pの中でDIVを利用するなど、HTMLのセマンティクスに違反する場合 型エラーとなる

Slide 12

Slide 12 text

html-typeのやっていること • 型の提供 • 各HTMLのタグに対応する型を提供 • Html, Body, Div, P • TypeScript Compiler APIによる型解析

Slide 13

Slide 13 text

html-typeのやっていること • 型の提供 • 各HTMLのタグに対応する型を提供 • Html, Body, Div, P • TypeScript Compiler APIによる型解析

Slide 14

Slide 14 text

TypeScript Compiler API

Slide 15

Slide 15 text

TypeScript Compiler APIって知ってます? • TypeScriptを解析するためのAPI • 型情報やコメント等をAST(抽象構文木)で取得できたり、型生成したりできる • 実際に使われているプロダクト • ESLint • openapi-typescript • etc • TypeScript Goの登場でjsから直接触ることはできなくなる可能性があるので注意…

Slide 16

Slide 16 text

• TypeScriptの型をASTに変換する TypeScript Compiler APIでできること

Slide 17

Slide 17 text

html-typeのやっていること • 型の提供 • 各HTMLのタグに対応する型を提供 • Html, Body, Div, P • TypeScript Compiler APIによる型解析

Slide 18

Slide 18 text

TypeScript Compiler APIを使っていく

Slide 19

Slide 19 text

よくみるサンプルコード そもそもこれって何?

Slide 20

Slide 20 text

よくみるサンプルコード

Slide 21

Slide 21 text

͍͢͝Ͱ͔͍ΦϒδΣΫτ͕දࣔ͞ΕΔʜ

Slide 22

Slide 22 text

オブジェクトがよくわからな い…

Slide 23

Slide 23 text

まずは外部ツールでASTを見てみる • TypeScript AST Viewer (https://ts-ast-viewer.com/) を使えばどうい うASTに変換されるかわかる • 実際に型を解析する前にこのサイトで解析したい型がどういうASTかを 見てみるとよい

Slide 24

Slide 24 text

まずは外部ツールでASTを見てみる

Slide 25

Slide 25 text

ちなみに… • ts.Nodeにはkindというフィールドがあり、ここにASTの種類を表す 値(数字)がありますが300種類以上あります→ • あくまで実装時のデバッグに使う、くらいの気持ちで いるほうがいいかも

Slide 26

Slide 26 text

ちなみに…

Slide 27

Slide 27 text

それぞれのkindにあわせてparse • 先程のnode.kindを使って解析してみると…

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

Kindだけでは(型的に)絞り込めない • kindだけではASTの種別を型的に識別できない • (残念ながら)Disciminated Unionではない • TypeAliasDeclaration型の継承関係を見てみるとこんなに複雑 TypeAliasDeclaration DeclarationState LocalsContainer JSDocContainer NamedDeclaration Statement Declaration Node

Slide 30

Slide 30 text

isXXXを使う • isXXXを使うとに単true/falseを返すだけでなく型ガードにより 推論してくれる • 今回だとisTypeAliasDeclaration

Slide 31

Slide 31 text

isXXXを使う • isXXXを使うとに単true/falseを返すだけでなく型ガードにより 推論してくれる • 今回だとisTypeAliasDeclaration

Slide 32

Slide 32 text

実際にHTML型をparseしていく

Slide 33

Slide 33 text

軽くhtml-typeの型について • 、、

について型として定義されている • HTMLとして子要素に持てるもの(HTMLNodeとする)は • 文字列(Text) • HTMLNode • HTMLNodeあるいはTextを含むリスト • 例えばタグは右のように定義されている

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

• 以下のようなシンプルなASTをパースしていく まずはシンプルなケースでパースしていく

Slide 36

Slide 36 text

• SourceFile自体がファイルのrootに位置するのでそこから深さ優先で 再帰処理していく 頭にあるSourceFileを識別 SourceFile

Slide 37

Slide 37 text

• TypeAliasにヒットすると次は型の内部を見ていく TypeAliasを識別 TypeAlias

Slide 38

Slide 38 text

• 内部構造見てみるとIdentifierとTypeReferenceを持つ TypeAliasを識別 TypeReference Identifier

Slide 39

Slide 39 text

• node.type.typneNameがIdentifier TypeAliasを識別 TypeReference Identifier

Slide 40

Slide 40 text

• getTypeAtLocation、typeToTypeNode を使ってちゃんとTypeReferenceを 解析していく TypeReferenceを解析していく

Slide 41

Slide 41 text

むずかしい

Slide 42

Slide 42 text

前提知識 • よく出てくる登場人物はこれ • Type: 型システムが扱う型そのもの(ASTではない) • Node: AST Node全般 • TypeNode: 型に関するNode(Nodeを継承している)

Slide 43

Slide 43 text

前提知識 • NodeからTypeの情報が欲しいとき • TypeからそのNodeの情報が欲しいとき

Slide 44

Slide 44 text

• getTypeAtLocation、typeToTypeNode を使ってちゃんとTypeReferenceを 解析していく • typeToTypeNodeの第三引数が かなり大事!(一旦undefinedで) • TypeReferenceだと中の型情報が 取り出せない! TypeReferenceを解析していく

Slide 45

Slide 45 text

• node.membersでそれぞれの構成要素を取得できる ひたすらTypeLiteralNodeを解析していく Identifier PropertySignature

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

ひたすらTypeLiteralNodeを解析していく • node.membersでそれぞれの構成要素を取得できる • Indentifier: 型名が取得できる • PropertySignature: 今回の場合は Genericsの部分 Identifier PropertySignature

Slide 48

Slide 48 text

PropertySignatureを解析する前に…

Slide 49

Slide 49 text

軽くhtml-typeの型について(再掲) • 、、

について型として定義されている • HTMLとして子要素に持てるもの(HTMLNodeとする)は • 文字列(Text) • HTMLNode • HTMLNodeあるいはTextを含むリスト • 例えばタグは右のように定義されている

Slide 50

Slide 50 text

PropertySignatureをパース • PropertySignatureは3種類ありうる • 文字列リテラル: StringLiteral • タプル(配列): TupleTypeNode • Typeリテラル: TypeLiteralNode

Slide 51

Slide 51 text

• PropertySignatureは3種類ありうる • 文字列リテラル: StringLiteral • タプル(配列): TupleTypeNode • Typeリテラル: TypeLiteralNode PropertySignatureをパース

Slide 52

Slide 52 text

• 文字列リテラル: StringLiteral • そのまま文字列を取り出す PropertySignatureをパース StringLiteral

Slide 53

Slide 53 text

• タプル(配列): TupleTypeNode • タプル内要素を再度解析する PropertySignatureをパース TupleTypeNode

Slide 54

Slide 54 text

• Typeリテラル: TypeLiteralNode • 要素が1つだけなのでその要素を再度解析 PropertySignatureをパース TypeLiteralNode

Slide 55

Slide 55 text

• PropertySignatureは3種類ありうる • 文字列リテラル: StringLiteral • そのまま文字列を取り出す • タプル(配列): TupleTypeNode • タプル内要素を再度解析する • Typeリテラル: TypeLiteralNode • 要素が1つだけなのでその要素を再度解析 PropertySignatureをパース StringLiteral TupleTypeNode TypeLiteralNode

Slide 56

Slide 56 text

実際のASTの構造

Slide 57

Slide 57 text

生成ができた

Slide 58

Slide 58 text

より複雑なものをparseしてみる • もっと複雑なものをparseしてみると…

Slide 59

Slide 59 text

• ここは本来、TypeLiteral>TypeLiteral>StringLiteralとはいるはず… 何が起こっているかを見てみる

Slide 60

Slide 60 text

何が起こっているかを見てみる

Slide 61

Slide 61 text

• ここは本来、TypeLiteral>TypeLiteral>StringLiteralとはいるはず… 何が起こっているかを見てみる どうやら省略されてそう…

Slide 62

Slide 62 text

型解析は不可能

Slide 63

Slide 63 text

と言う前に…

Slide 64

Slide 64 text

Configでいじれるところを見る • 今までのコードの中で、何かしらConfigを 入れられそうなのは右の5つ

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

No content

Slide 69

Slide 69 text

No content

Slide 70

Slide 70 text

TypeToTypeNodeを見てみると… • 今までundefinedで誤魔化してましたが第三引数が大事 • NodeBuildingFlagsにいろいろなフラグがある

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

NodeBuildingFlagsを使う • 今回はTruncate(…)を防ぎたいのでNoTruncateを使う • シフト演算でそれぞれのフラグ定義されているので、 複数のフラグも指定可能!

Slide 73

Slide 73 text

ちゃんとTruncationが治った!

Slide 74

Slide 74 text

ここだけの話 • Truncateが出たときにClaude Codeに相談したところ、 すごい力技でTruncateした部分を解消しようとしてました… • ↑まだTypeScript CompilerのことはClaudeもわからない?  あるいはそれくらい情報が少ない?

Slide 75

Slide 75 text

まとめ • まずはAST ViewerでASTを見てみるべし • Compiler Optionは要注意 • TypeScript自体のコードを見るべし • GitHubじゃなくて手元にClone!(GitHubだと多分みれない…)

Slide 76

Slide 76 text

良い型ライフを!