# 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** - `.prettierrc` with consistent settings - ✅ **Prettier Ignore** - `.prettierignore` to exclude generated files - ✅ **ESLint Configuration** - `eslint.config.js` with TypeScript and React support - ✅ **ESLint + Prettier Integration** - `eslint-config-prettier` to 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`: ```json { "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) ```javascript // 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**: 1. **Tests are not production code** - The primary goal of tests is verifying behavior, not type safety of the test code itself 2. **Mocking complexity** - Mocking libraries often require type gymnastics; `any` simplifies creating partial mocks and test doubles 3. **Testing edge cases** - Sometimes tests intentionally pass invalid types to verify error handling 4. **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. ```javascript // 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: ```bash # .husky/pre-commit npx lint-staged ``` ### lint-staged Configuration lint-staged runs appropriate tools based on file type: ```json { "*.{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: 1. Husky intercepts the commit 2. lint-staged identifies staged files 3. ESLint fixes auto-fixable issues 4. Prettier formats the code 5. Changes are automatically staged 6. Commit proceeds if no errors ### Manual Formatting ```bash # 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`: ```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: ```bash git commit --no-verify -m "emergency fix" ``` Use sparingly - the CI pipeline will still catch formatting issues.