jsdoc-best-practices

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

JSDoc 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
eslint-plugin-jsdoc
with these rules:
项目通过
eslint-plugin-jsdoc
强制执行JSDoc规范,具体规则如下:

Required Documentation

必填文档要求

RuleSettingWhat It Enforces
jsdoc/require-jsdoc
errorJSDoc on function declarations, interfaces, type aliases, and PascalCase arrow functions
jsdoc/require-param-description
errorAll
@param
tags must have descriptions
jsdoc/require-returns-description
errorAll
@returns
tags must have descriptions
jsdoc/require-property-description
errorAll
@property
tags must have descriptions
规则配置值校验内容
jsdoc/require-jsdoc
error函数声明、接口、类型别名、帕斯卡命名的箭头函数必须添加JSDoc
jsdoc/require-param-description
error所有
@param
标签必须包含描述
jsdoc/require-returns-description
error所有
@returns
标签必须包含描述
jsdoc/require-property-description
error所有
@property
标签必须包含描述

Allowed Tags

允许使用的标签

RuleSettingEffect
jsdoc/check-tag-names
definedTags: ["remarks"]
Allows
@remarks
for "why" documentation
jsdoc/no-types
offTypeScript types in JSDoc are optional
jsdoc/require-param-type
offTypes come from TypeScript, not JSDoc
jsdoc/require-returns-type
offTypes come from TypeScript, not JSDoc
规则配置值作用
jsdoc/check-tag-names
definedTags: ["remarks"]
允许使用
@remarks
标签记录「为什么」相关的内容
jsdoc/no-types
offJSDoc中可以不写TypeScript类型
jsdoc/require-param-type
off类型从TypeScript签名中获取,无需在JSDoc中声明
jsdoc/require-returns-type
off类型从TypeScript签名中获取,无需在JSDoc中声明

What Requires Documentation

需要添加文档的场景

Per
jsdoc/require-jsdoc
configuration:
javascript
{
  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-jsdoc
配置:
javascript
{
  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
 */
TagPurpose
@file
The filename (for navigation and search)
@description
What this file provides
@module
The feature module this belongs to
每个文件顶部都需要添加序言注释:
typescript
/**
 * @file complexity.plugin.ts
 * @description Apollo Server plugin for query complexity analysis and limiting
 * @module graphql
 */
标签用途
@file
文件名(用于导航和搜索)
@description
该文件提供的功能说明
@module
该文件所属的功能模块

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
@remarks
to document the "why" and important context:
使用
@remarks
标签记录「为什么」相关的内容和重要上下文:

When to Use @remarks

何时使用 @remarks

Use CaseExample
Design decisions
@remarks Uses closure pattern to cache between Lambda invocations
Usage constraints
@remarks Call getLoaders() once per GraphQL request in context factory
Non-obvious behavior
@remarks Maintains input order for DataLoader compatibility
Important caveats
@remarks All methods are idempotent - safe to retry
Integration details
@remarks Connects on module initialization, disconnects on destruction
使用场景示例
设计决策
@remarks Uses closure pattern to cache between Lambda invocations
使用约束
@remarks Call getLoaders() once per GraphQL request in context factory
非显性行为
@remarks Maintains input order for DataLoader compatibility
重要注意事项
@remarks All methods are idempotent - safe to retry
集成细节
@remarks Connects on module initialization, disconnects on destruction

@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

参数描述编写指南

IncludeAvoid
Valid value rangesRestating the parameter name
Format requirementsRestating the type
Default behaviorObvious information
Edge casesImplementation 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
@Injectable()
,
@Processor('queue-name')
), JSDoc will interpret the
@
as a tag marker. This causes lint errors because JSDoc sees
@Processor('qpr-v2')
as a single unknown tag name (including the parentheses and arguments).
The problem: Adding decorator names to
definedTags
doesn't help because JSDoc parses the entire string
@Processor('qpr-v2')
as the tag name, not just
@Processor
.
当你要在文档中记录包含TypeScript/NestJS装饰器的代码时(比如
@Injectable()
@Processor('queue-name')
),JSDoc会将
@
识别为标签标记,导致lint报错,因为JSDoc会把
@Processor('qpr-v2')
整体识别为一个未知的标签名(包含括号和参数)。
问题说明: 将装饰器名称添加到
definedTags
中无法解决该问题,因为JSDoc会把
@Processor('qpr-v2')
整个字符串解析为标签名,而不仅仅是
@Processor

Solution 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
@example
blocks, use fenced code blocks and escape
@
as
\@
:
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

转义快速参考

ContextApproachExample
Prose/descriptionWrap in backticks
`@Injectable()`
@example blockEscape with backslash
\@Processor('name')
Code commentsNo escaping needed
// Uses @Injectable
场景处理方式示例
正文/描述中用反引号包裹
`@Injectable()`
@example 块中用反斜杠转义
\@Processor('name')
代码注释中无需转义
// Uses @Injectable

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:
  1. File preamble exists:
    @file
    ,
    @description
    ,
    @module
  2. Function declarations have JSDoc: Required by ESLint
  3. Interfaces have JSDoc: Required by ESLint
  4. Type aliases have JSDoc: Required by ESLint
  5. Parameters have meaningful descriptions: Not just restating the name
  6. Returns have meaningful descriptions: Explain conditions and edge cases
  7. @remarks used for "why": Design decisions, constraints, non-obvious behavior
  8. No TypeScript types in JSDoc: Types come from the signature
提交代码前,请确认:
  1. 文件序言已添加:包含
    @file
    @description
    @module
  2. 函数声明已添加JSDoc:ESLint强制要求
  3. 接口已添加JSDoc:ESLint强制要求
  4. 类型别名已添加JSDoc:ESLint强制要求
  5. 参数描述有实际意义:不仅仅是复述参数名
  6. 返回值描述有实际意义:解释了返回条件和边界情况
  7. 已使用@remarks记录「为什么」:包括设计决策、约束、非显性行为
  8. JSDoc中没有重复声明TypeScript类型:类型从函数签名中获取