Skip to main content
These guidelines help maintain code quality and consistency across the AFFiNE codebase.

Code Style

Formatting

We use automated tools to enforce consistent formatting:
  • Prettier: JavaScript, TypeScript, CSS, JSON, Markdown
  • Rustfmt: Rust code
  • Taplo: TOML files
# Format all files
yarn lint:fix

# Format specific files
yarn prettier --write path/to/file.ts

Linting

We use ESLint with strict rules:
# Run linter
yarn lint

# Auto-fix issues
yarn lint:eslint:fix

TypeScript Guidelines

Type Safety

Always use explicit types, avoid any:
// Good
function greet(name: string): string {
  return `Hello, ${name}`;
}

interface User {
  id: string;
  name: string;
  email: string;
}

// Bad
function greet(name) {
  return `Hello, ${name}`;
}

const user: any = { id: '123' };

Type Inference

Use type inference where obvious:
// Good: Type is inferred
const name = 'John';
const age = 30;
const user = { name, age };

// Bad: Unnecessary annotation
const name: string = 'John';
const age: number = 30;

Strict Null Checks

Handle null/undefined explicitly:
// Good
function getUserName(user: User | null): string {
  return user?.name ?? 'Anonymous';
}

// Bad
function getUserName(user: User): string {
  return user.name; // May crash if user is null
}

Generics

Use generics for reusable type-safe code:
// Good
function getById<T>(items: T[], id: string): T | undefined {
  return items.find(item => item.id === id);
}

// Bad
function getById(items: any[], id: string): any {
  return items.find(item => item.id === id);
}

React Guidelines

Component Structure

import { type ReactNode } from 'react';

interface ButtonProps {
  children: ReactNode;
  onClick?: () => void;
  disabled?: boolean;
  variant?: 'primary' | 'secondary';
}

export function Button({
  children,
  onClick,
  disabled = false,
  variant = 'primary'
}: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={variant}
    >
      {children}
    </button>
  );
}

Hooks

Follow React hooks rules:
// Good
function useWorkspace(id: string) {
  const [workspace, setWorkspace] = useState<Workspace | null>(null);
  
  useEffect(() => {
    fetchWorkspace(id).then(setWorkspace);
  }, [id]);
  
  return workspace;
}

// Bad: Conditional hooks
function useWorkspace(id: string | null) {
  if (!id) return null; // Don't call hooks conditionally
  const [workspace, setWorkspace] = useState<Workspace | null>(null);
  return workspace;
}

State Management

Use Jotai for global state:
import { atom, useAtom } from 'jotai';

const userAtom = atom<User | null>(null);

export function useUser() {
  return useAtom(userAtom);
}

// Usage
function Profile() {
  const [user, setUser] = useUser();
  return <div>{user?.name}</div>;
}

Naming Conventions

Variables & Functions

// camelCase for variables and functions
const userName = 'John';
function getUserById(id: string) {}

// PascalCase for classes and components
class UserService {}
function UserProfile() {}

// SCREAMING_SNAKE_CASE for constants
const MAX_RETRY_COUNT = 3;
const API_BASE_URL = 'https://api.example.com';

Files & Folders

// kebab-case for files and folders
user-service.ts
workspace-list.tsx
api/auth/login.ts

// PascalCase for component files
UserProfile.tsx
WorkspaceList.tsx

Booleans

Use clear boolean naming:
// Good
const isLoading = true;
const hasPermission = false;
const canEdit = true;

// Bad
const loading = true;
const permission = false;
const edit = true;

Comments

When to Comment

// Good: Explain WHY, not WHAT
function calculateScore(attempts: number): number {
  // Penalize excessive retries to prevent brute force
  return Math.max(0, 100 - attempts * 10);
}

// Bad: Comment explains obvious code
function add(a: number, b: number): number {
  // Add a and b
  return a + b;
}

JSDoc for Public APIs

/**
 * Fetches a workspace by ID.
 * 
 * @param id - The workspace ID
 * @returns The workspace object, or null if not found
 * @throws {NetworkError} If the request fails
 * 
 * @example
 * const workspace = await fetchWorkspace('abc123');
 */
