typescript-code-review
1
总安装量
1
周安装量
#51028
全站排名
安装命令
npx skills add https://github.com/jpoutrin/product-forge --skill typescript-code-review
Agent 安装分布
windsurf
1
amp
1
opencode
1
kimi-cli
1
codex
1
github-copilot
1
Skill 文档
TypeScript/React Code Review Patterns
This skill provides TypeScript and React-specific code review guidelines. Use alongside typescript-style for comprehensive review.
Critical Security Issues
XSS Vulnerabilities
// VULNERABLE - dangerouslySetInnerHTML without sanitization
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// VULNERABLE - innerHTML assignment
element.innerHTML = userContent;
// SAFE - use DOMPurify
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />
// SAFER - avoid innerHTML entirely
<div>{userContent}</div> // React auto-escapes
Insecure Data Exposure
// VULNERABLE - logging sensitive data
console.log("User data:", user); // May include tokens, passwords
console.log("Request:", request.headers); // Auth headers
// VULNERABLE - exposing in error messages
throw new Error(`Auth failed for ${credentials}`);
// SAFE - sanitize before logging
console.log("User ID:", user.id); // Only necessary fields
Unsafe eval/Function Constructor
// VULNERABLE - code execution from strings
eval(userInput);
new Function(userInput)();
setTimeout(userInput, 1000); // String argument
// SAFE - avoid string evaluation
setTimeout(() => { processInput(userInput); }, 1000);
Missing CSRF Protection
// VULNERABLE - no CSRF token
fetch('/api/delete', { method: 'POST', body: data });
// SAFE - include CSRF token
fetch('/api/delete', {
method: 'POST',
headers: { 'X-CSRF-Token': csrfToken },
body: data,
});
High Priority Type Safety Issues
Unsafe Type Assertions
// DANGEROUS - bypasses type checking
const user = data as User;
const items = response as any[];
// SAFER - use type guards
function isUser(data: unknown): data is User {
return typeof data === 'object' && data !== null && 'id' in data;
}
if (isUser(data)) {
// data is now typed as User
}
Non-null Assertion Abuse
// DANGEROUS - runtime error if null
const name = user!.profile!.name!;
// SAFE - explicit null handling
const name = user?.profile?.name ?? 'Unknown';
// Or with explicit checks
if (user?.profile?.name) {
const name = user.profile.name;
}
Missing Error Handling in Async Code
// BUG - unhandled promise rejection
async function fetchData() {
const response = await fetch(url);
return response.json(); // What if fetch fails?
}
// CORRECT - handle errors
async function fetchData() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
} catch (error) {
console.error('Fetch failed:', error);
throw error; // Re-throw or return default
}
}
Ignoring Promise Results
// BUG - fire and forget
saveUser(userData); // No await, no error handling
// CORRECT
await saveUser(userData);
// or
saveUser(userData).catch(console.error);
React Anti-Patterns
Missing useEffect Dependencies
// BUG - stale closure, missing dependency
useEffect(() => {
fetchUser(userId); // userId not in deps!
}, []); // Empty deps = runs once with initial userId
// CORRECT - include all dependencies
useEffect(() => {
fetchUser(userId);
}, [userId]);
State Updates on Unmounted Components
// BUG - memory leak, state update after unmount
useEffect(() => {
fetchData().then(setData);
}, []);
// CORRECT - cleanup with flag or AbortController
useEffect(() => {
let mounted = true;
fetchData().then((data) => {
if (mounted) setData(data);
});
return () => { mounted = false; };
}, []);
// BETTER - use AbortController
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
return () => controller.abort();
}, [url]);
Missing Key Prop in Lists
// BUG - using index as key (causes issues on reorder)
items.map((item, index) => <Item key={index} data={item} />)
// CORRECT - use stable unique identifier
items.map((item) => <Item key={item.id} data={item} />)
Inline Functions in JSX (Performance)
// PERFORMANCE - new function on every render
<Button onClick={() => handleClick(id)} />
// BETTER - memoize if passed to memoized child
const handleButtonClick = useCallback(() => handleClick(id), [id]);
<MemoizedButton onClick={handleButtonClick} />
Props Drilling Deep
// CODE SMELL - passing props through many levels
<App user={user}>
<Layout user={user}>
<Sidebar user={user}>
<UserProfile user={user} />
// BETTER - use Context for cross-cutting concerns
const UserContext = createContext<User | null>(null);
<UserContext.Provider value={user}>
<App />
</UserContext.Provider>
Performance Anti-Patterns
Re-rendering Entire Lists
// SLOW - all items re-render when one changes
function ItemList({ items }: { items: Item[] }) {
return items.map(item => <ItemRow item={item} />);
}
// FAST - memoize individual items
const MemoizedItemRow = memo(ItemRow);
function ItemList({ items }: { items: Item[] }) {
return items.map(item => <MemoizedItemRow key={item.id} item={item} />);
}
Expensive Calculations Without Memoization
// SLOW - recalculates on every render
function ExpensiveComponent({ data }: { data: number[] }) {
const sorted = [...data].sort((a, b) => a - b); // Every render!
const sum = data.reduce((a, b) => a + b, 0); // Every render!
return <div>{sorted.join(',')}: {sum}</div>;
}
// FAST - memoize expensive operations
function ExpensiveComponent({ data }: { data: number[] }) {
const sorted = useMemo(() => [...data].sort((a, b) => a - b), [data]);
const sum = useMemo(() => data.reduce((a, b) => a + b, 0), [data]);
return <div>{sorted.join(',')}: {sum}</div>;
}
Unnecessary State
// OVERHEAD - derived state should be computed
const [items, setItems] = useState<Item[]>([]);
const [itemCount, setItemCount] = useState(0); // Unnecessary!
// Update both when items change - easy to forget
setItems(newItems);
setItemCount(newItems.length); // Must keep in sync
// BETTER - derive from source of truth
const [items, setItems] = useState<Item[]>([]);
const itemCount = items.length; // Always in sync
Large Bundle Imports
// HEAVY - imports entire library
import _ from 'lodash';
import moment from 'moment'; // 300KB+
// LIGHT - import specific functions
import debounce from 'lodash/debounce';
import { format } from 'date-fns'; // Much smaller
Accessibility Issues
Missing ARIA Labels
// INACCESSIBLE - icon-only button without label
<button onClick={onClose}>
<CloseIcon />
</button>
// ACCESSIBLE
<button onClick={onClose} aria-label="Close dialog">
<CloseIcon />
</button>
Non-Interactive Elements with Handlers
// INACCESSIBLE - div with click handler
<div onClick={handleClick}>Click me</div>
// ACCESSIBLE - use button or add role/keyboard
<button onClick={handleClick}>Click me</button>
// or
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyDown={(e) => e.key === 'Enter' && handleClick()}
>
Click me
</div>
Missing Form Labels
// INACCESSIBLE - input without label
<input type="email" placeholder="Email" />
// ACCESSIBLE
<label>
Email
<input type="email" />
</label>
// or
<label htmlFor="email">Email</label>
<input id="email" type="email" />
Color-Only Indicators
// INACCESSIBLE - status only indicated by color
<span style={{ color: isError ? 'red' : 'green' }}>
{status}
</span>
// ACCESSIBLE - include text or icon
<span style={{ color: isError ? 'red' : 'green' }}>
{isError ? 'â ' : 'â '}{status}
</span>
Common TypeScript Mistakes
Improper Null Checking
// BUG - falsy check catches 0 and ''
if (!value) return; // Fails for value = 0 or ''
// CORRECT - explicit null/undefined check
if (value == null) return; // Catches null and undefined
// or
if (value === null || value === undefined) return;
Object Spread Overwrites
// BUG - order matters, later overwrites earlier
const config = { ...defaultConfig, ...userConfig, timeout: 5000 };
// timeout is always 5000, even if userConfig.timeout was set!
// CORRECT - put defaults first
const config = { timeout: 5000, ...defaultConfig, ...userConfig };
Array Method Return Values
// BUG - forEach doesn't return
const doubled = items.forEach(x => x * 2); // undefined!
// CORRECT - use map for transformation
const doubled = items.map(x => x * 2);
// BUG - filter callback should return boolean
const active = items.filter(item => item.status); // Works but unclear
// CORRECT - explicit boolean return
const active = items.filter(item => item.status === 'active');
Test Coverage Gaps
Flag missing tests for:
- Error states: Network failures, invalid inputs, edge cases
- User interactions: Button clicks, form submissions, keyboard navigation
- Async behavior: Loading states, success/error handling
- Accessibility: Screen reader compatibility, keyboard navigation
- Edge cases: Empty arrays, null values, boundary conditions
// Missing test coverage examples
it('should show error message on fetch failure', async () => {});
it('should be keyboard navigable', () => {});
it('should handle empty item list', () => {});
it('should show loading state while fetching', () => {});
Code Review Checklist
Security
- No XSS vectors (dangerouslySetInnerHTML, innerHTML)
- No sensitive data in logs or error messages
- No eval or dynamic code execution
- CSRF protection on mutations
Type Safety
- No
anytypes (useunknown+ type guards) - No unsafe type assertions without validation
- Proper null/undefined handling
- All promises handled (await or .catch)
React Patterns
- useEffect dependencies complete
- Cleanup functions for subscriptions/timers
- Stable keys in lists (not array index)
- No unnecessary state (derive when possible)
Performance
- Expensive operations memoized (useMemo)
- Event handlers memoized when needed (useCallback)
- Large components use React.memo
- Tree-shakeable imports
Accessibility
- Interactive elements have labels
- Keyboard navigation works
- Form inputs have associated labels
- Status not conveyed by color alone