Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 141 additions & 13 deletions rules/typescript/coding-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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

```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<User>, name: string): User {
return {
...user,
name
Expand All @@ -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<User>

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

return 'Unexpected error'
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The getErrorMessage helper appears in both the "Avoid any" section and the "Error Handling" section with different fallback strings ('Unknown error' vs 'Unexpected error'). In a style guide, readers are likely to copy one of these as a reference — having two slightly different versions of the same function creates unnecessary ambiguity. Align the fallback message so both examples are consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At rules/typescript/coding-style.md, line 158:

<comment>The `getErrorMessage` helper appears in both the "Avoid `any`" section and the "Error Handling" section with different fallback strings (`'Unknown error'` vs `'Unexpected error'`). In a style guide, readers are likely to copy one of these as a reference — having two slightly different versions of the same function creates unnecessary ambiguity. Align the fallback message so both examples are consistent.</comment>

<file context>
@@ -31,31 +140,50 @@ function updateUser(user, name) {
+    return error.message
+  }
+
+  return 'Unexpected error'
+}
+
</file context>
Suggested change
return 'Unexpected error'
return 'Unknown error'
Fix with Cubic

}

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:
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<typeof userSchema>

const validated: UserInput = userSchema.parse(input)
```

## Console.log
Expand Down