Files
everything-claude-code/rules/typescript/coding-style.md

4.0 KiB

paths
paths
**/*.ts
**/*.tsx
**/*.js
**/*.jsx

TypeScript/JavaScript Coding Style

This file extends common/coding-style.md with TypeScript/JavaScript specific content.

Types and Interfaces

Use types to make public APIs, shared models, and component props explicit, readable, and reusable.

Public APIs

  • Add parameter and return types to exported functions, shared utilities, and public class methods
  • Let TypeScript infer obvious local variable types
  • Extract repeated inline object shapes into named types or interfaces
// WRONG: Exported function without explicit types
export function formatUser(user) {
  return `${user.firstName} ${user.lastName}`
}

// CORRECT: Explicit types on public APIs
interface User {
  firstName: string
  lastName: string
}

export function formatUser(user: User): string {
  return `${user.firstName} ${user.lastName}`
}

Interfaces vs. Type Aliases

  • Use interface for object shapes that may be extended or implemented
  • Use type for unions, intersections, tuples, mapped types, and utility types
  • Prefer string literal unions over enum unless an enum is required for interoperability
interface User {
  id: string
  email: string
}

type UserRole = 'admin' | 'member'
type UserWithRole = User & {
  role: UserRole
}

Avoid any

  • Avoid any in application code
  • Use unknown for external or untrusted input, then narrow it safely
  • Use generics when a value's type depends on the caller
// WRONG: any removes type safety
function getErrorMessage(error: any) {
  return error.message
}

// CORRECT: unknown forces safe narrowing
function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message
  }

  return 'Unknown error'
}

React Props

  • Define component props with a named interface or type
  • Type callback props explicitly
  • Do not use React.FC unless there is a specific reason to do so
interface User {
  id: string
  email: string
}

interface UserCardProps {
  user: User
  onSelect: (id: string) => void
}

function UserCard({ user, onSelect }: UserCardProps) {
  return <button onClick={() => onSelect(user.id)}>{user.email}</button>
}

JavaScript Files

  • In .js and .jsx files, use JSDoc when types improve clarity and a TypeScript migration is not practical
  • Keep JSDoc aligned with runtime behavior
/**
 * @param {{ firstName: string, lastName: string }} user
 * @returns {string}
 */
export function formatUser(user) {
  return `${user.firstName} ${user.lastName}`
}

Immutability

Use spread operator for immutable updates:

interface User {
  id: string
  name: string
}

// WRONG: Mutation
function updateUser(user: User, name: string): User {
  user.name = name // MUTATION!
  return user
}

// CORRECT: Immutability
function updateUser(user: Readonly<User>, name: string): User {
  return {
    ...user,
    name
  }
}

Error Handling

Use async/await with try-catch and narrow unknown errors safely:

interface User {
  id: string
  email: string
}

declare function riskyOperation(userId: string): Promise<User>

function getErrorMessage(error: unknown): string {
  if (error instanceof Error) {
    return error.message
  }

  return 'Unexpected error'
}

async function loadUser(userId: string): Promise<User> {
  try {
    const result = await riskyOperation(userId)
    return result
  } catch (error: unknown) {
    console.error('Operation failed:', error)
    throw new Error(getErrorMessage(error))
  }
}

Input Validation

Use Zod for schema-based validation and infer types from the schema:

import { z } from 'zod'

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

type UserInput = z.infer<typeof userSchema>

const validated: UserInput = userSchema.parse(input)

Console.log

  • No console.log statements in production code
  • Use proper logging libraries instead
  • See hooks for automatic detection