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