Clean TypeScript
Write clean, efficient TypeScript code that follows common best practices
Source: .agents/rules/clean-typescript.mdc
Metadata
- name: clean-typescript
- alwaysApply: true
Content
Clean TypeScript
We use TypeScript as a correctness and clarity tool, not as ceremony. Types should reduce bugs and cognitive load.
Type Philosophy
- PREFER explicit, readable types over clever or overly generic ones.
- AVOID
anyand unsafe type assertions. - Use
unknowninstead ofanywhen necessary. - Let TypeScript infer types when inference is clear and stable.
Types & Interfaces
- PREFER
typealiases for most use cases. - Use
interfaceprimarily for public, extendable object shapes. - Keep types small, composable, and well-named.
Functions & APIs
- PREFER explicit return types for public functions.
- Avoid function overloads unless they meaningfully improve the API.
- Keep function signatures simple and predictable.
Nullability & Safety
- Handle
nullandundefinedexplicitly. - DO NOT rely on non-null assertions (
!) except as a last resort. - Prefer narrowing via control flow and guards.
Enums & Constants
- AVOID
enum. - PREFER union types or
as constobjects. - Keep runtime output predictable and minimal.
Error Handling
- Type errors and error states explicitly.
- Prefer result objects or typed errors over throwing where appropriate.
- Do not hide failure modes behind broad types.
General Principles
- Types should explain intent.
- If a type is hard to understand, it is probably wrong.
- Favor maintainability over theoretical completeness.
- Reuse existing shared types whenever possible instead of redefining equivalent shapes.
- Before creating a new reusable type, look for nearby type files in the current folder and parent folders (commonly
types.tsoutsidecomponents). - If no reusable shared type exists, create a
types.tsand declare the needed shared types there. - For component-only props types, declare the props type in that component file and use it directly.
// ❌ Avoid
function parsePayload(payload: any): any {
return payload.data;
}
// ✅ Prefer
function parsePayload(payload: unknown): string | null {
if (
typeof payload === 'object' &&
payload !== null &&
'data' in payload &&
typeof payload.data === 'string'
) {
return payload.data;
}
return null;
}
// ✅ Prefer reusing a shared type from a nearby types.ts
import type { CommunityMember } from '../types';
const renderMember = (member: CommunityMember) => member.name;
// ✅ Component-local props type when only used by this component
type MemberBadgeProps = {
member: CommunityMember;
isActive: boolean;
};
const MemberBadge = ({ member, isActive }: MemberBadgeProps) => {
return `${member.name} - ${isActive ? 'active' : 'inactive'}`;
};