Skip to content
Production Ready — Part 13 of 30

Linting and Formatting on Autopilot

Written by claude-sonnet-4 · Edited by claude-sonnet-4
eslintprettierlintingformattingautomationci-cdpre-commitcode-qualitypythontypescriptruffhuskylint-stagedproduction

Production Ready — Part 13 of 30


The PR That Took Three Hours to Review

Last year, a senior engineer I know told me about a pull request that broke his team for an afternoon. Not because the logic was wrong. Not because there were security holes. Because the code was a mess.

Different indentation styles in the same file. Single quotes fighting double quotes. An import block that was half-sorted alphabetically and half not. Console logs left in from debugging. A var where every other file used const.

The reviewer spent forty-five minutes leaving nit comments. The author spent ninety minutes arguing about which comments were valid and fixing the ones they agreed with. A second review was needed. The actual logic — which was fine, by the way — got three minutes of attention.

This is what codebases look like when you skip the tools that automate the boring stuff. And in 2025, as Faros AI's research on 10,000 developers showed, this problem is getting worse: AI-generated code has increased average PR size by 154%, and code review time has jumped 91%. Your reviewers are drowning. Automated linting and formatting isn't a nice-to-have anymore. It's the thing that keeps human review time focused on what actually matters.

This article is about getting that setup right — from scratch, in about an hour, in a way that will outlast any individual team member.


What ESLint Does vs. What Prettier Does

Before you set anything up, you need to understand the division of labor. These two tools have different jobs, and conflating them leads to config fights.

Prettier is a formatter. It doesn't care about correctness — it cares about appearance. Indentation, semicolons, quote style, line length, trailing commas: Prettier decides all of that and reformats your code to match. You don't argue with Prettier. You configure it once and forget it.

ESLint is a linter. It cares about correctness and quality. Unused variables, missing return types, calling .then() without a .catch(), using == instead of ===: ESLint catches the patterns that can cause real bugs. ESLint also used to do formatting, but that role now belongs entirely to Prettier.

The key insight: Prettier handles formatting, ESLint handles code quality. They have a well-known overlap problem — some ESLint rules conflict with Prettier's output — which is solved by eslint-config-prettier. Run them in that order and they coexist cleanly.

ESLint has been on an extraordinary growth trajectory. According to ESLint's 2025 year-in-review, weekly npm downloads grew from 42.7 million at the start of 2025 to 70.7 million by year-end — a 65% increase. Nearly three billion downloads in a single year. Every serious JavaScript and TypeScript project is running it. The question is whether yours is running it well.


Setting Up the Stack

Here's the production-grade setup. This works for a JavaScript or TypeScript project. Python setup follows after.

Step 1: Install the Tools

# Install ESLint, Prettier, and the glue between them
npm install --save-dev eslint prettier eslint-config-prettier

# For TypeScript projects, add the TypeScript parser and rules
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin

# For pre-commit automation
npm install --save-dev husky lint-staged

Step 2: Configure Prettier

Create a .prettierrc at your project root. This is your one source of truth for formatting. Every developer on your team, every CI run, every editor integration reads from this file:

{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5",
  "printWidth": 80,
  "tabWidth": 2,
  "arrowParens": "avoid"
}

Also add a .prettierignore — Prettier will try to format everything otherwise:

node_modules/
dist/
build/
*.min.js
coverage/

Step 3: Configure ESLint (Flat Config)

ESLint v9, which became the default in 2024 and saw full legacy config removal in v10, uses a flat config format. If you're on a new project, start here. Create eslint.config.js:

import js from '@eslint/js';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import prettier from 'eslint-config-prettier';

export default [
  js.configs.recommended,
  {
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      parser: tsParser,
      parserOptions: {
        project: './tsconfig.json',
      },
    },
    plugins: {
      '@typescript-eslint': tsPlugin,
    },
    rules: {
      '@typescript-eslint/no-unused-vars': 'error',
      '@typescript-eslint/no-explicit-any': 'error',
      'no-console': 'warn',
      'prefer-const': 'error',
      'no-var': 'error',
      'eqeqeq': 'error',
    },
  },
  prettier, // Always last — disables ESLint rules that conflict with Prettier
  {
    ignores: ['node_modules/**', 'dist/**', 'build/**', '**/*.min.js'],
  },
];

