Standards & Reference

Code Documentation

This document defines the code documentation standards using TSDoc syntax.

Table of Contents

  1. Overview
  2. TSDoc vs JSDoc
  3. Required Documentation
  4. TSDoc Syntax Reference
  5. Documentation Patterns
  6. React Component Documentation
  7. Hook Documentation
  8. Type Documentation
  9. Documentation Generation
  10. Coverage Enforcement
  11. IDE Integration
  12. Best Practices

Overview

Purpose

Code documentation serves multiple critical functions:

  • Developer Experience: Enables IntelliSense and hover documentation in IDEs
  • Onboarding: Helps new team members understand the codebase
  • API Reference: Generates browsable API documentation
  • Maintenance: Preserves intent and context for future changes
  • Quality Gate: Ensures thoughtful API design

Scope

TSDoc documentation is required for:

  • All exported functions, classes, and types
  • React components and their props
  • Custom hooks and their parameters/return values
  • Complex internal logic requiring explanation

TSDoc vs JSDoc

We use TSDoc syntax, which extends JSDoc with TypeScript-specific features:

FeatureJSDocTSDoc
Basic tags (@param, @returns)YesYes
Type annotations in commentsRequiredOptional (use TypeScript types)
@remarks tagNoYes
@packageDocumentationNoYes
{@link} inline referencesLimitedFull support
@public/@internal/@betaNoYes

Key Difference

// JSDoc - types in comments (don't do this)
/**
 * @param {string} name - User name
 * @returns {boolean}
 */
function validate(name) { ... }

// TSDoc - types in TypeScript (do this)
/**
 * Validates user name format.
 * @param name - User name to validate
 * @returns True if name is valid
 */
function validate(name: string): boolean { ... }

Required Documentation

Coverage Requirements

ElementCoverageEnforcement
Exported Functions100%Block PR
Exported Classes100%Block PR
Exported Types/Interfaces100%Block PR
React Components100%Block PR
Custom Hooks100%Block PR
Component Props100%Block PR
Internal Functions (complex)80%Warning
Internal Functions (simple)OptionalNone

What Requires Documentation

// REQUIRED - Exported function
/**
 * Calculates the total cost including tax.
 * @param items - Line items to calculate
 * @param taxRate - Tax rate as decimal (0.08 = 8%)
 * @returns Total cost with tax applied
 */
export function calculateTotal(items: LineItem[], taxRate: number): number { ... }

// REQUIRED - Exported type
/**
 * Represents a line item in an invoice.
 */
export interface LineItem {
  /** Unique identifier for the line item */
  id: string;
  /** Product or service description */
  description: string;
  /** Unit price in cents */
  unitPrice: number;
  /** Quantity ordered */
  quantity: number;
}

// OPTIONAL - Simple internal helper
function addNumbers(a: number, b: number): number {
  return a + b;
}

// REQUIRED - Complex internal logic
/**
 * Applies progressive discount tiers based on total quantity.
 *
 * @remarks
 * Discount tiers:
 * - 0-9 items: 0%
 * - 10-49 items: 5%
 * - 50-99 items: 10%
 * - 100+ items: 15%
 *
 * @param quantity - Total quantity across all items
 * @param subtotal - Subtotal before discount
 * @returns Discount amount in cents
 */
function calculateTierDiscount(quantity: number, subtotal: number): number { ... }

TSDoc Syntax Reference

Standard Tags

TagPurposeExample
@paramDocument function parameter@param name - User's display name
@returnsDocument return value@returns User object or null if not found
@throwsDocument thrown exceptions@throws {@link ValidationError} When input invalid
@exampleProvide usage exampleSee below
@remarksAdditional contextExtended explanation
@seeCross-reference@see {@link RelatedFunction}
@deprecatedMark as deprecated@deprecated Use newFunction instead

Visibility Tags

TagPurpose
@publicPart of public API
@internalInternal implementation detail
@betaUnstable API, may change
@alphaExperimental, not for production

Inline Tags

TagPurposeExample
{@link}Link to symbol{@link UserService}
{@inheritDoc}Inherit documentation{@inheritDoc BaseClass.method}

