jsdoc-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseJSDoc Best Practices
JSDoc最佳实践
Overview
概述
This skill defines the JSDoc documentation standards for this project. The core principle is that documentation should explain "why", not just "what". Code already shows what it does—good documentation explains the reasoning, context, and non-obvious details that help developers understand and maintain the code.
本技能定义了本项目的JSDoc文档标准,核心原则是:文档应该解释「为什么」,而非仅仅说明「是什么」。代码本身已经展示了它的功能,优秀的文档会解释背后的设计逻辑、上下文背景和非显性细节,帮助开发者理解和维护代码。
Core Philosophy: Why Over What
核心理念:「为什么」优先于「是什么」
The Problem with "What" Documentation
只讲「是什么」的文档存在的问题
typescript
// Bad: Just restates the code
/**
* Gets the user by ID
* @param id - The ID
* @returns The user
*/
function getUserById(id: string): User { ... }This documentation adds no value—the function name already tells us it gets a user by ID.
typescript
// 反面示例:仅仅复述了代码的功能
/**
* Gets the user by ID
* @param id - The ID
* @returns The user
*/
function getUserById(id: string): User { ... }这类文档没有任何价值——函数名本身已经告诉我们它的作用是通过ID获取用户。
The Solution: Document "Why"
解决方案:重点记录「为什么」
typescript
// Good: Explains context, constraints, and non-obvious behavior
/**
* Retrieves a user by their unique identifier
* @param id - The user's UUID (not the legacy numeric ID)
* @returns The user if found, null if not found or soft-deleted
* @remarks Used by DataLoader for batching - maintains input order
*/
function getUserById(id: string): User | null { ... }This documentation adds value by explaining:
- What kind of ID (UUID vs legacy)
- What happens when not found
- Why this specific implementation exists (DataLoader batching)
typescript
// 正面示例:解释了上下文、约束和非显性行为
/**
* Retrieves a user by their unique identifier
* @param id - The user's UUID (not the legacy numeric ID)
* @returns The user if found, null if not found or soft-deleted
* @remarks Used by DataLoader for batching - maintains input order
*/
function getUserById(id: string): User | null { ... }这份文档通过补充以下信息提供了额外价值:
- 接收的ID类型(UUID而非旧版数字ID)
- 查询不到时的返回结果
- 该实现存在的原因(用于DataLoader批量查询)
ESLint Enforcement
ESLint 规则强制校验
The project enforces JSDoc through with these rules:
eslint-plugin-jsdoc项目通过 强制执行JSDoc规范,具体规则如下:
eslint-plugin-jsdocRequired Documentation
必填文档要求
| Rule | Setting | What It Enforces |
|---|---|---|
| error | JSDoc on function declarations, interfaces, type aliases, and PascalCase arrow functions |
| error | All |
| error | All |
| error | All |
| 规则 | 配置值 | 校验内容 |
|---|---|---|
| error | 函数声明、接口、类型别名、帕斯卡命名的箭头函数必须添加JSDoc |
| error | 所有 |
| error | 所有 |
| error | 所有 |
Allowed Tags
允许使用的标签
| Rule | Setting | Effect |
|---|---|---|
| | Allows |
| off | TypeScript types in JSDoc are optional |
| off | Types come from TypeScript, not JSDoc |
| off | Types come from TypeScript, not JSDoc |
| 规则 | 配置值 | 作用 |
|---|---|---|
| | 允许使用 |
| off | JSDoc中可以不写TypeScript类型 |
| off | 类型从TypeScript签名中获取,无需在JSDoc中声明 |
| off | 类型从TypeScript签名中获取,无需在JSDoc中声明 |
What Requires Documentation
需要添加文档的场景
Per configuration:
jsdoc/require-jsdocjavascript
{
require: {
FunctionDeclaration: true, // function foo() {}
MethodDefinition: false, // class methods (optional)
ClassDeclaration: false, // classes (optional but recommended)
ArrowFunctionExpression: false, // const foo = () => {} (optional)
FunctionExpression: false, // const foo = function() {} (optional)
},
contexts: [
"TSInterfaceDeclaration", // interface Foo {}
"TSTypeAliasDeclaration", // type Foo = ...
// PascalCase arrow functions (React components, factories):
"VariableDeclaration[declarations.0.init.type='ArrowFunctionExpression']:has([id.name=/^[A-Z]/])"
]
}根据 配置:
jsdoc/require-jsdocjavascript
{
require: {
FunctionDeclaration: true, // 函数声明:function foo() {}
MethodDefinition: false, // 类方法:可选
ClassDeclaration: false, // 类:可选但推荐添加
ArrowFunctionExpression: false, // 普通箭头函数:const foo = () => {} 可选
FunctionExpression: false, // 函数表达式:const foo = function() {} 可选
},
contexts: [
"TSInterfaceDeclaration", // 接口声明:interface Foo {}
"TSTypeAliasDeclaration", // 类型别名:type Foo = ...
// 帕斯卡命名的箭头函数(React组件、工厂函数):
"VariableDeclaration[declarations.0.init.type='ArrowFunctionExpression']:has([id.name=/^[A-Z]/])"
]
}Documentation Patterns
文档编写模式
File Preambles
文件序言
Every file should have a preamble comment at the top:
typescript
/**
* @file complexity.plugin.ts
* @description Apollo Server plugin for query complexity analysis and limiting
* @module graphql
*/| Tag | Purpose |
|---|---|
| The filename (for navigation and search) |
| What this file provides |
| The feature module this belongs to |
每个文件顶部都需要添加序言注释:
typescript
/**
* @file complexity.plugin.ts
* @description Apollo Server plugin for query complexity analysis and limiting
* @module graphql
*/| 标签 | 用途 |
|---|---|
| 文件名(用于导航和搜索) |
| 该文件提供的功能说明 |
| 该文件所属的功能模块 |
Service Documentation
服务类文档
typescript
/**
* Service for managing user accounts
* @description Provides CRUD operations for user entities
* @remarks
* - All methods are idempotent
* - Throws NotFoundException for missing resources
* - Uses DataLoader batching for bulk operations
*/
@Injectable()
export class UserService { ... }typescript
/**
* Service for managing user accounts
* @description Provides CRUD operations for user entities
* @remarks
* - All methods are idempotent
* - Throws NotFoundException for missing resources
* - Uses DataLoader batching for bulk operations
*/
@Injectable()
export class UserService { ... }Method Documentation
方法文档
typescript
/**
* Batch loads entities by IDs (for DataLoader)
* @param ids - Array of entity IDs to load
* @returns Promise resolving to array of entities in same order as input
* @remarks Used by DataLoader for batching - maintains input order
*/
async findByIds(ids: readonly string[]): Promise<Entity[]> { ... }typescript
/**
* Batch loads entities by IDs (for DataLoader)
* @param ids - Array of entity IDs to load
* @returns Promise resolving to array of entities in same order as input
* @remarks Used by DataLoader for batching - maintains input order
*/
async findByIds(ids: readonly string[]): Promise<Entity[]> { ... }Interface Documentation
接口文档
typescript
/**
* Interface for authentication services
* @description Defines the contract for both Cognito and Local auth implementations.
* This interface ensures both AuthService (production) and LocalAuthService
* (local development) provide the same public API for authentication operations.
*/
export interface IAuthService {
/**
* Initiates the sign-in flow by sending an OTP to the user
* @param input - The sign-in input containing the user identifier (phone/email)
* @returns A promise resolving to the sign-in result with session and challenge info
*/
signIn(input: SignInInput): Promise<SignInResult>;
}typescript
/**
* Interface for authentication services
* @description Defines the contract for both Cognito and Local auth implementations.
* This interface ensures both AuthService (production) and LocalAuthService
* (local development) provide the same public API for authentication operations.
*/
export interface IAuthService {
/**
* Initiates the sign-in flow by sending an OTP to the user
* @param input - The sign-in input containing the user identifier (phone/email)
* @returns A promise resolving to the sign-in result with session and challenge info
*/
signIn(input: SignInInput): Promise<SignInResult>;
}Type/Constant Documentation
类型/常量文档
typescript
/**
* Default complexity configuration
* @description Tune these values based on your server capacity
*/
const COMPLEXITY_CONFIG = {
/** Maximum allowed query complexity */
maxComplexity: 100,
/** Default complexity for fields without explicit complexity */
defaultComplexity: 1,
} as const;typescript
/**
* Default complexity configuration
* @description Tune these values based on your server capacity
*/
const COMPLEXITY_CONFIG = {
/** Maximum allowed query complexity */
maxComplexity: 100,
/** Default complexity for fields without explicit complexity */
defaultComplexity: 1,
} as const;The @remarks Tag
@remarks 标签使用说明
Use to document the "why" and important context:
@remarks使用 标签记录「为什么」相关的内容和重要上下文:
@remarksWhen to Use @remarks
何时使用 @remarks
| Use Case | Example |
|---|---|
| Design decisions | |
| Usage constraints | |
| Non-obvious behavior | |
| Important caveats | |
| Integration details | |
| 使用场景 | 示例 |
|---|---|
| 设计决策 | |
| 使用约束 | |
| 非显性行为 | |
| 重要注意事项 | |
| 集成细节 | |
@remarks Format
@remarks 格式要求
Use bullet points for multiple remarks:
typescript
/**
* Apollo Server plugin that calculates and limits query complexity
* @description Prevents expensive queries from overwhelming the server
* @remarks
* - Uses field extensions estimator for custom complexity values
* - Falls back to simple estimator with default complexity of 1
* - Rejects queries that exceed the configured maximum complexity
*/Use inline for single remarks:
typescript
/**
* Creates all DataLoader instances for a single request
* @returns Object containing all typed DataLoaders
* @remarks Called in GraphQL context factory - creates fresh instances per request
*/多条备注使用项目符号列表:
typescript
/**
* Apollo Server plugin that calculates and limits query complexity
* @description Prevents expensive queries from overwhelming the server
* @remarks
* - Uses field extensions estimator for custom complexity values
* - Falls back to simple estimator with default complexity of 1
* - Rejects queries that exceed the configured maximum complexity
*/单条备注可以直接 inline 编写:
typescript
/**
* Creates all DataLoader instances for a single request
* @returns Object containing all typed DataLoaders
* @remarks Called in GraphQL context factory - creates fresh instances per request
*/Parameter Descriptions
参数描述编写规范
Bad: Restating the Name
反面示例:仅复述参数名
typescript
/**
* @param id - The id
* @param name - The name
* @param options - The options
*/typescript
/**
* @param id - The id
* @param name - The name
* @param options - The options
*/Good: Adding Value
正面示例:补充有效信息
typescript
/**
* @param id - The user's UUID (not the legacy numeric ID from v1 API)
* @param name - Display name, max 50 characters, sanitized for XSS
* @param options - Configuration for the query, see QueryOptions type
*/typescript
/**
* @param id - The user's UUID (not the legacy numeric ID from v1 API)
* @param name - Display name, max 50 characters, sanitized for XSS
* @param options - Configuration for the query, see QueryOptions type
*/Parameter Description Guidelines
参数描述编写指南
| Include | Avoid |
|---|---|
| Valid value ranges | Restating the parameter name |
| Format requirements | Restating the type |
| Default behavior | Obvious information |
| Edge cases | Implementation details |
| Units (ms, bytes, etc.) | Internal variable names |
| 建议包含内容 | 需避免内容 |
|---|---|
| 有效值范围 | 复述参数名 |
| 格式要求 | 复述参数类型 |
| 默认行为 | 显而易见的信息 |
| 边界情况 | 实现细节 |
| 单位(毫秒、字节等) | 内部变量名 |
Return Value Descriptions
返回值描述编写规范
Bad: Restating the Type
反面示例:仅复述返回类型
typescript
/**
* @returns The user
* @returns A promise
* @returns The result
*/typescript
/**
* @returns The user
* @returns A promise
* @returns The result
*/Good: Explaining Behavior
正面示例:解释返回行为
typescript
/**
* @returns The user if found, null if not found or soft-deleted
* @returns Promise resolving to array of entities in same order as input
* @returns Authentication tokens on success, error message on failure
*/typescript
/**
* @returns The user if found, null if not found or soft-deleted
* @returns Promise resolving to array of entities in same order as input
* @returns Authentication tokens on success, error message on failure
*/Anti-Patterns to Avoid
需要避免的反模式
Don't Document the Obvious
不要为显而易见的内容添加文档
typescript
// Wrong: Adds no value
/**
* Constructor
*/
constructor() {}
/**
* Gets the name
* @returns The name
*/
getName(): string { return this.name; }typescript
// 错误示例:没有任何价值
/**
* Constructor
*/
constructor() {}
/**
* Gets the name
* @returns The name
*/
getName(): string { return this.name; }Don't Duplicate TypeScript Types
不要重复声明TypeScript已有的类型
typescript
// Wrong: Type is already in signature
/**
* @param id - {string} The user ID
* @returns {Promise<User>} The user
*/
async getUser(id: string): Promise<User> { ... }
// Correct: Description only, type from TypeScript
/**
* @param id - The user's UUID identifier
* @returns The user entity with populated relations
*/
async getUser(id: string): Promise<User> { ... }typescript
// 错误示例:类型已经在函数签名中声明
/**
* @param id - {string} The user ID
* @returns {Promise<User>} The user
*/
async getUser(id: string): Promise<User> { ... }
// 正确示例:仅写描述,类型从TypeScript签名中获取
/**
* @param id - The user's UUID identifier
* @returns The user entity with populated relations
*/
async getUser(id: string): Promise<User> { ... }Don't Write Implementation Comments
不要写实现细节类的注释
typescript
// Wrong: Documents how, not why
/**
* Loops through users and filters by active status
*/
const activeUsers = users.filter(u => u.active);
// Correct: Self-documenting code needs no comment
// If explanation is needed, explain WHY:
// Active users are filtered first to avoid unnecessary permission checks
const activeUsers = users.filter(u => u.active);typescript
// 错误示例:记录了「如何实现」而非「为什么实现」
/**
* Loops through users and filters by active status
*/
const activeUsers = users.filter(u => u.active);
// 正确示例:自解释代码不需要额外注释
// 如果确实需要解释,请说明原因:
// 先过滤活跃用户是为了避免不必要的权限校验
const activeUsers = users.filter(u => u.active);Escaping @ Symbols in JSDoc
JSDoc中@符号的转义方法
When documenting code that contains TypeScript/NestJS decorators (like , ), JSDoc will interpret the as a tag marker. This causes lint errors because JSDoc sees as a single unknown tag name (including the parentheses and arguments).
@Injectable()@Processor('queue-name')@@Processor('qpr-v2')The problem: Adding decorator names to doesn't help because JSDoc parses the entire string as the tag name, not just .
definedTags@Processor('qpr-v2')@Processor当你要在文档中记录包含TypeScript/NestJS装饰器的代码时(比如、),JSDoc会将识别为标签标记,导致lint报错,因为JSDoc会把整体识别为一个未知的标签名(包含括号和参数)。
@Injectable()@Processor('queue-name')@@Processor('qpr-v2')问题说明: 将装饰器名称添加到中无法解决该问题,因为JSDoc会把整个字符串解析为标签名,而不仅仅是。
definedTags@Processor('qpr-v2')@ProcessorSolution 1: Backticks in Prose
解决方案1:正文中使用反引号包裹
When mentioning decorators in description text, wrap them in backticks:
typescript
/**
* Queue processor for QPR calculations
* @description Handles jobs from the `@Processor('qpr-v2')` queue
* @remarks Uses `@Injectable()` scope for request isolation
*/在描述文本中提到装饰器时,用反引号将其包裹:
typescript
/**
* Queue processor for QPR calculations
* @description Handles jobs from the `@Processor('qpr-v2')` queue
* @remarks Uses `@Injectable()` scope for request isolation
*/Solution 2: Escape in @example Blocks
解决方案2:@example块中使用反斜杠转义
In blocks, use fenced code blocks and escape as :
@example@\@typescript
/**
* Creates a queue processor
* @example
* ```typescript
* \@Processor('my-queue')
* export class MyProcessor {
* \@Process()
* async handle(job: Job) { ... }
* }
* ```
*/在块中,使用代码块包裹,并且将转义为:
@example@\@typescript
/**
* Creates a queue processor
* @example
* ```typescript
* \@Processor('my-queue')
* export class MyProcessor {
* \@Process()
* async handle(job: Job) { ... }
* }
* ```
*/Quick Reference for Escaping
转义快速参考
| Context | Approach | Example |
|---|---|---|
| Prose/description | Wrap in backticks | |
| @example block | Escape with backslash | |
| Code comments | No escaping needed | |
| 场景 | 处理方式 | 示例 |
|---|---|---|
| 正文/描述中 | 用反引号包裹 | |
| @example 块中 | 用反斜杠转义 | |
| 代码注释中 | 无需转义 | |
Quick Reference
快速参考
Required Structure for Services
服务类必填结构
typescript
/**
* @file feature.service.ts
* @description Service providing feature functionality
* @module feature
*/
/**
* Service for feature operations
* @description Brief description of what this service handles
* @remarks
* - Important architectural decisions
* - Usage patterns or constraints
*/
@Injectable()
export class FeatureService {
/**
* Brief description of what this method does
* @param paramName - What this parameter represents and any constraints
* @returns What is returned and under what conditions
* @remarks Any non-obvious behavior or usage notes
*/
methodName(paramName: Type): ReturnType { ... }
}typescript
/**
* @file feature.service.ts
* @description Service providing feature functionality
* @module feature
*/
/**
* Service for feature operations
* @description Brief description of what this service handles
* @remarks
* - Important architectural decisions
* - Usage patterns or constraints
*/
@Injectable()
export class FeatureService {
/**
* Brief description of what this method does
* @param paramName - What this parameter represents and any constraints
* @returns What is returned and under what conditions
* @remarks Any non-obvious behavior or usage notes
*/
methodName(paramName: Type): ReturnType { ... }
}Required Structure for Interfaces
接口必填结构
typescript
/**
* Interface for feature operations
* @description Explains the contract this interface defines
*/
export interface IFeature {
/**
* Method description
* @param param - Parameter description with constraints
* @returns Return description with conditions
*/
method(param: Type): ReturnType;
}typescript
/**
* Interface for feature operations
* @description Explains the contract this interface defines
*/
export interface IFeature {
/**
* Method description
* @param param - Parameter description with constraints
* @returns Return description with conditions
*/
method(param: Type): ReturnType;
}Required Structure for Types
类型必填结构
typescript
/**
* Represents a feature configuration
* @description Used to configure feature behavior at initialization
*/
export type FeatureConfig = {
/** Maximum retry attempts before failing */
maxRetries: number;
/** Timeout in milliseconds */
timeoutMs: number;
};typescript
/**
* Represents a feature configuration
* @description Used to configure feature behavior at initialization
*/
export type FeatureConfig = {
/** Maximum retry attempts before failing */
maxRetries: number;
/** Timeout in milliseconds */
timeoutMs: number;
};Verification Checklist
校验清单
Before committing code, verify:
- File preamble exists: ,
@file,@description@module - Function declarations have JSDoc: Required by ESLint
- Interfaces have JSDoc: Required by ESLint
- Type aliases have JSDoc: Required by ESLint
- Parameters have meaningful descriptions: Not just restating the name
- Returns have meaningful descriptions: Explain conditions and edge cases
- @remarks used for "why": Design decisions, constraints, non-obvious behavior
- No TypeScript types in JSDoc: Types come from the signature
提交代码前,请确认:
- 文件序言已添加:包含、
@file、@description@module - 函数声明已添加JSDoc:ESLint强制要求
- 接口已添加JSDoc:ESLint强制要求
- 类型别名已添加JSDoc:ESLint强制要求
- 参数描述有实际意义:不仅仅是复述参数名
- 返回值描述有实际意义:解释了返回条件和边界情况
- 已使用@remarks记录「为什么」:包括设计决策、约束、非显性行为
- JSDoc中没有重复声明TypeScript类型:类型从函数签名中获取