iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Lint / Format

Linting in CI catches code-quality regressions before they merge. Run formatters, linters, type checkers, security scanners, and conventional-commit checks on every PR; the goal is not to gate every commit, but to make “style and shape” reviews a thing of the past.

ESLint, Prettier, types, conventional

EXAMPLE
# 1) GitHub Actions — basic lint workflow
# .github/workflows/lint.yml
name: lint
on:
    pull_request:
    push:
        branches: [main]
concurrency:
    group: lint-${{ github.ref }}
    cancel-in-progress: true
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
            - run: npm run lint
            - run: npm run format:check
            - run: npm run typecheck

# 2) package.json scripts
{
    "scripts": {
        "lint":           "eslint . --ext .ts,.tsx,.js,.jsx --max-warnings=0",
        "lint:fix":       "eslint . --ext .ts,.tsx,.js,.jsx --fix",
        "format":         "prettier --write .",
        "format:check":   "prettier --check .",
        "typecheck":      "tsc --noEmit",
        "test":           "vitest run"
    }
}

# 3) Modern ESLint flat config
// eslint.config.js (ESLint 9+)
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import security from 'eslint-plugin-security';
import prettier from 'eslint-config-prettier';

export default [
    js.configs.recommended,
    ...tseslint.configs.recommendedTypeChecked,
    {
        languageOptions: {
            parserOptions: { project: './tsconfig.json' },
        },
        plugins: { react, 'react-hooks': reactHooks, security },
        rules: {
            'react/jsx-key':            'error',
            'react-hooks/rules-of-hooks': 'error',
            'react-hooks/exhaustive-deps': 'warn',
            'security/detect-object-injection': 'off',
        },
    },
    prettier,   // MUST be last — disables ESLint stylistic rules
];

# 4) Prettier config
// .prettierrc.json
{
    "semi":             true,
    "singleQuote":      true,
    "trailingComma":    "all",
    "printWidth":       100,
    "tabWidth":         2,
    "arrowParens":      "always"
}

# .prettierignore
node_modules
dist
build
coverage
.next

# 5) Pre-commit (catch BEFORE CI)
# .lintstagedrc.json
{
    "*.{ts,tsx,js,jsx}": ["eslint --fix", "prettier --write"],
    "*.{json,md,yaml,yml,css}": ["prettier --write"]
}

# .husky/pre-commit
npx lint-staged

# Setup
npm install -D husky lint-staged
npx husky init

# 6) Run only on changed files (faster CI)
- name: Get changed files
  id: changed
  uses: tj-actions/changed-files@v44
  with: { files: 'src/**/*.{ts,tsx}' }
- if: steps.changed.outputs.any_changed == 'true'
  run: npx eslint ${{ steps.changed.outputs.all_changed_files }}

# 7) Type-checking on PRs
# Use the project's lockfile; never reinstall types
- run: npm ci
- run: npx tsc --noEmit

# For monorepos, cache typecheck results:
- uses: actions/cache@v4
  with:
      path: .turbo
      key: turbo-typecheck-${{ github.sha }}
- run: npx turbo run typecheck --filter=...[origin/main]

# 8) Inline annotations on PRs
# ESLint --format=@microsoft/eslint-formatter-sarif → upload SARIF → GitHub Security tab
- run: npx eslint . --format=@microsoft/eslint-formatter-sarif --output-file eslint.sarif || true
- uses: github/codeql-action/upload-sarif@v3
  with: { sarif_file: eslint.sarif }

# 9) Conventional commits (PR title)
# .github/workflows/pr-title.yml
name: pr-title
on: { pull_request: { types: [opened, edited, synchronize] } }
jobs:
    lint-title:
        runs-on: ubuntu-latest
        steps:
            - uses: amannn/action-semantic-pull-request@v5
              env: { GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} }
              with:
                  types: |
                      fix
                      feat
                      docs
                      chore
                      refactor
                      test
                      perf
                      build
                      ci

# 10) Per-language linters worth wiring up
# Python:        ruff, mypy, black
# Go:            gofmt, golangci-lint, go vet
# Rust:          cargo fmt, cargo clippy --deny=warnings
# Ruby:          rubocop, brakeman
# Java:          spotbugs, checkstyle, errorprone
# C#:            dotnet format, roslyn analyzers
# PHP:           psalm/phpstan, php-cs-fixer
# CSS:           stylelint
# Shell:         shellcheck, shfmt
# Markdown:      markdownlint
# YAML:          yamllint
# Dockerfile:    hadolint
# Terraform:     terraform fmt -check, tflint, tfsec
# Kubernetes:    kubeval, kube-linter, polaris
# OpenAPI:       redocly lint, spectral

# 11) Security linting in the same workflow
# • semgrep — pattern-based + community ruleset
# • gitleaks / trufflehog — secrets in commits
# • npm audit / pnpm audit / yarn npm audit — dep CVEs
# • osv-scanner — cross-ecosystem CVE check
- uses: returntocorp/semgrep-action@v1
  with: { config: p/owasp-top-ten }
- uses: gitleaks/gitleaks-action@v2

# 12) Performance + caching
# • Cache npm/yarn/pnpm; lockfile-hash key
# • Cache ESLint cache: .eslintcache
# • Cache TypeScript build info: tsBuildInfoFile
# • Skip lint for docs-only changes (paths-ignore)

on:
    pull_request:
        paths:
            - '!docs/**'
            - '**/*.md'

# 13) Required checks + branch protection
# • Make 'lint', 'typecheck', 'test' REQUIRED in GitHub branch protection
# • Require code-owner review for sensitive paths
# • Auto-merge when checks pass + reviewer approves

# 14) Auto-fix bots
# Renovate / Dependabot keep deps current; auto-merge MINOR/PATCH after CI passes.
# Sweep removes unused exports; mend.io for security upgrades.

# 15) Common bugs
# • Lint passing locally, failing in CI — different Node/npm versions; pin in .nvmrc + cache lockfile
# • Format-on-save + CI format check disagree — pin Prettier version in package.json
# • ESLint warnings ignored — set --max-warnings=0 in CI
# • Type-check on huge monorepo SLOW — use --incremental + project references + turborepo cache
# • Pre-commit hook bypassed (--no-verify) — that's the user's choice, but CI catches it later
# • Conventional commit rule rejects 'Revert ...' style — extend allowed types
# • Linter rule too aggressive — comment-out + open ticket; don't disable globally
# • Format-only PRs pile up — use Renovate/dependabot config to auto-fix in scheduled PRs

Why it matters

Wire lint, format-check, type-check, security scan, and conventional-commit checks into CI on every PR; pre-commit hooks for fast feedback; required status checks on protected branches. Make warnings fail (--max-warnings=0), cache aggressively, and reach for inline SARIF annotations so reviewers see issues right in the diff.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
- run: npm run lint -- --max-warnings 0
- run: npm run typecheck
- run: npx prettier --check .
Try it Yourself »

Discussion

Loading…