| 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
const config = { timeout: 5000 };
const items = ['a', 'b', 'c'];
let count = 0;
for (const item of items) {
count++;
}
var data = fetchData();
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 });
}
}
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts()
]);
const users = await fetchUsers();
const posts = await fetchPosts();
Error Handling
class ValidationError extends Error {
constructor(
message: string,
public field: string,
public code: string
) {
super(message);
this.name = 'ValidationError';
}
}
function isApiError(error: unknown): error is ApiError {
return error instanceof ApiError;
}
try {
await riskyOperation();
} catch (error) {
if (isApiError(error)) {
handleApiError(error);
} else {
throw error;
}
}
try {
await riskyOperation();
} catch (e) {
}
Nullish Handling
const value = input ?? defaultValue;
const name = user?.profile?.name;
const result = callback?.();
const value = input || defaultValue;
const name = user && user.profile && user.profile.name;
Array Methods
const activeUsers = users
.filter(u => u.active)
.map(u => ({ id: u.id, name: u.name }));
const admin = users.find(u => u.role === 'admin');
const index = users.findIndex(u => u.id === targetId);
const byId = users.reduce((acc, user) => {
acc[user.id] = user;
return acc;
}, {} as Record<string, User>);
const results = [];
users.forEach(u => {
if (u.active) results.push(u.name);
});
Object Operations
const updated = { ...user, name: 'New Name' };
const merged = { ...defaults, ...options };
const { id, name, email } = user;
const { data, error } = await fetchUser(id);
const key = 'dynamicKey';
const obj = { [key]: value };
Object.assign(user, { name: 'New Name' });
String Operations
const message = `Hello, ${name}! You have ${count} items.`;
const multiline = `
First line
Second line
`;
const query = sql`SELECT * FROM users WHERE id = ${userId}`;
const message = 'Hello, ' + name + '!';
Anti-Patterns to Avoid
Type Coercion Issues
if (value == null) { }
const str = '' + num;
if (value === null || value === undefined) { }
if (value == null) { }
const str = String(num);
const num = Number(str);
const bool = Boolean(value);
Callback Hell
getData((data) => {
process(data, (result) => {
save(result, (saved) => {
notify(saved, () => {
console.log('Done');
});
});
});
});
const data = await getData();
const result = await process(data);
const saved = await save(result);
await notify(saved);
Floating Promises
fetchData();
await fetchData();
fetchData().catch(handleError);
void fetchData();
this Binding Issues
class Handler {
name = 'Handler';
handleClick() {
console.log(this.name);
}
}
class Handler {
name = 'Handler';
handleClick = () => {
console.log(this.name);
};
}
TypeScript Best Practices
Type Definitions
interface User {
id: string;
name: string;
email: string;
}
type Status = 'pending' | 'active' | 'archived';
type ID = string | number;
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
Type Guards
interface SuccessResponse { status: 'success'; data: unknown }
interface ErrorResponse { status: 'error'; message: string }
type Response = SuccessResponse | ErrorResponse;
function handleResponse(res: Response) {
if (res.status === 'success') {
return res.data;
} else {
throw new Error(res.message);
}
}
Avoid any
function process(data: any): any { }
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 ` |
| 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):
python .claude/skills/code/javascript-patterns/scripts/js_patterns.py path/to/file.js
Run via Bash (stdin — paste code or pipe):
cat path/to/file.ts | python .claude/skills/code/javascript-patterns/scripts/js_patterns.py -
The script outputs:
- A JSON object with
findings (list of anti-patterns with rule, severity, line, message) and a summary of counts by severity.
- 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:
- Raise MEDIUM findings in code review comments.
- LOW findings (console.log) flag debug code left in — ask author to remove before merge.
- CALLBACK_NESTING findings suggest refactoring to async/await.