Code Quality Standards¶
This document outlines the code quality standards, tools, and practices for the Coaching App project.
Table of Contents¶
- Overview
- ESLint Configuration
- Prettier Configuration
- TypeScript Configuration
- Pre-commit Hooks
- Code Coverage
- Running Quality Checks
Overview¶
We use automated tools to maintain consistent code quality across all packages:
- ESLint: Catches code quality issues and enforces best practices
- Prettier: Ensures consistent code formatting
- TypeScript: Provides type safety and catches errors at compile time
- Jest: Generates code coverage reports
- Husky + lint-staged: Runs checks automatically before commits
ESLint Configuration¶
Root Configuration¶
Create .eslintrc.js in the project root:
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2022,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
env: {
browser: true,
node: true,
es2022: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
'prettier', // Must be last to override other configs
],
plugins: ['@typescript-eslint', 'import'],
rules: {
// TypeScript
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
// Import order
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
alphabetize: { order: 'asc', caseInsensitive: true },
},
],
// General
'no-console': ['warn', { allow: ['warn', 'error'] }],
'prefer-const': 'error',
},
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
},
},
},
};
Package-Specific Overrides¶
Each package can have its own .eslintrc.js extending the root:
Frontend/Mobile (React):
module.exports = {
extends: ['../. eslintrc.js', 'plugin:react/recommended', 'plugin:react-hooks/recommended'],
plugins: ['react', 'react-hooks'],
settings: {
react: {
version: 'detect',
},
},
rules: {
'react/react-in-jsx-scope': 'off', // Not needed with new JSX transform
'react/prop-types': 'off', // Using TypeScript for prop types
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
};
Backend (Hono):
module.exports = {
extends: ['../.eslintrc.js'],
env: {
node: true,
},
rules: {
'no-console': 'off', // Console is fine in backend
},
};
Prettier Configuration¶
Create .prettierrc.json in the project root:
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "always",
"endOfLine": "lf",
"bracketSpacing": true,
"jsxSingleQuote": false,
"proseWrap": "preserve"
}
Create .prettierignore:
# Dependencies
node_modules
.yarn
# Build outputs
dist
build
.expo
.next
# Coverage
coverage
# Docker
.docker
# Misc
.DS_Store
*.log
.env
.env.local
TypeScript Configuration¶
Root Configuration¶
Create tsconfig.json in the project root:
{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"lib": ["ES2022"],
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"incremental": true,
// Strict type checking
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
// Additional checks
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// Path mapping for workspace
"baseUrl": ".",
"paths": {
"@coaching-app/shared": ["./shared/src"],
"@coaching-app/shared/*": ["./shared/src/*"]
}
},
"exclude": ["node_modules", "dist", "build", ".expo"]
}
Package-Specific Configurations¶
Each package extends the root config:
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
Pre-commit Hooks¶
Husky Setup¶
Initialize Husky:
Create .husky/pre-commit:
lint-staged Configuration¶
Add to package.json:
{
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write",
"jest --bail --findRelatedTests --passWithNoTests"
],
"*.{json,md,yml,yaml}": ["prettier --write"]
}
}
This runs on staged files only:
- ESLint fixes issues
- Prettier formats code
- Jest runs tests for affected files
- Prettier formats non-code files
Code Coverage¶
Coverage Requirements¶
- Minimum threshold: 80% for all metrics
- Metrics: Statements, Branches, Functions, Lines
Jest Configuration¶
Add to each package's jest.config.js:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/**/*.test.{ts,tsx}',
'!src/**/*.spec.{ts,tsx}',
'!src/**/index.ts',
],
coverageThreshold: {
global: {
statements: 80,
branches: 80,
functions: 80,
lines: 80,
},
},
coverageReporters: ['text', 'text-summary', 'html', 'lcov'],
};
Viewing Coverage Reports¶
After running yarn test:coverage:
# Open HTML report
open coverage/lcov-report/index.html # macOS
xdg-open coverage/lcov-report/index.html # Linux
start coverage/lcov-report/index.html # Windows
Running Quality Checks¶
Local Development¶
# Format all files
yarn format
# Check formatting without changing files
yarn format:check
# Lint all packages
yarn lint
# Auto-fix linting issues
yarn lint:fix
# Type check all packages
yarn type-check
# Run all tests
yarn test
# Run tests in watch mode
yarn test:watch
# Generate coverage report
yarn test:coverage
# Run all checks (recommended before committing)
yarn format && yarn lint && yarn type-check && yarn test
Package-Specific Checks¶
# Frontend only
yarn workspace @coaching-app/frontend lint
yarn workspace @coaching-app/frontend test
# Mobile only
yarn workspace @coaching-app/mobile type-check
# Shared package only
yarn workspace @coaching-app/shared test:coverage
CI/CD Pipeline¶
Quality checks run automatically in CI/CD:
- Linting:
yarn lint- must pass with no errors - Type checking:
yarn type-check- must pass with no errors - Tests:
yarn test:coverage- must pass with 80%+ coverage - Formatting:
yarn format:check- must pass
Best Practices¶
Writing Maintainable Code¶
- Use meaningful names
// Bad
const d = new Date();
const f = (x) => x * 2;
// Good
const currentDate = new Date();
const doubleValue = (value: number) => value * 2;
- Keep functions small and focused
// Each function does one thing
const validateEmail = (email: string): boolean => {
/* ... */
};
const sendEmail = (to: string, subject: string, body: string): Promise<void> => {
/* ... */
};
- Avoid deep nesting
// Bad
if (user) {
if (user.isActive) {
if (user.hasPermission) {
// ...
}
}
}
// Good - early returns
if (!user) return;
if (!user.isActive) return;
if (!user.hasPermission) return;
// ...
- Use TypeScript features
// Use proper types
import { UserRole } from '@coaching-app/shared';
interface User {
id: string;
email: string;
role: UserRole; // enum: admin, manager, coach, teacher
}
// Avoid 'any'
const getUser = (id: string): Promise<User> => {
/* ... */
};
- Write self-documenting code
Code Review Checklist¶
- Code follows ESLint rules
- Code is properly formatted (Prettier)
- No TypeScript errors or
anytypes - Tests are included and pass
- Coverage meets 80% threshold
- No console.logs in production code
- Error handling is appropriate
- Code is readable and maintainable
Troubleshooting¶
ESLint Issues¶
"Cannot find module" errors:
ESLint not picking up changes:
Prettier Conflicts¶
Prettier and ESLint fighting:
- Ensure
eslint-config-prettieris the LAST item inextends - Run
yarn lint:fixthenyarn format
TypeScript Errors¶
Cannot find '@coaching-app/shared':
- Check
tsconfig.jsonpaths configuration - Run
yarn installto link workspace packages
Coverage Issues¶
Coverage below threshold:
- Run
yarn test:coverageto see which files need tests - Focus on critical business logic first
- Exclude generated/boilerplate code in
collectCoverageFrom