🧊

TypeScriptのASTにおける、JSDocの扱いについて

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://github.com/microsoft/TypeScript/blob/df8d755c1d76eaf0a8f1c1046a46061b53315718/src/compiler/types.ts#L405

さっきの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,

そのほか、FirstJSDocTagNodeLastJSDocTagNodeというものもマーカーとして定義されてたけど、よくわかってない。

基本の流れ

SourceCode ~~ scanner ~~> Token Stream Token Stream ~~ parser ~~> AST https://basarat.gitbook.io/typescript/overview

というわけで、ScannerとParserを読めば、だいたいの扱いはわかるはず。

Scanner

つまりこの時点では、とりあえずどんな場所であれ、JSDocを見つけたらフラグだけ立てておく。

Parser

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も公開されてた。

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