Step 4: Wire Up Pre-Commit Hooks

This is the part most teams skip, and it's the part that actually prevents bad code from entering the repository. Husky and lint-staged run your tools automatically before every commit — catching issues when the developer is still local, not when CI catches them ten minutes later:

# Initialize husky
npx husky init

Then configure lint-staged in your package.json:

{
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix --max-warnings=0",
      "prettier --write"
    ],
    "*.{json,md,yml,yaml,css}": [
      "prettier --write"
    ]
  }
}

And add the hook:

echo "npx lint-staged" > .husky/pre-commit

Now every commit automatically formats and lints only the files being committed. It's fast (lint-staged only processes staged files), it's automatic, and it's team-wide.


The Python Equivalent

For Python projects, the stack is different but the philosophy is identical:

# Install the tools
pip install ruff black pre-commit

# Or with uv (faster)
uv add --dev ruff black pre-commit

Ruff has largely replaced both Flake8 and isort in the Python ecosystem — it's dramatically faster and handles import sorting, code quality rules, and more. Black handles formatting the same way Prettier does: opinionated, non-negotiable.

Create pyproject.toml configuration:

[tool.black]
line-length = 88
target-version = ['py311']

[tool.ruff]
line-length = 88
target-version = "py311"
select = ["E", "F", "I", "N", "UP", "B", "SIM"]
ignore = []

[tool.ruff.isort]
known-first-party = ["your_package_name"]

Pre-commit config (.pre-commit-config.yaml):

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  - repo: https://github.com/psf/black
    rev: 24.10.0
    hooks:
      - id: black

Install the hooks:

pre-commit install

From this point forward, every commit is automatically formatted and linted.


CI/CD: The Safety Net Nobody Skips

Pre-commit hooks run locally. But developers can bypass them (git commit --no-verify), and new machines might not have them installed. Your CI pipeline is the mandatory enforcement layer.

Here's a GitHub Actions workflow that enforces both:

# .github/workflows/lint.yml
name: Lint and Format Check

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      
      - name: Check formatting
        run: npx prettier --check .
      
      - name: Lint
        run: npx eslint . --max-warnings=0

The --max-warnings=0 flag is important. Without it, warnings accumulate silently and eventually get promoted to errors after months of drift. Start strict. If you inherit a legacy codebase with thousands of violations, use ESLint's bulk suppression feature (introduced in ESLint v9 in 2025) to suppress existing violations without hiding new ones.


The AI Code Problem

Here's why this matters even more in 2026 than it did two years ago.

According to research tracking 4.2 million developers from November 2025 through February 2026, AI-authored code now makes up 26.9% of all production code — up from 22% the previous quarter. Nearly a third of the code your daily AI users merge into production is written by AI.

AI-generated code has a specific failure mode: it's often syntactically correct and logically coherent, but it routinely violates team conventions. It uses var in a codebase that standardized on const. It forgets to remove console logs. It uses double quotes when everyone else uses single. It generates code that's functionally fine but stylistically foreign.

As Swarmia's analysis of the DORA 2025 report put it: AI-generated code "appears to work in isolation but systematically violates the patterns your team spent years establishing." And it passes code review because reviewers can't spot style drift at scale.

Automated linting catches this mechanically. It doesn't matter if the code was written by a human or an AI — the tools run the same checks. When no-var is set to error, a var from an AI assistant fails the commit hook just like it would from a junior developer. The standard is the standard, and the machine enforces it.


Editor Integration: Making It Invisible

The best linting setup is one developers don't think about. Wire it into their editor so issues appear as they type:

VS Code — Install the ESLint and Prettier extensions, then add to .vscode/settings.json:

{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
  "eslint.validate": ["javascript", "typescript", "typescriptreact"]
}

Commit this file to your repository. Every developer who clones the repo gets a pre-configured editor that auto-formats on save. Onboarding friction drops to near zero.

Cursor / Windsurf — These AI editors inherit VS Code's extension ecosystem. The same settings file works.

In November 2025, GitHub announced ESLint integration with Copilot code review as a public preview — ESLint violations now surface directly in Copilot's code review suggestions. The tooling is converging. Linting results are increasingly available to AI assistants, code review tools, and IDEs in a unified way.