Complete Example

/**
 * Fetches user data from the API with caching support.
 *
 * @remarks
 * This function implements a stale-while-revalidate caching strategy.
 * Cached results are returned immediately while a background refresh occurs.
 *
 * The cache TTL is configurable via the options parameter but defaults
 * to 5 minutes for optimal performance.
 *
 * @example
 * Basic usage:
 * ```typescript
 * const user = await fetchUser('user-123');
 * console.log(user.name);
 * ```
 *
 * @example
 * With caching options:
 * ```typescript
 * const user = await fetchUser('user-123', {
 *   cacheTTL: 60000, // 1 minute
 *   forceRefresh: true
 * });
 * ```
 *
 * @param userId - The unique identifier of the user to fetch
 * @param options - Optional configuration for the request
 * @param options.cacheTTL - Cache time-to-live in milliseconds (default: 300000)
 * @param options.forceRefresh - Skip cache and fetch fresh data (default: false)
 * @returns Promise resolving to the user object
 * @throws {@link NotFoundError} When user does not exist
 * @throws {@link NetworkError} When API is unreachable
 *
 * @see {@link UserService} for batch user operations
 * @see {@link cacheManager} for cache configuration
 *
 * @public
 */
export async function fetchUser(
  userId: string,
  options?: FetchUserOptions
): Promise<User> {
  // Implementation
}

Documentation Patterns

Function Documentation

/**
 * Brief description of what the function does.
 *
 * @param paramName - Description of parameter
 * @returns Description of return value
 */
export function functionName(paramName: ParamType): ReturnType { ... }

Function with Options Object

/**
 * Creates a new event with the specified configuration.
 *
 * @param config - Event configuration options
 * @param config.name - Display name for the event
 * @param config.startDate - Event start date
 * @param config.endDate - Event end date (optional, defaults to startDate)
 * @param config.portfolio - Portfolio assignment
 * @returns The created event object
 */
export function createEvent(config: CreateEventConfig): Event { ... }

Async Function

/**
 * Submits an approval request for the specified deliverable.
 *
 * @remarks
 * Sends email notification to all assigned approvers.
 * Creates audit trail entry for compliance tracking.
 *
 * @param deliverableId - ID of the deliverable requiring approval
 * @param approvers - List of user IDs to request approval from
 * @returns Promise resolving to the created approval request
 * @throws {@link ValidationError} When deliverable is already approved
 * @throws {@link PermissionError} When user lacks submission rights
 */
export async function submitApproval(
  deliverableId: string,
  approvers: string[]
): Promise<ApprovalRequest> { ... }

Class Documentation

/**
 * Manages event data persistence and retrieval.
 *
 * @remarks
 * Uses Parse Server as the backend. Implements caching for
 * frequently accessed events using a LRU strategy.
 *
 * @example
 * ```typescript
 * const service = new EventService();
 * const events = await service.getByPortfolio('domestic');
 * ```
 */
export class EventService {
  /**
   * Creates an instance of EventService.
   * @param config - Optional service configuration
   */
  constructor(config?: EventServiceConfig) { ... }

  /**
   * Retrieves all events for a portfolio.
   * @param portfolio - Portfolio identifier
   * @returns Promise resolving to array of events
   */
  async getByPortfolio(portfolio: string): Promise<Event[]> { ... }
}

React Component Documentation

Component with Props Interface

/**
 * Displays event details in a card format.
 *
 * @remarks
 * Supports multiple display modes and integrates with the
 * event context for state management.
 *
 * @example
 * ```tsx
 * <EventCard
 *   event={event}
 *   variant="compact"
 *   onSelect={handleSelect}
 * />
 * ```
 */
export interface EventCardProps {
  /** The event data to display */
  event: Event;
  /** Display variant */
  variant?: 'default' | 'compact' | 'detailed';
  /** Callback when card is selected */
  onSelect?: (event: Event) => void;
  /** Additional CSS class names */
  className?: string;
}

export function EventCard({
  event,
  variant = 'default',
  onSelect,
  className
}: EventCardProps): JSX.Element {
  // Implementation
}

