Skip to content

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