-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
TL;DR
Proposing typescript-best-practices skill to complement react-best-practices. Would maintainers be open to this?
Overview
A new skill category focused on TypeScript type system best practices. This would complement existing skills like react-best-practices by providing comprehensive guidance on TypeScript-specific patterns, with emphasis on types, interfaces, utility types, and type safety.
Skill Name: typescript-best-practices
Description: Review TypeScript code for type system correctness, safety, and best practices. Focuses on proper usage of types, interfaces, utility types, generics, and type narrowing to catch type-related bugs before runtime.
Trigger Phrases:
- "Review my TypeScript types"
- "Check TypeScript type safety"
- "Audit TypeScript code for type issues"
- "Improve my TypeScript types"
Scope
Targets TypeScript's type system and type-level programming only - not runtime behavior, frameworks, or tooling. Emphasizes rules impractical for standard linters.
Sample Rules
1. Avoid `any`, Prefer `unknown`
Severity: CRITICAL
Category: Type Safety
Issue: Using any completely disables type checking and undermines TypeScript's safety guarantees.
// ❌ BAD: Type checking disabled
function processData(data: any) {
console.log(data.name.toUpperCase()); // No error even if data.name doesn't exist
}
// ✅ GOOD: Use unknown with type guards
function processData(data: unknown) {
if (typeof data === 'object' && data !== null && 'name' in data) {
const record = data as { name: unknown };
if (typeof record.name === 'string') {
console.log(record.name.toUpperCase());
}
}
}Why: unknown forces explicit type checking, catching runtime errors at compile time. Use any only during JS migration.
2. Use Interfaces for Object Inheritance
Severity: HIGH
Category: Type Definitions
Issue: Type intersections with & are less performant than interface extension.
// ❌ AVOID: Type intersection (less performant)
type User = { id: string; name: string };
type UserWithEmail = User & { email: string };
// ✅ PREFERRED: Interface extension (better performance)
interface User {
id: string;
name: string;
}
interface UserWithEmail extends User {
email: string;
}Why: TypeScript caches interface relationships, improving performance. Use interface for inheritance, type for unions/intersections.
3. Leverage Utility Types Instead of Manual Duplication
Severity: MEDIUM
Category: Type Reusability
Issue: Manually duplicating or partially redefining types is error-prone and harder to maintain.
// ❌ BAD: Manual duplication
interface User {
id: string;
name: string;
email: string;
role: string;
}
interface UserUpdate {
id: string;
name?: string;
email?: string;
role?: string;
}
// ✅ GOOD: Use utility types
interface User {
id: string;
name: string;
email: string;
role: string;
}
type UserUpdate = Required<Pick<User, 'id'>> & Partial<Omit<User, 'id'>>;Why: Utility types maintain consistency with source types. Changes automatically propagate.
4. Enable Strict Null Checks
Severity: CRITICAL
Category: Compiler Configuration
Issue: Without strictNullChecks, null and undefined can be assigned to any type, causing runtime errors.
// With strictNullChecks: off (dangerous)
function getLength(str: string): number {
return str.length; // Runtime error if str is null!
}
// With strictNullChecks: on (safe)
function getLength(str: string | null): number {
if (str === null) {
return 0;
}
return str.length; // TypeScript ensures str is not null here
}Why: Forces explicit null/undefined handling, preventing null reference errors.
5. Use Type Guards for Runtime Type Narrowing
Severity: HIGH
Category: Type Narrowing
Issue: Type assertions (as) bypass type checking and can lead to runtime errors.
// ❌ BAD: Type assertion without validation
function processUser(data: unknown) {
const user = data as User; // Dangerous - no runtime check
console.log(user.name.toUpperCase());
}
// ✅ GOOD: Type guard with validation
function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'name' in data &&
typeof (data as any).id === 'string' &&
typeof (data as any).name === 'string'
);
}
function processUser(data: unknown) {
if (isUser(data)) {
console.log(data.name.toUpperCase()); // Type-safe
}
}Why: Provides runtime validation with safe type narrowing.
References
Next Steps
If this proposal is accepted, I'd be happy to submit a PR to start building out the rule set. I'm not a TypeScript expert but there's enough low hanging fruit to get a lot going.