Component with Children

/**
 * Provides event context to child components.
 *
 * @example
 * ```tsx
 * <EventProvider eventId="evt-123">
 *   <EventDetails />
 *   <EventTimeline />
 * </EventProvider>
 * ```
 */
export interface EventProviderProps {
  /** Event ID to load */
  eventId: string;
  /** Child components that consume the event context */
  children: React.ReactNode;
}

export function EventProvider({ eventId, children }: EventProviderProps): JSX.Element { ... }

Compound Component

/**
 * Tabbed interface for organizing content.
 *
 * @example
 * ```tsx
 * <Tabs defaultValue="details">
 *   <Tabs.List>
 *     <Tabs.Trigger value="details">Details</Tabs.Trigger>
 *     <Tabs.Trigger value="timeline">Timeline</Tabs.Trigger>
 *   </Tabs.List>
 *   <Tabs.Content value="details">
 *     <EventDetails />
 *   </Tabs.Content>
 *   <Tabs.Content value="timeline">
 *     <EventTimeline />
 *   </Tabs.Content>
 * </Tabs>
 * ```
 */
export interface TabsProps {
  /** Default active tab value */
  defaultValue?: string;
  /** Controlled active tab value */
  value?: string;
  /** Callback when active tab changes */
  onValueChange?: (value: string) => void;
  /** Tab content */
  children: React.ReactNode;
}

Hook Documentation

Basic Hook

/**
 * Manages event list state with pagination and filtering.
 *
 * @remarks
 * Uses React Query for server state management. Automatically
 * refetches when filters change. Implements optimistic updates
 * for improved UX.
 *
 * @example
 * ```typescript
 * const { events, isLoading, setFilter, refetch } = useEventList({
 *   initialFilter: { status: 'active' }
 * });
 * ```
 *
 * @param options - Hook configuration
 * @returns Event list state and controls
 */
export function useEventList(options?: UseEventListOptions): UseEventListResult {
  // Implementation
}

/**
 * Configuration options for useEventList hook.
 */
export interface UseEventListOptions {
  /** Initial filter criteria */
  initialFilter?: EventFilter;
  /** Page size for pagination */
  pageSize?: number;
  /** Enable real-time updates */
  enableRealtime?: boolean;
}

/**
 * Return value from useEventList hook.
 */
export interface UseEventListResult {
  /** List of events matching current filter */
  events: Event[];
  /** Loading state */
  isLoading: boolean;
  /** Error if fetch failed */
  error: Error | null;
  /** Current filter criteria */
  filter: EventFilter;
  /** Update filter criteria */
  setFilter: (filter: EventFilter) => void;
  /** Force refetch */
  refetch: () => Promise<void>;
  /** Pagination controls */
  pagination: PaginationState;
}

Hook with Complex Return

/**
 * Manages form state for event editing with validation.
 *
 * @remarks
 * Integrates with React Hook Form and Zod validation.
 * Handles dirty state tracking and unsaved changes warning.
 *
 * @example
 * ```typescript
 * const {
 *   register,
 *   handleSubmit,
 *   errors,
 *   isDirty,
 *   isSubmitting
 * } = useEventForm(eventId);
 *
 * return (
 *   <form onSubmit={handleSubmit(onSubmit)}>
 *     <input {...register('name')} />
 *     {errors.name && <span>{errors.name.message}</span>}
 *   </form>
 * );
 * ```
 *
 * @param eventId - ID of event to edit, or undefined for new event
 * @returns Form state and controls
 */
export function useEventForm(eventId?: string): UseEventFormResult { ... }

Type Documentation

Interface

/**
 * Represents an event in the dashboard.
 *
 * @remarks
 * Events are the primary entity in the system. They represent
 * tradeshows, conferences, and other marketing activities.
 */
