ESLint Reference: Flat Config, TypeScript Rules, Disable Comments & lint-staged
ESLint 9 introduced a breaking change: the new flat config system (eslint.config.js) replaces .eslintrc.*. If you’re on ESLint 8 or earlier, the old config still works but is deprecated. The flat config is simpler — no more cascading config files, no more overrides arrays, just plain JavaScript objects in an array. This reference covers both systems but leads with flat config since that’s where the ecosystem is heading.
1. Flat Config — eslint.config.js (ESLint 9)
eslint.config.js structure, recommended configs, TypeScript, and file patterns
// npm install -D eslint @eslint/js
// For TypeScript: npm install -D typescript-eslint
// For React: npm install -D eslint-plugin-react eslint-plugin-react-hooks
// eslint.config.js (flat config — ESLint 9):
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import reactPlugin from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import globals from "globals";
export default tseslint.config(
// Ignore files (replaces .eslintignore):
{ ignores: ["dist/**", "node_modules/**", "coverage/**", "*.config.js"] },
// Base JavaScript rules:
js.configs.recommended,
// TypeScript rules:
...tseslint.configs.recommended, // or .strict or .stylistic
// React:
{
plugins: { react: reactPlugin, "react-hooks": reactHooks },
rules: {
...reactPlugin.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
"react/react-in-jsx-scope": "off", // not needed with React 17+
},
settings: { react: { version: "detect" } },
},
// Custom rules + environment:
{
languageOptions: {
globals: { ...globals.browser, ...globals.node },
parserOptions: { ecmaFeatures: { jsx: true } },
},
rules: {
"no-console": "warn",
"no-unused-vars": "off", // use TS version:
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
},
},
// Test file overrides:
{
files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
rules: { "@typescript-eslint/no-explicit-any": "off" },
},
);
2. Legacy Config — .eslintrc (ESLint 8)
.eslintrc.js / .eslintrc.json for ESLint 8, extends, plugins, and overrides
// .eslintrc.js (ESLint 8 — still works in ESLint 9 with ESLINT_USE_FLAT_CONFIG=false):
module.exports = {
root: true, // stop looking for config in parent directories
env: {
browser: true,
es2022: true,
node: true,
},
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: { jsx: true },
project: "./tsconfig.json", // needed for type-aware rules
},
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier", // eslint-config-prettier — disables style rules that conflict with Prettier
],
plugins: ["@typescript-eslint", "react", "react-hooks"],
rules: {
"no-console": "warn",
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
"react/react-in-jsx-scope": "off",
},
overrides: [
{
files: ["**/*.test.ts", "**/*.spec.ts"],
env: { jest: true },
rules: { "@typescript-eslint/no-explicit-any": "off" },
},
],
};
3. Rules, Severity & Inline Disabling
Rule severity levels, inline disable comments, and when to use each
// Severity levels:
// "off" | 0 — rule disabled
// "warn" | 1 — warning (exit code 0)
// "error" | 2 — error (exit code 1, fails CI)
// Common rule patterns:
rules: {
"no-var": "error", // always use let/const
"prefer-const": "error", // use const when not reassigned
"eqeqeq": ["error", "always"], // always use === not ==
"no-shadow": "error", // no variable shadowing
"no-unreachable": "error", // code after return/throw
"curly": ["error", "all"], // always use braces for if/else
// TypeScript-specific:
"@typescript-eslint/consistent-type-imports": "error", // import type {...}
"@typescript-eslint/no-floating-promises": "error", // must await promises
"@typescript-eslint/strict-null-checks": "off", // handled by tsconfig
"@typescript-eslint/no-non-null-assertion": "warn", // avoid ! operator
}
// Inline disable (use sparingly — add comment explaining why):
// Disable next line:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const data: any = JSON.parse(rawData);
// Disable entire file:
/* eslint-disable @typescript-eslint/no-explicit-any */
// Disable a section:
/* eslint-disable no-console */
console.log("Intentional debug output");
/* eslint-enable no-console */
// Disable all rules (really sparingly):
/* eslint-disable */
/* eslint-enable */
4. Running ESLint & Auto-fix
CLI flags, –fix, lint-staged for pre-commit, and CI integration
# Run ESLint:
npx eslint src/ # lint entire src directory
npx eslint src/index.ts # single file
npx eslint "src/**/*.{ts,tsx}" # glob pattern (quote the glob)
npx eslint src/ --ext .ts,.tsx # by extension (legacy config)
# Auto-fix fixable rules:
npx eslint src/ --fix # fixes in place
npx eslint src/ --fix-dry-run # preview what would be fixed
# Output formats:
npx eslint src/ --format stylish # default (grouped by file)
npx eslint src/ --format json # JSON (for CI reporting)
npx eslint src/ --format compact # one line per error
# CI — fail on errors only (not warnings):
npx eslint src/ --max-warnings 0 # fail if any warnings
# package.json scripts:
{
"scripts": {
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"lint:check": "eslint src/ --max-warnings 0"
}
}
# lint-staged — only lint changed files before commit:
# npm install -D lint-staged husky
# .lintstagedrc.json:
{
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": ["prettier --write"]
}
# package.json: "prepare": "husky install"
# .husky/pre-commit: npx lint-staged
5. Custom Rules & Plugin Creation
Writing a simple custom rule, testing rules, and when to write your own
// Custom rule — enforce no console.log in production code:
// eslint-plugin-my-org/rules/no-debug-logging.js:
module.exports = {
meta: {
type: "suggestion",
docs: { description: "Disallow console.log (use structured logger)" },
fixable: "code",
schema: [], // no options
},
create(context) {
return {
CallExpression(node) {
if (
node.callee.type === "MemberExpression" &&
node.callee.object.name === "console" &&
node.callee.property.name === "log"
) {
context.report({
node,
message: "Use logger.info() instead of console.log()",
fix(fixer) {
// Auto-fix: replace console.log with logger.info
return fixer.replaceText(node.callee, "logger.info");
},
});
}
},
};
},
};
// Use in config:
// rules: { "my-org/no-debug-logging": "error" }
// Useful community plugins (all work with flat config):
// eslint-plugin-unicorn — many opinionated best practices
// eslint-plugin-import — import/export ordering and validation
// eslint-plugin-jsx-a11y — accessibility rules for JSX
// eslint-plugin-sonarjs — code smell detection
// eslint-plugin-n — Node.js-specific rules
Track Node.js and toolchain releases at ReleaseRun. Related: TypeScript Reference | Prettier Reference | Vite Reference
🔍 Free tool: npm Package Health Checker — check ESLint and its plugins — @typescript-eslint, eslint-config-airbnb — for known CVEs and latest versions.
Founded
2023 in London, UK
Contact
hello@releaserun.com