End-to-end rules for integrating Prettier and ESLint to enforce consistent code style, catch logical errors, and automate quality checks in modern JavaScript & TypeScript projects.
Your team spends too much time in code review arguing about semicolons, indentation, and formatting instead of discussing business logic and architecture. Meanwhile, subtle bugs slip through because manual code reviews can't catch every logical error or inconsistent pattern.
Every JavaScript project faces the same productivity killers:
These aren't just minor annoyances—they're measurable drains on development velocity.
These Cursor Rules implement a battle-tested combination of Prettier and ESLint that eliminates code quality discussions entirely. Your code gets automatically formatted on save, logical errors get caught before they reach production, and your team focuses on building features.
Here's what changes immediately:
Before: Manual formatting and inconsistent review feedback
// Team member A writes this
function fetchUser(id){
return fetch(`/api/users/${id}`)
.then(response => response.json());
}
// Team member B writes this
const getUser = (userId) => {
return fetch("/api/users/" + userId).then((res) => {
return res.json()
})
}
After: Consistent, automatically enforced standards
// Everyone writes this (automatically formatted)
const fetchUser = (id: string): Promise<User> => {
return fetch(`/api/users/${id}`)
.then((response) => response.json())
.catch((error) => {
throw new ApiError('Failed to fetch user', error);
});
};
Eliminate 80% of style-related review comments. Your PRs get approved faster because reviewers focus on logic, not formatting.
Catch 15+ categories of common JavaScript bugs before they reach production:
New team members become productive immediately—no style guide memorization required. The tools enforce consistency automatically.
Prettier's fast CLI processes entire codebases in under 2 seconds. ESLint's flat config reduces linting time by 40%.
Before:
After:
Before:
After:
Before:
After:
npm install --save-dev eslint@^9.0.0 prettier@^3.6.0 @typescript-eslint/eslint-plugin@^7.0.0 eslint-config-prettier
Create eslint.config.mjs
:
import js from '@eslint/js';
import ts from '@typescript-eslint/eslint-plugin';
import prettier from 'eslint-config-prettier';
export default [
js.configs.recommended,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parser: '@typescript-eslint/parser'
},
plugins: {
'@typescript-eslint': ts
},
rules: {
'@typescript-eslint/explicit-function-return-type': ['error', { allowExpressions: true }],
'no-unused-vars': 'error',
'prefer-arrow-callback': 'error',
'promise/always-return': 'error',
'no-await-in-loop': 'error',
},
},
prettier // ALWAYS LAST - prevents conflicts
];
Create .prettierrc.json
:
{
"printWidth": 100,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "es5",
"endOfLine": "lf"
}
Update your package.json
:
{
"scripts": {
"format": "prettier . --write",
"format:check": "prettier . --cache --check",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"ci": "npm run format:check && npm run lint"
}
}
Create .github/workflows/code-quality.yml
:
name: Code Quality
on: [push, pull_request]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx prettier . --cache --check
- run: npx eslint . --max-warnings 0
For VS Code, add to .vscode/settings.json
:
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
Enforce typed error handling across your codebase:
// Custom ESLint rule enforces this pattern
class ApiError extends BaseError {
constructor(message: string, cause?: Error) {
super(message, cause);
}
}
// Instead of generic throws
throw new ApiError('User not found', originalError);
Automatically catch and fix performance issues:
// ESLint catches this performance anti-pattern
for (const user of users) {
await processUser(user); // no-await-in-loop error
}
// Suggests this optimized approach
await Promise.all(users.map(processUser));
Built-in security rules prevent common vulnerabilities:
// These patterns trigger errors automatically
eval(userInput); // no-eval
new Function(userInput); // no-new-func
obj[userInput]; // security/detect-object-injection
Unlike basic ESLint configurations that focus on catching errors after they're written, this system prevents problems from being written in the first place. The combination of Prettier's formatting automation and ESLint's advanced rule enforcement creates a development environment where quality code is the path of least resistance.
Your team will ship features faster, review code more effectively, and maintain higher standards—all without additional mental overhead. The tools work invisibly in the background, letting developers focus on what they do best: solving business problems with code.
Ready to eliminate code quality discussions forever? Implement these rules and watch your team's productivity transform overnight.
You are an expert in JavaScript, TypeScript, ESLint v9+, Prettier 3.x, Node.js, and CI/CD automation.
Key Principles
- Treat formatting and linting as non-negotiable gatekeepers; PRs must be clean before review.
- Formatting (Prettier) and linting (ESLint) MUST be configured separately; never overlap responsibilities.
- Use ESLint Flat Config (`eslint.config.mjs`) for composability and speed; keep Prettier rules last in the extends chain.
- All code is auto-formatted on save (editor) and validated in CI.
- All new rules default to `"error"`; use `"warn"` only for progressive adoption or non-breaking guidance.
JavaScript & TypeScript
- Always write TypeScript in `*.ts`/`*.tsx` files; bridge to ESLint with `@typescript-eslint`.
- Prefer `const` over `let`; forbid `var` (`no-var`).
- Enforce arrow functions for callbacks (`prefer-arrow-callback`).
- 2-space indentation; no tabs (`prettier/tabWidth: 2`).
- Max line length 100 (`printWidth: 100`).
- Require single quotes, except when avoiding escape (`singleQuote: true`).
- Trailing commas on ES2017 multi-line objects & arrays (`trailingComma: "es5"`).
- File naming: `kebab-case` for config, `PascalCase` for React components, `camelCase` for everything else.
- Disable unused vars (`no-unused-vars: error`). Use `_.<var>` prefix to intentionally ignore.
Error Handling & Validation
- Validate promise chains: `promise/always-return`, `promise/no-return-in-finally` (error).
- Disallow `async` functions without `try/catch` or `.catch()` (`@typescript-eslint/require-await`: warn).
- Enforce early returns to reduce nesting (`unicorn/prefer-early-return`).
- Custom rule: every `throw` must throw a typed error class extending `BaseError` (`project/typed-error`: error).
ESLint Rules (Framework-Specific)
- Flat config only; delete legacy `.eslintrc.*`.
- Sample `eslint.config.mjs` skeleton:
```js
import js from "eslint-plugin-js";
import ts from "@typescript-eslint/eslint-plugin";
import prettier from "eslint-config-prettier";
export default [
js.configs.recommended,
{
files: ["**/*.ts", "**/*.tsx"],
languageOptions: { parser: "@typescript-eslint/parser" },
plugins: { "@typescript-eslint": ts },
rules: {
"@typescript-eslint/explicit-function-return-type": ["error", { allowExpressions: true }],
},
},
prettier // ALWAYS LAST
];
```
- Differentiate severity: production CI fails on `error`; `warn` surfaces locally but does not break builds.
Prettier Rules (Framework-Specific)
- `.prettierrc.json` at repo root:
```json
{
"printWidth": 100,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "es5",
"endOfLine": "lf"
}
```
- `.prettierignore` mirrors `.gitignore` plus build artifacts.
- Run Prettier in CI using the fast CLI:
```bash
npx prettier . --cache --check
```
Additional Sections
CI/CD Integration
- GitHub Actions example (`.github/workflows/lint.yml`):
```yaml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npx prettier . --cache --check
- run: npx eslint . --max-warnings 0
```
Naming Conventions
- ESLint rule `@typescript-eslint/naming-convention`:
- `enum` & `type` ⇒ `PascalCase`.
- constants ⇒ `UPPER_CASE`.
- private members ⇒ `camelCase` prefixed with `#`.
Performance
- Enable `no-await-in-loop` (error); advise using `Promise.all`.
- Prettier fast CLI caches results via `.prettiercache` for sub-second re-runs.
Testing
- Add `lint` and `format` scripts to `package.json`:
```json
{
"scripts": {
"format": "prettier . --write",
"lint": "eslint .",
"ci": "npm run format -- --check && npm run lint"
}
}
- Hook Husky `pre-commit` to run `npm run lint` & `npm run format`.
Security
- Disallow `eval`, `Function` constructor (`no-implied-eval`, `no-new-func`).
- Turn on `security/detect-object-injection` (error).
Common Pitfalls & Fixes
- Pitfall: Duplicate Prettier & ESLint stylistic rules → fix by extending `eslint-config-prettier` LAST.
- Pitfall: Editor overrides Prettier line endings → enforce `endOfLine: lf` and git attribute `*.js text eol=lf`.
Directory Structure Example
```
repo/
├─ src/
│ ├─ components/
│ │ └─ MyCard.tsx
│ ├─ lib/
│ └─ index.ts
├─ .prettierrc.json
├─ eslint.config.mjs
├─ .prettierignore
└─ package.json
```
Reference Versions
- ESLint v9.x
- Prettier v3.6+
- @typescript-eslint v7.x
- Node.js ≥ 18.17