---
name: javascript-patterns
skill_category: code
description: >-
  JavaScript and TypeScript patterns, anti-patterns, and quality rules — with
  deterministic regex-based anti-pattern detection for var usage, loose equality
  (==), leftover console.log, async without await, and common JS pitfalls.
  Covers Node.js, npm, ESLint, promise patterns, and async code. Use proactively
  when reviewing JS or TS source files for anti-patterns, running a pre-review
  scan before ESLint, catching known JS pitfalls, or auditing Node.js or browser
  codebases. Run the checker for deterministic scanning.
version: 2.0.0
source: converted from .claude/skills/code/javascript-patterns.md (2026-05-20); L3 lint-lite checker added
when_to_use:
  - Reviewing JavaScript or TypeScript source files for anti-patterns
  - Quick pre-review scan before a full ESLint pass
  - Catching well-known JS pitfalls (var, ==, leftover console.log)
  - Node.js or browser codebase audits
allowed-tools:
  - Read
  - Grep
  - Glob
  - Bash
tags: [javascript, typescript, nodejs, patterns, quality, async]
related_skills: [react-patterns, testing-patterns]
trigger_files: ["*.js", "*.ts", "*.mjs", "*.cjs", "package.json", "tsconfig.json"]
trigger_keywords: [javascript, typescript, nodejs, async, promise, eslint, npm]
---

# JavaScript Patterns

Modern JavaScript/TypeScript patterns, anti-patterns, and quality rules.

## Core Principles

| Principle | Description |
|-----------|-------------|
| **Immutability** | Prefer const, avoid mutation |
| **Pure Functions** | Same input = same output, no side effects |
| **Async/Await** | Over raw promises and callbacks |
| **Type Safety** | Use TypeScript for non-trivial projects |

## Patterns vs Anti-Patterns

### Variable Declaration

```typescript
// GOOD: const by default
const config = { timeout: 5000 };
const items = ['a', 'b', 'c'];

// OK: let when reassignment needed
let count = 0;
for (const item of items) {
  count++;
}

// BAD: var (hoisting issues)
var data = fetchData();  // Never use var
```

### Async/Await

```typescript
// GOOD: async/await
async function fetchUsers(): Promise<User[]> {
  try {
    const response = await fetch('/api/users');
    return await response.json();
  } catch (error) {
    throw new ApiError('Failed to fetch users', { cause: error });
  }
}

// GOOD: Parallel with Promise.all
const [users, posts] = await Promise.all([
  fetchUsers(),
  fetchPosts()
]);

// BAD: Sequential when parallel possible
const users = await fetchUsers();
const posts = await fetchPosts();  // Waits unnecessarily
```

### Error Handling

```typescript
// GOOD: Custom error classes
class ValidationError extends Error {
  constructor(
    message: string,
    public field: string,
    public code: string
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

// GOOD: Error boundary with type narrowing
function isApiError(error: unknown): error is ApiError {
  return error instanceof ApiError;
}

try {
  await riskyOperation();
} catch (error) {
  if (isApiError(error)) {
    handleApiError(error);
  } else {
    throw error;  // Re-throw unknown errors
  }
}

// BAD: Catch and ignore
try {
  await riskyOperation();
} catch (e) {
  // Silent failure - never do this
}
```

### Nullish Handling

```typescript
// GOOD: Nullish coalescing
const value = input ?? defaultValue;  // Only null/undefined

// GOOD: Optional chaining
const name = user?.profile?.name;
const result = callback?.();

// BAD: OR for defaults (falsy issues)
const value = input || defaultValue;  // 0, '', false become default!

// BAD: Manual null checks
const name = user && user.profile && user.profile.name;
```

### Array Methods

```typescript
// GOOD: Functional array methods
const activeUsers = users
  .filter(u => u.active)
  .map(u => ({ id: u.id, name: u.name }));

// GOOD: find/findIndex for single items
const admin = users.find(u => u.role === 'admin');
const index = users.findIndex(u => u.id === targetId);

// GOOD: reduce for accumulation
const byId = users.reduce((acc, user) => {
  acc[user.id] = user;
  return acc;
}, {} as Record<string, User>);

// BAD: forEach with mutation
const results = [];
users.forEach(u => {
  if (u.active) results.push(u.name);
});
```

### Object Operations

```typescript
// GOOD: Spread for immutable updates
const updated = { ...user, name: 'New Name' };
const merged = { ...defaults, ...options };

// GOOD: Destructuring
const { id, name, email } = user;
const { data, error } = await fetchUser(id);

// GOOD: Computed property names
const key = 'dynamicKey';
const obj = { [key]: value };

// BAD: Object.assign mutation
Object.assign(user, { name: 'New Name' });  // Mutates!
```

### String Operations

```typescript
// GOOD: Template literals
const message = `Hello, ${name}! You have ${count} items.`;
const multiline = `
  First line
  Second line
