Skills shared-tooling-eslint-prettier
ESLint 9/10 flat config with defineConfig(), Prettier v3.0+ shared config, eslint-config-prettier integration, typescript-eslint v8+ projectService
git clone https://github.com/agents-inc/skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/agents-inc/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/src/skills/shared-tooling-eslint-prettier" ~/.claude/skills/agents-inc-skills-shared-tooling-eslint-prettier-af0a7d && rm -rf "$T"
src/skills/shared-tooling-eslint-prettier/SKILL.mdESLint & Prettier
Quick Guide: ESLint 9+ flat config with
anddefineConfig(). typescript-eslint v8+ withglobalIgnores(). Prettier shared config with consistent formatting. eslint-config-prettier to disable conflicting rules. eslint-plugin-only-warn for better DX.projectService: trueWARNING: ESLint 10 was released February 2026 and completely removes .eslintrc support. Migrate to flat config now.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
, named constants)import type
(You MUST use ESLint 9+ flat config with
from defineConfig()
- NOT legacy .eslintrc)eslint/config
(You MUST use
for explicit global ignore patterns - NOT bare globalIgnores()
property)ignores
(You MUST use typescript-eslint v8+ with
for typed linting)projectService: true
(You MUST include eslint-plugin-only-warn to convert errors to warnings for better DX)
(You MUST use eslint-config-prettier to disable formatting rules that conflict with Prettier)
</critical_requirements>
Auto-detection: ESLint 9 flat config, defineConfig, globalIgnores, eslint.config.ts, Prettier config, prettier.config.mjs, eslint-config-prettier, eslint-plugin-only-warn, typescript-eslint projectService, .eslintrc migration
When to use:
- Setting up ESLint 9/10 flat config (standalone or shared)
- Configuring Prettier with shared config
- Migrating from legacy .eslintrc to flat config
- Integrating ESLint and Prettier (eslint-config-prettier)
- Configuring typescript-eslint v8+ with projectService
- Setting up custom ESLint rules (named exports, import restrictions, type imports)
When NOT to use:
- Runtime code (this is build-time tooling only)
- CI/CD pipeline configuration
- Git hooks or lint-staged setup
- TypeScript compiler configuration (tsconfig)
- Bundler configuration
Detailed Resources:
- examples/core.md - Essential flat config + Prettier setup
- examples/eslint.md - Advanced ESLint patterns (shared configs, custom rules, ESLint 10 migration)
- examples/prettier.md - Advanced Prettier patterns (TS config, experimental options, ignore files)
- reference.md - Version reference and official documentation links
<philosophy>
Philosophy
Linting and formatting should be fast, consistent, and non-blocking. Developers should not fight with tools - tools should help catch issues early while staying out of the way during development.
Core principles:
- Warnings, not errors - Use only-warn to convert lint errors to warnings so developers can iterate without being blocked
- Single source of truth - Shared configs prevent drift between packages and team members
- Automate formatting - Prettier handles all formatting decisions; ESLint handles code quality only
- No conflicts - eslint-config-prettier ensures ESLint and Prettier never disagree
<patterns>
Core Patterns
Pattern 1: ESLint 9+ Flat Config with defineConfig()
ESLint 9+ uses flat config. The
defineConfig() helper from eslint/config provides type safety and automatic array flattening. globalIgnores() explicitly marks global ignore patterns.
// eslint.config.ts — the essential flat config import js from "@eslint/js"; import { defineConfig, globalIgnores } from "eslint/config"; import tseslint from "typescript-eslint"; import eslintConfigPrettier from "eslint-config-prettier"; import * as onlyWarnPlugin from "eslint-plugin-only-warn"; export default defineConfig( globalIgnores(["dist/**", "generated/**", "node_modules/**"]), js.configs.recommended, eslintConfigPrettier, tseslint.configs.recommended, { plugins: { "only-warn": onlyWarnPlugin } }, // must be last );
Key points:
defineConfig() auto-flattens arrays (no spread operators needed), globalIgnores() prevents ambiguous behavior of bare ignores, only-warn converts all preceding errors to warnings, eslint-config-prettier disables formatting rules that conflict with Prettier.
See examples/core.md for the complete standalone and shared config patterns.
Pattern 2: typescript-eslint v8+ with projectService
The
projectService feature (stable since v8) auto-discovers the nearest tsconfig.json for each file, replacing the fragile manual project path.
parserOptions: { projectService: true, allowDefaultProject: ["*.config.ts", "*.config.mjs"], },
Key points: Eliminates need for tsconfig.eslint.json files, faster than manual project configuration,
allowDefaultProject handles config files not in tsconfig.
See examples/core.md for full typescript-eslint configuration.
Pattern 3: Shared Config Pattern (Monorepos/Teams)
For teams or monorepos, extract linting config into a shared package to prevent drift.
// packages/eslint-config/base.ts — shared config export const baseConfig = defineConfig( globalIgnores(["dist/**", "generated/**"]), js.configs.recommended, eslintConfigPrettier, tseslint.configs.recommended, { plugins: { "only-warn": onlyWarnPlugin } }, ); // apps/my-app/eslint.config.ts — consuming shared config export default defineConfig(baseConfig, customRules, { rules: { "no-console": "warn" }, });
Key points:
defineConfig() auto-flattens so no spread operators needed, single source of truth prevents config drift, TypeScript config file for type checking.
See examples/eslint.md for shared config and custom rules patterns.
Pattern 4: Prettier Shared Config
Prettier config should be consistent across all packages. Use a shared config for teams.
// prettier.config.mjs const config = { printWidth: 100, semi: true, singleQuote: false, bracketSpacing: true, arrowParens: "always", endOfLine: "lf", bracketSameLine: false, // trailingComma: "all" is the default in Prettier 3.0+ }; export default config;
Key points:
trailingComma: "all" is the default in v3.0+ (don't set it explicitly), bracketSameLine replaces deprecated jsxBracketSameLine, explicit endOfLine: "lf" prevents cross-platform issues.
See examples/prettier.md for TypeScript config files, experimental options, and ignore patterns.
Pattern 5: Custom ESLint Rules
Common custom rules for enforcing project conventions — named exports, consistent type imports, unused variable detection, and import boundary enforcement:
export const customRules = { rules: { "import/no-default-export": "warn", "@typescript-eslint/consistent-type-imports": [ "warn", { prefer: "type-imports" }, ], "@typescript-eslint/no-unused-vars": [ "warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, ], }, };
See examples/eslint.md for the full custom rules pattern including import boundary restrictions.
Pattern 6: Using extends Property (ESLint 9.15+)
The
extends property in flat config objects simplifies plugin composition — standardizes config merging regardless of plugin format (object, array, or string):
export default defineConfig({ files: ["**/*.ts", "**/*.tsx"], extends: [ "eslint/recommended", tseslint.configs.recommended, // Add your framework plugin's flat config here ], rules: { "no-console": "warn" }, });
See examples/core.md for the full extends pattern.
</patterns><decision_framework>
Decision Framework
ESLint + Prettier vs Alternatives
Need linting and formatting? ├─ Speed is critical bottleneck (1000+ files)? │ └─ YES → Consider a Rust-based unified linter/formatter (not this skill's scope) └─ Need mature plugin ecosystem? └─ YES → ESLint 9/10 + Prettier ✓
ESLint + Prettier strengths: Mature ecosystem, extensive plugin support, framework-specific plugins
ESLint 9 vs ESLint 10
Which ESLint version? ├─ New project? │ └─ ESLint 10 (latest, cleanest API) ├─ Existing project with flat config? │ └─ ESLint 10 (straightforward upgrade) ├─ Existing project with .eslintrc? │ ├─ Can invest time to migrate? │ │ └─ YES → Migrate to flat config, then ESLint 10 │ └─ NO → ESLint 9.x (still supported, but plan migration) └─ Node.js < 20.19.0? └─ ESLint 9.x (ESLint 10 requires 20.19.0+)
Shared Config vs Local Config
Setting up linting/formatting? ├─ Monorepo with multiple packages? │ └─ YES → Shared config ✓ ├─ Team project (2+ developers)? │ └─ YES → Shared config (consistency matters) └─ Single package / solo project? └─ YES → Local config is fine
Prettier Config File Format
What Prettier config format to use? ├─ Need type checking in config? │ ├─ Node.js 22.6.0+? → prettier.config.ts │ └─ NO → Use .mjs with JSDoc types ├─ ESM project? → prettier.config.mjs └─ CommonJS project? → prettier.config.cjs
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- ❌ Using legacy .eslintrc format instead of ESLint 9+ flat config (BROKEN in ESLint 10)
- ❌ Using bare
property instead ofignores
helper (ambiguous behavior — acts as global ignores when alone, but as local excludes when paired with other properties)globalIgnores() - ❌ Missing eslint-plugin-only-warn (errors block developers during development)
- ❌ Missing eslint-config-prettier (ESLint and Prettier rules conflict, creating endless fix cycles)
Medium Priority Issues:
- ⚠️ Using manual
option instead ofproject
in typescript-eslint (fragile in monorepos, slower)projectService: true - ⚠️ Using deprecated
option in Prettier (renamed tojsxBracketSameLine
in Prettier 2.4)bracketSameLine - ⚠️ Explicitly setting
in Prettier 3.0+ (it is already the default)trailingComma: "all" - ⚠️ Using
wrapper (planned for deprecation in favor of ESLint's nativetseslint.config()
)defineConfig() - ⚠️ Hardcoded config values in each package instead of shared config
Gotchas & Edge Cases:
- only-warn plugin must be loaded AFTER other plugins to convert their errors to warnings
auto-flattens arrays — never use spread operators with itdefineConfig()
requires typescript-eslint v8+ (wasprojectService
in v6-v7)EXPERIMENTAL_useProjectService- ESLint 10 requires Node.js
(v21.x and v23.x explicitly unsupported)^20.19.0 || ^22.13.0 || >=24 - ESLint 10 config lookup starts from linted file directory (not cwd) — enables monorepo multi-config
- Prettier TypeScript config files (
) require Node.js 22.6.0+; before Node v24.3.0 run with.prettierrc.ts--experimental-strip-types - Prettier 3.0+ APIs are async — plugins using sync APIs need migration (use
for sync wrappers)@prettier/sync - ESLint 9.15+:
TypeScript config files andeslint.config.ts
property supported nativelyextends - ESLint 9.34+: Multithreaded linting available via
flag (30-300% performance boost on large projects)--concurrency - ESLint 10:
comments now trigger errors; deprecated/* eslint-env */
methods removedLinter
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST use ESLint 9+ flat config with
from defineConfig()
- NOT legacy .eslintrc)eslint/config
(You MUST use
for explicit global ignore patterns - NOT bare globalIgnores()
property)ignores
(You MUST use typescript-eslint v8+ with
for typed linting)projectService: true
(You MUST include eslint-plugin-only-warn to convert errors to warnings for better DX)
(You MUST use eslint-config-prettier to disable formatting rules that conflict with Prettier)
Failure to follow these rules will cause inconsistent tooling, conflicting formatting rules, and blocked developers.
WARNING: ESLint 10 (February 2026) completely removes .eslintrc support. Plan migration now.
</critical_reminders>