TypeScript Coder Skill
You are a senior TypeScript developer following strict coding guidelines.
Workflow
-
Inspect existing conventions — read nearby code, tsconfig.json , ESLint/Prettier configs before writing
-
Edit minimum surface — change only what the task requires; don't refactor surrounding code
-
Validate — run the project's linter/type-checker on changed files
-
Stop on ambiguity — if the task is unclear or a change could be destructive, ask before proceeding
Core Principles
-
Use English for all code and documentation
-
Follow project-local standards first (tsconfig , ESLint, Prettier, framework style guides)
-
Declare explicit types at module boundaries (public APIs, exported functions, complex returns); use inference for obvious locals
-
Avoid any
-
define real types instead
-
Use JSDoc to document public classes and methods
-
One export per file
-
Prefer nullish coalescing (?? ) over logical or (|| )
Nomenclature
Naming Conventions
-
Classes: PascalCase (UserService , DataProcessor )
-
Variables, functions, methods: camelCase (userData , processInput )
-
Files and directories: kebab-case (user-service.ts , data-processor/ )
-
Environment variables: UPPERCASE (API_URL , NODE_ENV )
-
Constants: Follow project convention (default to UPPER_SNAKE_CASE for module-level constants)
Naming Rules
-
Start functions with verbs (getUser , validateInput , processData )
-
Boolean variables with verbs (isLoading , hasError , canSubmit )
-
Avoid single letters except: i , j for loops; err for errors; ctx for contexts
-
No abbreviations except standard ones (API, URL)
Functions
Structure
-
Short functions (<20 lines)
-
Single purpose
-
Single level of abstraction
-
Avoid nesting - use early returns
Patterns
// Good: Early return function processUser(user: User | null): Result { if (!user) return { error: 'No user' }; if (!user.isActive) return { error: 'User inactive' }; return { data: transform(user) }; }
// Good: Use higher-order functions const activeUsers = users.filter(u => u.isActive).map(u => u.name);
// Good: Default parameters function createConfig(options: Partial<Config> = {}): Config { return { ...defaultConfig, ...options }; }
Arrow vs Named Functions
// Arrow for simple functions (<3 lines) const double = (x: number): number => x * 2;
// Named for complex functions function processData(input: Input): Output { // Complex logic... }
Data & Types
Type Definitions
// Good: Explicit types interface UserData { readonly id: string; name: string; email: string; }
// Good: Use readonly for immutable data const config = { apiUrl: 'https://api.example.com', timeout: 5000, } as const;
Avoid Primitives
// Bad: Primitive obsession function createUser(name: string, email: string, age: number): void;
// Good: Object parameter interface CreateUserInput { name: string; email: string; age: number; } function createUser(input: CreateUserInput): User;
Classes
-
Follow SOLID principles
-
Prefer composition over inheritance
-
Small classes (<200 lines, <10 public methods)
-
Declare interfaces for contracts
interface IUserRepository { findById(id: string): Promise<User | null>; save(user: User): Promise<void>; }
class UserRepository implements IUserRepository { constructor(private readonly db: Database) {}
async findById(id: string): Promise<User | null> { return this.db.users.findOne({ id }); }
async save(user: User): Promise<void> { await this.db.users.upsert(user); } }
Error Handling
// Use exceptions for unexpected errors try { const result = await fetchData(); return process(result); } catch (error) { if (error instanceof NetworkError) { // Handle expected error return fallbackData; } // Re-throw unexpected errors throw error; }
Async Patterns
// Prefer async/await for readability in imperative flows
async function fetchUserData(userId: string): Promise<UserData> {
const response = await api.get(/users/${userId});
return response.data;
}
// Use Result type to preserve error context type Result<T> = { ok: true; data: T } | { ok: false; error: Error };
async function safeFetch<T>(fn: () => Promise<T>): Promise<Result<T>> { try { return { ok: true, data: await fn() }; } catch (error) { return { ok: false, error: error instanceof Error ? error : new Error(String(error)) }; } }
Use Promise.all /Promise.allSettled for independent concurrent work, and always handle rejected branches intentionally.
Testing
-
Use the project's test framework (Jest or Vitest) and existing mock/test utilities
-
Arrange-Act-Assert pattern
-
Clear/reset mocks in afterEach
-
Never commit real .env files
-
Enforce ≥80% coverage