TypeScriptでの開発時、exportしているモジュールに対してはTSDocを書くようコーディング規約を定めたが、ヒアドキュメントを書く習慣のないメンバーが多く、書き漏れが頻発した。
都度コードレビューで指摘するのも面倒なので、ESLintで何とかならないかと思い、調べたのでメモ。
環境
ESLint v8.21.0、eslint-plugin-jsdoc v39.3.4。
ESLintへのプラグイン追加
昔はESLintにJSDocサポートが組み込まれていたが、廃止済み。
Microsoftがeslint-plugin-tsdocを公開しているが、これはTSDoc仕様に準拠したドキュメントかのチェックのため、やりたいこととは異なる。
上記ブログでも移行先として指定されていた、 eslint-plugin-jsdoc がTSDocにも対応している。
以下の記事で設定の実例もあった。
yarn add -D eslint-plugin-jsdoc で追加。
.eslintrcの設定
READMEを元に設定。
plugins に jsdoc 、 extends に plugin:jsdoc/recommended を追加する。
settings.jsdoc.mode で、 typescript を指定できるが、 @typescript-eslint を使用している場合は省略可能。
これでひとまず動くが、以下を変更したい。
- TSDocが書かれていない場合はパスするため、exportしているモジュールなどファイル外に公開されている部分には必須化
- 型情報があるため、
@returnsおよび@paramの型定義を省略可能とする @paramがない場合や、@paramに説明がない場合、デフォルトではWarningのため、Errorにするexportされたtypeやinterfaceで定義したフィールドなどのTSDocも必須にする
それぞれ設定していく。
公開モジュールでのJSDoc/TSDoc必須化
jsdoc/require-jsdoc が対象。
publicOnly を true にすると、exportされたモジュールのみ対象となる。
また、 require で対象を細かく指定できる。
- ArrowFunctionExpression: アロー関数式
- ClassDeclaration: クラス宣言(
class MyClass { ... }) - ClassExpression: クラス式(
const MyClass = class { ... }) - FunctionDeclaration: 関数宣言(
function myFunction { ... }) - FunctionExpression: 関数式(
const myFunction = function { ... }) - MethodDefinition: メソッド定義(
const obj = { myMethod() { ... } })
デフォルトでは FunctionDeclaration のみ true なので、すべて有効にする。
{ "rules": { "jsdoc/require-jsdoc": [ "error", { "publicOnly": true, "require": { "ArrowFunctionExpression": true, "ClassDeclaration": true, "ClassExpression": true, "FunctionDeclaration": true, "FunctionExpression": true, "MethodDefinition": true } } ] } }
module.exportsのJSDocを省略可能にする
"publicOnly": true, の場合、デフォルトではES Modules形式の export ... とCommonJS形式の module.exports = ... が対象となる。
TypeScriptでコーディングする場合、 module.exports は使わないが、ライブラリ等の設定ファイルである *.config.js では module.exports が使われており、それらも警告される。
設定ファイルにJSDocを書く意味はなく、またプロダクトコード内でCommonJS形式を用いることもないため、ES Modulesのみチェックするよう、 "publicOnly": { "esm": true, "cjs": false }, に変更。
公開されたclassのプロパティや、type・interfaceでのJSDoc/TSDoc必須化
これでexportされた関数やクラスにTSDocがないとエラーが出るようになったが、クラスのプロパティや、type・interfaceおよびそのプロパティやメソッドに対してはチェックが効かない。
調べると、以下のStack Overflowの質問やissueにて、contextsに文字列を設定している。
- How do I configure ESLint to check for TypeScript class property JSDoc comments? - Stack Overflow
- require-jsdoc ignores TypeScript interface members · Issue #647 · gajus/eslint-plugin-jsdoc · GitHub
contextsに指定できる値は、ESLintパーサーによって異なるが、AST定義名の模様。
READMEからリンクされているAST Explorerにて、コードを張り付け、パーサーを @typescript-eslint/parser にすることで、TypeScriptのAST定義名を確認できた。

