6.6 KiB
ADR-021: Code Formatting and Linting Unification
Date: 2025-12-12
Status: Accepted
Implemented: 2026-01-09
Context
The project contains both frontend (React) and backend (Node.js) code. While linters may be in use, there isn't a single, enforced standard for code style and quality across the entire repository. This leads to inconsistent code and time wasted in code reviews on stylistic debates.
Decision
We will mandate the use of Prettier for automated code formatting and a unified ESLint configuration for code quality rules across both frontend and backend. This will be enforced automatically using a pre-commit hook managed by Husky and lint-staged.
Consequences
Positive: Improves developer experience and team velocity by automating code consistency. Reduces time spent on stylistic code review comments. Enhances code readability and maintainability.
Negative: Requires an initial setup and configuration of Prettier, ESLint, and Husky. May require a one-time reformatting of the entire codebase.
Implementation Status
What's Implemented
- ✅ Prettier Configuration -
.prettierrcwith consistent settings - ✅ Prettier Ignore -
.prettierignoreto exclude generated files - ✅ ESLint Configuration -
eslint.config.jswith TypeScript and React support - ✅ ESLint + Prettier Integration -
eslint-config-prettierto avoid conflicts - ✅ Husky Pre-commit Hooks - Automatic enforcement on commit
- ✅ lint-staged - Run linters only on staged files for performance
Implementation Details
Prettier Configuration
The project uses a consistent Prettier configuration in .prettierrc:
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"endOfLine": "auto"
}
ESLint Configuration
ESLint is configured with:
- TypeScript support via
typescript-eslint - React hooks rules via
eslint-plugin-react-hooks - React Refresh support for HMR
- Prettier compatibility via
eslint-config-prettier - Relaxed rules for test files (see below)
// eslint.config.js (ESLint v9 flat config)
import globals from 'globals';
import tseslint from 'typescript-eslint';
import pluginReact from 'eslint-plugin-react';
import pluginReactHooks from 'eslint-plugin-react-hooks';
import pluginReactRefresh from 'eslint-plugin-react-refresh';
import eslintConfigPrettier from 'eslint-config-prettier';
export default tseslint.config(
// ... configurations
eslintConfigPrettier, // Must be last to override formatting rules
);
Relaxed Linting Rules for Test Files
Decision Date: 2026-01-09
Status: Active (revisit when product nears final release)
The following ESLint rules are relaxed for test files (*.test.ts, *.test.tsx, *.spec.ts, *.spec.tsx):
| Rule | Setting | Rationale |
|---|---|---|
@typescript-eslint/no-explicit-any |
off |
Mocking complexity often requires any; strict typing in tests adds friction without proportional benefit |
Rationale:
- Tests are not production code - The primary goal of tests is verifying behavior, not type safety of the test code itself
- Mocking complexity - Mocking libraries often require type gymnastics;
anysimplifies creating partial mocks and test doubles - Testing edge cases - Sometimes tests intentionally pass invalid types to verify error handling
- Development velocity - Strict typing in tests slows down test writing without proportional benefit during active development
Future Consideration: This decision should be revisited when the product is nearing its final stages. At that point, stricter linting in tests may be warranted to ensure long-term maintainability.
// eslint.config.js - Test file overrides
{
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
},
}
Pre-commit Hook
The pre-commit hook runs lint-staged automatically:
# .husky/pre-commit
npx lint-staged
lint-staged Configuration
lint-staged runs appropriate tools based on file type:
{
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,css,html,yml,yaml}": ["prettier --write"]
}
NPM Scripts
| Script | Description |
|---|---|
npm run format |
Format all files with Prettier |
npm run lint |
Run ESLint on all TypeScript/JavaScript files |
npm run validate |
Run Prettier check + TypeScript check + ESLint |
Key Files
| File | Purpose |
|---|---|
.prettierrc |
Prettier configuration |
.prettierignore |
Files to exclude from formatting |
eslint.config.js |
ESLint flat configuration (v9) |
.husky/pre-commit |
Pre-commit hook script |
.lintstagedrc.json |
lint-staged configuration |
Developer Workflow
Automatic Formatting on Commit
When you commit changes:
- Husky intercepts the commit
- lint-staged identifies staged files
- ESLint fixes auto-fixable issues
- Prettier formats the code
- Changes are automatically staged
- Commit proceeds if no errors
Manual Formatting
# Format entire codebase
npm run format
# Check formatting without changes
npx prettier --check .
# Run ESLint
npm run lint
# Run all validation checks
npm run validate
IDE Integration
For the best experience, configure your IDE:
VS Code - Install extensions:
- Prettier - Code formatter
- ESLint
Add to .vscode/settings.json:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}
Troubleshooting
"eslint --fix failed"
ESLint may fail on unfixable errors. Review the output and manually fix the issues.
"prettier --write failed"
Check for syntax errors in the file that prevent parsing.
Bypassing Hooks (Emergency)
In rare cases, you may need to bypass hooks:
git commit --no-verify -m "emergency fix"
Use sparingly - the CI pipeline will still catch formatting issues.