ESLint and Prettier: Building Consistent Code Style

Master code linting configuration, rule customization and team collaboration best practices

ESLint and Prettier: Building Consistent Code Style

Code quality tools are fundamental for team collaboration. This article explores ESLint and Prettier configuration and best practices.

Tool Overview

ESLint vs Prettier

Tool Focus:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   ESLint                                            │
│   ├── Code quality checks                          │
│   ├── Find potential issues                        │
│   ├── Enforce coding standards                     │
│   └── Auto-fix some problems                       │
│                                                     │
│   Prettier                                          │
│   ├── Code formatting                              │
│   ├── Unified code style                           │
│   ├── No debates about format                      │
│   └── Multi-language support                       │
│                                                     │
└─────────────────────────────────────────────────────┘
ToolFocusExamples
ESLintCode logicUnused variables, equality checks
PrettierCode formatIndentation, line breaks, quotes

ESLint Configuration

Basic Installation

# Install ESLint
pnpm add -D eslint

# Initialize configuration
pnpm eslint --init

# Install TypeScript support
pnpm add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin

# Install React support
pnpm add -D eslint-plugin-react eslint-plugin-react-hooks

Flat Config (ESLint 9+)

// eslint.config.js
import js from '@eslint/js';
import typescript from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';

export default [
  // Ignored files
  {
    ignores: ['dist/**', 'node_modules/**', '*.config.js'],
  },

  // Base configuration
  js.configs.recommended,

  // TypeScript configuration
  {
    files: ['**/*.ts', '**/*.tsx'],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        project: './tsconfig.json',
      },
    },
    plugins: {
      '@typescript-eslint': typescript,
    },
    rules: {
      ...typescript.configs.recommended.rules,
      '@typescript-eslint/no-unused-vars': ['error', {
        argsIgnorePattern: '^_',
        varsIgnorePattern: '^_',
      }],
      '@typescript-eslint/explicit-function-return-type': 'off',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },

  // React configuration
  {
    files: ['**/*.jsx', '**/*.tsx'],
    plugins: {
      react,
      'react-hooks': reactHooks,
    },
    rules: {
      ...react.configs.recommended.rules,
      ...reactHooks.configs.recommended.rules,
      'react/react-in-jsx-scope': 'off',
      'react/prop-types': 'off',
    },
    settings: {
      react: {
        version: 'detect',
      },
    },
  },
];

Common Rules

// eslint.config.js
export default [
  {
    rules: {
      // Code quality
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'no-debugger': 'error',
      'no-unused-vars': 'error',
      'no-undef': 'error',

      // Best practices
      'eqeqeq': ['error', 'always'],
      'no-eval': 'error',
      'no-implied-eval': 'error',
      'no-new-func': 'error',
      'prefer-const': 'error',

      // Code style (consider leaving to Prettier)
      'semi': ['error', 'always'],
      'quotes': ['error', 'single'],
      'indent': ['error', 2],

      // Complexity control
      'complexity': ['warn', 10],
      'max-depth': ['warn', 4],
      'max-lines-per-function': ['warn', 50],
    },
  },
];

Prettier Configuration

Basic Installation

# Install Prettier
pnpm add -D prettier

# Install ESLint integration
pnpm add -D eslint-config-prettier eslint-plugin-prettier

Configuration File

// prettier.config.js
export default {
  // Basic formatting
  printWidth: 80,              // Line width
  tabWidth: 2,                 // Indentation
  useTabs: false,              // Use spaces
  semi: true,                  // Semicolons
  singleQuote: true,           // Single quotes
  quoteProps: 'as-needed',     // Object property quotes

  // JSX
  jsxSingleQuote: false,       // JSX double quotes

  // Trailing commas
  trailingComma: 'es5',        // ES5 compatible

  // Brackets
  bracketSpacing: true,        // Object bracket spacing
  bracketSameLine: false,      // JSX bracket new line
  arrowParens: 'always',       // Arrow function parens

  // Special handling
  endOfLine: 'lf',             // Line endings
  proseWrap: 'preserve',       // Markdown wrapping

  // File-specific overrides
  overrides: [
    {
      files: '*.md',
      options: {
        proseWrap: 'always',
      },
    },
    {
      files: '*.json',
      options: {
        printWidth: 200,
      },
    },
  ],
};

