| name | code-review-typescript |
| description | Performs structured code reviews on TypeScript codebases covering correctness, type safety, async patterns, security, testing, and style. Use when reviewing TypeScript code, pull requests, or when asked to do a code review on a TypeScript or Node.js project. |
Code Review โ TypeScript
When asked to review code โ a file, a function, a PR diff, or a module โ follow this checklist systematically. Always provide specific, actionable feedback with line references and concrete improvement suggestions. Never give vague praise or criticism.
1. Understand the Intent First
Before writing any comment:
- Read the code top-to-bottom at least once without commenting.
- Understand what the code is supposed to do, not just what it does.
- Note the surrounding context (imports, types, callers).
2. Correctness & Logic
Check for bugs, edge cases, and incorrect assumptions.
- Null/undefined safety: are optional values guarded before use? Look for unchecked
?. chains that silently return undefined instead of throwing.
- Type narrowing gaps: is
typeof, instanceof, or a discriminated union guard used before accessing a narrowed type?
- Return completeness: does every code path return a value? Does TypeScript's
noImplicitReturns flag these?
- Array/object mutation: is the caller's data mutated unintentionally? Flag in-place sorts or push/splice on received arrays.
- Equality:
== vs === โ always use === in TypeScript.
- Off-by-one errors, incorrect loop bounds, or fence-post mistakes.
Example:
const name = users.find(u => u.id === id)?.name;
const user = users.find(u => u.id === id);
if (!user) throw new Error(`User ${id} not found`);
const name = user.name;
3. Type Safety
any usage: flag every any. Suggest unknown + type guard, or a proper interface.
- Type assertions (
as): flag casts that bypass the type system without a guard. as unknown as X is almost always wrong.
- Non-null assertions (
!): flag every ! postfix operator โ it disables null checking. Require a comment explaining why it's safe, or replace with a guard.
strict: true: if the tsconfig lacks strict: true, flag it. At minimum, strictNullChecks and noImplicitAny must be enabled.
- Overly wide types:
string | number | boolean | object โ suggest a discriminated union or a named type.
interface vs type: prefer interface for object shapes that may be extended; type for unions, intersections, and aliases.
Example:
const result = response.data as UserProfile;
if (!isUserProfile(response.data)) throw new TypeError('Unexpected API response shape');
const result: UserProfile = response.data;
4. Async & Concurrency
- Unhandled promise rejections: every
Promise must be either await-ed, .catch()-ed, or explicitly void-ed with a comment.
- Floating
async functions: async functions called without await silently drop errors.
- Blocking in async context:
fs.readFileSync, execSync, or CPU-heavy loops in an async function block the event loop โ flag them.
- Sequential vs parallel: independent
await calls that could run with Promise.all are a performance issue.
Promise.all failure modes: Promise.all fails fast on the first rejection; use Promise.allSettled when partial failure is acceptable.
- Race conditions: shared mutable state mutated across
await points without a lock is a race condition.
Example:
const user = await fetchUser(id);
const orders = await fetchOrders(id);
const [user, orders] = await Promise.all([fetchUser(id), fetchOrders(id)]);
5. Security
- Hardcoded secrets: flag any hardcoded API keys, tokens, passwords, or connection strings as CRITICAL.
- Never log secrets or PII: check
console.log / logger calls for request bodies, auth headers, or user data.
- Input validation: all external inputs (HTTP request bodies, query params, env vars) must be validated โ flag raw
req.body.x access without a schema (Zod, Valibot, or class-validator).
- Prototype pollution:
Object.assign({}, userInput) or {...userInput} with untrusted input can pollute prototypes โ prefer explicit field extraction.
- SQL/NoSQL injection: never use template literals or string concatenation to build queries; always use parameterized queries or an ORM.
eval / Function(): flag any dynamic code execution.
- Path traversal:
path.join(baseDir, userInput) without normalization and boundary-checking is a vulnerability.
6. Design & Architecture
- Single Responsibility: each function/class should do one thing. If a name needs "and", flag it.
- DRY: flag duplicated logic and suggest extraction.
- Dependency direction: high-level modules should not import implementation details from lower-level modules.
- Magic values: hardcoded strings and numbers should be named constants or config entries.
- Long parameter lists (4+): suggest an options object with a typed interface.
- Boolean parameters: a
doX(data, true) call is opaque โ suggest two functions or a discriminated union.
- God functions: functions over ~50 lines are a signal to decompose.
7. Error Handling
- Silent swallowing:
catch (e) {} or catch (e) { return null; } hides failures โ require logging and re-throwing or explicit handling.
- Error types: throwing plain strings (
throw "something failed") loses stack traces โ always throw Error instances or subclasses.
- Error propagation: errors at async boundaries must be caught or they become unhandled rejections.
- Result types: in domain logic, consider
Result<T, E> patterns over throwing for expected failures.
throw "user not found";
throw new UserNotFoundError(`User ${id} not found`);
8. Testing
- Coverage: is the changed code covered? Call out specific untested paths explicitly.
- Test scope: unit tests must mock external I/O (HTTP clients, DB, filesystem). Integration tests may use real services.
- Edge cases: are error paths, empty inputs, and boundary values tested โ not just the happy path?
- Assertions: tests must assert on the expected output or side effect โ not just that the code ran without throwing.
- Isolation: tests must not share mutable state or depend on execution order. Use
beforeEach/afterEach to reset.
- Naming:
it('should <do X> when <condition>') โ not it('test 1').
expect.assertions(n): use in async tests to catch cases where the assertion is never reached.
9. Style & Consistency
- ESLint: flag obvious ESLint violations if a config is present.
const over let: prefer const for all values that are not reassigned.
- Imports: external packages before internal modules; no wildcard imports (
import * as foo).
- Naming:
camelCase for variables/functions, PascalCase for types/classes/components, SCREAMING_SNAKE_CASE for module-level constants.
console.log: flag any left in production code โ use a proper logger.
- TODO/FIXME comments: must include context and ideally a ticket reference. Flag vague or stale ones.
- Unused variables: flag
_unused that are not intentional; TypeScript's noUnusedLocals should catch these.
10. Output Format
Structure your response as follows:
Summary
A 2โ4 sentence overview: what does the code do, is it generally well-written, what is the most important concern?
Critical Issues ๐ด
Issues that must be fixed before merging: security vulnerabilities, data loss risks, correctness bugs, broken contracts. Number each item.
Improvements ๐ก
Things that are not blocking but meaningfully improve quality: design issues, missing tests, performance problems, unclear naming. Number each item.
Minor / Nits ๐ข
Style, convention, and cleanup items that improve consistency but have minimal functional impact. Can be bulleted for brevity.
Positive Highlights โ
Call out what is done well. This is not optional โ good feedback is balanced.
Quick Reference Checklist