Docs /contributing/code-style

Code Style Guide

Coding standards and best practices for Izri

๐Ÿ“‹ Overview

This guide defines the coding standards for Izri to ensure consistent, maintainable, and high-quality code across the project.

๐Ÿ”ง Tooling

Biome (Formatter & Linter)

We use Biome for code formatting and linting:

# Format code
pnpm format:all

# Check formatting
pnpm biome check .

# Fix issues
pnpm biome check --write .

TypeScript

# Type checking
pnpm type:check

# Per package
pnpm type:check:apps
pnpm type:check:packages

๐Ÿ“ TypeScript Style Guide

Type Annotations

// โœ… Use explicit return types for functions
function getProject(id: string): Promise<Project | null> {
  return db.query.projects.findFirst({ where: eq(projects.id, id) })
}

// โœ… Use type inference for obvious cases
const count = projects.length
const isValid = true

// โŒ Avoid unnecessary annotations
const count: number = projects.length  // Redundant

Interfaces vs Types

// โœ… Use interfaces for object shapes
interface Project {
  id: string
  name: string
  repository: string
}

// โœ… Use types for unions, intersections, utilities
type Status = 'pending' | 'running' | 'completed' | 'failed'
type ProjectWithTests = Project & { testRuns: TestRun[] }
type PartialProject = Partial<Project>

Naming Conventions

// PascalCase for types, interfaces, classes, components
interface ProjectSettings {}
class RepositoryAnalyzer {}
function ProjectCard() {}

// camelCase for variables, functions, methods
const projectId = 'proj_123'
function fetchProjects() {}

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

// Use descriptive names
const user = await getUser(id)  // โœ…
const u = await getU(i)         // โŒ

Async/Await

// โœ… Use async/await
async function createProject(data: ProjectData) {
  const project = await db.insert(projects).values(data)
  return project
}

// โŒ Avoid promise chains when possible
function createProject(data: ProjectData) {
  return db.insert(projects).values(data)
    .then(project => project)
}

// โœ… Handle errors properly
try {
  const project = await createProject(data)
} catch (error) {
  logger.error({ error }, 'Failed to create project')
  throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR' })
}

Optional Chaining & Nullish Coalescing

// โœ… Use optional chaining
const email = user?.profile?.email

// โœ… Use nullish coalescing
const name = user.name ?? 'Anonymous'

// โŒ Avoid long chains
const value = data?.items?.[0]?.properties?.value?.toString() // Too deep

โš›๏ธ React Patterns

Component Structure

// โœ… Functional components with TypeScript
interface ProjectCardProps {
  project: Project
  onDelete?: (id: string) => void
}

export function ProjectCard({ project, onDelete }: ProjectCardProps) {
  // Hooks at the top
  const navigate = useNavigate()
  const [isDeleting, setIsDeleting] = useState(false)
  
  // Event handlers
  const handleDelete = async () => {
    setIsDeleting(true)
    try {
      await onDelete?.(project.id)
      navigate('/dashboard/projects')
    } finally {
      setIsDeleting(false)
    }
  }
  
  // Render
  return (
    <Card>
      <CardHeader>
        <CardTitle>{project.name}</CardTitle>
      </CardHeader>
      <CardContent>
        <Button onClick={handleDelete} disabled={isDeleting}>
          Delete
        </Button>
      </CardContent>
    </Card>
  )
}

Hooks

// โœ… Custom hooks start with 'use'
function useProject(projectId: string) {
  const { data, isLoading } = trpc.projects.getProject.useQuery({ projectId })
  return { project: data?.project, isLoading }
}

// โœ… Extract complex logic into hooks
function useProjectForm() {
  const form = useForm<ProjectForm>({
    resolver: zodResolver(projectSchema)
  })
  
  const createProject = trpc.projects.createProject.useMutation()
  
  const onSubmit = async (values: ProjectForm) => {
    await createProject.mutateAsync(values)
  }
  
  return { form, onSubmit, isSubmitting: createProject.isLoading }
}

Component Composition

// โœ… Compose small, focused components
function ProjectDetails({ project }: Props) {
  return (
    <div>
      <ProjectHeader project={project} />
      <ProjectStats project={project} />
      <ProjectTestRuns projectId={project.id} />
    </div>
  )
}

