ESLint Complete Guide | Flat Config, Rules, Plugins & TypeScript Integration
이 글의 핵심
ESLint 9 ships a new flat config system (eslint.config.js) that replaces .eslintrc. This guide covers migration, TypeScript-ESLint setup, React rules, custom rules, Prettier integration, and enforcement in CI.
Setup: ESLint 9 Flat Config
# Initialize ESLint (generates eslint.config.js)
npm init @eslint/config@latest
# Or install manually
npm install -D eslint
Flat Config (eslint.config.js)
// eslint.config.js — ESLint 9 default format
import js from '@eslint/js';
import globals from 'globals';
export default [
// Apply recommended JS rules globally
js.configs.recommended,
{
// Files this config applies to
files: ['**/*.{js,mjs,cjs,jsx}'],
// Language options
languageOptions: {
ecmaVersion: 2024,
sourceType: 'module',
globals: {
...globals.browser,
...globals.node,
},
},
// Custom rules
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'eqeqeq': ['error', 'always'],
},
},
// Ignore patterns
{
ignores: ['dist/**', 'node_modules/**', '*.min.js'],
},
];
TypeScript Integration
npm install -D typescript-eslint
// eslint.config.js — with TypeScript
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
js.configs.recommended,
// TypeScript rules
...tseslint.configs.recommended,
// Or stricter: tseslint.configs.strict
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parserOptions: {
project: './tsconfig.json', // Enable type-aware rules
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
// Type-aware rules (require project: tsconfig)
'@typescript-eslint/no-floating-promises': 'error', // Catch unhandled async
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/await-thenable': 'error',
// Overrides from recommended
'@typescript-eslint/no-explicit-any': 'warn', // Allow any with warning
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_', // Allow _prefix for unused args
varsIgnorePattern: '^_',
}],
},
},
{
ignores: ['dist/**', '*.js', '*.mjs'],
},
);
React Integration
npm install -D eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-jsx-a11y
// eslint.config.js — with React
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import jsxA11y from 'eslint-plugin-jsx-a11y';
import tseslint from 'typescript-eslint';
export default tseslint.config(
// ...
{
files: ['**/*.{jsx,tsx}'],
plugins: {
react,
'react-hooks': reactHooks,
'jsx-a11y': jsxA11y,
},
settings: {
react: { version: 'detect' }, // Auto-detect React version
},
rules: {
// React
...react.configs.recommended.rules,
'react/react-in-jsx-scope': 'off', // Not needed with React 17+
'react/prop-types': 'off', // Use TypeScript instead
// Hooks rules
...reactHooks.configs.recommended.rules,
'react-hooks/exhaustive-deps': 'warn',
// Accessibility
...jsxA11y.configs.recommended.rules,
},
},
);
Key Rules Explained
Critical Rules (error level)
rules: {
// Prevent common bugs
'no-unused-vars': 'error', // Remove unused variables
'no-undef': 'error', // No undeclared variables
'eqeqeq': ['error', 'always'], // === instead of ==
'no-var': 'error', // Use let/const not var
// Async safety
'no-floating-promises': 'error', // Await all promises
'require-await': 'error', // Don't async without await
// Security
'no-eval': 'error', // No eval()
'no-implied-eval': 'error', // No setTimeout("code")
}
Quality Rules (warning level)
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }], // Allow console.warn/error
'prefer-const': 'warn', // Use const when not reassigned
'no-shadow': 'warn', // Avoid variable shadowing
'max-lines-per-function': ['warn', { max: 50 }],
}
Prettier Integration
npm install -D prettier eslint-config-prettier
// eslint.config.js — add prettier last (disables conflicting rules)
import prettier from 'eslint-config-prettier';
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
// ... other configs
prettier, // Must be LAST — disables ESLint formatting rules
);
// .prettierrc
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2
}
// package.json scripts
{
"scripts": {
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check ."
}
}
Inline Suppressions
// Disable for entire file
/* eslint-disable */
// Disable specific rule for file
/* eslint-disable @typescript-eslint/no-explicit-any */
// Disable for next line
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = JSON.parse(raw);
// Disable for a block
/* eslint-disable no-console */
console.log('intentional debug log');
/* eslint-enable no-console */
// ✅ Always add a comment explaining WHY
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// Third-party library returns untyped data — typed at boundary
const result: any = thirdPartyLib.getData();
Custom Rules
// rules/no-hardcoded-urls.js — custom rule example
export default {
meta: {
type: 'problem',
docs: {
description: 'Disallow hardcoded production URLs in source code',
},
schema: [],
},
create(context) {
return {
// Check all string literals
Literal(node) {
if (
typeof node.value === 'string' &&
node.value.includes('https://api.myapp.com')
) {
context.report({
node,
message: 'Hardcoded production URL. Use process.env.API_URL instead.',
});
}
},
};
},
};
// eslint.config.js — use custom rule
import noHardcodedUrls from './rules/no-hardcoded-urls.js';
export default [
{
plugins: {
local: { rules: { 'no-hardcoded-urls': noHardcodedUrls } },
},
rules: {
'local/no-hardcoded-urls': 'error',
},
},
];
CI Integration
# .github/workflows/lint.yml
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 # Fails if lint errors
- run: npm run format:check # Fails if formatting off
VS Code Integration
// .vscode/settings.json
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
}
Install extensions: ESLint (dbaeumer.vscode-eslint) + Prettier (esbenp.prettier-vscode)
Migration from .eslintrc to Flat Config
# Automated migration tool
npx @eslint/migrate-config .eslintrc.json
# Generates eslint.config.mjs
// .eslintrc.json (old)
{
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"plugins": ["@typescript-eslint"],
"rules": { "no-console": "warn" }
}
// eslint.config.js (new)
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(
js.configs.recommended,
...tseslint.configs.recommended,
{ rules: { 'no-console': 'warn' } }
);
Related posts: