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の場合を追記。