Ignore File

# .prettierignore
dist
build
coverage
node_modules
pnpm-lock.yaml
*.min.js
*.min.css

ESLint + Prettier Integration

Configuration Integration

// eslint.config.js
import prettier from 'eslint-plugin-prettier';
import prettierConfig from 'eslint-config-prettier';

export default [
  // ... other configs

  // Prettier integration (place last)
  prettierConfig,
  {
    plugins: {
      prettier,
    },
    rules: {
      'prettier/prettier': 'error',
    },
  },
];

package.json Scripts

{
  "scripts": {
    "lint": "eslint src --ext .ts,.tsx,.js,.jsx",
    "lint:fix": "eslint src --ext .ts,.tsx,.js,.jsx --fix",
    "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
    "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
    "check": "pnpm lint && pnpm format:check"
  }
}

Editor Integration

VS Code Configuration

// .vscode/settings.json
{
  // Format on save
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",

  // ESLint auto-fix
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },

  // Language-specific settings
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

  // ESLint configuration
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact"
  ]
}
// .vscode/extensions.json
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "bradlc.vscode-tailwindcss",
    "streetsidesoftware.code-spell-checker"
  ]
}

Git Hooks Integration

Husky + lint-staged

# Install
pnpm add -D husky lint-staged

# Initialize husky
pnpm husky install
pnpm husky add .husky/pre-commit "pnpm lint-staged"
// package.json
{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix",
      "prettier --write"
    ],
    "*.{json,css,md}": [
      "prettier --write"
    ]
  }
}

Commit Message Checking

# Install commitlint
pnpm add -D @commitlint/cli @commitlint/config-conventional

# Add hook
pnpm husky add .husky/commit-msg "npx --no -- commitlint --edit $1"
// commitlint.config.js
export default {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore'],
    ],
    'subject-max-length': [2, 'always', 72],
  },
};

Custom Rules

ESLint Custom Rule

// eslint-plugin-custom/no-console-log.js
export default {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'Disallow console.log',
    },
    fixable: 'code',
    schema: [],
  },
  create(context) {
    return {
      CallExpression(node) {
        if (
          node.callee.type === 'MemberExpression' &&
          node.callee.object.name === 'console' &&
          node.callee.property.name === 'log'
        ) {
          context.report({
            node,
            message: 'Do not use console.log, use logger instead',
            fix(fixer) {
              return fixer.replaceText(
                node.callee,
                'logger.debug'
              );
            },
          });
        }
      },
    };
  },
};

Best Practices Summary

Code Quality Tool Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Configuration Strategy                            │
│   ├── ESLint for code quality                      │
│   ├── Prettier for formatting                      │
│   ├── Use eslint-config-prettier to avoid conflicts│
│   └── Keep configuration simple                    │
│                                                     │
│   Team Collaboration                                │
│   ├── Unified editor settings                      │
│   ├── Use Git hooks for enforcement               │
│   ├── Run checks in CI/CD                          │
│   └── Regularly update dependencies                │
│                                                     │
│   Gradual Adoption                                  │
│   ├── Start with relaxed rules                     │
│   ├── Gradually tighten rules                      │
│   ├── New code enforced, old code gradual         │
│   └── Document rule decisions                      │
│                                                     │
└─────────────────────────────────────────────────────┘
ScenarioRecommendation
New projectsESLint + Prettier + Husky
Large teamsShared config packages
Open sourceUse popular configs
Legacy projectsGradual migration

Good code style isn’t debated into existence - it’s enforced by tools.