Linting — ESLint & Prettier
1. Philosophy
- Flat config only —
eslint.config.js(ESLint 9+). Legacy.eslintrc*not supported. - Prettier for formatting — ESLint only for code quality. No rule overlap.
- Pre-commit via husky —
lint-stagedruns on staged files only. Fast, deterministic. - CI fails on warnings — Zero-tolerance in pipeline. Local can warn.
- TypeScript-aware — Type-checked rules via
typescript-eslint.
2. Minimum Versions
| Technology | Minimum Version |
|---|---|
| ESLint | 9.0+ |
| Prettier | 3.3+ |
| Node.js | 22+ |
| pnpm | 11+ |
3. ESLint — Flat Config (eslint.config.js)
Structure
// eslint.config.js
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginReact from "eslint-plugin-react";
import pluginReactHooks from "eslint-plugin-react-hooks";
import pluginJsxA11y from "eslint-plugin-jsx-a11y";
import prettierConfig from "eslint-config-prettier";
export default tseslint.config(
{ ignores: ["dist/", "node_modules/", "*.config.js", "*.config.ts"] },
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
parserOptions: {
project: "./tsconfig.json",
tsconfigRootDir: import.meta.dirname,
},
},
settings: { react: { version: "18.3" } },
plugins: {
react: pluginReact,
"react-hooks": pluginReactHooks,
"jsx-a11y": pluginJsxA11y,
},
rules: {
// React
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
// JSX a11y
"jsx-a11y/anchor-is-valid": "error",
"jsx-a11y/click-events-have-key-events": "warn",
"jsx-a11y/no-noninteractive-element-interactions": "warn",
// TypeScript (type-checked)
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-floating-promises": "warn",
// General
"no-console": ["warn", { allow: ["warn", "error"] }],
eqeqeq: ["error", "always"],
},
},
prettierConfig,
);
Key rules
| Rule | Level | Why |
|---|---|---|
react/react-in-jsx-scope |
off |
React 17+ auto-import |
react/prop-types |
off |
TypeScript replaces it |
react-hooks/exhaustive-deps |
warn |
Catches stale closures |
@typescript-eslint/no-unused-vars |
error |
Clean code, allows _ prefix |
@typescript-eslint/consistent-type-imports |
error |
Enables import type |
no-console |
warn (allow warn/error) |
Keeps debug logs intentional |
eqeqeq |
error |
Strict equality |
4. Prettier
.prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"bracketSpacing": true,
"arrowParens": "avoid",
"endOfLine": "lf",
"plugins": ["prettier-plugin-organize-imports"]
}
Rules
printWidth: 100— Modern screens, matches MD013 prose wraptrailingComma: "es5"— Clean diffs, valid ES5+arrowParens: "avoid"—x => xnot(x) => xprettier-plugin-organize-imports— Auto-sorts imports
5. ESLint + Prettier Integration
pnpm add -D eslint-config-prettier
// eslint.config.js (add to config array)
import prettierConfig from "eslint-config-prettier";
export default [
// ... other configs
prettierConfig, // Must be LAST — disables formatting rules
];
What it does
- Disables all ESLint rules that conflict with Prettier
- No
.prettierrcneeded in ESLint config - Prettier runs separately (via
lint-stagedor editor)
6. Husky + lint-staged
Setup owned by
gitskill — this skill references it.
Minimal setup (in package.json)
{
"scripts": {
"prepare": "husky",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check ."
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,yml,yaml,md,css}": ["prettier --write"]
}
}
Husky hook (.husky/pre-commit)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm lint-staged
Rules Husky
lint-stagedonly — runs on staged files, not whole repo- ESLint first, then Prettier — fix logic, then format
preparescript — auto-installs husky onpnpm install
7. CI/CD
Full workflow in
deployskill — this skill defines the lint command.
# .github/workflows/lint.yml
- name: Lint
run: pnpm lint
- name: Format check
run: pnpm format:check
Rules CI/CD
pnpm lint— fails on error, warns on warningpnpm format:check— fails if Prettier would change files- Run in PR pipeline — blocks merge on failure
8. Editor Config (VS Code)
.vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"typescript",
"javascriptreact",
"typescriptreact"
],
"prettier.requireConfig": true
}
Rules Editor
- Format on save — Prettier via extension
- ESLint fix on save — explicit (not auto) to avoid conflicts
- Require Prettier config — prevents formatting without config
9. Linting for Other Files
JSON / YAML / Markdown
// eslint.config.js (add to config array)
import pluginJson from "eslint-plugin-jsonc";
import pluginYaml from "eslint-plugin-yml";
import pluginMarkdown from "eslint-plugin-markdown";
export default [
// ... other configs
{
files: ["**/*.json", "**/*.jsonc"],
plugins: { jsonc: pluginJson },
languageOptions: { parser: pluginJson.parsers.jsonc },
rules: {
"jsonc/sort-keys": ["warn", { order: { type: "asc" } }],
"jsonc/no-comments": "off", // JSONC allows comments
},
},
{
files: ["**/*.yml", "**/*.yaml"],
plugins: { yml: pluginYaml },
languageOptions: { parser: pluginYaml.parsers.yaml },
rules: { "yml/sort-keys": "warn" },
},
{
files: ["**/*.md"],
plugins: { markdown: pluginMarkdown },
processor: "markdown/markdown",
rules: {
"markdown/no-html": "off",
"markdown/fenced-code-blocks": "warn",
},
},
];
CSS (via Stylelint — separate tool)
pnpm add -D stylelint stylelint-config-standard stylelint-prettier
// .stylelintrc.json
{
"extends": ["stylelint-config-standard", "stylelint-prettier"],
"rules": { "color-hex-case": "lower" }
}
Stylelint runs separately:
pnpm stylelint "**/*.css"
10. Methodology
Before using ANY ESLint/Prettier config/rule/plugin not documented in this skill:
- MCP Context7 (priority):
context7_resolve-library-id+context7_query-docsfor ESLint/Prettier/plugins. - Official docs: eslint.org, prettier.io — verify current rules + options.
- Project config:
eslint.config.js,.prettierrc,package.jsonscripts — verify against actual setup. - HARD RULE: If not in this skill AND cannot be verified against 2 authoritative sources → DO NOT USE IT. Document as assumption or risk in report to orchestrator.
11. Prohibitions
- ❌ Do not use legacy
.eslintrc*— flat config only (ESLint 9+) - ❌ Do not enable formatting rules in ESLint — Prettier owns formatting
- ❌ Do not run
eslint .on whole repo in pre-commit — uselint-staged - ❌ Do not skip
format:checkin CI — catches formatting drift - ❌ Do not disable
react-hooks/exhaustive-deps— catches real bugs - ❌ Do not use
anyin TypeScript — enable@typescript-eslint/no-explicit-any - ❌ Do not ignore
dist/,node_modules/,*.config.*— add toignores - ❌ Do not run Prettier without config —
prettier.requireConfig: true
12. References
Note: For Git hooks (husky/lint-staged), see Git Note: For CI/CD workflow templates, see Deploy Note: For JavaScript conventions, see JavaScript Note: For TypeScript rules, see TypeScript
Last updated: 2026-08