mcpField
M
method-shorthand-jsdoc

method-shorthand-jsdoc

github.com/epicenterhq开发4.8k56 次安装2 周前更新
代码仓库

关于 method-shorthand-jsdoc

这项技能介绍如何组织 TypeScript 工厂函数,让返回方法上的 JSDoc 对使用者可见。重点是将仅供返回对象方法使用的辅助函数写成对象内的方法简写。

主要功能

  1. 01保留返回方法上的 JSDoc
  2. 02在返回对象中使用方法简写
  3. 03指导辅助函数的放置位置
  4. 04说明用 this 调用同级方法

适用场景

  1. 01设计 TypeScript 工厂函数 API
  2. 02让 IDE 悬停文档保持可见
  3. 03选择私有辅助函数或对象方法

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/epicenterhq/epicenter --skill method-shorthand-jsdoc

安装后在 Claude Code 中输入/method-shorthand-jsdoc确认已加载。

详细介绍

保留 JSDoc 的方法简写

当工厂函数中的辅助函数仅供返回的方法使用时,将它们移入返回对象,并使用方法简写。这样可以确保 JSDoc 注释正确传递给使用者。

相关技能:参阅 factory-function-composition,了解工厂函数的四区结构以及 this 的判断规则。

问题

你编写了一个带有详细文档的辅助函数的工厂函数:

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 直接写在返回的方法或 getter 上,这样悬停提示、自动补全和“转到定义”都会指向同一个成员。

JSDoc 保留和“转到定义”的行为取决于同一个选择:使用者的工具会解析到哪里。第 4 区中的方法简写能让两者都指向真实定义。手写的接口注解、对模块级对象进行解构并重新导出,或仅重新导出函数的包装器,都会导致两者分离。请参阅 typescript 中的“转到定义注意事项”,了解导航方面的规则。

原理

  1. JSDoc 会附加到方法定义处——方法直接内联在返回对象中时,JSDoc 就位于 TypeScript 所识别的属性上
  2. 方法简写采用 function 语义——this 会绑定到对象,因此 this.getEpoch() 可以正常工作
  3. 无需单独的辅助函数——如果它只由同级方法使用,就应该放在同一个对象中

模式

// 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 区):也就是公共 API。

使用 this.method() 还是直接调用,取决于函数所在的区域:

情况所在位置调用方式
仅由返回对象中的同级方法使用第 4 区(返回对象,方法简写)this.method()
由返回对象中的方法和 return 之前的初始化逻辑共同使用第 3 区(私有辅助函数,独立函数)直接调用:helperFn()
仅在初始化期间使用,不对外公开第 3 区(私有辅助函数)直接调用:helperFn()

当辅助函数必须位于第 3 区时,它的 JSDoc 不会对使用者可见;这是正确的,因为它属于私有实现细节。只有第 4 区的方法才需要面向使用者的 JSDoc。

有关工厂函数完整的四区结构,请参阅闭包比关键字更适合实现隐私。

参考资料

评论

支持 Markdown