Chapter 30: Code Quality Tooling
Automated code quality tools catch issues before they reach code review. This chapter sets up ESLint, Prettier, and pre-commit hooks for a consistent, maintainable codebase.
ESLint Flat Config​
ESLint's flat config (eslint.config.js) is the modern standard:
// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import jsxA11y from "eslint-plugin-jsx-a11y";
import prettier from "eslint-plugin-prettier/recommended";
export default tseslint.config(
// Base JavaScript rules
js.configs.recommended,
// TypeScript rules
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
// React hooks rules
{
plugins: { "react-hooks": reactHooks },
rules: reactHooks.configs.recommended.rules,
},
// React Refresh (Fast Refresh compatibility)
{
plugins: { "react-refresh": reactRefresh },
rules: {
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
},
// Accessibility
jsxA11y.flatConfigs.recommended,
// Prettier (must be last — disables conflicting rules)
prettier,
// Custom rules
{
rules: {
// TypeScript
"@typescript-eslint/no-unused-vars": ["error", {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
}],
"@typescript-eslint/consistent-type-imports": ["error", {
prefer: "type-imports",
}],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
// Import boundaries (enforce feature structure)
"no-restricted-imports": ["error", {
patterns: [
{
group: ["@/features/*/components/*", "@/features/*/hooks/*", "@/features/*/services/*"],
message: "Import from the feature's index.ts instead.",
},
],
}],
},
},
// Ignore patterns
{
ignores: [
"dist/",
"node_modules/",
"src/route-tree.gen.ts",
"*.config.js",
"*.config.ts",
],
}
);
Prettier Configuration​
// .prettierrc
{
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf"
}
// .prettierignore
dist/
node_modules/
pnpm-lock.yaml
src/route-tree.gen.ts
*.md
Husky + lint-staged​
Pre-commit hooks ensure quality before code reaches the repository:
# Initialize Husky
pnpm husky init
# The init command creates .husky/pre-commit
# .husky/pre-commit
pnpm lint-staged
// package.json
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{json,css,md}": [
"prettier --write"
]
}
}
What runs when:​
- Developer commits code
- Husky intercepts the commit
- lint-staged runs ESLint + Prettier only on staged files
- If any errors — commit is blocked
- If clean — commit proceeds
TypeScript Type Checking​
Add type checking to your CI pipeline (it is too slow for pre-commit on large projects):
pnpm typecheck # runs: tsc --noEmit
This catches:
- Type errors that ESLint does not find
- Missing imports
- Wrong function arguments
- Unused type-only imports
Summary​
- ✅ ESLint flat config with TypeScript, React, and a11y rules
- ✅ Prettier for consistent formatting
- ✅ Husky + lint-staged blocks commits with linting errors
- ✅ Type checking via
tsc --noEmitin CI - ✅ Import boundary rules enforce feature module structure