method-shorthand-jsdoc について
TypeScript のファクトリー関数を構成し、返却メソッドの JSDoc を利用側から見えるようにするスキルです。返却オブジェクトのメソッドだけが使うヘルパーには、メソッド簡略記法を用います。
主な機能
- 01返却メソッドの JSDoc を保持
- 02返却オブジェクトでメソッド簡略記法を使用
- 03ヘルパーの配置方法を提示
- 04this による同階層メソッド呼び出しを説明
活用シーン
- 01TypeScript ファクトリー API の設計
- 02IDE のホバードキュメントの維持
- 03プライベートヘルパーとメソッドの選択
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ epicenterhq/ epicenter --skill method-shorthand-jsdocインストール後、Claude Code で/method-shorthand-jsdocを入力して読み込まれたか確認します。
詳細
JSDoc を保持するためのメソッド省略記法
ファクトリー関数に、返されるメソッドからのみ使用されるヘルパー関数がある場合は、メソッド省略記法を使ってそのヘルパーを返却オブジェクトの中に移動します。これにより、JSDoc コメントが利用側に正しく引き継がれます。
関連スキル:4 つのゾーンからなるファクトリーの構成と
thisの判断ルールについては、factory-function-compositionを参照してください。
問題
ドキュメントが充実したヘルパーを持つファクトリー関数を書くとします。
function createHeadDoc(options: { workspaceId: string }) {
const { workspaceId } = options;
/**
* Get the current epoch number.
*
* Computes the maximum of all client-proposed epochs.
* This ensures concurrent bumps converge to the same version.
*
* @returns The current epoch (0 if no bumps have occurred)
*/
function getEpoch(): number {
let max = 0;
for (const value of epochsMap.values()) {
max = Math.max(max, value);
}
return max;
}
return {
workspaceId,
getEpoch, // JSDoc is NOT visible when hovering on returned object!
bumpEpoch(): number {
const next = getEpoch() + 1; // Calling internal helper
return next;
},
};
}
IDE で head.getEpoch() にカーソルを合わせても、何も表示されません。JSDoc が失われています。
解決策
メソッド省略記法を使って、ヘルパーを返却オブジェクトの中に移動します。
function createHeadDoc(options: { workspaceId: string }) {
const { workspaceId } = options;
return {
workspaceId,
/**
* Get the current epoch number.
*
* Computes the maximum of all client-proposed epochs.
* This ensures concurrent bumps converge to the same version.
*
* @returns The current epoch (0 if no bumps have occurred)
*/
getEpoch(): number {
let max = 0;
for (const value of epochsMap.values()) {
max = Math.max(max, value);
}
return max;
},
bumpEpoch(): number {
const next = this.getEpoch() + 1; // Use this.methodName()
return next;
},
};
}
これで head.getEpoch() にカーソルを合わせると、JSDoc 全体が表示されます。
公開型をファクトリーから導出する場合は、さらに重要です。
export type HeadDoc = ReturnType<typeof createHeadDoc>;
ReturnType では、返却オブジェクトが公開型のソースになります。利用者向けの JSDoc は、返却されるメソッドまたはゲッターに直接記述してください。そうすれば、ホバー表示、補完、定義へ移動のすべてが同じメンバーを参照します。
JSDoc の保持と定義へ移動の流れは、どちらも利用者のツールがどこを参照するかという同じ選択から生じます。ゾーン 4 でメソッド省略記法を使うと、どちらも実際の定義を参照します。手書きのインターフェース注釈、モジュールレベルのオブジェクトを分割代入して再エクスポートする方法、あるいは関数をそのまま再出力するだけのラッパーは、両者を分断する問題を引き起こします。ナビゲーションに関するルールについては、typescript の「定義へ移動の認識」を参照してください。
これが機能する理由
- JSDoc はメソッドの定義箇所に付与されます - メソッドを返却オブジェクト内に直接記述すると、JSDoc は TypeScript が認識するプロパティに直接付与されます
- メソッド省略記法は
functionと同じセマンティクスを使います -thisはオブジェクトに束縛されるため、this.getEpoch()が機能します - 別のヘルパーは不要です - 同じオブジェクト内の他のメソッドからのみ使用されるなら、そのオブジェクトに置くべきです
パターン
// BAD: Helper defined separately, JSDoc lost on return
function createService(client) {
/** Fetches user data with caching. */
function fetchUser(id: string) { ... }
return {
fetchUser, // JSDoc not visible to consumers!
getProfile(id: string) {
return fetchUser(id); // Works, but consumers can't see docs
},
};
}
// GOOD: Method shorthand, JSDoc preserved
function createService(client) {
return {
/** Fetches user data with caching. */
fetchUser(id: string) { ... },
getProfile(id: string) {
return this.fetchUser(id); // Use this.method()
},
};
}
判断ルール
ヘルパーが返却されるメソッドからのみ使用され、利用者がそのメソッドのホバー表示で JSDoc を必要とし、初期化中には実行されない場合は、返却オブジェクト内に移動します。
return の前に呼び出される場合、複数のファクトリー間で共有される場合、または公開されない真に内部的な処理である場合は、ヘルパーを分離したままにします。
アロー関数では機能しません
アロー関数は独自の this を持ちません。
// BAD: Arrow function, this is undefined
return {
getEpoch: () => { ... },
bumpEpoch: () => {
this.getEpoch(); // ERROR: this is undefined!
},
};
// GOOD: Method shorthand has correct this binding
return {
getEpoch() { ... },
bumpEpoch() {
this.getEpoch(); // Works!
},
};
実例
packages/epicenter/src/core/docs/head-doc.ts より:
export function createHeadDoc(options: { workspaceId: string; ydoc?: Y.Doc }) {
const { workspaceId } = options;
const ydoc = options.ydoc ?? new Y.Doc({ guid: workspaceId });
const epochsMap = ydoc.getMap<number>('epochs');
return {
ydoc,
workspaceId,
/**
* Get the current epoch number.
*
* Computes the maximum of all client-proposed epochs.
* This ensures concurrent bumps converge to the same version
* without skipping epoch numbers.
*
* @returns The current epoch (0 if no bumps have occurred)
*/
getEpoch(): number {
let max = 0;
for (const value of epochsMap.values()) {
max = Math.max(max, value);
}
return max;
},
/**
* Bump the epoch to the next version.
*
* @returns The new epoch number after bumping
*/
bumpEpoch(): number {
const next = this.getEpoch() + 1;
epochsMap.set(ydoc.clientID.toString(), next);
return next;
},
// ... other methods using this.getEpoch()
};
}
まとめ
| 方法 | JSDoc が表示されるか | this が機能するか |
|---|---|---|
| 分離したヘルパー+参照 | いいえ | 該当なし |
| 返却オブジェクト内のアロー関数 | はい | いいえ |
| 返却オブジェクト内のメソッド省略記法 | はい | はい |
メソッド省略記法だけが、JSDoc を保持し、さらに this を使ってメソッド同士を呼び出せる方法です。
ファクトリー関数の構成における位置づけ
ファクトリー関数の内部構造は、4 つのゾーンで構成されます。不変状態 → 可変状態 → プライベートヘルパー → 返却オブジェクト。メソッド省略記法はゾーン 4(返却オブジェクト)、つまり公開 API に置きます。
this.method() を使うか直接呼び出すかの判断は、その関数がどのゾーンにあるかによって決まります。
| 状況 | 配置場所 | 呼び出し方 |
|---|---|---|
| 返却オブジェクト内の同じ階層のメソッドからのみ使用される | ゾーン 4(返却オブジェクト、メソッド省略記法) | this.method() |
返却オブジェクトのメソッドと、return 前の初期化処理の両方から使用される | ゾーン 3(プライベートヘルパー、独立した関数) | 直接呼び出し:helperFn() |
| 初期化中にのみ使用され、公開されない | ゾーン 3(プライベートヘルパー) | 直接呼び出し:helperFn() |
ヘルパーをゾーン 3 に置く必要がある場合、その JSDoc は利用者には表示されません。これは正しい動作です。これはプライベートな実装詳細だからです。利用者向けの JSDoc が必要なのは、ゾーン 4 のメソッドだけです。
ファクトリー関数の構成全体については、Closures Are Better Privacy Than Keywords を参照してください。
参考資料
- docs/articles/method-shorthand-jsdoc-preservation.md - 記事と同じ内容
- docs/articles/closures-are-better-privacy-than-keywords.md - ファクトリー関数の構造とゾーンシステム