export async function fetchWorkspace(id: string): Promise<Workspace | null> {
  // Implementation
}

Error Handling

Throw Meaningful Errors

// Good
function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error('Division by zero is not allowed');
  }
  return a / b;
}

// Bad
function divide(a: number, b: number): number {
  return a / b; // May return Infinity or NaN
}

Handle Errors Gracefully

// Good
async function saveWorkspace(data: Workspace) {
  try {
    await api.save(data);
  } catch (error) {
    if (error instanceof NetworkError) {
      toast.error('Network error. Please try again.');
    } else {
      toast.error('Failed to save workspace.');
      logger.error('Save failed', error);
    }
  }
}

// Bad
async function saveWorkspace(data: Workspace) {
  await api.save(data); // Errors crash the app
}

Performance

Avoid Unnecessary Re-renders

import { memo, useCallback, useMemo } from 'react';

// Memoize expensive components
export const WorkspaceList = memo(function WorkspaceList({ workspaces }: Props) {
  return <div>{/* ... */}</div>;
});

// Memoize callbacks
function Parent() {
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return <Child onClick={handleClick} />;
}

// Memoize expensive computations
function Stats({ data }: Props) {
  const stats = useMemo(() => {
    return calculateStats(data); // Heavy computation
  }, [data]);
  
  return <div>{stats}</div>;
}

Lazy Loading

import { lazy, Suspense } from 'react';

const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Settings />
    </Suspense>
  );
}

Security

Sanitize User Input

import DOMPurify from 'dompurify';

// Good: Sanitize HTML
function RenderHTML({ html }: Props) {
  const clean = DOMPurify.sanitize(html);
  return <div dangerouslySetInnerHTML={{ __html: clean }} />;
}

// Bad: XSS vulnerability
function RenderHTML({ html }: Props) {
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
}

Validate Input

import { z } from 'zod';

const userSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().min(0).max(150)
});

function createUser(data: unknown) {
  const validated = userSchema.parse(data);
  // Safe to use validated data
}

Protect Sensitive Data

// Good: Don't log sensitive data
logger.info('User logged in', { userId: user.id });

// Bad: Logs password
logger.info('User logged in', { user });

Git Workflow

Commit Messages

Follow Conventional Commits:
feat: add workspace sharing
fix: resolve sync conflict
docs: update API documentation
style: format code with prettier
refactor: simplify auth logic
test: add workspace tests
chore: update dependencies

Branch Naming

feat/workspace-sharing
fix/sync-conflict
docs/api-documentation
refactor/auth-logic

Pull Requests

Good PR:
  • Clear title and description
  • Linked to issue
  • Small, focused changes
  • Tests included
  • Documentation updated
Example:
## Description
Adds workspace sharing functionality allowing users to invite collaborators.

## Changes
- Added share dialog component
- Implemented invite API endpoint
- Added permission checks

## Testing
- [x] Unit tests added
- [x] E2E tests added
- [x] Manually tested

Fixes #123

Code Review

As a Reviewer

  • Be respectful and constructive
  • Focus on code, not the person
  • Explain your suggestions
  • Approve when ready, don’t nitpick

As an Author

  • Respond to all comments
  • Don’t take feedback personally
  • Ask questions if unclear
  • Update and re-request review

Documentation

Code Documentation

/**
 * Service for managing workspaces.
 * 
 * Handles workspace creation, updates, deletion,
 * and permission management.
 */
export class WorkspaceService {
  /**
   * Creates a new workspace.
   * 
   * @param name - The workspace name
   * @param ownerId - The owner's user ID
   * @returns The created workspace
   */
  async createWorkspace(name: string, ownerId: string): Promise<Workspace> {
    // Implementation
  }
}

README Files

Every package should have a README:
# Package Name

Brief description of the package.

## Installation

How to install and use the package.

## Usage

Code examples showing common use cases.

## API

List of main exports and their purpose.

Development Setup

Set up your local environment

Testing Guide

Learn how to write tests

Architecture

Understand the codebase

TypeScript Handbook

TypeScript documentation

Build docs developers (and LLMs) love