TypeScriptでの開発時、exportしているモジュールに対してはTSDocを書くようコーディング規約を定めたが、ヒアドキュメントを書く習慣のないメンバーが多く、書き漏れが頻発した。
都度コードレビューで指摘するのも面倒なので、ESLintで何とかならないかと思い、調べたのでメモ。
環境
ESLint v8.21.0、eslint-plugin-jsdoc v39.3.4。
昔はESLintにJSDocサポートが組み込まれていたが、廃止済み。
eslint.org
Microsoft がeslint-plugin-tsdoc を公開しているが、これはTSDoc仕様に準拠したドキュメントかのチェックのため、やりたいこととは異なる。
上記ブログでも移行先として指定されていた、 eslint-plugin-jsdoc
がTSDocにも対応している。
github.com
以下の記事で設定の実例もあった。
this.aereal.org
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に文字列を設定している。
contextsに指定できる値は、ESLintパーサーによって異なる が、AST定義名の模様。
READMEからリンクされているAST Explorer にて、コードを張り付け、パーサーを @typescript-eslint/parser
にすることで、TypeScriptのAST定義名を確認できた。
AST Explorer でのパーサーの設定
構文
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 のものと同じように指定することも可能な模様。
.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' ,
]
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 の場合を追記。