Skip to content

Code Quality Standards

This document outlines the code quality standards, tools, and practices for the Coaching App project.

Table of Contents

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:

yarn add -D husky
yarn husky install

Create .husky/pre-commit:

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn lint-staged

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:

  1. ESLint fixes issues
  2. Prettier formats code
  3. Jest runs tests for affected files
  4. 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:

  1. Linting: yarn lint - must pass with no errors
  2. Type checking: yarn type-check - must pass with no errors
  3. Tests: yarn test:coverage - must pass with 80%+ coverage
  4. Formatting: yarn format:check - must pass

Best Practices

Writing Maintainable Code

  1. Use meaningful names
// Bad
const d = new Date();
const f = (x) => x * 2;

// Good
const currentDate = new Date();
const doubleValue = (value: number) => value * 2;
  1. 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> => {
  /* ... */
};
  1. 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;
// ...
  1. 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> => {
  /* ... */
};
  1. Write self-documenting code
    // Code explains itself
    const isEligibleForDiscount = (user: User): boolean => {
      return user.membershipTier === 'premium' && user.accountAge >= 365;
    };
    

Code Review Checklist

  • Code follows ESLint rules
  • Code is properly formatted (Prettier)
  • No TypeScript errors or any types
  • 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:

# Reinstall dependencies
yarn install

ESLint not picking up changes:

# Restart VS Code ESLint server
# CMD/CTRL + Shift + P -> "ESLint: Restart ESLint Server"

Prettier Conflicts

Prettier and ESLint fighting:

  • Ensure eslint-config-prettier is the LAST item in extends
  • Run yarn lint:fix then yarn format

TypeScript Errors

Cannot find '@coaching-app/shared':

  • Check tsconfig.json paths configuration
  • Run yarn install to link workspace packages

Coverage Issues

Coverage below threshold:

  • Run yarn test:coverage to see which files need tests
  • Focus on critical business logic first
  • Exclude generated/boilerplate code in collectCoverageFrom

Resources