The One Mistake Teams Make

They turn on too many rules at once.

A developer thread on Reddit from February 2026 described running seven overlapping code quality tools — ESLint, Prettier, SonarQube, Semgrep, Dependabot, Snyk, and GitHub checks — all flagging the same issues in slightly different ways. The result: developers muted alerts, reviews slowed down, and teams spent more time fixing tool complaints than actual code problems.

Start with the essentials. Enforce the basics hard. Add rules incrementally as the team demonstrates it can handle them. Three rules enforced 100% of the time are worth more than thirty rules ignored because they produce too much noise.

A realistic starting ruleset for a new project:

  • prefer-const — error
  • no-var — error
  • eqeqeq — error
  • no-unused-vars — error
  • no-console — warn
  • Prettier for everything else

That's it to start. When those feel invisible — when the team doesn't think about them because they're just caught automatically — add the next tier.


Your Linting and Formatting Checklist

Initial Setup

  • Prettier installed and configured (.prettierrc committed to repo)
  • ESLint installed with appropriate plugins for your language stack
  • eslint-config-prettier installed and placed last in ESLint config
  • .eslintignore / ignores configured to skip generated files and node_modules

Pre-Commit Automation

  • Husky (JS) or pre-commit (Python) installed and initialized
  • lint-staged configured to run only on staged files
  • Pre-commit hook runs both formatter and linter with auto-fix

CI/CD Enforcement

  • CI pipeline runs prettier --check and fails on unformatted code
  • CI pipeline runs ESLint with --max-warnings=0
  • CI job runs before merge — no exceptions

Editor Integration

  • .vscode/settings.json committed with formatOnSave and ESLint auto-fix
  • Team onboarding docs mention required extensions

Team Health

  • No more than 10-15 ESLint rules active at launch — start focused
  • Rules added incrementally as existing rules become invisible
  • If inheriting legacy code with violations, use bulk suppression rather than turning off rules
  • Revisit your config every quarter — deprecate rules that never catch anything

Ask The Guild

This week's community prompt: What's the worst code review argument your team has had about style rather than substance — tabs vs. spaces, semicolons, quote style, you name it? How long did it take before you automated it away? Or are you still having it? Drop your war story in the thread. The more painful, the more useful.


Sources: ESLint's 2025 Year in Review (ESLint Blog) | The AI Productivity Paradox: Telemetry from 10,000 Developers (Faros AI) | AI-Authored Code Now 26.9% of Production Code (ShiftMag) | What the 2025 DORA Report Tells Us About AI Readiness (Swarmia) | How Many Code Quality Tools Is Too Many? (Reddit DevOps)

Copy A Prompt Next

Review and debug

If this article changed how you think about the problem, copy a prompt that turns that judgment into one safe, reviewable next step.

Matching public prompts

23

Keep the task scoped, copy the prompt, then inspect one reviewable diff before the agent continues.

Need the safest first move instead? Open the curated sample prompts before you browse the broader library.

Working With AI ToolsWorking With AI Tools

v0 by Vercel — UI Components From a Text Prompt

Generate production-ready UI components with v0 and integrate them into your projects.

Preview
"I want v0 to generate a React component for this screen:
[describe the UI, data fields, visual style, empty state, loading state, and mobile behavior]
The component must:
1. work in a Next.js + Tailwind project
2. be easy to wire to real data later
Production Ready

Use this production insight inside a full build sequence

Production articles show you what breaks in the real world. The right path turns that lesson into a sequence you can ship with instead of just nodding at.

Best Next Path

DevOps and Deployment

Guild Member · $29/mo

Connect the code to production: CI/CD, hosting, observability, DNS, and the runtime habits that keep launches boring.

25 lessonsIncluded with the full Guild Member library

Need the free route first?

Start with Start Here — Build Safely With AI if you want the workflow and vocabulary before you dive into the deeper path above.

T

About Tom Hundley

Tom Hundley writes for builders who need stronger technical judgment around AI-assisted software work. The Guild turns production experience into public articles, copy-paste prompts, and structured learning paths that help non-software developers supervise AI agents more safely.

Do this next

Leave this article with one concrete move. Copy the matching prompt, or start with the path that teaches the safest next skill in sequence.