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って何なんやろうね?