// โŒ Avoid monolithic components
function ProjectDetails({ project }: Props) {
  return (
    <div>
      {/* 500 lines of JSX */}
    </div>
  )
}

๐ŸŽจ Styling Guidelines

Tailwind CSS

// โœ… Use Tailwind utility classes
<div className="flex items-center gap-4 p-4 rounded-lg bg-white shadow-md">
  <span className="text-lg font-semibold text-gray-900">Title</span>
</div>

// โœ… Use responsive modifiers
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {/* Content */}
</div>

// โœ… Use cn() for conditional classes
<div className={cn(
  "base-classes",
  isActive && "active-classes",
  isPending && "pending-classes"
)}>
  {/* Content */}
</div>

Component Variants (CVA)

import { cva, type VariantProps } from 'class-variance-authority'

const buttonVariants = cva(
  // Base classes
  "inline-flex items-center justify-center rounded-md font-medium transition-colors",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline: "border border-input bg-background hover:bg-accent"
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 px-3",
        lg: "h-11 px-8"
      }
    },
    defaultVariants: {
      variant: "default",
      size: "default"
    }
  }
)

๐Ÿ“ File Organization

Directory Structure

src/
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ ui/           # shadcn/ui components
โ”‚   โ”œโ”€โ”€ features/     # Feature-specific components
โ”‚   โ””โ”€โ”€ layouts/      # Layout components
โ”œโ”€โ”€ hooks/            # Custom hooks
โ”œโ”€โ”€ lib/              # Utilities and helpers
โ”œโ”€โ”€ routes/           # React Router routes
โ””โ”€โ”€ types/            # Shared TypeScript types

File Naming

// Components: PascalCase
ProjectCard.tsx
UserProfile.tsx

// Utilities: camelCase
formatDate.ts
apiClient.ts

// Types: PascalCase
Project.ts
User.ts

// Hooks: camelCase (use prefix)
useProject.ts
useAuth.ts

๐ŸŽฏ Best Practices

Error Handling

// โœ… Use try-catch for async operations
try {
  const result = await riskyOperation()
  return result
} catch (error) {
  logger.error({ error }, 'Operation failed')
  
  if (error instanceof SpecificError) {
    // Handle specific error
  }
  
  throw new TRPCError({
    code: 'INTERNAL_SERVER_ERROR',
    message: 'Operation failed',
    cause: error
  })
}

Logging

// โœ… Use structured logging
logger.info({ userId, projectId }, 'Project created')

// โœ… Include context in error logs
logger.error({ error, userId, data }, 'Failed to create project')

// โŒ Avoid string concatenation
logger.info('User ' + userId + ' created project ' + projectId)

Performance

// โœ… Use React.memo for expensive components
export const ExpensiveComponent = React.memo(({ data }: Props) => {
  // Complex rendering logic
})

// โœ… Use useMemo for expensive calculations
const sortedProjects = useMemo(
  () => projects.sort((a, b) => a.name.localeCompare(b.name)),
  [projects]
)

// โœ… Use useCallback for event handlers passed to children
const handleDelete = useCallback(
  (id: string) => {
    deleteProject.mutate({ id })
  },
  [deleteProject]
)

๐Ÿงช Testing

Test File Naming

// Next to source file
Button.tsx
Button.test.tsx

// Or in __tests__ directory
Button.tsx
__tests__/Button.test.tsx

Test Structure

describe('ProjectCard', () => {
  it('renders project name', () => {
    const project = { name: 'Test Project', ...otherFields }
    render(<ProjectCard project={project} />)
    expect(screen.getByText('Test Project')).toBeInTheDocument()
  })
  
  it('calls onDelete when delete button clicked', async () => {
    const onDelete = vi.fn()
    const project = { id: 'proj_123', ...otherFields }
    
    render(<ProjectCard project={project} onDelete={onDelete} />)
    
    await userEvent.click(screen.getByRole('button', { name: /delete/i }))
    
    expect(onDelete).toHaveBeenCalledWith('proj_123')
  })
})

๐Ÿ”— Related Documentation


Next: Commit Conventions โ†’

Reading this with an agent? /docs/contributing/code-style.md serves the raw markdown.

All docs