From d4c644971e4ea753dca797dcbc6354569f9ef567 Mon Sep 17 00:00:00 2001 From: Jason Davey Date: Mon, 9 Mar 2026 14:41:46 -0400 Subject: [PATCH] feat: enhance TypeScript coding style guidelines with detailed examples and best practices esp interfaces and types --- rules/typescript/coding-style.md | 154 ++++++++++++++++++++++++++++--- 1 file changed, 141 insertions(+), 13 deletions(-) diff --git a/rules/typescript/coding-style.md b/rules/typescript/coding-style.md index db62a9bcd..582c40338 100644 --- a/rules/typescript/coding-style.md +++ b/rules/typescript/coding-style.md @@ -9,19 +9,128 @@ paths: > This file extends [common/coding-style.md](../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 + +```typescript +// 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 + +```typescript +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 + +```typescript +// 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 + +```typescript +interface User { + id: string + email: string +} + +interface UserCardProps { + user: User + onSelect: (id: string) => void +} + +function UserCard({ user, onSelect }: UserCardProps) { + return +} +``` + +### 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 + +```javascript +/** + * @param {{ firstName: string, lastName: string }} user + * @returns {string} + */ +export function formatUser(user) { + return `${user.firstName} ${user.lastName}` +} +``` + ## Immutability Use spread operator for immutable updates: ```typescript +interface User { + id: string + name: string +} + // WRONG: Mutation -function updateUser(user, name) { - user.name = name // MUTATION! +function updateUser(user: User, name: string): User { + user.name = name // MUTATION! return user } // CORRECT: Immutability -function updateUser(user, name) { +function updateUser(user: Readonly, name: string): User { return { ...user, name @@ -31,31 +140,50 @@ function updateUser(user, name) { ## Error Handling -Use async/await with try-catch: +Use async/await with try-catch and narrow unknown errors safely: ```typescript -try { - const result = await riskyOperation() - return result -} catch (error) { - console.error('Operation failed:', error) - throw new Error('Detailed user-friendly message') +interface User { + id: string + email: string +} + +declare function riskyOperation(userId: string): Promise + +function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message + } + + return 'Unexpected error' +} + +async function loadUser(userId: string): Promise { + 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: +Use Zod for schema-based validation and infer types from the schema: ```typescript import { z } from 'zod' -const schema = z.object({ +const userSchema = z.object({ email: z.string().email(), age: z.number().int().min(0).max(150) }) -const validated = schema.parse(input) +type UserInput = z.infer + +const validated: UserInput = userSchema.parse(input) ``` ## Console.log