ESLint 9 & Biome Linting
When NOT to Use This Skill
- Legacy ESLint (.eslintrc) - Use
eslint skill for old config format
- TypeScript-only rules - Use
typescript-eslint skill for deep TypeScript linting
- Java/Kotlin linting - Use
sonarqube skill
- Code quality principles - Use
quality-common for SOLID/Clean Code
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: eslint or biome for comprehensive documentation.
Official References
ESLint 9 Flat Config
Basic Setup
npm install --save-dev eslint @eslint/js typescript typescript-eslint
// eslint.config.mjs
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
);
TypeScript with Type Checking
// eslint.config.mjs
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
);
Full Configuration Example
// eslint.config.mjs
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
export default tseslint.config(
// Ignores
{
ignores: ['dist/**', 'node_modules/**', '*.config.js'],
},
// Base configs
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
// TypeScript files
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// Prevent bugs
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
// Code quality
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/consistent-type-imports': 'error',
// Complexity
'complexity': ['warn', 10],
'max-depth': ['warn', 4],
'max-lines-per-function': ['warn', 50],
},
},
// React files
{
files: ['**/*.tsx'],
plugins: {
react: reactPlugin,
'react-hooks': reactHooksPlugin,
},
rules: {
...reactPlugin.configs.recommended.rules,
...reactHooksPlugin.configs.recommended.rules,
'react/react-in-jsx-scope': 'off',
},
settings: {
react: { version: 'detect' },
},
},
// Test files
{
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts'],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'max-lines-per-function': 'off',
},
},
);
typescript-eslint Presets
| Preset |
Description |
recommended |
Core rules without type checking |
recommendedTypeChecked |
Recommended + type-aware rules |
strict |
All recommended + stricter rules |
strictTypeChecked |
Strict + type-aware rules |
stylistic |
Style/convention rules |
stylisticTypeChecked |
Stylistic + type-aware |
Key Type-Checked Rules
rules: {
// Async/Promise safety
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
'@typescript-eslint/require-await': 'error',
// Type safety
'@typescript-eslint/no-unsafe-argument': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-call': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
'@typescript-eslint/no-unsafe-return': 'error',
// Best practices
'@typescript-eslint/no-unnecessary-condition': 'error',
'@typescript-eslint/prefer-nullish-coalescing': 'error',
'@typescript-eslint/prefer-optional-chain': 'error',
}
Biome
Installation
npm install --save-dev @biomejs/biome
npx @biomejs/biome init
Configuration (biome.json)
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"complexity": {
"noExcessiveCognitiveComplexity": {
"level": "warn",
"options": { "maxAllowedComplexity": 15 }
}
},
"correctness": {
"noUnusedImports": "error",
"noUnusedVariables": "error",
"useExhaustiveDependencies": "warn"
},
"suspicious": {
"noExplicitAny": "error",
"noConsoleLog": "warn"
},
"style": {
"useConst": "error",
"noNonNullAssertion": "warn"
},
"security": {
"noDangerouslySetInnerHtml": "error"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "always"
}
}
}
Rule Categories
| Category |
Rules |
Description |
| a11y |
40+ |
Accessibility problems |
| complexity |
15+ |
Code simplification |
| correctness |
60+ |
Guaranteed errors |
| performance |
10+ |
Efficiency improvements |
| security |
5+ |
Security flaws |
| style |
50+ |
Consistent code style |
| suspicious |
50+ |
Likely incorrect patterns |
| nursery |
100+ |
Experimental rules |
Commands
# Check all
npx biome check .
# Fix auto-fixable issues
npx biome check --write .
# Lint only
npx biome lint .
# Format only
npx biome format --write .
# CI mode (no writes)
npx biome ci .
Migration: ESLint to Biome
# Automatic migration
npx @biomejs/biome migrate eslint --write
# With inspired rules
npx @biomejs/biome migrate eslint --include-inspired --write
# Migrate prettier config too
npx @biomejs/biome migrate prettier --write
Supported ESLint Plugins
| Plugin |
Biome Support |
| @typescript-eslint |
Full |
| eslint-plugin-react |
Full |
| eslint-plugin-react-hooks |
Full |
| eslint-plugin-jsx-a11y |
Full |
| eslint-plugin-unicorn |
Partial |
| eslint-plugin-import |
Partial |
Rule Name Mapping
| ESLint |
Biome |
no-unused-vars |
noUnusedVariables |
no-console |
noConsoleLog |
prefer-const |
useConst |
@typescript-eslint/no-explicit-any |
noExplicitAny |
ESLint vs Biome
| Feature |
ESLint 9 |
Biome |
| Speed |
~3-5s/10k lines |
~200ms/10k lines |
| Config |
JavaScript |
JSON/JSONC |
| Plugins |
1000+ |
Built-in only |
| Formatter |
Needs Prettier |
Built-in |
| Type-aware |
Full support |
~85% coverage |
| Languages |
JS/TS + plugins |
JS/TS/JSON/CSS |
When to Use ESLint
- Need specific plugins (import sorting, testing rules)
- Require full type-aware linting
- Have complex dynamic configuration
When to Use Biome
- Speed is critical (large codebase, CI)
- Want unified linting + formatting
- Don't need extensive plugin ecosystem
CI Integration
ESLint (GitHub Actions)
- name: Lint
run: npx eslint . --max-warnings=0
# With caching
- name: Lint with cache
run: npx eslint . --cache --cache-location node_modules/.cache/eslint
Biome (GitHub Actions)
- name: Setup Biome
uses: biomejs/setup-biome@v2
with:
version: latest
- name: Run Biome
run: biome ci .
Checklist
ESLint Setup
Biome Setup
Metrics
| Metric |
Target |
| Lint warnings |
0 |
| Lint errors |
0 |
| Type coverage |
> 95% |
| Lint time (CI) |
< 30s |
Anti-Patterns
| Anti-Pattern |
Why It's Bad |
Correct Approach |
| Using ESLint without type checking |
Misses 50% of TS issues |
Use recommendedTypeChecked |
| Biome + ESLint both linting same files |
Duplicate work, conflicts |
Choose one per file type |
| No ignores in flat config |
Lints dist/, node_modules/ |
Add ignores at top of config |
| Type checking all JS files |
Slow, JS has no types |
Limit to **/*.ts files only |
| Not using Biome CI mode |
Different results locally vs CI |
Use biome ci . in pipelines |
| Mixing .eslintrc and flat config |
Confusing, deprecated pattern |
Migrate fully to flat config |
Quick Troubleshooting
| Issue |
Likely Cause |
Solution |
| ESLint not finding types |
projectService not enabled |
Add projectService: true to parserOptions |
| Biome migration creates 1000+ errors |
Stricter rules than ESLint |
Use --write to auto-fix, adjust rules |
| Linting takes 2+ minutes |
Type checking entire codebase |
Use files: ['**/*.ts'] for type rules |
| Biome doesn't support plugin X |
Limited plugin ecosystem |
Stick with ESLint or find alternative |
| Flat config not recognized |
Wrong file name |
Must be eslint.config.mjs (not .js) |
| Rules overriding each other |
Order in config array |
Later configs override earlier ones |
Related Skills