JSDoc Best Practices
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.
Core Philosophy: Why Over What
The Problem with "What" Documentation
typescript
// Bad: Just restates the code
function getUserById(id: string): User { ... }
This documentation adds no value—the function name already tells us it gets a user by ID.
The Solution: Document "Why"
typescript
// Good: Explains context, constraints, and non-obvious behavior
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)
ESLint Enforcement
The project enforces JSDoc through
with these rules:
Required Documentation
| Rule | Setting | What It Enforces |
|---|
| error | JSDoc on function declarations, interfaces, type aliases, and PascalCase arrow functions |
jsdoc/require-param-description
| error | All tags must have descriptions |
jsdoc/require-returns-description
| error | All tags must have descriptions |
jsdoc/require-property-description
| error | All tags must have descriptions |
Allowed Tags
| Rule | Setting | Effect |
|---|
| | Allows for "why" documentation |
| off | TypeScript types in JSDoc are optional |
| off | Types come from TypeScript, not JSDoc |
jsdoc/require-returns-type
| off | Types come from TypeScript, not JSDoc |
What Requires Documentation
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]/])"
]
}
Documentation Patterns
File Preambles
Every file should have a preamble comment at the top:
| Tag | Purpose |
|---|
| The filename (for navigation and search) |
| What this file provides |
| The feature module this belongs to |
Service Documentation
typescript
@Injectable()
export class UserService { ... }
Method Documentation
typescript
async findByIds(ids: readonly string[]): Promise<Entity[]> { ... }
Interface Documentation
typescript
export interface IAuthService {
signIn(input: SignInInput): Promise<SignInResult>;
}
Type/Constant Documentation
typescript
const COMPLEXITY_CONFIG = {
maxComplexity: 100,
defaultComplexity: 1,
} as const;
The @remarks Tag
Use
to document the "why" and important context:
When to Use @remarks
| Use Case | Example |
|---|
| 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 Format
Use bullet points for multiple remarks:
Use inline for single remarks:
Parameter Descriptions
Bad: Restating the Name
Good: Adding Value
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
Good: Explaining Behavior
Anti-Patterns to Avoid
Don't Document the Obvious
typescript
// Wrong: Adds no value
constructor() {}
getName(): string { return this.name; }
Don't Duplicate TypeScript Types
typescript
// Wrong: Type is already in signature
async getUser(id: string): Promise<User> { ... }
// Correct: Description only, type from TypeScript
async getUser(id: string): Promise<User> { ... }
Don't Write Implementation Comments
typescript
// Wrong: Documents how, not why
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);
Escaping @ Symbols in 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).
The problem: Adding decorator names to
doesn't help because JSDoc parses the entire string
as the tag name, not just
.
Solution 1: Backticks in Prose
When mentioning decorators in description text, wrap them in backticks:
Solution 2: Escape in @example Blocks
In
blocks, use fenced code blocks and escape
as
:
Quick Reference for Escaping
| Context | Approach | Example |
|---|
| Prose/description | Wrap in backticks | |
| @example block | Escape with backslash | |
| Code comments | No escaping needed | |
Quick Reference
Required Structure for Services
typescript
@Injectable()
export class FeatureService {
methodName(paramName: Type): ReturnType { ... }
}
Required Structure for Interfaces
typescript
export interface IFeature {
method(param: Type): ReturnType;
}
Required Structure for Types
typescript
export type FeatureConfig = {
maxRetries: number;
timeoutMs: number;
};
Verification Checklist
Before committing code, verify:
- File preamble exists: , ,
- 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