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.