TSDocとCompiler API
TS Compiler API座談会
#tsc_api_study @akito0107
Introduction
自己紹介
書いたもの
今日のテーマ
コメント
- (Compiler API的には Trivia と呼ばれる)
- TypeScriptのDoc Commentの仕様であるTSDocの紹介
/**
* これとか
*/
function aaa(a: number, b: string) {
// これ
}
DocComments
DocCommentsとは
- ソースコード書かれたコメントからDocumentationを出力する仕組み
/**
* sum function
*
* @remarks
* demo
*
* @param a
* @param b
*/
export function sum(a: number, b: number) {
return a + b;
}
ex) TypeDoc
TypeScriptのDocComments
- Third Partyのツールが乱立するなか、microsoft/tsdocが出てきた
- TypeScript Compilerの開発チームもメンテナに含まれており、これが標準になっていく雰囲気
- 現時点(2020/3)で仕様等のドキュメントはなし
- TSDocに準拠したドキュメンテーションツールもなし
- (*上記のrepositoryにはparser/eslint-pluginなどが含まれていて、ドキュメンテーション生成ツールは含まれていない)
TSDoc今わかっていること
- JSDoc Basedのsyntaxになる
- VSCodeが対応していて、TSDocのSyntax Hightlightingなどが効く
- customTagによる拡張が可能
TSDocをさわってみる
- 典型的なTSDocのフォーマット
- 4つのsectionから構成される
/**
* 1. Summary Section
* コンポーネントのサマリーを完結に書く
* Webなどで表示するとき、タイトルやindexになる部分
*
* @remarks
* 2. Remark Block
* コンポーネントの詳細を書く
*
*
* 3. Additional Block(s)
* コンポーネントの具体的な仕様などを書く。@param, @example, @return など
* @param a
* @param b
*
* (4. modifier tags)
* blockには紐づかない。コンポーネントの状態(aspect)などを書く
* @beta @public @private など
*/
export function sum(a: number, b: number) {
return a + b;
}
TSDocのtags
- Block Tag
- tagの後に文字(block)が入る
- ex)
@remarks
,@param
,@example
など
- Modifier Tag
- blockが紐づかないtag
- ex)
@private
,@public
,@beta
など
- Inline Tag
- どのsectionにでも書けるtag
{}
で囲う。 - ex)
{@link}
など
- どのsectionにでも書けるtag
その他TSDoc役立ちそうな?Link集
- playground
- tsdoc-config
- customTagをparserのコードを書かずにjsonの定義ファイルだけで拡張出来るようにする仕組み
- tsdocでsupportしているtag一覧
- ここのコードに書いてある
- api-extractorのdoc
- TSDocのメンテナと同じ人が作っているpackage、TSDoc自体のドキュメントではないが、Syntaxに関するDocumentとしては一番情報がある。
TSDocとDocumentation Tests
Documentation Tests
- documentationに書いたsample codeを実行し、sample codeが実際に正しく実行されるかどうかをチェックする仕組み
- Python, Elixir, Rustなどなどで実装されている
- Documentationのメンテナンスし忘れを防止出来る
ex) Rust
/// ```
/// let result = add::sum(2, 3);
/// assert_eq!(result, 5);
/// ```
pub fn sum(a: i32, b: i32) -> i32 {
a + b
}
TSDocとjestを使って作ってみた
- tsdoc-testify
@example
blockを整形して、jest
で実行出来るようなtestのファイルにして書き出すツール
/**
* sum function
*
* @remarks
* demo
*
* @example
*
* ```
* import * as assert from "assert";
* import { sum } from "./sample";
*
* assert.equal(sum(2, 1), 3);
* ```
*/
export function sum(a: number, b: number) {
return a + b;
}
から
import * as assert from "assert";
import { sum } from "./sample";
test("/Users/akito/workspace/tsdoc-testify/examples/sample.ts_0", () => {
assert.equal(sum(2, 1), 3);
});
が生成される。
demo
- 基本的なコード生成
- custom tags
実装の流れ
- 対象のfileをTS Compiler APIでparse
DeclartionKind
のNodeを抽出し、exportされている関数・クラスを取得syntaxKind
によりts.getTrailingCommentRanges
とts.getLeadingCommentRanges
を使い分けts.CommentRange
を取得する- 取得した
ts.CommentRange
とそれに紐づくts.Node
をtsdoc.TSDocParser
でさらにparseし、@example
ブロックを集める @example
blockのts
のコードをTS Compier APIでさらにParse、import
のblockとコードのblockを分ける- 各exampleの
import
blockをmergeし、コードのblockをtest
のCallExpression
のbodyにいれたASTを作り、ASTをprintする
実装の流れ
- 対象のfileをTS Compiler APIでparse
DeclartionKind
のNodeを抽出し、exportされている関数・クラスを取得`syntaxKind`により `ts.getTrailingCommentRanges` と `ts.getLeadingCommentRanges` を使い分け `ts.CommentRange`を取得する
取得した`ts.CommentRange`とそれに紐づく`ts.Node`を`tsdoc.TSDocParser`でさらにparseし、`@example`ブロックを集める
@example
blockのts
のコードをTS Compier APIでさらにParse、import
のblockとコードのblockを分ける- 各exampleの
import
blockをmergeし、コードのblockをtest
のCallExpression
のbodyにいれたASTを作り、ASTをprintする
Triviaの取得
- Compiler API的にはCommentはTriviaとして扱われる(*Trivia = 取るに足らないもの)
- Commentそのものを直接取得するAPIはないが、コメントが書かれている範囲(
CommentRange
)を取得することで、TSDoc Parserでparseに必要な情報を集めることができる - 対象のNodeの前に定義されているrangeを取得するには
ts.getLeadingCommentRanges
を使う - 対象のNodeの後ろ(改行まで)に定義されてるrangeを取得するには
ts.getTrailingCommentRanges
を使う
/**
* comment A (ts.getLeadingCommentRangeを使う)
*
* @remarks
* remarks
*/
export const a = {...} // commentB (ts.getTrailingCommentRangesを使う)
実際のコード (抜粋)
export function extractComments(source: ts.SourceFile) {
function walk(node: ts.Node) {
if (!isDeclarationKind(node.kind)) {
node.forEachChild(walk);
return false;
}
const buffer = source.getFullText();
// ファイルのfull textが必要
const range = ts.getLeadingCommentRanges(buffer, node.pos)
...
node.forEachChild(walk);
return false;
}
walk(source)
TSDoc Parserの使い方
- コメントをparseするためには
parseRange
とparseString
のメソッドがある - TS Compiler APIと組み合わせるには
parseRange
の方が便利 parseRange
を呼び出すためには、ts.CommentRage
ではなくtsdoc.TextRange
が必要だが、tsdoc
に変換するメソッドが提供されている- DocCommentのASTが取得できたら、あとはAST Viewerを見ながらなんとかする
const textRange: tsdoc.TextRange = tsdoc.TextRange.fromStringRange(
buffer, // ts.SourceFileのgetFullText()から
range.pos,
range.end
);
const tsdocParser: tsdoc.TSDocParser = new tsdoc.TSDocParser(config);
const parserContext = tsdocParser.parseRange(comment.textRange);
return parserContext.docComment; // docのASTが入っている
(Tips) TSのソースコード生成
ts.createSourceFile
&ts.createPrinter
を使ってASTからソースコードを生成する- 複数の
statements
をfileに書き込む時は、ts.updateSourceFileNode
を使うと便利
const ast = ts.updateSourceFileNode(
ts.createSourceFile( // 空のfileを作る
"filename",
"",
ts.ScriptTarget.Latest,
false,
ts.ScriptKind.TS
),
[...statements] // ここに書き込む対象のstatementsを渡す
);
終わりに
もうちょいしたかった話
- typescript-json-schema
- TypeScriptの型定義からjson schemaを生成するツール
- コメントにより、TypeScriptには存在しない型を定義できる(
=integer
など)
- コメントをうまく使えば、ソースコード自動生成の幅を広げられそう(な気がする)
export interface Shape {
/**
* The size of the shape.
*
* @minimum 0
* @TJS-type integer
*/
size: number;
}
まとめ
- DocCommentsとTSDocにまつわる話
- TSDocの仕様と現状について
- Documentation Tests
- Compiler APIとTSDoc Parserで擬似的に再現してみた
- Compiler APIのtipsとTSDoc Parserの使い方