export interface Event {
  /** Unique identifier */
  id: string;
  /** Display name */
  name: string;
  /** Event start date (ISO 8601) */
  startDate: string;
  /** Event end date (ISO 8601) */
  endDate: string;
  /**
   * Current status
   * @see {@link EventStatus} for possible values
   */
  status: EventStatus;
  /** Portfolio assignment */
  portfolio: Portfolio;
  /** Tier level (1-4) */
  tier: 1 | 2 | 3 | 4;
  /** Budget in USD cents */
  budget?: number;
  /** Creation timestamp */
  createdAt: string;
  /** Last update timestamp */
  updatedAt: string;
}

Enum

/**
 * Possible event statuses.
 *
 * @remarks
 * Status transitions follow a linear progression:
 * Draft -> Planning -> Active -> Complete
 *
 * Cancelled can be set from any status.
 */
export enum EventStatus {
  /** Initial state, not visible to most users */
  Draft = 'draft',
  /** Active planning phase */
  Planning = 'planning',
  /** Currently running */
  Active = 'active',
  /** Successfully finished */
  Complete = 'complete',
  /** Terminated early */
  Cancelled = 'cancelled'
}

Type Alias

/**
 * Portfolio categories for event classification.
 *
 * @remarks
 * - Domestic portfolios serve US-based events
 * - International portfolios serve non-US events
 */
export type Portfolio =
  | 'domestic-commercial'
  | 'domestic-defense'
  | 'domestic-services'
  | 'international-commercial'
  | 'international-defense'
  | 'international-services';

Union Type

/**
 * Possible results from an API operation.
 *
 * @remarks
 * Uses discriminated union pattern for type-safe error handling.
 *
 * @example
 * ```typescript
 * const result = await createEvent(data);
 * if (result.success) {
 *   console.log('Created:', result.data.id);
 * } else {
 *   console.error('Failed:', result.error.message);
 * }
 * ```
 */
export type ApiResult<T> =
  | { success: true; data: T }
  | { success: false; error: ApiError };

Documentation Generation

TypeDoc Configuration

Create typedoc.json in the project root:

{
  "$schema": "https://typedoc.org/schema.json",
  "entryPoints": ["src/index.ts"],
  "out": "docs/api",
  "plugin": ["typedoc-plugin-markdown"],
  "excludePrivate": true,
  "excludeInternal": true,
  "readme": "none",
  "categoryOrder": [
    "Components",
    "Hooks",
    "Services",
    "Types",
    "Utilities"
  ]
}

NPM Scripts

Add to package.json:

{
  "scripts": {
    "docs:generate": "typedoc",
    "docs:api": "typedoc --out docs/api",
    "docs:coverage": "typedoc --validation.notExported --validation.notDocumented",
    "docs:lint": "tsc --noEmit && eslint src --ext .ts,.tsx --rule '@typescript-eslint/no-floating-promises: error'",
    "docs:watch": "typedoc --watch"
  }
}

Generation Commands

# Generate API documentation
npm run docs:generate

# Generate with coverage validation
npm run docs:coverage

# Watch mode during development
npm run docs:watch

Output Structure

docs/
├── api/
│   ├── index.md
│   ├── modules.md
│   ├── classes/
│   │   └── EventService.md
│   ├── interfaces/
│   │   ├── Event.md
│   │   └── EventCardProps.md
│   ├── functions/
│   │   └── calculateTotal.md
│   └── types/
│       └── Portfolio.md

Coverage Enforcement

ESLint Rules

Add to .eslintrc.js:

module.exports = {
  plugins: ['jsdoc'],
  rules: {
    'jsdoc/require-jsdoc': ['warn', {
      publicOnly: true,
      require: {
        FunctionDeclaration: true,
        MethodDefinition: true,
        ClassDeclaration: true,
        ArrowFunctionExpression: false,
        FunctionExpression: false
      }
    }],
    'jsdoc/require-param': 'warn',
    'jsdoc/require-param-description': 'warn',
    'jsdoc/require-returns': 'warn',
    'jsdoc/require-returns-description': 'warn',
    'jsdoc/check-param-names': 'error',
    'jsdoc/check-tag-names': 'error',
    'jsdoc/valid-types': 'off' // TypeScript handles types
  }
};

CI Pipeline

# .github/workflows/docs.yml
name: Documentation