`;

// GOOD: Tagged templates for escaping
const query = sql`SELECT * FROM users WHERE id = ${userId}`;

// BAD: Concatenation
const message = 'Hello, ' + name + '!';
```

## Anti-Patterns to Avoid

### Type Coercion Issues

```typescript
// BAD: Implicit coercion
if (value == null) { }  // Catches both null and undefined
const str = '' + num;   // Use String(num)

// GOOD: Explicit comparison
if (value === null || value === undefined) { }
if (value == null) { }  // OK - intentional loose equality for null/undefined

// GOOD: Explicit conversion
const str = String(num);
const num = Number(str);
const bool = Boolean(value);
```

### Callback Hell

```typescript
// BAD: Nested callbacks
getData((data) => {
  process(data, (result) => {
    save(result, (saved) => {
      notify(saved, () => {
        console.log('Done');
      });
    });
  });
});

// GOOD: async/await
const data = await getData();
const result = await process(data);
const saved = await save(result);
await notify(saved);
```

### Floating Promises

```typescript
// BAD: Unhandled promise
fetchData();  // Fire and forget - errors lost!

// GOOD: Handle or await
await fetchData();
// OR
fetchData().catch(handleError);
// OR
void fetchData();  // Explicit discard (use sparingly)
```

### `this` Binding Issues

```typescript
// BAD: Lost context
class Handler {
  name = 'Handler';
  handleClick() {
    console.log(this.name);  // undefined when used as callback!
  }
}

// GOOD: Arrow function or bind
class Handler {
  name = 'Handler';
  handleClick = () => {
    console.log(this.name);  // Works!
  };
}
```

## TypeScript Best Practices

### Type Definitions

```typescript
// GOOD: Interface for objects
interface User {
  id: string;
  name: string;
  email: string;
}

// GOOD: Type alias for unions/primitives
type Status = 'pending' | 'active' | 'archived';
type ID = string | number;

// GOOD: Generics for reusable types
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };
```

### Type Guards

```typescript
// Discriminated unions
interface SuccessResponse { status: 'success'; data: unknown }
interface ErrorResponse { status: 'error'; message: string }
type Response = SuccessResponse | ErrorResponse;

function handleResponse(res: Response) {
  if (res.status === 'success') {
    // TypeScript knows res.data exists
    return res.data;
  } else {
    // TypeScript knows res.message exists
    throw new Error(res.message);
  }
}
```

### Avoid `any`

```typescript
// BAD: any disables type checking
function process(data: any): any { }

// GOOD: unknown + type guard
function process(data: unknown): Result {
  if (isValidData(data)) {
    return transform(data);
  }
  throw new ValidationError('Invalid data');
}
```

## Quality Checklist

| Check | Rule |
|-------|------|
| No `var` | Use `const` by default, `let` when needed |
| No `any` | Use `unknown` with type guards |
| Async/await | Over raw promises |
| Nullish ops | `??` and `?.` over `||` and `&&` |
| Immutable | Spread over mutation |
| Type safety | All exports typed |
| Error handling | No silent catches |
| No floating promises | Always handle or await |

## Invocation — JavaScript Anti-Pattern Checker (L3 Script)

Run the checker on any JavaScript or TypeScript source file. Consume its **output only** — the script source never enters context.

**Scope note:** This is a regex-based lint-lite tool, not a full AST parser. It reliably catches the named closed-set patterns (var declarations, loose equality, leftover debug calls, callback nesting depth). It does NOT replace ESLint — use ESLint for comprehensive coverage. Treat its findings as confirmed anti-patterns; treat its silence as "none of these specific patterns found."

**Run via Bash (file argument):**
```bash
python .claude/skills/code/javascript-patterns/scripts/js_patterns.py path/to/file.js
```

**Run via Bash (stdin — paste code or pipe):**
```bash
cat path/to/file.ts | python .claude/skills/code/javascript-patterns/scripts/js_patterns.py -
```

**The script outputs:**
1. A JSON object with `findings` (list of anti-patterns with rule, severity, line, message) and a `summary` of counts by severity.
2. A human-readable markdown table sorted by severity descending.

**Detected rules:**
- `VAR_DECL` MEDIUM — `var` declaration (use const/let)
- `LOOSE_EQUALITY` MEDIUM — `==` or `!=` operator (use `===`/`!==`)
- `CONSOLE_LOG` LOW — leftover `console.log(` call
- `CALLBACK_NESTING` MEDIUM — callback nesting depth >= 3 (likely callback hell)

**Error handling:** Script exits 1 on unreadable file. Exits 0 even if findings are present.

**What the agent does with the output:**
1. Raise MEDIUM findings in code review comments.
2. LOW findings (console.log) flag debug code left in — ask author to remove before merge.
3. CALLBACK_NESTING findings suggest refactoring to async/await.