| 構文 | AST定義名 |
|---|---|
| classのプロパティ | PropertyDefinition |
| interface | TSInterfaceDeclaration |
| type | TSTypeAliasDeclaration |
| interfaceやtypeのプロパティ | TSPropertySignature |
| interfaceやtypeのメソッド | TSMethodSignature |
| enum | TSEnumDeclaration |
| enumのメンバー | TSEnumMember |
これらを jsdoc/require-jsdoc の contexts に設定すると、TSDocなしではエラーが出るが、中身が空でもJSDoc/TSDocのブロック( /** ~ */ )があればエラーにならない。
説明がない場合もエラーにするには、 jsdoc/require-description の contexts にもAST定義を設定すればいいが、 contexts は追記ではなく上書きのため、 jsdoc/require-jsdoc の contexts と同じものを設定すると、 require で指定している ArrowFunctionExpression などに対するチェックが行われなくなった。
jsdoc/require-jsdoc では、「require で true にしたもの + contexts」が対象となるのに対し、その他のルールでは contexts で指定したものを対象としている模様。
以下の様に、 jsdoc/require-description の contexts に require で true にしたものも含めることで、空ブロックもエラーとなった。
"jsdoc/require-jsdoc": [ "error", { "publicOnly": { "esm": true, "cjs": false }, "require": { "ArrowFunctionExpression": true, "ClassDeclaration": true, "ClassExpression": true, "FunctionDeclaration": true, "FunctionExpression": true, "MethodDefinition": true }, "contexts": [ "PropertyDefinition", "TSInterfaceDeclaration", "TSTypeAliasDeclaration", "TSPropertySignature", "TSMethodSignature", "TSEnumDeclaration", "TSEnumMember" ] } ], "jsdoc/require-description": [ "error", { "contexts": [ "ArrowFunctionExpression", "ClassDeclaration", "ClassExpression", "FunctionDeclaration", "FunctionExpression", "MethodDefinition", "PropertyDefinition", "TSInterfaceDeclaration", "TSTypeAliasDeclaration", "TSPropertySignature", "TSMethodSignature", "TSEnumDeclaration", "TSEnumMember" ] } ],
ちなみに、未検証だが、 jsdoc/require-jsdoc の require を消して、 contexts を jsdoc/require-description のものと同じように指定することも可能な模様。
追記: JavaScriptで変数を用いた場合
.eslintrc をJavaScriptにし、変数を用いることで、記述をまとめることができた。
const jsRequire = { ArrowFunctionExpression: true, ClassDeclaration: true, ClassExpression: true, FunctionDeclaration: true, FunctionExpression: true, MethodDefinition: true, } const tsContexts = [ 'PropertyDefinition', 'TSInterfaceDeclaration', 'TSTypeAliasDeclaration', 'TSPropertySignature', 'TSMethodSignature', 'TSEnumDeclaration', 'TSEnumMember', ] /** @type {import('eslint').Linter.Config} */ module.exports = { rules: { 'jsdoc/require-jsdoc': [ 'error', { publicOnly: { esm: true, cjs: false }, require: jsRequire, contexts: tsContexts, }, ], 'jsdoc/require-description': [ 'error', { contexts: [Object.keys(jsRequire), tsContexts].flat(), }, ], }, }
@returns および @param の型定義を省略可能にする
@returns の省略はjsdoc/require-returns、 @param の型定義はjsdoc/require-param-typeが対象のため、それぞれ off に変更。
ただ、require-returnsをoffにした場合、 @returns 自体を省略することは可能になるが、戻り値の詳細を書くために @returns を明示的に記述すると、やはり型定義を書くよう警告が出る。
@returns の型定義はjsdoc/require-returns-typeのため、これも off にする。
@param がない、または説明がない場合Errorにする
jsdoc/require-param が @param がない場合のルール、 jsdoc/require-param-description が説明がない場合のルールのため、それぞれ error に変更。
引数を分割代入している場合の警告の抑制
デフォルトでは引数を分割代入していると、以下の様に、それぞれの値ごとに @param を書く必要がある。
/** * @param root0 * @param root0.id * @param root0.name */ function example({ id, name }: exampleArguments) {}
型情報があれば値ごとの記述は不要のため、抑制したい。
jsdoc/require-param には、渡された引数自体の @param を制御する checkDestructuredRoots と、展開された値に対する @param を制御する checkDestructured がある。
今回は展開された値への抑制ができればいいため、 checkDestructured に false を指定する。
これで @param の記述は不要となったが、別途 Missing @param "root0.id" といった警告が jsdoc/check-param-names にて発生するようになった。
こちらにも同様の checkDestructured が存在するため、合わせて false に設定。
{ "rules": { "jsdoc/check-param-names": ["error", { "checkDestructured": false }], "jsdoc/require-param": ["error", { "checkDestructured": false }] } }
これで、以下の様な記述でも警告が出なくなる。
/** * @param arguments */ function example({ id, name }: exampleArguments) {}
なお、 checkTypesPattern で、型名に対する正規表現による抑制もできるが、対象となるのが @param の型定義であり、TypeScriptとはそぐわなかったため checkDestructured を使用した。
export していない関数の @param の省略
exportしていないモジュールにも、TSDocで概要だけ記述することがあるが、その場合 jsdoc/require-param から @param の記載がないと警告が出る。
jsdoc/require-jsdoc の publicOnly を true にすることでTSDocを省略できるが、省略せずに書いた場合はタグの記述が必要となる。
公開していない関数であれば、 @param の省略をできないか確認したが、対応できそうなオプションは見つけられなかった。
回避策として、 @private や @internal が付与されていればルールを無効化する設定があるため、そちらを有効化する。
TSDocのドキュメント を見ると、 @private は記載がないが、 @internal は記載されている。
ただ、エディタとしてVSCodeを利用しているが、JSDoc/TSDocのブロック( /** ~ */ )内で、 @private はコード補完が効くが、 @internal は効かなかった。
VSCodeにコードスニペットの追加はできるが、TSDocのブロック内でのみ有効化する、といった設定方法がわからなかったので、コーディングの負荷軽減を優先し、 @private が付与されている場合のルールを無効化するよう設定した。
{ "settings": { "jsdoc": { "ignorePrivate": true } } }
@internal が付与されていればルールを無効化する場合、 settings.jsdoc.ignoreInternal を true にすればいい。
@jsxImportSource の警告の抑制
@emotion/react を使っているが、 /** @jsxImportSource @emotion/react */ に対して、 jsdoc/check-tag-names から `Invalid JSDoc tag name "jsxImportSource". が発生する。
JSX関連のタグを有効化する jsxTags が用意されているため、 true に設定する。
{ "rules": { "jsdoc/check-tag-names": ["error", { "jsxTags": true }], } }
断念した設定
引数名による @param の省略
現在のプロジェクトでは、Reactコンポーネントのプロパティとして コンポーネント名Properties という型を定義し、 properties という名前でコンポーネントに渡している。
コンポーネントとの関係は明らかなため、できれば @param を省略したいと思ったが、引数名での制御はできない模様。
最終的な.eslintrcへの変更
今回の設定で変更した部分は、以下の様になった。
{ "plugins": ["jsdoc"], "extends": ["plugin:jsdoc/recommended"], "rules": { "jsdoc/require-jsdoc": [ "error", { "publicOnly": { "esm": true, "cjs": false }, "require": { "ArrowFunctionExpression": true, "ClassDeclaration": true, "ClassExpression": true, "FunctionDeclaration": true, "FunctionExpression": true, "MethodDefinition": true }, "contexts": [ "PropertyDefinition", "TSInterfaceDeclaration", "TSTypeAliasDeclaration", "TSPropertySignature", "TSMethodSignature", "TSEnumDeclaration", "TSEnumMember" ] } ], "jsdoc/check-param-names": ["error", { "checkDestructured": false }], "jsdoc/check-tag-names": ["error", { "jsxTags": true }], "jsdoc/require-description": [ "error", { "contexts": [ "ArrowFunctionExpression", "ClassDeclaration", "ClassExpression", "FunctionDeclaration", "FunctionExpression", "MethodDefinition", "PropertyDefinition", "TSInterfaceDeclaration", "TSTypeAliasDeclaration", "TSPropertySignature", "TSMethodSignature", "TSEnumDeclaration", "TSEnumMember" ] } ], "jsdoc/require-param": ["error", { "checkDestructured": false }], "jsdoc/require-param-description": "error", "jsdoc/require-param-type": "off", "jsdoc/require-returns": "off", "jsdoc/require-returns-type": "off" }, "settings": { "jsdoc": { "ignorePrivate": true } } }
振り返り
これでTSDocの書き漏れや、書き方の不備についてチェックできるようになった。
export していないモジュールに対してTSDocで説明だけを書きたい場合、 @private を付ける必要があるが、特に問題なく運用できている。
jsdoc/require-jsdoc と jsdoc/require-description のcontextsなどに指定する文字列が重複しているのがやや気持ち悪い。 .eslintrc をJSONではなくJavaScriptやYAMLで書いておけば、それぞれ変数やアンカーでまとめられるので、そのうち変更しようかな。
-> 2021/10/21、JavaScriptの場合を追記。