Comprehensive Rules focused on maximizing code readability in JavaScript & TypeScript projects.
Your codebase doesn't have to be a puzzle. When every function name makes sense, every component has a single purpose, and every file follows predictable patterns, development becomes what it should be: fast, confident, and collaborative.
You've been there. Staring at a function you wrote three months ago, trying to decode what processData(x, y, z) actually does. Or worse, debugging a teammate's "elegant" one-liner that handles five different edge cases.
The reality: Readable code isn't just nice-to-have—it's the difference between shipping features confidently and spending half your sprint deciphering existing logic.
The problem: Most development teams optimize for the wrong metrics. They chase performance micro-optimizations while ignoring that 80% of development time is spent reading existing code, not writing new code.
These Cursor Rules transform your JavaScript and TypeScript development by enforcing a single principle: code should be instantly comprehensible to any team member, including your future self.
Here's what changes:
// Before: Clever but cryptic
const processUserData = (u: any) => {
return u.map((x: any) => ({
...x,
n: x.firstName + ' ' + x.lastName,
a: new Date().getFullYear() - new Date(x.birthDate).getFullYear()
}));
};
// After: Immediately clear
interface User {
firstName: string;
lastName: string;
birthDate: string;
}
interface ProcessedUser extends User {
fullName: string;
age: number;
}
function enrichUserProfiles(users: User[]): ProcessedUser[] {
return users.map(transformUserProfile);
}
function transformUserProfile(user: User): ProcessedUser {
const currentYear = new Date().getFullYear();
const birthYear = new Date(user.birthDate).getFullYear();
return {
...user,
fullName: `${user.firstName} ${user.lastName}`,
age: currentYear - birthYear
};
}
No mental gymnastics. No context switching. Just code that explains itself.
When functions have single responsibilities and descriptive names, reviewers focus on business logic instead of decoding implementation details. Reviews become about "is this the right approach?" not "what does this do?"
Consistent ESLint + Prettier automation removes subjective formatting debates. Every developer sees identical code structure, eliminating confusion from inconsistent styles.
New team members can contribute meaningfully within days when every component follows predictable patterns and self-documenting naming conventions.
Clear module boundaries and consistent file organization mean you spend less time hunting for related code across your project structure.
// Clear error boundaries and validation patterns
async function fetchUserProfile(userId: string): Promise<UserProfile> {
const validatedUserId = validateUserId(userId);
try {
const response = await api.get(`/users/${validatedUserId}`);
return parseUserProfileResponse(response.data);
} catch (error) {
throw new UserProfileFetchError(
`Failed to fetch profile for user ${userId}`,
{ userId, originalError: error }
);
}
}
interface UserCardProps {
user: User;
onEdit: (userId: string) => void;
isEditable?: boolean;
}
function UserCard({ user, onEdit, isEditable = false }: UserCardProps) {
const handleEditClick = () => onEdit(user.id);
const canEdit = isEditable && user.status === 'active';
return (
<div className="user-card">
<UserAvatar src={user.avatarUrl} alt={`${user.fullName}'s avatar`} />
<UserInfo user={user} />
{canEdit && (
<EditButton onClick={handleEditClick} />
)}
</div>
);
}
class ValidationError extends Error {
constructor(
message: string,
public readonly field: string,
public readonly receivedValue: unknown
) {
super(message);
this.name = 'ValidationError';
}
}
function validateEmail(email: string): string {
if (!email) {
throw new ValidationError(
'Email address is required',
'email',
email
);
}
if (!isValidEmailFormat(email)) {
throw new ValidationError(
'Please enter a valid email address',
'email',
email
);
}
return email;
}
.cursorrules filenpm install -D eslint prettier @typescript-eslint/eslint-plugin
npm install -D eslint-config-airbnb-base eslint-plugin-promise
npm install -D husky lint-staged
Add to your package.json:
{
"scripts": {
"lint": "eslint src --ext .ts,.tsx",
"lint:fix": "eslint src --ext .ts,.tsx --fix",
"format": "prettier --write src/**/*.{ts,tsx}"
},
"lint-staged": {
"src/**/*.{ts,tsx}": ["eslint --fix", "prettier --write"]
}
}
Update your tsconfig.json:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true
}
}
npx husky add .husky/pre-commit "npx lint-staged"
Ready to transform your development workflow? Install these Cursor Rules and experience the difference that truly readable code makes in your daily development process. Your future self—and your teammates—will thank you.
You are an expert in JavaScript (ES2023), TypeScript 5.x, Node.js 20+, React 18+, ESLint, Prettier, Jest, SonarQube.
Key Principles
- Enforce a single automated style guide (ESLint + Prettier) for uniform formatting and zero subjective debates.
- Write self-documenting code: explicit names, minimal side effects, and transparent data flow.
- Decompose relentlessly; one module/function/component does exactly one thing (Single Responsibility Principle).
- Flatten control flow: use early returns and guard clauses; never nest more than two levels deep.
- Comment WHY, not WHAT; code itself must reveal the HOW.
- Optimize readability for the least-experienced team member without insulting experts.
JavaScript / TypeScript
- Enable "strict": true in tsconfig; forbid any (`@typescript-eslint/no-explicit-any`).
- 2-space indent; max line length: 100 chars; statements end with semicolons.
- Use `const` by default, `let` when re-assignment is required; never `var`.
- Naming
• camelCase for vars & functions (e.g., `isLoading`), PascalCase for types/classes (`UserDto`), SCREAMING_SNAKE for constants (`MAX_RETRIES`).
- Prefer named exports; one public export per file; avoid default exports.
- Choose `interface` over `type` except for unions/intersections.
- Arrow functions for inline callbacks; `function foo()` for public or recursive functions.
- Prefer template literals over string concatenation.
- Destructure parameters for objects with ≥3 props and provide defaults (`{ page = 1 }`).
- Use optional chaining (`?.`) and nullish coalescing (`??`).
- Treat parameters as read-only; never mutate.
- Break long expressions into well-named helper predicates.
- Import order: builtin, external, internal; each alphabetized and newline-separated.
- Use map/filter/reduce for transformation; for..of for side-effect loops.
- File layout: Imports → Types → Constants → Main export → Helpers → Exports.
Error Handling and Validation
- Validate all external input immediately with Zod/Yup; fail fast.
- Create custom error classes extending `Error` (fields: code, message, metadata).
- Throw early, catch late: handle at API boundaries only.
- Async: wrap awaits in try/catch; return `Result<T, E>` when recovery is expected.
- Provide actionable, user-friendly messages; log diagnostic detail separately.
- ESLint: enable `no-floating-promises`, `promise/prefer-await-to-then`.
- Never swallow errors; either rethrow or handle explicitly.
React (18+)
- Functional components exclusively; no classes.
- Components named in PascalCase, file/folder mirrored (`UserCard/UserCard.tsx`).
- Props & state must be fully typed; no implicit any.
- Components ≤200 lines; extract hooks or sub-components when exceeded.
- Derive everything; avoid duplicate state.
- Use `React.memo`/`useMemo`/`useCallback` after profiling.
- Side effects live in `useEffect`/`useLayoutEffect` with exhaustive deps.
- Define event handlers outside JSX (`const handleClick = () => {}`).
- Style with CSS Modules or styled-components; adhere to BEM or hashed class names.
- Wrap route roots with Error Boundaries that log via the central handler.
Testing
- Jest + React Testing Library; one `.test.ts` per module/component.
- Follow Arrange-Act-Assert.
- 100% branch coverage on pure functions, 80% overall.
- Snapshot test only static, intentionally visual components.
Performance
- Profile continuously with React DevTools & Lighthouse CI.
- Prevent unnecessary re-renders: memoize selectors; pass primitives when possible.
- Debounce/throttle high-frequency events.
Security
- ESLint plugin security: forbid `eval`, `Function`, raw innerHTML.
- Sanitize user HTML via DOMPurify.
- Store secrets in env vars, never committed.
Documentation
- JSDoc/TSDoc for all public APIs: purpose, params, returns, examples.
- Auto-generate docs with TypeDoc on CI.
Tooling
- ESLint preset: `eslint-config-airbnb-base` + `@typescript-eslint/recommended` + `eslint-plugin-promise` + `plugin:prettier/recommended`.
- Prettier & ESLint run pre-commit via Husky + lint-staged.
- SonarQube enforces an A grade on new code (reliability, security, maintainability).
Directory Naming
- Lowercase-dash-case for folders (`user-profile`) and central `index.ts` barrel exports.
- Absolute imports via `@/` alias mapping to `src`.
Commit Messages
- Use Conventional Commits (`feat:`, `fix:`, `docs:`, etc.) to keep history readable.
Continuous Improvement
- Weekly readability audits: ESLint warnings must be zero.
- Code reviews start with a readability checklist before performance or edge-case analysis.