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…