on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Lint documentation
        run: npm run docs:lint

      - name: Check documentation coverage
        run: npm run docs:coverage

      - name: Generate documentation
        run: npm run docs:generate

      - name: Upload documentation
        uses: actions/upload-artifact@v4
        with:
          name: api-docs
          path: docs/api/

Coverage Thresholds

LevelThresholdAction
Public API100%Block merge
Internal API80%Warning
Overall90%CI failure

IDE Integration

VS Code Extensions

Install these extensions for optimal TSDoc support:

ExtensionPurpose
Document ThisGenerate TSDoc templates
TSDoc CommentSyntax highlighting
Better CommentsCategorized comments
Error LensInline lint errors

Settings

Add to .vscode/settings.json:

{
  "docthis.includeAuthorTag": false,
  "docthis.includeDescriptionTag": true,
  "docthis.enableHungarianNotationRecognition": false,
  "editor.quickSuggestions": {
    "comments": true
  }
}

Snippets

Add to .vscode/typescript.code-snippets:

{
  "TSDoc Function": {
    "prefix": "tsdf",
    "body": [
      "/**",
      " * ${1:Description}",
      " *",
      " * @param ${2:param} - ${3:Parameter description}",
      " * @returns ${4:Return description}",
      " */"
    ]
  },
  "TSDoc Component": {
    "prefix": "tsdc",
    "body": [
      "/**",
      " * ${1:Component description}",
      " *",
      " * @example",
      " * ```tsx",
      " * <${2:Component} ${3:prop}={${4:value}} />",
      " * ```",
      " */"
    ]
  },
  "TSDoc Hook": {
    "prefix": "tsdh",
    "body": [
      "/**",
      " * ${1:Hook description}",
      " *",
      " * @example",
      " * ```typescript",
      " * const { ${2:result} } = use${3:Hook}(${4:options});",
      " * ```",
      " *",
      " * @param ${5:options} - ${6:Options description}",
      " * @returns ${7:Return description}",
      " */"
    ]
  }
}

Best Practices

Do

  • Write for humans first - Documentation should be readable and helpful
  • Be specific - "Validates email format using RFC 5322" is better than "Validates email"
  • Include examples - Show how to use the API with realistic code
  • Document edge cases - What happens with null, empty arrays, etc.?
  • Keep it current - Update docs when behavior changes
  • Use cross-references - Link related functions and types

Don't

  • Don't repeat the type - TypeScript already shows the type
  • Don't state the obvious - "Sets the name" for setName() adds no value
  • Don't use implementation details - "Uses regex internally" is irrelevant
  • Don't forget @throws - Document exceptions that callers must handle
  • Don't mix styles - Use TSDoc consistently, not JSDoc

Examples of Good vs Bad

// BAD - States the obvious
/**
 * Gets user.
 * @param id - ID
 * @returns User
 */
function getUser(id: string): User { ... }

// GOOD - Adds value
/**
 * Retrieves a user by their unique identifier.
 *
 * @remarks
 * Results are cached for 5 minutes. Use forceRefresh option
 * to bypass the cache when fresh data is required.
 *
 * @param id - The user's unique identifier (UUID format)
 * @returns The user object, or null if not found
 * @throws {@link NetworkError} When API is unreachable
 */
function getUser(id: string): User | null { ... }

Documentation Checklist

Before submitting code for review, verify:

  • [ ] All exported functions have TSDoc comments
  • [ ] All exported types/interfaces have TSDoc comments
  • [ ] All React components have documented props
  • [ ] All custom hooks have documented parameters and return values
  • [ ] Complex logic has @remarks explaining the approach
  • [ ] Public APIs have @example blocks with realistic usage
  • [ ] Exceptions are documented with @throws
  • [ ] Deprecated code uses @deprecated with migration path
  • [ ] @param descriptions are meaningful, not just type names
  • [ ] @returns descriptions explain what is returned, not just the type
  • [ ] Cross-references use {@link} where helpful
  • [ ] No TSDoc linting errors


Last Updated: 2026-01-15

Previous
Compliance