TSのASTは、JS界のASTのデファクトであるESTreeとは違うらしい。
まぁそれはそれとして、JSDocの扱いがどうなっているのかを調べたかった。
JSについては、以前に調べてた。
JavaScriptのASTにおける、コメントの扱いについて | Memory ice cubes https://leaysgur.github.io/posts/2024/01/30/132331/
TSのASTにおけるJSDoc
- 通常のコメントは、ASTに含まれない
- ESTreeと同じく
 
- しかし、JSDocは含まれる
- PlaygroundでASTを見ると.jsDocというプロパティがあるのが見える- https://www.typescriptlang.org/play
- SyntaxKind.JSDocCommentというノードの配列
 
さっきのJSDocCommentという名はdeprecatedで、JSDocになってた。
https://jsdoc.app/ ではもっとたくさんのタグが定義されてるけど、すべてがノードとして定義されてるわけではないらしい。
JSDocTag, // 以下に含まれないものはコレ
JSDocAugmentsTag,
JSDocImplementsTag,
JSDocAuthorTag,
JSDocDeprecatedTag,
JSDocClassTag,
JSDocPublicTag,
JSDocPrivateTag,
JSDocProtectedTag,
JSDocReadonlyTag,
JSDocOverrideTag,
JSDocCallbackTag,
JSDocOverloadTag,
JSDocEnumTag,
JSDocParameterTag,
JSDocReturnTag,
JSDocThisTag,
JSDocTypeTag,
JSDocTemplateTag,
JSDocTypedefTag,
JSDocSeeTag,
JSDocPropertyTag,
JSDocThrowsTag,
JSDocSatisfiesTag,そのほか、FirstJSDocTagNodeとLastJSDocTagNodeというものもマーカーとして定義されてたけど、よくわかってない。
基本の流れ
SourceCode ~~ scanner ~~> Token Stream Token Stream ~~ parser ~~> AST https://basarat.gitbook.io/typescript/overview
というわけで、ScannerとParserを読めば、だいたいの扱いはわかるはず。
Scanner
- 複数行コメントをパースするときに、フラグを立ててる
- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/scanner.ts#L1961-L1984
- tokenFlags |= TokenFlags.PrecedingJSDocComment
 
- Parserは、scanner.hasPrecedingJSDocComment()すれば、あるかどうかわかる
 
- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/scanner.ts#L1961-L1984
つまりこの時点では、とりあえずどんな場所であれ、JSDocを見つけたらフラグだけ立てておく。
Parser
- withJSDoc(node, hasJSDoc)- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/parser.ts#L1825
- getJSDocCommentRanges(node, sourceText)して得た配列に
- JSDocParser.parseJSDocComment(comment)した結果を
- node.jsDocとして生やす
 
- Parserが各nodeをパースする最後に、高階関数として適応してる
- hasJSDocは、さっきの- hasPrecedingJSDocComment()した結果
 
- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/parser.ts#L1825
- getJSDocCommentRanges(node, text)- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/utilities.ts#L2410
- 対象のnodeによって、leadingだけでなくtrailingなコメントも取得してる(なぜ?)
- ただのコメントではなく、JSDocコメントだけを返す
 
- JSDocParser
- JSDocParser.parseJSDocComment(node, startPos, len)- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/parser.ts#L8751
- parseJSDocCommentWorker(start, len)- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/parser.ts#L8784
- commentsと- tagsをパースして、ノードにして返す
 
- createJSDocComment(comment, tags)
 
withJSDoc()で書かれたノードが、真に知りたいJSDocの記述を許されたノードではあるけど、それを列挙するには愚直にgrepするしかなさそう・・・。
そのノードに対するコメントとは
JSDocに関するトピックで一番厄介なやつ。
たとえば、こういうコードを書いたとする。
let my = /** @type {MyObj} */ ({ x: 1 });このとき、このコメントは誰のもの?
- 文字としては、最も近い()(=ParenthesizedExpression)のものであるべき
- 意味としては、その中の{ x: 1 }に対するものであってほしい- だからこそ、{ x: 1 }が所有すべき?
- だからこそ、let my =が所有すべき?
 
- だからこそ、
というようなあたりを解釈するのがすごく大変で、未だに正解がわからない。
https://github.com/microsoft/TypeScript/issues/7393#issuecomment-413285773
このIssueでは中の人が「常に後者でいいのでは」って言ってるから、たぶんそうなんやろう。
という用途のための便利なAPIも公開されてた。
- ts.getJSDocTags(node)- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/utilitiesPublic.ts#L1220
- そのnodeにアタッチされるコメントそれ自身ではなく
- 意味的にそのnodeに適応されるべきタグを、親まで遡って探す
 
- canHaveJSDoc(node)(プライベート)- https://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/utilities.ts#L4195
- getJSDocTags()の中で使われてる
- コメント自体が書かれてても、リストにないものは取得しない
 
getDocumentationComment()という、コメント自体を取得する風のAPIもコードベースには存在してた。(どうやらLSまわりで使ってるっぽい?)
canHaveJSDoc()とは?
これは単語の通り”can have”であり、JSDocがそこに書かれる保証は別にないってところがポイント。
たとえば関数の引数に対するアノテーション。
/** @param {string} x */
function foo(x) {}このJSDocコメントが書かれてるのは、関数宣言に対してであり、引数であるParameterに対してではない。
ただ引数xからしてみると、自身に対しての@param {string}なので、“can have”ということになり、getJSDocTags()で引いてこれるってわけ。
そういう意味では、“can write(attach)“なんて判断はないのである。(だってコメントはどこにでも書けてしまうから)
おまけ: JSDocとは一体
TypeScriptでサポートするJSDocタグとして、@nonnullっていうのを追加しないか?っていうIssueがあった。
Support a @nonnull/@nonnullable JSDoc assertion comment · Issue #23405 · microsoft/TypeScript https://github.com/microsoft/TypeScript/issues/23405
もちろんJSDoc本家である https://jsdoc.app の一覧には載ってない。
JSDocって何なんやろうね?