Ultimate Guide of ESLint Rules to Build Production Ready Projects
Dive into the vibe coding scene - it's undeniably engaging, yet prone to errors if not handled with care. One of the most effective strategies is integrating a robust linter. These tools not only flag potential issues but also enforce best practices, such as generating TSDoc comments, which significantly enhance code readability and enable early bug detection. Moreover, they strengthen rules for type safety, empowering you to build a secure, type-safe application - a strategic approach that, in my opinion, offers excellent context for LLMs.
Additionally, identifying and removing dead code is essential, as iterative processes with LLMs can degrade code quality over time. Maintaining this context ensures your LLM code assistant functions differently from a conventional runtime, avoiding common pitfalls like code misinterpretation. To address this, I've compiled a comprehensive context list for mastering linters in TypeScript, drawing from 5-6 in-depth researches combined with insights from Gemini 2.5 Pro—it turned out exceptionally well. You can copy-paste it directly for use. Then, consult Claude to determine which elements suit your needs and construct a customized system; it'll elevate your TypeScript projects to the next level.
For those who need quick copy/paste, follow the given link: https://gist.githubusercontent.com/yigitkonur/d953dfd13f96089421fc73b7fbcbd6ea/raw/ebc8c91a6a80ec6a0aa47afb24b869d8ee66806a/comprehensive-eslint-rules.md
A Strategic Framework for Code Quality: From Linting Rules to Production Resilience
Let's try to understand each of them by reviewing that summary:
ESLint Core "Possible Problems" Rules (Summary Table)
Rule Name | Core Problem Prevented | Fixable (🔧) | Recommended (✅) | Primary Production Benefit |
---|---|---|---|---|
array-callback-return | Unexpected undefined values from array methods. | 🔧 | ✅ | Data Integrity |
constructor-super | ReferenceError in derived class constructors. | ✅ | Stability | |
for-direction | Infinite loops due to incorrect counter updates. | 🔧 | ✅ | Performance & Stability |
getter-return | Getters implicitly returning undefined. | ✅ | Predictability | |
no-async-promise-executor | Unhandled promise rejections and race conditions. | ✅ | Reliability | |
no-await-in-loop | Performance bottlenecks from sequential async operations. | Performance | ||
no-class-assign | Overwriting class declarations, causing TypeError. | ✅ | Stability | |
no-compare-neg-zero | Unexpected behavior when comparing with -0. | 🔧 | ✅ | Correctness |
no-cond-assign | Accidental assignment in conditional statements. | 🔧 | ✅ | Bug Prevention |
no-const-assign | TypeError from reassigning const variables. | ✅ | Stability | |
no-constant-binary-expression | Logically flawed conditions that are always true/false. | 🔧 | ✅ | Bug Prevention |
no-constant-condition | Infinite loops or dead code from constant conditions. | ✅ | Stability & Correctness | |
no-constructor-return | Unpredictable object instantiation. | ✅ | Predictability | |
no-control-regex | Invisible, problematic characters in regular expressions. | ✅ | Security & Correctness | |
no-debugger | Pausing execution in production environments. | 🔧 | ✅ | Production Readiness |
no-dupe-args | Ambiguous function behavior due to shadowed arguments. | ✅ | Correctness | |
no-dupe-class-members | Overwritten methods/properties in classes. | ✅ | Correctness | |
no-dupe-else-if | Redundant, unreachable code in if-else chains. | 🔧 | ✅ | Maintainability |
no-dupe-keys | Overwritten properties in object literals. | ✅ | Correctness | |
no-duplicate-case | Unreachable code in switch statements. | ✅ | Correctness | |
no-duplicate-imports | Increased bundle size and code clutter. | 🔧 | ✅ | Performance & Maintainability |
no-empty-character-class | Regex that matches nothing, causing logic errors. | ✅ | Correctness | |
no-empty-pattern | No-op destructuring that can hide bugs. | ✅ | Bug Prevention | |
no-ex-assign | Swallowing critical error information. | ✅ | Debuggability | |
no-fallthrough | Unintended execution of multiple switch cases. | ✅ | Bug Prevention | |
no-func-assign | Overwriting function declarations, breaking calls. | ✅ | Stability | |
no-import-assign | Mutating imported modules, causing TypeError. | ✅ | Stability | |
no-inner-declarations | Inconsistent function/variable availability. | ✅ | Predictability | |
no-invalid-regexp | SyntaxError at runtime from invalid regex. | ✅ | Stability | |
no-irregular-whitespace | Hard-to-debug syntax errors. | 🔧 | ✅ | Stability |
no-loss-of-precision | Data corruption from floating-point inaccuracies. | ✅ | Data Integrity | |
no-misleading-character-class | Incorrect regex matching due to multi-codepoint chars. | 🔧 | ✅ | Correctness |
no-new-native-nonconstructor | TypeError from using new on Symbol or BigInt. | ✅ | Stability | |
no-obj-calls | TypeError from calling Math, JSON, etc., as functions. | ✅ | Stability | |
no-promise-executor-return | Misleading return values in Promise executors. | 🔧 | ✅ | Bug Prevention |
no-prototype-builtins | Potential for prototype pollution vulnerabilities. | ✅ | Security & Stability | |
no-self-assign | Redundant code that can mask logic errors. | 🔧 | ✅ | Maintainability |
no-self-compare | Pointless comparisons, often indicating a NaN check error. | ✅ | Bug Prevention | |
no-setter-return | TypeError from returning a value in a setter. | ✅ | Predictability | |
no-sparse-arrays | Unpredictable array lengths and "empty" slots. | ✅ | Data Integrity | |
no-template-curly-in-string | Accidental template literal syntax in regular strings. | ✅ | Bug Prevention | |
no-this-before-super | ReferenceError from accessing this too early. | ✅ | Stability | |
no-unassigned-vars | ReferenceError from using unassigned variables. | ✅ | Stability | |
no-undef | ReferenceError from using undeclared variables. | ✅ | Stability | |
no-unexpected-multiline | Syntax errors from misleading line breaks. | ✅ | Stability | |
no-unmodified-loop-condition | Infinite loops from a condition that never changes. | ✅ | Performance & Stability | |
no-unreachable | Dead code that indicates a logical error. | ✅ | Maintainability | |
no-unreachable-loop | Loops that execute only once, hiding logic flaws. | ✅ | Correctness | |
no-unsafe-finally | Losing exceptions or unexpected control flow. | ✅ | Reliability | |
no-unsafe-negation | TypeError from incorrect use of in or instanceof. | 🔧 | ✅ | Bug Prevention |
no-unsafe-optional-chaining | TypeError in arithmetic or relational operations. | ✅ | Bug Prevention | |
no-unused-private-class-members | Dead code within classes. | ✅ | Maintainability | |
no-unused-vars | Dead code, memory leaks, and cluttered namespaces. | 🔧 | ✅ | Maintainability |
no-use-before-define | ReferenceError due to temporal dead zone or hoisting. | Bug Prevention | ||
no-useless-assignment | Redundant assignments that can mask bugs. | 🔧 | ✅ | Maintainability |
no-useless-backreference | Inefficient or incorrect regex backreferences. | ✅ | Correctness | |
require-atomic-updates | Race conditions and data corruption in async code. | ✅ | Data Integrity | |
use-isnan | Incorrectly checking for NaN with == or ===. | 🔧 | ✅ | Correctness |
valid-typeof | TypeError from typos in typeof comparison strings. | 🔧 | ✅ | Bug Prevention |
ESLint Core "Suggestions" Rules (Summary Table)
Rule Name | Best Practice Enforced | Fixable (🔧) | Frozen (❄️) | Primary Production Benefit |
---|---|---|---|---|
accessor-pairs | Prevents creating write-only or read-only properties unintentionally. | Bug Prevention | ||
arrow-body-style | Consistency in arrow function syntax. | 🔧 | ❄️ | Readability |
block-scoped-var | Prevents var leakage outside its intended block. | Bug Prevention | ||
camelcase | Standardizes variable naming conventions. | ❄️ | Readability | |
capitalized-comments | Improves comment clarity and consistency. | 🔧 | ❄️ | Maintainability |
class-methods-use-this | Ensures class methods are not just static functions in disguise. | Code Organization | ||
complexity | Limits cyclomatic complexity to keep functions understandable. | Maintainability | ||
consistent-return | Prevents functions from sometimes returning a value and sometimes not. | Predictability | ||
consistent-this | Standardizes the alias for this. | ❄️ | Readability | |
curly | Requires braces for all control blocks, preventing ambiguity. | 🔧 | ❄️ | Bug Prevention |
default-case | Ensures switch statements handle all possible cases. | Reliability | ||
default-case-last | Improves switch statement readability. | Readability | ||
default-param-last | Enforces logical parameter ordering. | ❄️ | Usability | |
dot-notation | Prefers dot notation for property access for clarity. | 🔧 | ❄️ | Readability |
eqeqeq | Eliminates bugs from type coercion by requiring === and !==. | 🔧 | Bug Prevention | |
func-name-matching | Improves debuggability by matching function names to variables. | ❄️ | Debuggability | |
func-names | Encourages named functions for better stack traces. | Debuggability | ||
func-style | Enforces a consistent function declaration style. | ❄️ | Readability | |
grouped-accessor-pairs | Improves class and object readability by grouping getters/setters. | 🔧 | Readability | |
guard-for-in | Prevents iterating over inherited properties in for...in loops. | Bug Prevention | ||
id-denylist | Disallows specific, problematic variable names. | ❄️ | Maintainability | |
id-length | Prevents overly short or long variable names. | ❄️ | Readability | |
id-match | Enforces a consistent naming pattern for identifiers. | ❄️ | Maintainability | |
init-declarations | Enforces variable initialization at declaration. | ❄️ | Predictability | |
logical-assignment-operators | Promotes concise modern syntax. | 🔧 | ❄️ | Readability |
max-classes-per-file | Promotes single responsibility principle for files. | Code Organization | ||
max-depth | Prevents deeply nested code blocks. | Readability | ||
max-lines | Limits file length to encourage modularity. | Maintainability | ||
max-lines-per-function | Limits function length to encourage small, focused functions. | Maintainability | ||
max-nested-callbacks | Prevents "callback hell." | Readability | ||
max-params | Limits function parameters to prevent complex signatures. | Maintainability | ||
max-statements | Limits statements per function to encourage simplicity. | Maintainability | ||
new-cap | Enforces constructor naming conventions for clarity. | Readability | ||
no-alert | Prevents using intrusive, browser-only UI elements. | User Experience | ||
no-array-constructor | Prevents confusing behavior of the Array constructor. | 🔧 | Bug Prevention | |
no-bitwise | Discourages bitwise operators, which are often typos. | Bug Prevention | ||
no-caller | Disallows deprecated and performance-harming features. | Performance | ||
no-case-declarations | Prevents variable leakage within switch cases. | ✅ | Bug Prevention | |
no-console | Prevents leaving debug logs in production code. | Production Readiness | ||
no-continue | Discourages a control flow statement that can reduce readability. | ❄️ | Readability | |
no-delete-var | Prevents deleting variables, which is not allowed and causes errors. | ✅ | Stability | |
no-div-regex | Prevents confusing regex literals. | 🔧 | ❄️ | Readability |
no-else-return | Simplifies if statements by removing redundant else blocks. | 🔧 | ❄️ | Readability |
no-empty | Flags empty blocks, which may indicate an error. | 🔧 | ✅ | Bug Prevention |
no-empty-function | Flags empty functions, which may be incomplete logic. | Bug Prevention | ||
no-empty-static-block | Flags empty class static blocks. | ✅ | Bug Prevention | |
no-eq-null | Prevents bugs from loose comparison to null. | 🔧 | Bug Prevention | |
no-eval | Disallows eval(), a major security and performance risk. | Security | ||
no-extend-native | Prevents modifying built-in objects, a dangerous practice. | Stability | ||
no-extra-bind | Removes unnecessary .bind() calls. | 🔧 | Performance | |
no-extra-boolean-cast | Removes redundant boolean casting. | 🔧 | ❄️ | Readability |
no-extra-label | Removes unused labels. | 🔧 | ❄️ | Maintainability |
no-global-assign | Prevents modification of read-only global variables. | ✅ | Stability | |
no-implicit-coercion | Disallows confusing shorthand type conversions. | 🔧 | ❄️ | Readability |
no-implicit-globals | Prevents accidental creation of global variables. | Bug Prevention | ||
no-implied-eval | Disallows eval()-like methods like setTimeout with strings. | Security | ||
no-inline-comments | Promotes a cleaner comment style. | ❄️ | Readability | |
no-invalid-this | Prevents using this in a context where it is undefined. | Bug Prevention | ||
no-iterator | Disallows the deprecated iterator property. | Modernization | ||
no-label-var | Prevents confusion between labels and variables. | ❄️ | Bug Prevention | |
no-labels | Discourages the use of labels, which can lead to complex code. | ❄️ | Readability | |
no-lone-blocks | Removes unnecessary nesting blocks. | 🔧 | Readability | |
no-lonely-if | Simplifies else blocks containing only an if. | 🔧 | ❄️ | Readability |
no-loop-func | Prevents functions in loops that capture loop variables incorrectly. | Bug Prevention | ||
no-magic-numbers | Encourages named constants over unexplained numbers. | ❄️ | Maintainability | |
no-multi-assign | Discourages chained assignments for clarity. | Readability | ||
no-multi-str | Disallows an outdated way of creating multiline strings. | ❄️ | Modernization | |
no-negated-condition | Prefers positive conditions for better readability. | ❄️ | Readability | |
no-nested-ternary | Prevents hard-to-read nested ternary expressions. | Readability | ||
no-new | Disallows new for side effects, requiring assignment. | Clarity | ||
no-new-func | Disallows new Function(), which is similar to eval(). | Security | ||
no-new-wrappers | Prevents creating wrapper objects for primitives. | Bug Prevention | ||
no-nonoctal-decimal-escape | Disallows confusing legacy escape sequences. | 🔧 | ✅ | Correctness |
no-object-constructor | Prefers object literal syntax {}. | Readability | ||
no-octal | Disallows legacy octal literals. | ✅ | Modernization | |
no-octal-escape | Disallows legacy octal escape sequences in strings. | Modernization | ||
no-param-reassign | Prevents mutation of function arguments, promoting purity. | Debuggability | ||
no-plusplus | Discourages ++ and -- to avoid subtle bugs. | ❄️ | Bug Prevention | |
no-proto | Disallows the deprecated proto property. | Modernization | ||
no-redeclare | Prevents redeclaring the same variable. | ✅ | Bug Prevention | |
no-regex-spaces | Simplifies regex by removing multiple spaces. | 🔧 | ✅ | Readability |
no-restricted-exports | Disallows exporting certain names from a module. | Architectural Integrity | ||
no-restricted-globals | Disallows use of specified global variables. | Bug Prevention | ||
no-restricted-imports | Disallows importing from specified modules. | Architectural Integrity | ||
no-restricted-properties | Disallows using specific properties on objects. | API Compliance | ||
no-restricted-syntax | Disallows any specified language syntax. | Architectural Integrity | ||
no-return-assign | Prevents assignments within return statements. | Clarity | ||
no-script-url | Disallows javascript: URLs, a security risk. | Security | ||
no-sequences | Disallows the comma operator, which can obscure side effects. | Clarity | ||
no-shadow | Prevents inner variables from shadowing outer ones. | Bug Prevention | ||
no-shadow-restricted-names | Prevents shadowing of keywords like undefined. | ✅ | Bug Prevention | |
no-ternary | Disallows ternary operators entirely. | ❄️ | Code Style | |
no-throw-literal | Requires throwing Error objects instead of strings. | Debuggability | ||
no-undef-init | Prevents initializing variables to undefined. | 🔧 | ❄️ | Readability |
no-undefined | Disallows using undefined as a variable name. | ❄️ | Bug Prevention | |
no-underscore-dangle | Discourages dangling underscores in identifiers. | ❄️ | Code Style | |
no-unneeded-ternary | Simplifies ternaries that resolve to booleans. | 🔧 | ❄️ | Readability |
no-unused-expressions | Disallows expressions that have no effect. | Bug Prevention | ||
no-unused-labels | Removes unused labels. | 🔧 | ✅ | Maintainability |
no-useless-call | Prevents unnecessary .call() and .apply(). | Performance | ||
no-useless-catch | Removes catch blocks that only re-throw the error. | Readability | ||
no-useless-computed-key | Simplifies object keys. | 🔧 | ❄️ | Readability |
no-useless-concat | Prevents concatenating literals. | ❄️ | Readability | |
no-useless-constructor | Removes empty or default constructors. | Readability | ||
no-useless-escape | Removes unnecessary backslash escapes in strings. | 🔧 | Readability | |
no-useless-rename | Prevents renaming imports/exports to the same name. | 🔧 | Readability | |
no-useless-return | Removes redundant return statements. | 🔧 | Readability | |
no-var | Requires let or const instead of var. | 🔧 | Modernization | |
no-void | Discourages the void operator. | ❄️ | Clarity | |
no-warning-comments | Flags TODO or FIXME comments. | ❄️ | Project Management | |
no-with | Disallows the confusing and slow with statement. | ✅ | Performance & Stability | |
object-shorthand | Enforces modern, concise object literal syntax. | 🔧 | ❄️ | Modernization |
one-var | Enforces a consistent variable declaration style. | 🔧 | ❄️ | Readability |
operator-assignment | Promotes shorthand assignment operators like +=. | 🔧 | ❄️ | Readability |
prefer-arrow-callback | Prefers arrow functions for callbacks. | 🔧 | ❄️ | Modernization |
prefer-const | Prefers const for variables that are not reassigned. | 🔧 | Maintainability | |
prefer-destructuring | Promotes destructuring for cleaner data access. | 🔧 | ❄️ | Modernization |
prefer-exponentiation-operator | Prefers ** over Math.pow(). | 🔧 | ❄️ | Modernization |
prefer-named-capture-group | Improves regex readability with named groups. | Readability | ||
prefer-numeric-literals | Prefers modern numeric literal syntax. | 🔧 | ❄️ | Modernization |
prefer-object-has-own | Prefers modern Object.hasOwn() over hasOwnProperty. | 🔧 | Modernization | |
prefer-object-spread | Prefers object spread over Object.assign. | 🔧 | ❄️ | Modernization |
prefer-promise-reject-errors | Requires rejecting Promises with Error objects. | Debuggability | ||
prefer-regex-literals | Prefers regex literals over the RegExp constructor. | Readability | ||
prefer-rest-params | Prefers rest parameters over the arguments object. | Modernization | ||
prefer-spread | Prefers the spread operator over .apply(). | 🔧 | ❄️ | Modernization |
prefer-template | Prefers template literals over string concatenation. | 🔧 | ❄️ | Modernization |
radix | Requires the radix parameter for parseInt(). | Bug Prevention | ||
require-await | Flags async functions that don't use await. | Clarity | ||
require-unicode-regexp | Enforces the u flag on regex for Unicode correctness. | 🔧 | Correctness | |
require-yield | Flags generator functions that don't use yield. | Correctness | ||
sort-imports | Enforces sorted import declarations. | 🔧 | ❄️ | Readability |
sort-keys | Enforces sorted object keys. | 🔧 | ❄️ | Readability |
sort-vars | Enforces sorted variable declarations. | 🔧 | ❄️ | Readability |
strict | Enforces strict mode. | 🔧 | Bug Prevention | |
symbol-description | Requires descriptions for Symbols. | Debuggability | ||
vars-on-top | Requires var declarations at the top of their scope. | ❄️ | Readability | |
yoda | Discourages "Yoda" conditions (if ("red" === color)). | 🔧 | ❄️ | Readability |
ESLint Unicorn Master Index (Summary Table)
Rule Name | One-Line Description | Primary Benefit Category | Recommended (✅) | Auto-Fixable (🔧/💡) |
---|---|---|---|---|
better-regex | Improve regexes by making them shorter, consistent, and safer. | Bug Prevention | ❌ | 🔧 |
catch-error-name | Enforce a specific parameter name in catch clauses. | Consistency | ✅ | 🔧 |
consistent-assert | Enforce consistent assertion style with node:assert. | Consistency | ✅ | 🔧 |
consistent-date-clone | Prefer passing Date directly to the constructor when cloning. | Modernization | ✅ | 🔧 |
consistent-destructuring | Use destructured variables over properties. | Clarity | ❌ | 💡 |
consistent-empty-array-spread | Prefer consistent types when spreading a ternary in an array literal. | Bug Prevention | ✅ | 🔧 |
consistent-existence-index-check | Enforce consistent style for element existence checks. | Consistency | ✅ | 🔧 |
consistent-function-scoping | Move function definitions to the highest possible scope. | Performance | ✅ | ❌ |
custom-error-definition | Enforce correct Error subclassing. | Bug Prevention | ❌ | 🔧 |
empty-brace-spaces | Enforce no spaces between braces. | Consistency | ✅ | 🔧 |
error-message | Enforce passing a message value when creating a built-in error. | Bug Prevention | ✅ | ❌ |
escape-case | Require escape sequences to use uppercase or lowercase values. | Consistency | ✅ | 🔧 |
expiring-todo-comments | Add expiration conditions to TODO comments. | Governance | ✅ | ❌ |
explicit-length-check | Enforce explicitly comparing the length or size property of a value. | Clarity | ✅ | 🔧/💡 |
filename-case | Enforce a case style for filenames. | Consistency | ✅ | ❌ |
import-style | Enforce specific import styles per module. | Consistency | ✅ | ❌ |
new-for-builtins | Enforce new for all builtins, except primitive wrappers. | Bug Prevention | ✅ | 🔧/💡 |
no-abusive-eslint-disable | Enforce specifying rules to disable in eslint-disable comments. | Governance | ✅ | ❌ |
no-accessor-recursion | Disallow recursive access to this within getters and setters. | Bug Prevention | ✅ | ❌ |
no-anonymous-default-export | Disallow anonymous functions and classes as the default export. | Clarity | ✅ | 💡 |
no-array-callback-reference | Prevent passing a function reference directly to iterator methods. | Bug Prevention | ✅ | 💡 |
no-array-for-each | Prefer for…of over the forEach method. | Modernization | ✅ | 🔧/💡 |
no-array-method-this-argument | Disallow using the this argument in array methods. | Bug Prevention | ✅ | 🔧/💡 |
no-array-reduce | Disallow Array#reduce() and Array#reduceRight(). | Clarity | ✅ | ❌ |
no-array-reverse | Prefer Array#toReversed() over Array#reverse(). | Modernization | ✅ | 💡 |
no-array-sort | Prefer Array#toSorted() over Array#sort(). | Modernization | ✅ | 💡 |
no-await-expression-member | Disallow member access from await expression. | Clarity | ✅ | 🔧 |
no-await-in-promise-methods | Disallow using await in Promise method parameters. | Bug Prevention | ✅ | 💡 |
no-console-spaces | Do not use leading/trailing space between console.log parameters. | Consistency | ✅ | 🔧 |
no-document-cookie | Do not use document.cookie directly. | Bug Prevention | ✅ | ❌ |
no-empty-file | Disallow empty files. | Governance | ✅ | ❌ |
no-for-loop | Do not use a for loop that can be replaced with a for-of loop. | Modernization | ✅ | 🔧/💡 |
no-hex-escape | Enforce the use of Unicode escapes instead of hexadecimal escapes. | Consistency | ✅ | 🔧 |
no-instanceof-builtins | Disallow instanceof with built-in objects. | Bug Prevention | ✅ | 🔧/💡 |
no-invalid-fetch-options | Disallow invalid options in fetch() and new Request(). | Bug Prevention | ✅ | ❌ |
no-invalid-remove-event-listener | Prevent calling removeEventListener() with the result of an expression. | Bug Prevention | ✅ | ❌ |
no-keyword-prefix | Disallow identifiers starting with new or class. | Clarity | ❌ | ❌ |
no-lonely-if | Disallow if statements as the only statement in if blocks without else. | Clarity | ✅ | 🔧 |
no-magic-array-flat-depth | Disallow a magic number as the depth argument in Array#flat(…). | Clarity | ✅ | ❌ |
no-named-default | Disallow named usage of default import and export. | Consistency | ✅ | 🔧 |
no-negated-condition | Disallow negated conditions. | Clarity | ✅ | 🔧 |
no-negation-in-equality-check | Disallow negated expression in equality check. | Clarity | ✅ | 💡 |
no-nested-ternary | Disallow nested ternary expressions. | Clarity | ✅ | 🔧 |
no-new-array | Disallow new Array(). | Bug Prevention | ✅ | 🔧/💡 |
no-new-buffer | Enforce Buffer.from() and Buffer.alloc() over new Buffer(). | Security | ✅ | 🔧/💡 |
no-null | Disallow the use of the null literal. | Bug Prevention | ✅ | 🔧/💡 |
no-object-as-default-parameter | Disallow the use of objects as default parameters. | Bug Prevention | ✅ | ❌ |
no-process-exit | Disallow process.exit(). | Governance | ✅ | ❌ |
no-single-promise-in-promise-methods | Disallow passing single-element arrays to Promise methods. | Bug Prevention | ✅ | 🔧/💡 |
no-static-only-class | Disallow classes that only have static members. | Clarity | ✅ | 🔧 |
no-thenable | Disallow then property. | Bug Prevention | ✅ | ❌ |
no-this-assignment | Disallow assigning this to a variable. | Clarity | ✅ | ❌ |
no-typeof-undefined | Disallow comparing undefined using typeof. | Bug Prevention | ✅ | 🔧/💡 |
no-unnecessary-array-flat-depth | Disallow using 1 as the depth argument of Array#flat(). | Clarity | ✅ | 🔧 |
no-unnecessary-array-splice-count | Disallow using .length or Infinity as deleteCount for splice. | Clarity | ✅ | 🔧 |
no-unnecessary-await | Disallow awaiting non-promise values. | Performance | ✅ | 🔧 |
no-unnecessary-polyfills | Enforce built-in methods over unnecessary polyfills. | Performance | ✅ | ❌ |
no-unnecessary-slice-end | Disallow using .length or Infinity as the end argument of slice. | Clarity | ✅ | 🔧 |
no-unreadable-array-destructuring | Disallow unreadable array destructuring. | Clarity | ✅ | 🔧 |
no-unreadable-iife | Disallow unreadable IIFEs. | Clarity | ✅ | ❌ |
no-unused-properties | Disallow unused object properties. | Maintenance | ❌ | ❌ |
no-useless-error-capture-stack-trace | Disallow unnecessary Error.captureStackTrace(…). | Performance | ✅ | 🔧 |
no-useless-fallback-in-spread | Disallow useless fallback when spreading in object literals. | Clarity | ✅ | 🔧 |
no-useless-length-check | Disallow useless array length check. | Clarity | ✅ | 🔧 |
no-useless-promise-resolve-reject | Disallow useless Promise.resolve/reject in async functions. | Clarity | ✅ | 🔧 |
no-useless-spread | Disallow unnecessary spread. | Clarity | ✅ | 🔧 |
no-useless-switch-case | Disallow useless case in switch statements. | Bug Prevention | ✅ | 💡 |
no-useless-undefined | Disallow useless undefined. | Clarity | ✅ | 🔧 |
no-zero-fractions | Disallow number literals with zero fractions or dangling dots. | Consistency | ✅ | 🔧 |
number-literal-case | Enforce proper case for numeric literals. | Consistency | ✅ | 🔧 |
numeric-separators-style | Enforce the style of numeric separators by correctly grouping digits. | Consistency | ✅ | 🔧 |
prefer-add-event-listener | Prefer .addEventListener() over on- functions. | Modernization | ✅ | 🔧 |
prefer-array-find | Prefer .find() over the first element from .filter(). | Performance | ✅ | 🔧/💡 |
prefer-array-flat | Prefer Array#flat() over legacy flattening techniques. | Modernization | ✅ | 🔧 |
prefer-array-flat-map | Prefer .flatMap() over .map().flat(). | Modernization | ✅ | 🔧 |
prefer-array-index-of | Prefer indexOf over findIndex when looking for an index. | Performance | ✅ | 🔧/💡 |
prefer-array-some | Prefer .some() over .filter().length and .find(). | Performance | ✅ | 🔧/💡 |
prefer-at | Prefer .at() method for index access. | Modernization | ✅ | 🔧/💡 |
prefer-blob-reading-methods | Prefer Blob#arrayBuffer() and Blob#text() over FileReader. | Modernization | ✅ | ❌ |
prefer-class-fields | Prefer class field declarations over constructor assignments. | Modernization | ✅ | 🔧/💡 |
prefer-code-point | Prefer String#codePointAt() over String#charCodeAt(). | Modernization | ✅ | 💡 |
prefer-date-now | Prefer Date.now() over new Date().getTime(). | Performance | ✅ | 🔧 |
prefer-default-parameters | Prefer default parameters over reassignment. | Modernization | ✅ | 💡 |
prefer-dom-node-append | Prefer Node#append() over Node#appendChild(). | Modernization | ✅ | 🔧 |
prefer-dom-node-dataset | Prefer .dataset over attribute methods for data-* attributes. | Modernization | ✅ | 🔧 |
prefer-dom-node-remove | Prefer childNode.remove() over parentNode.removeChild(). | Modernization | ✅ | 🔧/💡 |
prefer-dom-node-text-content | Prefer .textContent over .innerText. | Performance | ✅ | 💡 |
prefer-event-target | Prefer EventTarget over EventEmitter. | Modernization | ✅ | ❌ |
prefer-export-from | Prefer export…from when re-exporting. | Clarity | ✅ | 🔧/💡 |
prefer-global-this | Prefer globalThis over window, self, and global. | Modernization | ✅ | 🔧 |
prefer-import-meta-properties | Prefer import.meta properties over legacy techniques. | Modernization | ❌ | 🔧 |
prefer-includes | Prefer .includes() over .indexOf() for existence checks. | Clarity | ✅ | 🔧/💡 |
prefer-json-parse-buffer | Prefer reading a JSON file as a buffer. | Performance | ❌ | 🔧 |
prefer-keyboard-event-key | Prefer KeyboardEvent#key over KeyboardEvent#keyCode. | Modernization | ✅ | 🔧 |
prefer-logical-operator-over-ternary | Prefer using a logical operator over a ternary. | Clarity | ✅ | 💡 |
prefer-math-min-max | Prefer Math.min() and Math.max() over ternaries. | Clarity | ✅ | 🔧 |
prefer-math-trunc | Enforce Math.trunc instead of bitwise operators. | Clarity | ✅ | 🔧/💡 |
prefer-modern-dom-apis | Prefer modern DOM APIs like .before() and .replaceWith(). | Modernization | ✅ | 🔧 |
prefer-modern-math-apis | Prefer modern Math APIs over legacy patterns. | Modernization | ✅ | 🔧 |
prefer-module | Prefer JavaScript modules (ESM) over CommonJS. | Modernization | ✅ | 🔧/💡 |
prefer-native-coercion-functions | Prefer using String, Number, etc., directly for coercion. | Clarity | ✅ | 🔧 |
prefer-negative-index | Prefer negative index over .length - index. | Clarity | ✅ | 🔧 |
prefer-node-protocol | Prefer using the node: protocol for Node.js imports. | Consistency | ✅ | 🔧 |
prefer-number-properties | Prefer Number static properties over global ones. | Modernization | ✅ | 🔧/💡 |
prefer-object-from-entries | Prefer Object.fromEntries() to transform key-value pairs. | Modernization | ✅ | 🔧 |
prefer-optional-catch-binding | Prefer omitting the catch binding parameter when unused. | Modernization | ✅ | 🔧 |
prefer-prototype-methods | Prefer borrowing methods from the prototype. | Performance | ✅ | 🔧 |
prefer-query-selector | Prefer .querySelector() over older getElementById etc. | Modernization | ✅ | 🔧 |
prefer-reflect-apply | Prefer Reflect.apply() over Function#apply(). | Modernization | ✅ | 🔧 |
prefer-regexp-test | Prefer RegExp#test() over String#match() for boolean checks. | Performance | ✅ | 🔧/💡 |
prefer-set-has | Prefer Set#has() over Array#includes() for existence checks in Sets. | Performance | ✅ | 🔧/💡 |
prefer-set-size | Prefer using Set#size instead of Array#length. | Consistency | ✅ | 🔧 |
prefer-single-call | Enforce combining multiple calls like Array#push into one. | Performance | ✅ | 🔧/💡 |
prefer-spread | Prefer the spread operator over Array.from(), concat(), etc. | Modernization | ✅ | 🔧/💡 |
prefer-string-raw | Prefer using String.raw to avoid escaping backslashes. | Clarity | ✅ | 🔧 |
prefer-string-replace-all | Prefer String#replaceAll() over global regex for simple replacements. | Modernization | ✅ | 🔧 |
prefer-string-slice | Prefer String#slice() over substr() and substring(). | Modernization | ✅ | 🔧 |
prefer-string-starts-ends-with | Prefer String#startsWith() & endsWith() over RegExp#test(). | Clarity | ✅ | 🔧/💡 |
prefer-string-trim-start-end | Prefer trimStart()/trimEnd() over trimLeft()/trimRight(). | Modernization | ✅ | 🔧 |
prefer-structured-clone | Prefer using structuredClone to create a deep clone. | Modernization | ✅ | 💡 |
prefer-switch | Prefer switch over multiple else-if. | Clarity | ✅ | 🔧 |
prefer-ternary | Prefer ternary expressions over simple if-else statements. | Clarity | ✅ | 🔧 |
prefer-top-level-await | Prefer top-level await over top-level promises. | Modernization | ✅ | 💡 |
prefer-type-error | Enforce throwing TypeError in type checking conditions. | Bug Prevention | ✅ | 🔧 |
prevent-abbreviations | Prevent abbreviations. | Clarity | ✅ | 🔧 |
relative-url-style | Enforce consistent relative URL style. | Consistency | ✅ | 🔧/💡 |
require-array-join-separator | Enforce using the separator argument with Array#join(). | Bug Prevention | ✅ | 🔧 |
require-module-specifiers | Require non-empty specifier list in import and export statements. | Bug Prevention | ✅ | 🔧/💡 |
require-number-to-fixed-digits-argument | Enforce using the digits argument with Number#toFixed(). | Bug Prevention | ✅ | 🔧 |
require-post-message-target-origin | Enforce using the targetOrigin argument with window.postMessage(). | Security | ❌ | 💡 |
string-content | Enforce better string content. | Clarity | ❌ | 🔧/💡 |
switch-case-braces | Enforce consistent brace style for case clauses. | Clarity | ✅ | 🔧 |
template-indent | Fix whitespace-insensitive template indentation. | Consistency | ✅ | 🔧 |
text-encoding-identifier-case | Enforce consistent case for text encoding identifiers. | Consistency | ✅ | 🔧/💡 |
throw-new-error | Require new when creating an error. | Bug Prevention | ✅ | 🔧 |
Introduction: Linting as a Proactive Quality Strategy
Core Thesis
Moving Beyond Style Checking
In modern software development, the pursuit of quality is not a final step but a continuous, integrated process. Within this process, static analysis tools like ESLint represent one of the most effective and economically sound strategies for building robust, maintainable, and resilient applications. To view ESLint as a mere style checker is to fundamentally underestimate its power. It is a sophisticated system for risk management, a proactive defense against common errors, and an automated enforcer of team-wide best practices. While the default ESLint rules provide a solid baseline, achieving a truly robust, maintainable, and high-velocity development process often requires a more opinionated and comprehensive approach.
This is the domain of specialized ESLint plugins, among which eslint-plugin-unicorn
stands out. It is a curated collection of over 100 powerful rules designed to push codebases towards modern, readable, explicit, and less error-prone JavaScript. The philosophy underpinning this plugin is not merely about stylistic preference; it is a strategic framework for reducing technical debt, improving developer experience, and increasing the long-term stability of an application. The rules often challenge common practices, advocating for newer APIs, stricter patterns, and greater clarity, thereby serving as an automated guide to writing better code.
The Goal: Production Impact Analysis
This report provides an exhaustive analysis of a comprehensive set of ESLint rules, moving beyond simple descriptions to offer a detailed "Production Impact Analysis" for each one. The goal is to equip technical leads, engineering managers, and senior developers with the deep understanding necessary to architect a linting strategy that directly contributes to production stability and developer velocity.
To make the benefits tangible, each rule analysis will follow a pragmatic, case-based format: If you enable X, then for cases like Y and Z, by doing A, it helps you be more B, which prevents C, so your app in production is more T. This framework translates abstract rules into concrete outcomes, clearly articulating each rule's value proposition in the context of real-world development and production environments.
The Four Pillars of Proactive Quality
The true value of a well-configured ESLint setup can be understood through four strategic pillars. These pillars illustrate how seemingly simple rules coalesce into a powerful framework for proactive quality assurance.
Pillar 1: Reducing Cognitive Load
A significant portion of a developer's time is spent reading and understanding existing code. The mental effort required for this comprehension is known as cognitive load. Ambiguous language features or inconsistent coding patterns dramatically increase this load. For instance, when a developer encounters a loose equality check (==
), their brain must pause to simulate JavaScript's complex type coercion rules to determine the outcome. This mental friction, multiplied across thousands of lines of code and an entire team, leads to slower development, misinterpretations, and a higher incidence of bugs. ESLint rules that enforce consistency (curly
, arrow-body-style
) and eliminate ambiguity (eqeqeq
, no-nested-ternary
) act as cognitive aids. By automating the enforcement of a single, predictable way of writing code, ESLint frees developers' mental bandwidth to focus on the core business logic, rather than deciphering syntactic quirks.
Pillar 2: Eradicating Latent "Time-Bomb" Bugs
Many of the most dangerous bugs are not those that cause immediate crashes, but those that lie dormant within the codebase, waiting for a specific set of circumstances to emerge. These are "time-bomb" bugs. A function defined inside a loop that captures a loop variable (no-loop-func
), or a non-atomic update within an async function (require-atomic-updates
), might pass all unit tests and function perfectly under normal conditions. The underlying flaw—a closure capturing an incorrect value or a subtle race condition—is latent. In a production environment, changes in request timing, network latency, or system load can trigger this flaw, leading to data corruption or system failure. These bugs are notoriously expensive to debug because they are intermittent and difficult to reproduce. ESLint rules like no-loop-func
and require-atomic-updates
are designed to identify and eliminate these fragile patterns at the moment of creation. This shifts the cost of remediation from days of high-stakes production firefighting to a few seconds of automated feedback in the developer's editor, dramatically improving system reliability.
Pillar 3: Driving Modernization
The JavaScript language has evolved significantly, introducing features in ES6 and beyond that are safer, more expressive, and less error-prone than their predecessors. Features like let
and const
offer superior block-scoping over var
, rest parameters (...args
) are safer than the arguments
object, and template literals are more readable than string concatenation. Without a forcing function, however, codebases often become a heterogeneous mix of old and new patterns, increasing complexity and creating an inconsistent developer experience. ESLint can act as a powerful modernization engine. By enabling rules such as no-var
, prefer-const
, prefer-rest-params
, and prefer-template
, a team can systematically guide its codebase toward modern best practices. This not only improves the intrinsic quality of the code but also ensures that all developers, regardless of experience level, are learning and applying the best available language features.
Pillar 4: Enhancing Security and Robustness
Certain JavaScript patterns are known security vulnerabilities. The use of eval()
can open the door to code injection attacks, javascript:
URLs can be exploited for cross-site scripting (XSS), and extending native object prototypes (no-extend-native
) can lead to conflicts with third-party libraries. ESLint provides a set of rules (no-eval
, no-script-url
, no-extend-native
) that act as a first line of defense against these common vulnerabilities. By flagging and forbidding these patterns at development time, ESLint helps to harden the application from the inside out, reducing the attack surface and preventing entire classes of security flaws from ever reaching the codebase.
The Economics and Culture of Linting
The Return on Investment (ROI) of Early Bug Detection
The return on investment (ROI) for a robust linting strategy is exceptionally high. The cost of fixing a bug increases exponentially as it progresses through the development lifecycle. A bug caught by the linter in the developer's editor costs mere seconds to fix. The same bug caught during a code review costs minutes or hours of two developers' time. If it's found in the QA stage, the cost grows to include testing and redeployment cycles. A bug that reaches production can cost thousands or even millions of dollars in downtime, data corruption, customer churn, and emergency engineering effort. ESLint effectively shifts the entire bug detection process to the left, catching errors at the earliest, cheapest possible stage.
The Social Contract of Code
An ESLint configuration file (.eslintrc.json
, for example) is more than a technical artifact; it is the codified representation of a team's collective agreement on quality. In the absence of such a tool, standards are enforced manually during code reviews. This process can be slow, inconsistent, and prone to interpersonal friction, as feedback can be perceived as subjective criticism. ESLint externalizes these standards into an objective, impartial arbiter. It provides immediate, consistent, and depersonalized feedback. This transforms the nature of code review. Instead of nitpicking syntax, spacing, or variable naming, reviews can focus on higher-level concerns: architectural soundness, algorithmic efficiency, and the fulfillment of business requirements. ESLint automates the enforcement of the team's social contract, allowing the team to scale its best practices effortlessly.
Part I: Fortifying the Foundation - Preventing Critical Bugs & Runtime Errors
Preamble: The Non-Negotiable Bedrock
This section covers the most critical set of ESLint rules. These rules are not matters of stylistic preference; they target code that is either syntactically incorrect, contains a demonstrable logical flaw, or exhibits behavior that is a well-known source of bugs. These are the highest ROI rules in production because they stop real bugs before they ship. These patterns can lead directly to runtime errors, infinite loops, data corruption, and unpredictable application states. Enabling the rules in this section should be considered the non-negotiable first step in establishing any professional code quality standard. They form the bedrock upon which a stable and reliable application is built.
Ensuring Correct Language Constructs and Variable Scoping
Class and Constructor Integrity
constructor-super
The constructor-super
rule requires super()
to be called in derived class constructors before this
or super
can be accessed. The purpose of this rule is to ensure that constructors of derived classes call super()
and that constructors of non-derived classes do not call super()
. This prevents runtime errors like ReferenceError
in derived classes without super()
and TypeError
or ReferenceError
in non-derived classes or classes extending null
.
If this rule is used, for cases like creating a class PremiumUser
that extends User
, by enforcing that super()
is the first statement in the PremiumUser
constructor, it will help the code to be compliant with the JavaScript object model. This is why a ReferenceError
never happens when the code attempts to access this.name
or this.subscription
before the parent User
object has been properly initialized. It will help the app in production to be more stable, preventing entire object initialization chains from failing catastrophically due to a misplaced this
keyword.
no-class-assign
This rule disallows reassigning class members, particularly class declarations, to prevent runtime errors and maintain class integrity. If you use the no-class-assign
rule, for cases where a developer accidentally reassigns a class identifier, such as class User {}; User = 1;
, by preventing this reassignment, it will help the code to be structurally immutable. This is why a TypeError: User is not a constructor
never happens later in the code when an attempt is made to instantiate the class with new User()
. It will help the app in production to be more robust, safeguarding critical class definitions from being inadvertently overwritten, which would otherwise break all functionality dependent on that class.
no-const-assign
This rule disallows reassigning const
variables, ensuring immutability and preventing runtime errors. If you use the no-const-assign
rule, for cases like defining a server endpoint const API_URL = '...';
and later attempting to modify it, by flagging this as an error, it will help the code to be declaratively immutable. This is why a TypeError
never happens at runtime when the reassignment is attempted. It will help the app in production to be more secure and predictable, ensuring that fundamental constants and configuration values cannot be accidentally or maliciously altered during the application's lifecycle.
no-constructor-return
The no-constructor-return
rule disallows returning values from constructors, preventing unexpected instance replacements. If you use the no-constructor-return
rule, for cases where a constructor for a Widget
class incorrectly contains return 5;
, by flagging this statement, it will help the code to adhere to standard object-oriented principles. This is why confusion where new Widget()
evaluates to the number 5 instead of a Widget
instance never happens. It will help the app in production to be more maintainable, as developers can trust that the new
keyword will always yield an instance of the specified class, preventing bizarre bugs where expected methods and properties are missing.
no-dupe-class-members
This rule disallows duplicate class members, preventing silent overwrites and unexpected behavior. If you use the no-dupe-class-members
rule, for cases where a class DataProcessor
has two methods named process()
, by highlighting the duplicate, it will help the code to be unambiguous. This is why a situation where the first process()
method is silently overwritten by the second, leading to the wrong logic being executed, never happens. It will help the app in production to be more correct, ensuring that all defined class methods are preserved and behave as intended, preventing subtle logic bugs that are hard to trace.
no-this-before-super
This rule is a more specific companion to constructor-super
, disallowing this
or super
before super()
in derived class constructors to prevent reference errors. If this rule is used, for cases in a derived Manager
class constructor where this.reports = [];
appears before super(name);
, by flagging the premature use of this
, it will help the code to respect the object initialization lifecycle. This is why a ReferenceError
never happens, as the this
context for a derived class is only established after the parent constructor completes via super()
. It will help the app in production to be more stable, guaranteeing that objects are constructed in the correct order, from parent to child, preventing runtime crashes.
no-accessor-recursion
This rule prevents a critical logic error where a class or object property's getter or setter recursively calls itself, which would immediately cause a stack overflow. For example, get value() { return this.value; }
will cause an infinite loop. This rule catches this guaranteed runtime failure during static analysis. It guards against recursive accessor calls.
If you use the no-accessor-recursion
rule, for cases where a developer accidentally creates a recursive property accessor, by flagging this at lint-time, it will help you be certain that your object properties will not crash the application. This is because it catches infinite recursion before the code is ever run, which is why a fatal stack overflow error from property access never happens, and it will help your app in production to be 100% free of this specific class of critical bug.
// ✗ BAD
class User {
get name() {
return this.name; // Infinite recursion
}
set name(value) {
this.name = value; // Infinite recursion
}
}
// ✓ GOOD
class User {
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
}
custom-error-definition
When creating custom error classes, it is crucial to subclass the built-in Error
object correctly by calling super(message)
and setting the this.name
property. This ensures custom errors are as informative as built-in ones, with correct names, messages, and stack traces.
If you use custom-error-definition
, for cases where a developer creates a custom error without setting this.name
, by flagging this omission, it will help your error logs be more informative and actionable. This is because the error will appear as MyCustomError
instead of a generic Error
, which is why engineers debugging a production issue never have to guess the type of error they are dealing with, and it will help your app in production to have more effective monitoring and faster incident resolution.
Variable and Function Declaration Safety
no-undef
The no-undef
rule disallows the use of undeclared variables unless mentioned in /*global */
comments, preventing ReferenceError
s. If you use this rule, for cases where a typo like console.log(userName)
is made instead of console.log(username)
, by flagging userName
as undeclared, it will help the code to be free of reference mistakes. This is why a ReferenceError: userName is not defined
which would crash the application or a specific feature never happens at runtime. It will help the app in production to be significantly more reliable, catching one of the most common and disruptive types of JavaScript errors during development.
no-func-assign
This rule disallows reassigning function declarations, preventing runtime TypeError
s. If you use no-func-assign
, for cases where a utility function function calculateTotal() {}
is later reassigned like calculateTotal = someValue;
, by preventing this action, it will help the code to maintain the integrity of its function references. This is why a TypeError: calculateTotal is not a function
never happens when other parts of the application try to call it. It will help the app in production to be more stable, preventing critical application logic from being disabled by an accidental reassignment.
no-import-assign
This rule disallows assigning to imported bindings, preventing modifications to read-only imports. If you use no-import-assign
, for cases where a utility is imported import { someUtil } from './utils';
and a developer tries to modify it someUtil = () => {};
, by flagging this as an error, it will help the code to treat modules as immutable APIs. This is why a TypeError
never happens when the reassignment is attempted, as imported bindings are read-only. It will help the app in production to be more predictable and easier to debug, ensuring that the behavior of imported modules is consistent across the entire application and not subject to local mutations.
no-use-before-define
This rule disallows using variables before they are defined, preventing reference errors from the Temporal Dead Zone (TDZ) or confusing hoisting behavior. It prevents TDZ errors and undefined reads. In production, this means fewer startup failures because all variables are defined before they are used.
no-inner-declarations
This rule disallows variable or function declarations in nested blocks where hoisting can confuse scope, preventing cross-browser and scope quirks. By requiring declarations to be in the outer scope, it ensures a predictable execution order and improves code reliability in production.
no-unassigned-vars
This rule disallows let
or var
variables that are read but never assigned a value. If you use this rule, for cases where a variable let userProfile;
is declared but never assigned before being used in a function call displayProfile(userProfile);
, by flagging this usage, it will help the code to be explicitly initialized. This is why a situation where displayProfile
receives undefined
and subsequently crashes or renders an empty state never happens. It will help your app in production to be more robust, preventing bugs that stem from logic operating on unintentionally undefined
values.
Eliminating Logical Fallacies and Ambiguities
Array and Collection Logic
array-callback-return
The array-callback-return
rule enforces that callbacks for array methods that are expected to return a value (like .map()
, .filter()
, .reduce()
) actually do so. If you use this rule, for cases like transforming an array of products into an array of prices with products.map(p => { p.price; })
, by flagging the missing return
statement, it will help the code to be functionally correct. This is why a scenario where the resulting prices
array becomes [undefined, undefined, undefined]
never happens.
For example, in const indexMap = myArray.reduce(function(memo, item, index) { memo[item] = index; }, {});
, forgetting to return memo
can result in errors like "cannot set property 'b' of undefined". By requiring a return
, the rule ensures the accumulator is updated correctly. This helps your app in production to be more data-resilient, preventing downstream components from failing when they receive an array of undefined values, avoiding silent data corruption and ensuring bad results never leak to users.
no-sparse-arrays
This rule disallows sparse array literals with missing elements (e.g., [1,,2]
), preventing unexpected undefined
slots and iteration surprises. This ensures a consistent array structure, which contributes to data integrity in production by avoiding sparse array overhead and making loops more deterministic.
consistent-empty-array-spread
If you use consistent-empty-array-spread
, for cases like conditional array building in UI rendering, by ensuring empty spreads are typed as arrays (e.g., [...(condition ? items : [])]
), it will help you to be more type-safe. This prevents TypeError
s in concatenations, which is why your app in production will be more stable without unexpected undefined
values. It keeps the types consistent when spreading a ternary expression in an array literal.
Conditional and Loop Logic
for-direction
This rule enforces that the update clause in a for
loop moves the counter in the correct direction relative to the condition. If you use for-direction
, for cases like a loop written as for (let i = 0; i < 10; i--)
, by identifying that the counter i
is moving away from the termination condition, it will help the code to be logically sound. This is why an infinite loop, which would freeze the browser tab or exhaust server resources, never happens. It will help the app in production to be more performant and stable, preventing a simple typo from causing a denial-of-service condition on the client or server.
no-cond-assign
This rule disallows assignment operators in conditional expressions, which is often a typo for a comparison operator. If you use no-cond-assign
, for cases like if (user = findUser(id))
(a typo for ==
or ===
), by flagging the assignment, it will help the code to be free of accidental mutations. This is why a bug where the if
block always executes (unless findUser
returns a falsy value) and the user
variable is unintentionally overwritten never happens. It will help the app in production to be more reliable, preventing logic errors where conditions are always met due to an assignment rather than a comparison.
no-constant-binary-expression
This rule disallows expressions where the operation has no effect on the value, often due to a typo or misunderstanding of operator precedence. For example, in const value = +x == null;
, the expression is constant and can be simplified. If you use this rule, for cases like const value = x | y + z;
where the developer meant const value = (x | y) + z;
, by flagging that y + z
will always be evaluated because +
has higher precedence than ||
, it will help the code to be logically precise. This is why a subtle calculation error, where the short-circuiting logic doesn't behave as the developer visually perceived it, never happens. It will help your app in production to be more correct, preventing bugs that stem from a misunderstanding of JavaScript's operator precedence rules.
no-constant-condition
This rule disallows the use of constant expressions in conditions (e.g., if (true)
, while (1)
). If you use this rule, for cases like while (true)
that was intended to be while (running)
but was left in from debugging, by flagging these constant conditions, it will help the code to be dynamically correct. This is why an accidental infinite loop or a block of unreachable dead code never happens. In a production scenario, if you use this rule for cases like building API payloads from lists, by guaranteeing each transform returns and branches terminate, you’ll be more correct, which is why silent data loss never happens, and your analytics stay trustworthy.
no-unmodified-loop-condition
This rule disallows loop conditions where the tested variables are not modified inside the loop. If you use no-unmodified-loop-condition
, for cases like while (node && node.type !== 'Target') { process(node); }
where the developer forgot to add node = node.parent;
inside the loop, by analyzing the code paths and seeing the node
variable is never updated, it will help the code to guarantee progress. This is why an infinite loop, which would freeze the user's browser or hang a server process, never happens. It will help the app in production to be more resilient, preventing logical oversights from causing catastrophic performance failures.
Value Comparison and Type Checking
no-compare-neg-zero
This rule disallows comparing a value against negative zero (-0
), as standard equality operators do not distinguish it from +0
. While x === -0
works, Object.is(x, -0)
is the clearer way to distinguish them. The main issue is that comparisons like x > -0
behave identically to x > 0
, which is counter-intuitive. If this rule is used, for cases where a developer writes if (value >= -0)
, by flagging this comparison, it will help the code to be unambiguous about its intent. This is why a developer's mistaken assumption that this comparison behaves differently from value >= 0
never happens. It will help the app in production to be more correct, especially in scientific or graphical applications where the distinction between +0
and -0
can be meaningful, ensuring that intent is expressed clearly with Object.is()
.
no-self-compare
This rule disallows comparing a variable to itself. The only practical reason to do so (x === x
) is to check if it is NaN
, since NaN
is the only JavaScript value not equal to itself. However, this is cryptic. If you use this rule, for cases where a developer writes if (value === value)
, by flagging this comparison, it will help the code to be idiomatically clear. This is why a confusing check for NaN
never happens, promoting the use of the much clearer Number.isNaN(value)
. It will help the app in production to be more readable, making the intent of the code immediately obvious to future maintainers.
use-isnan
This rule requires the use of isNaN()
or Number.isNaN()
when checking for the NaN
value. If you use this rule, for cases where a developer checks if (result === NaN)
, by flagging this incorrect comparison, it will help the code to be correct in its handling of special numeric values. Since NaN
is never equal to any value, including itself, result === NaN
is always false. This is why a bug where invalid numerical results are not properly handled never happens. It will help the app in production to be more robust, ensuring that calculations resulting in NaN
are correctly caught and managed, preventing further data corruption.
valid-typeof
This rule enforces that the string compared against a typeof
expression is a valid, known type (e.g., "string", "object", "undefined"). If you use this rule, for cases where a developer writes typeof foo === "undefimed"
due to a typo, by flagging the invalid string, it will help the code to be free of comparison errors. This is why a condition that is always false due to a typo, potentially hiding a critical code path, never happens. It will help the app in production to be more reliable, preventing bugs that are extremely difficult to spot visually but would cause entire logic blocks to be skipped.
no-typeof-undefined
This rule disallows comparing undefined
using typeof
, suggesting a direct comparison x === undefined
instead. For cases like variable checks (typeof x === 'undefined'
) and parameter validation, this rule fixes them to use the simpler and more concise x === undefined
, which avoids verbosity and makes the code more efficient and readable in production. This prevents typos in the 'undefined'
string and promotes a more consistent style.
Mastering Asynchronous Code and Promises
Promise Construction and Execution
no-async-promise-executor
This rule disallows using an async
function as a Promise
executor. If you use this rule, for cases like new Promise(async (resolve, reject) => {... })
, by flagging this pattern, it will help the code to have clear error handling semantics. An async
executor function can create a situation where an error thrown inside it results in an unhandled promise rejection, because the error is caught by the implicit try/catch
of the async
function, not the reject
callback of the Promise
. This is why a "silent failure," where an error is swallowed and the promise never settles, never happens. It will help the app in production to be more debuggable and reliable, ensuring all asynchronous errors are properly propagated and can be caught by .catch()
or try/catch
blocks.
no-promise-executor-return
This rule disallows returning a value from a Promise
executor function. If you use this rule, for cases like new Promise((resolve) => { return someValue; })
, by flagging the return statement, it will help the code to be idiomatically correct. The returned value from an executor is ignored and cannot be used; promises are resolved using the resolve
callback. This is why a developer's false assumption that the returned value resolves the promise never happens. It will help the app in production to be more maintainable, preventing confusing code that suggests a behavior (returning a value) that has no actual effect, and guiding developers to use the resolve()
function correctly.
no-thenable
If you enable no-thenable
, then for objects that mimic Promises via a then
property, by forbidding thenables, you’re more standards-aligned, which prevents confusing promise detection and unawaited flows, so production is more predictable. Disallowing the then
property on non-Promise objects avoids cases where they might be accidentally awaited or treated as promises.
no-single-promise-in-promise-methods
If you enable no-single-promise-in-promise-methods
, then for Promise.*
methods, by not wrapping single promises in arrays (e.g., Promise.all([onePromise])
), you’re more direct, which prevents wasted allocations and intent confusion, so production is leaner. It helps simplify the code and avoid unnecessary overhead.
await
Usage and Patterns
no-await-in-loop
This rule disallows the use of await
inside loops. If you use no-await-in-loop
, for cases like fetching user data for an array of IDs with for (const id of ids) { users.push(await fetchUser(id)); }
, by flagging the await
in the loop, it will help the developer to be conscious of performance. This pattern executes the asynchronous calls sequentially, which can be extremely slow. This is why a performance bottleneck, where the UI freezes or a server endpoint takes seconds to respond, never happens. It encourages refactoring to Promise.all
for parallel execution.
For example, if you use no-await-in-loop
with prefer-rest-params
and prefer-spread
, then for cases like sending N independent HTTP requests, by batching with Promise.all
, you’ll be more latency-efficient, which is why timeouts under load never happen, and it keeps your service’s p95/p99 low.
// Before
for (const id of ids) {
await fetch(`/items/${id}`); // serialized
}
// After
await Promise.all(ids.map(id => fetch(`/items/${id}`)));
require-atomic-updates
This rule disallows assignments to variables that could lead to race conditions when used with await
or yield
. If you use require-atomic-updates
, for cases like calculating a running total let total = 0; total = total + await getValue();
, by flagging this non-atomic update, it will help the code to be safe from race conditions. Between the time getValue()
is awaited and the assignment happens, another asynchronous operation could modify total
, leading to data loss. This is why a state corruption bug never happens. It will help your app in production to be more data-consistent, preventing subtle calculation errors that are nearly impossible to debug.
// Before
count = count + await getDelta(); // race
// After
const delta = await getDelta();
count += delta;
no-await-expression-member
If you enable no-await-expression-member
, then for code like (await foo).bar
, by avoiding member access off an await
expression, you’re more robust, which prevents null
/undefined
hazards and awkward precedence, so production is safer. It encourages assigning the awaited value to a variable first, which makes the code clearer and more debuggable.
no-await-in-promise-methods
If you enable no-await-in-promise-methods
, then for Promise.all/any/allSettled/race
, by avoiding await
inside the method arguments, you’re more concurrent, which prevents serializing work accidentally, so production is faster and cheaper. It ensures that promises are passed to these methods directly, allowing them to execute in parallel as intended.
no-unnecessary-await
If you enable no-unnecessary-await
, then for async functions, by forbidding await
on non-promise values, you’re more efficient, which prevents needless micro-ticks and masked sync errors, so production is faster. Removing pointless awaits can improve async performance and make the code's intent clearer.
Hardening Control Flow and Error Handling
Switch Statements
no-dupe-else-if
This rule disallows duplicate conditions in if-else-if
chains. If you use this rule, for cases where a long if-else-if
chain has if (x === 1)
and later else if (x === 1)
, by flagging the duplicate condition, it will help the code to be logically efficient. The second branch is unreachable dead code. This is why a situation where a developer adds logic to the second, unreachable branch and wonders why it never executes never happens. It will help the app in production to be more correct and maintainable, eliminating redundant code and preventing bugs caused by logic being placed in an unreachable block.
no-duplicate-case
This rule disallows duplicate case
labels in switch
statements. If you use this rule, for cases in a switch (status)
statement where case 'pending':
appears twice, by flagging the duplicate, it will help the code to be unambiguous. Only the first instance of a case label is ever reachable. This is why a bug where the logic in the second case 'pending':
block is silently ignored never happens. It will help the app in production to be more reliable, ensuring that all intended logic paths in a switch
statement are reachable and preventing subtle errors from copy-paste mistakes.
no-fallthrough
This rule disallows fallthrough in switch
cases (i.e., omitting break
, return
, or throw
). If you use this rule, for cases where a developer forgets a break;
statement in a switch
case, by flagging the potential fallthrough, it will help the code to be explicit about its control flow. This is why a bug where an action for status === 'approved'
also accidentally executes the code for status === 'shipped'
never happens. It will help the app in production to be more predictable, preventing unintended, cascading execution of multiple switch
cases, which can lead to corrupt state or incorrect behavior.
Error Handling
no-ex-assign
This rule disallows reassigning the exception variable in a catch
clause. If you use no-ex-assign
, for cases like catch (e) { e = new Error('...'); }
, by flagging the reassignment, it will help the code to preserve original error information. Reassigning the error variable loses the stack trace and other valuable context from the original exception. In production, when handling errors around critical operations like payment capture, this ensures that the original error bubbles up with a clean stack, preventing "swallowed error" incidents and reducing Mean Time To Resolution (MTTR).
no-unsafe-finally
This rule disallows control flow statements (return
, throw
, break
, continue
) inside a finally
block. If you use this rule, for cases where a return
statement is placed in a finally
block of a function that also has a return
in its try
block, by flagging the return
in finally
, it will help the code to have predictable exit points. A return
in finally
will always override a return
from try
or catch
, and can even swallow a thrown exception. This is why a critical error being silently discarded because of a return
in a finally
block never happens. This ensures reliable error propagation and helps the app in production to be more reliable.
error-message
This rule enforces that a descriptive message is always passed when creating a new instance of a built-in Error
object. If you use error-message
, for cases like throw new TypeError();
, by requiring a message like throw new TypeError('Invalid argument type');
, it will help your production error logs be immediately actionable. This is because the log entry contains a human-readable explanation of the problem, which is why on-call engineers can diagnose issues faster without needing to reproduce the error, and it will help your app in production to have a shorter mean time to resolution (MTTR) for incidents.
throw-new-error
This rule requires new
when creating and throwing an error. If you use throw-new-error
, for cases like throw Error('msg')
, by requiring throw new Error('msg')
, it helps you be more correct in your error handling. This ensures that a proper Error
instance with a full stack trace is thrown, which prevents non-instance errors from being thrown, so your app in production is more standard and easier to debug.
Sanitizing Regular Expressions and Strings
Regular Expressions
better-regex
The better-regex
rule improves regular expressions by making them shorter, consistent, and safer through automatic fixes. If you use better-regex
, for cases like a complex, unoptimized regex for input validation ([a-z]+
)+, by simplifying and securing it, it will help your application be more resilient against denial-of-service attacks. This is because it prevents Regular Expression Denial of Service (ReDoS) vulnerabilities, which is why a malicious user can never crash your server by submitting a carefully crafted string, and it will help your app in production to be more secure and performant. For example, it might change /[0-9]/
to the more efficient /\d/
.
no-control-regex
This rule disallows control characters (ASCII 0-31) in regular expressions. If you use no-control-regex
, for cases where a control character is accidentally pasted into a regex literal, by flagging these invisible characters, it will help the code to be more readable and less error-prone. This is why a regex that fails to match correctly because of a hidden, unprintable character never happens. It will help the app in production to be more robust, preventing subtle parsing and validation bugs.
no-empty-character-class
This rule disallows empty character classes (``) in regular expressions. If you use no-empty-character-class
, for cases where a developer writes /^abc[]xyz$/
, by flagging the empty character class, it will help the code to be logically correct. An empty character class matches nothing, so the regex will never match any string. This is why a validation function that always fails because of a malformed regex never happens. It will help the app in production to be more correct, preventing bugs where a regex is fundamentally broken and cannot perform its intended function.
no-invalid-regexp
This rule disallows invalid regular expression strings in RegExp
constructors, which prevents a SyntaxError
at runtime. By flagging patterns like new RegExp('[')
, which is invalid, it ensures that regular expressions used in the application are valid and reliable, contributing to overall stability in production.
no-misleading-character-class
This rule avoids mixed Unicode classes that look like ranges but aren't, preventing security or validation bypasses via homoglyphs. For cases like new RegExp('/[👩👩👧👦]/')
intended to match a family emoji, by flagging this pattern without the /u
flag, it will help the code to be Unicode-aware. Without the /u
flag, this regex is treated as a class of multiple, separate characters ([👩, , 👩, , 👧, , 👦]
), which is not the intent. This is why a bug where user input containing complex emojis or other multi-codepoint characters is incorrectly parsed or validated never happens. It will help the app in production to be more globally compatible and correct, ensuring that it properly handles modern Unicode text.
String Literals and Content
no-template-curly-in-string
This rule disallows template literal placeholder syntax (${...}
) inside regular strings. If you use this rule, for cases where a developer writes "Hello ${name}"
instead of `Hello ${name}`
, by flagging this, it will help the code to be free of interpolation errors. The regular string will not perform interpolation, and the literal characters ${name}
will be included. This is why a bug where a user sees "Hello ${name}" in the UI instead of "Hello Alice" never happens. It will help the app in production to be more user-friendly and correct, preventing string formatting errors caused by using the wrong type of quote marks.
string-content
If you enable string-content
, then for content patterns inside strings (like discouraging forbidden tokens, preferring String.raw
, or replacing awkward escapes), by enforcing configurable content rules, it helps you be more intentional, which prevents brittle escaping and copy-paste bugs, so your app in production is more robust. For example, it can be configured to find and fix common typos like 'teh' to 'the' within string literals, making production output more professional.
escape-case
If you enable escape-case
, then for strings with escape sequences (like \xNN
/ \uNNNN
), by enforcing a consistent upper/lower case, it helps you be more consistent, which prevents “looks different, means the same” confusion in reviews, so your app in production is more uniform and less error-prone. Standardizing these escape sequences improves readability and maintains a consistent style across the codebase.
Part II: Enhancing Craftsmanship - Suggestions for Maintainable & Modern Code
Preamble: Investing in Long-Term Health
Transitioning from preventing direct errors to promoting excellence, the rules in this section are about elevating the quality of the codebase. They guide developers toward writing code that is cleaner, more consistent, more modern, and significantly easier for other humans to read and maintain. While violations of these rules may not cause immediate crashes, they contribute to technical debt, reduce cognitive load, slow down development, and increase the likelihood of future bugs. Adopting these "suggestion" rules is a strategic investment in the long-term health of the software, the productivity of the engineering team, and cutting the surface area for bugs.
Promoting Readability and Reducing Ambiguity
Conditional Logic and Control Flow
curly
The curly
rule enforces consistent brace style for all control statements (if
, for
, while
, etc.), typically requiring braces even for single-line blocks. If this rule is used (with the "all" option), for cases like a simple if (condition) doSomething();
, by enforcing if (condition) { doSomething(); }
, it will help the code to be unambiguous and safer to edit. This is why a bug where a developer adds a second line doSomethingElse();
intending it to be part of the conditional, but it executes unconditionally (the infamous Apple goto fail;
bug), never happens. It will help the app in production to be more reliable, preventing one of the most classic and subtle bugs in C-style languages.
no-lonely-if
If you use no-lonely-if
, for cases like if (x) {... } else { if (y) {... } }
, by automatically fixing this to if (x) {... } else if (y) {... }
, it will help your code be more structurally intuitive. This is because it reduces unnecessary nesting, which is why bugs related to complex conditional paths are less likely to be introduced, and it will help your app in production to be more readable and robust. It avoids an if
statement as the sole statement in an if
block that has no else
.
no-nested-ternary
The no-nested-ternary
rule disallows the nesting of ternary expressions. While compact, nested ternaries are notoriously difficult to read. If you use this rule, for cases like const result = a ? b : c ? d : e;
, by refactoring this into a more explicit if-else
block, it will help your team be more efficient during code reviews and debugging. This is because the logic is immediately apparent, which is why logic errors from misinterpreting complex expressions never happen, and it will help your app in production to be more maintainable and stable.
no-negated-condition
If you use no-negated-condition
, for cases like if (!user.isActive) { deactivate(); } else { activate(); }
, by rewriting it as if (user.isActive) { activate(); } else { deactivate(); }
, it will help your code be more aligned with positive logic flows. This is because developers can reason about the "happy path" first, which is why misinterpretations of double negatives or complex boolean logic never happen, and it will help your app in production to be more logically straightforward and less error-prone.
prefer-switch
This rule suggests replacing a chain of three or more if-else if
statements with a switch
statement when all conditions are checking the same variable for equality. If you use prefer-switch
, for cases like a long if (type === 'A')... else if (type === 'B')... else if (type === 'C')...
, by refactoring this into a switch (type)
block, it will help your multi-conditional logic be more structured and performant. This is because the switch
statement is often optimized by JavaScript engines more effectively, which is why complex branching logic is easier to read, and it will help your app in production to have potentially faster execution paths.
prefer-ternary
This rule encourages the use of ternary expressions for simple conditional assignments. If you use prefer-ternary
, for cases like let value; if (condition) { value = 'A'; } else { value = 'B'; }
, by refactoring this to const value = condition ? 'A' : 'B';
, it will help your variable assignments be more concise and declarative. This is because it allows for the use of const
and expresses the assignment as a single expression, which is why bugs related to uninitialized variables are prevented, and it will help your app in production to be more robust.
Clarity in Naming and Structure
no-anonymous-default-export
If you use no-anonymous-default-export
, for cases like export default () => {... }
, by requiring a name like const MyComponent = () => {... }; export default MyComponent;
, it will help your codebase be more discoverable and debuggable. This is because you can reliably search for MyComponent
and see its name in stack traces, which is why time is never wasted trying to trace the origin of an anonymous function in an error log, and it will help your app in production to be more transparent and easier to troubleshoot.
no-unreadable-array-destructuring
This rule disallows the use of consecutive commas in array destructuring to skip elements (e.g., const [,, third] = arr;
). If you use no-unreadable-array-destructuring
, for cases like const [,, thirdElement] = data;
, by forcing a more explicit access method like const thirdElement = data[2];
, it will help your code be more self-documenting and unambiguous. This is because the index being accessed is explicit, which is why off-by-one errors or confusion during refactoring never happen, and it will help your app in production to be more maintainable.
prevent-abbreviations
If you use prevent-abbreviations
, for cases like variable names (e.g., props
to properties
), by requiring full names, it will help you be more explicit. This discourages abbreviations in identifiers, which is why confusion from ambiguous acronyms never happens, and it will help your app in production to be more readable and easier to navigate for new team members.
explicit-length-check
This rule enforces an explicit and consistent style for checking the length or size property of a value, disallowing reliance on truthiness. If you use explicit-length-check
, for cases like if (items.length)
to check for non-empty arrays, by auto-fixing it to if (items.length > 0)
, it will help your conditional logic be more explicit. This is because it removes ambiguity about whether a falsy value other than 0
is being handled, which is why bugs related to implicit type coercion never occur, and it will help your app in production to be more robust and predictable.
Enforcing Modern JavaScript Idioms and Performance
Modern Array and Collection Methods
Immutable Operations (no-array-sort
, no-array-reverse
)
These rules advocate for using the new, immutable array methods Array.prototype.toSorted()
and Array.prototype.toReversed()
instead of their legacy, mutating counterparts sort()
and reverse()
. If you use no-array-sort
, for cases where a shared array is sorted in a component (const sortedData = sharedData.sort()
), by forcing the use of const sortedData = sharedData.toSorted()
, it will help your application state be more predictable and free of side effects. This is because the original sharedData
array remains untouched, which is why bugs where one component's sorting action unexpectedly alters data in another component never happen. This helps your app in production be more robust, especially in complex state management scenarios like React. The same logic applies to no-array-reverse
and toReversed()
.
Iteration Patterns (no-array-for-each
, no-for-loop
)
These rules encourage using for...of
loops instead of older iteration patterns. If you use no-array-for-each
, for cases where you iterate over an array to find an element and then need to stop, by using a for...of
loop, it will help your code be more performant. This is because you can break
out of the loop as soon as the condition is met, which is why unnecessary iterations over potentially large arrays never happen, and it will help your app in production to be faster and more efficient. no-for-loop
similarly encourages replacing traditional for (i=0;...)
loops with the more modern and less error-prone for...of
syntax where possible.
Method Preferences (prefer-array-find
, prefer-array-some
, etc.)
This group of rules steers developers toward the most performant and readable array methods for common tasks.
prefer-array-find
: Prefers.find()
or.findLast()
over filtering an entire array and taking the first or last element (.filter()[0]
), which is more efficient as it stops as soon as a match is found.prefer-array-some
: Prefers.some()
over checking.filter().length
or using.find()
for simple existence checks, as.some()
is more performant and explicitly states the intent of checking for existence.prefer-array-index-of
: Prefers.indexOf()
over.findIndex()
when simply looking for the index of a known value, as it is a lighter and more direct method for this purpose.prefer-array-flat
: Prefers the nativeArray.prototype.flat()
method over legacy or custom flattening techniques, reducing dependencies and improving code simplicity.prefer-array-flat-map
: Prefers.flatMap()
over the less efficient.map().flat()
chain, as it combines both operations into a single, optimized step.
Advanced Patterns (no-array-reduce
, no-array-callback-reference
)
These rules address more advanced, and often misused, array patterns.
no-array-reduce
: This opinionated rule disallowsArray.prototype.reduce()
. The rationale is thatreduce
can often lead to complex, hard-to-read code. If you useno-array-reduce
for cases where a developer implements a complex data transformation inside areduce
callback, by encouraging a simplerfor...of
loop or a chain of.map().filter()
, it will help your codebase be more approachable. This is because the intent of the code is more explicit, which is why new developers are never intimidated by complex accumulator logic, and it will help your app in production to be more maintainable.no-array-callback-reference
: This rule prevents passing function references directly to array methods, addressing the classic['1', '2', '3'].map(parseInt)
bug which produces[1, NaN, NaN]
. By forcing an explicit callback['1', '2', '3'].map(str => parseInt(str, 10))
, it helps your array operations be predictable and safe from unintended side effects from extra callback parameters, making production code more robust.
Modern String and RegExp Methods
prefer-string-slice
/ prefer-string-starts-ends-with
/ prefer-string-replace-all
/ prefer-string-trim-start-end
If you enable these prefer-string-*
rules, then for common string operations, by steering toward modern, explicit methods (slice
, startsWith
/endsWith
, replaceAll
, trimStart
/trimEnd
), you’re more expressive and edge-case resistant, which prevents off-by-one and global-regex pitfalls, so production is cleaner and less bug-prone.
prefer-string-starts-ends-with
: If you use this rule for cases likeif (str.indexOf('prefix') === 0)
, by auto-fixing it toif (str.startsWith('prefix'))
, it will help your code be more semantic. This is because the method name perfectly describes the operation, which is why a future developer never has to mentally parse the logic to understand its purpose, making production code more maintainable.prefer-string-slice
prefers the consistentslice()
method over the legacy and sometimes confusingsubstr()
andsubstring()
.prefer-string-replace-all
prefers the modernreplaceAll()
over using a global regex for simple, non-regex replacements, improving readability and performance.prefer-string-trim-start-end
prefers the standardtrimStart()
andtrimEnd()
methods over the non-standard aliasestrimLeft()
andtrimRight()
.
prefer-regexp-test
If you enable prefer-regexp-test
, then for simple presence checks, by using re.test(str)
instead of str.match(...)
, it helps you be more clear and efficient, which prevents truthiness pitfalls and unnecessary memory allocation for a match array, so production is faster and simpler.
Modern Object and Class Features
prefer-class-fields
If you enable prefer-class-fields
, for cases like defining properties in a class constructor (this.prop = 1
), by preferring modern class field declarations (prop = 1;
), you make the code more concise. This reduces boilerplate in the constructor, which is why constructor clutter never happens, and it will help your app in production to be cleaner and more readable.
prefer-object-from-entries
If you enable prefer-object-from-entries
, then for building objects from key-value pairs, by using Object.fromEntries()
, you’re more declarative, which prevents manual reduce
-accumulator bugs, so production is cleaner. It replaces a common but error-prone pattern with a single, clear, and native method.
prefer-spread
/ no-useless-spread
If you enable prefer-spread
and no-useless-spread
, then for array and object construction, by using spread where it clarifies intent—and avoiding it where it adds nothing—it helps you be more clear and efficient. This prevents hidden allocations from methods like .concat()
and removes redundant code, so production is snappier and more readable.
Modern Asynchronous Patterns
prefer-top-level-await
If you enable prefer-top-level-await
, then for module startup logic, by using top-level await
instead of wrapping logic in async IIFEs or promises, you’re more straightforward. This prevents unhandled rejections and complex load-order hacks that can arise from older patterns, so production is simpler and more reliable.
Modern DOM APIs
Grouping of prefer-dom-node-*
and prefer-modern-dom-apis
If you enable these DOM modernizer rules, then for event and DOM manipulation, by using modern APIs (addEventListener
/removeEventListener
, append
/remove
, textContent
, .dataset
, querySelector
, .before
/.after
/.replaceWith
), you’re more standards-compliant and readable, which prevents cross-browser quirks and handler leaks, so production is smoother and lighter.
prefer-add-event-listener
: Replaceson-
functions (e.g.,onclick
) with the more flexibleaddEventListener
, which allows for multiple listeners.prefer-dom-node-append
: Prefers.append()
over.appendChild()
because it allows for multiple arguments and DOM strings, simplifying DOM additions.prefer-dom-node-remove
: Preferschild.remove()
over the more verboseparent.removeChild(child)
, simplifying element removal.prefer-dom-node-text-content
: Prefers.textContent
over.innerText
for its better performance and more predictable, cross-browser behavior.prefer-dom-node-dataset
: Prefers the convenient.dataset
API for accessingdata-*
attributes overgetAttribute()
andsetAttribute()
.prefer-query-selector
: Prefers.querySelector()
and.querySelectorAll()
over older methods likegetElementById
orgetElementsByTagName
for a unified and more powerful selection API.prefer-modern-dom-apis
: Prefers other modern APIs like.before()
,.after()
, and.replaceWith()
over legacy equivalents.
Enforcing Code Consistency and Maintainability
Naming and Identifier Conventions
filename-case
This rule enforces a consistent casing convention for filenames, such as kebab-case or camelCase. If you use filename-case
with a kebab-case
setting, for cases where developers create files like MyComponent.js
or my_component.js
, by flagging these and enforcing my-component.js
, it will help your project be more navigable and professional. This is because file naming is predictable, which is why "file not found" errors due to case-sensitivity issues on deployment servers (like Linux) never happen, and it will help your app in production to be more reliably deployable.
catch-error-name
This rule enforces a specific parameter name in catch
clauses, with the default being error
. If you use catch-error-name
, for cases like try {...} catch (e) {...}
, by auto-fixing them to try {...} catch (error) {...}
, it will help your error handling logic be more standardized and searchable. This is because every developer knows exactly what the error variable is called, which is why time is never wasted deciphering inconsistent variable names during a critical debugging session, and it will help your app in production to be more quickly analyzable.
Structural and Stylistic Consistency
consistent-destructuring
This rule encourages using already-destructured variables instead of accessing properties from the original object repeatedly. If you use consistent-destructuring
, for cases like const { user } = props; console.log(user.name, props.user.email);
, by flagging the redundant access, it will help your components be more concise and internally consistent. This is because once an object is destructured, its properties are always accessed via the resulting variables, which is why confusion about the "source of truth" for a property never happens during refactoring, and it will help your app in production to be more maintainable.
consistent-function-scoping
This rule moves function definitions to the highest possible scope to avoid unnecessary nesting. By hoisting functions out of loops or other functions where they don't depend on the inner scope, it helps you be more optimized in memory. This is because unnecessary closures are not created on each call, which is why your app in production is more efficient with lower garbage collection pressure.
import-style
This rule allows teams to enforce specific import styles (default, namespace, or named) for particular modules. If you use import-style
to enforce named imports for a large utility library, for cases like import utils from 'our-utils';
, by flagging it, it will help your application's bundle size be smaller. This is because it encourages importing only the specific functions needed, which is why tree-shaking by bundlers like Webpack or Rollup is more effective, and it will help your app in production to have a faster load time for end-users. It also prevents accidental export {}
noise.
Enhancing Safety and API Correctness
Preventing Type and this
Errors
no-array-method-this-argument
If you enable no-array-method-this-argument
, for map
/filter
/etc, by forbidding the rarely-used thisArg
parameter, it helps you be more predictable with this
, which prevents surprises with arrow functions and context, so production is less fragile. It promotes the use of modern arrow functions, which handle this
lexically and avoid context binding issues.
no-instanceof-builtins
If you enable no-instanceof-builtins
, for things like instanceof String/Number
, by forbidding these checks, you’re more type-correct, which prevents wrapper vs primitive confusion, so production is less surprising. It encourages the use of more accurate checks like Array.isArray
or typeof
.
no-object-as-default-parameter
If you enable no-object-as-default-parameter
, then for function signatures, by forbidding object literals as defaults (function fn(opts = {})
), you’re more predictable, which prevents shared-reference mutations across calls, so production is less stateful-buggy. It encourages patterns where defaults are created inside the function body if the parameter is null or undefined.
prefer-type-error
If you enable prefer-type-error
, then for error creation and type checking, by throwing TypeError
for type mismatches instead of a generic Error
, you’re more semantically correct, which prevents misleading stacks and inconsistent error types, so production is easier to debug and alert on.
Ensuring Secure and Correct API Usage
no-new-buffer
If you enable no-new-buffer
, then for Node buffers, by requiring Buffer.from()
or Buffer.alloc()
, you’re more secure, which prevents uninitialized memory reads from the deprecated new Buffer()
constructor and other deprecation pitfalls, so production is safer.
no-document-cookie
If you enable no-document-cookie
, then for authentication and tracking, by avoiding manual document.cookie
parsing/setting, you’re more secure and correct. This prevents malformed flags and "split cookie" bugs where parts of the cookie string are set incorrectly. It encourages using safer, modern APIs like CookieStore, so production is safer.
no-invalid-fetch-options
If you enable no-invalid-fetch-options
, then for fetch()
or new Request()
, by banning invalid option combinations (like a GET
or HEAD
request with a body
), you’re more API-correct. This prevents runtime TypeError
s that would occur when the request is made, so production is more reliable.
require-post-message-target-origin
If you enable require-post-message-target-origin
, then for window.postMessage
, by requiring a concrete targetOrigin
, you’re more secure. This prevents data exfiltration to unexpected origins if the recipient window's location changes. This makes cross-window messaging more secure by default, so production is safer.
no-invalid-remove-event-listener
A common source of memory leaks is failing to correctly remove event listeners. removeEventListener
requires a reference to the exact same function that was passed to addEventListener
. If you use no-invalid-remove-event-listener
, for cases like el.removeEventListener('click', () => {...})
, by flagging this invalid call, it will help your application be more memory-efficient. This is because it prevents "zombie" event listeners from accumulating, which is why memory leaks caused by dangling event listeners never happen, and it will help your app in production to be more stable, especially in long-running single-page applications.
Part III: Process & Governance
Preamble: Linting as Engineering Discipline
The rules in this final category transcend code syntax to govern the development process itself. These "meta" rules shape how developers interact with the linter, manage technical debt, and maintain the integrity of the code quality process. They elevate ESLint from a simple static analysis tool to a genuine framework for engineering discipline and governance, fostering a culture of accountability and proactive maintenance.
Enforcing Linter Integrity
no-abusive-eslint-disable
This rule is fundamental to maintaining the integrity of the entire linting process. It disallows broad eslint-disable
comments that do not specify which rule is being ignored. If you use no-abusive-eslint-disable
, for cases where a developer uses // eslint-disable-next-line
to bypass one error, by forcing them to write // eslint-disable-next-line some-specific-rule
, it will help your linting process be more robust and effective. This is because it prevents accidentally disabling other important rules on the same line, which is why a critical security or performance warning is never inadvertently suppressed, and it will help your app in production to be safer and more reliable.
Active Technical Debt Management
expiring-todo-comments
This rule transforms passive TODO
comments into an active technical debt management system. It allows developers to add expiration conditions to comments, such as a date (// TODO [2024-12-31]:...
), a package version, or an issue link. When the condition is met, the linter raises an error, forcing the team to address the comment. If you use expiring-todo-comments
, for cases like // TODO [2024-12-31]: Refactor this legacy module
, by turning this comment into a lint error after the specified date, it will help your team be more accountable for technical debt. This is because "temporary" fixes are no longer allowed to become permanent problems, which is why critical refactors or security updates are never forgotten or indefinitely postponed, and it will help your app in production to be more secure, modern, and less burdened by legacy code.
Repository and Process Hygiene
no-empty-file
This rule disallows empty source files within the project. If you use no-empty-file
, for cases where a developer deletes the contents of a file but forgets to delete the file itself, by flagging it, it will help your project repository be more clean and organized. This is because it eliminates zero-value files, which is why a new developer never wastes time opening an empty file wondering about its purpose, and it will help your app in production by ensuring the build artifact doesn't contain unnecessary, empty modules.
no-process-exit
In Node.js applications, process.exit()
is a "hard exit" that terminates the event loop immediately. This is a dangerous practice as it bypasses graceful shutdown logic. If you use no-process-exit
, for cases where a specific module calls process.exit(1)
on an error, by disallowing it, it will help your application be more resilient and manageable. This is because errors are propagated through exceptions, allowing for centralized logging and graceful shutdown, which is why a recoverable error in a minor module never brings down the entire server process unexpectedly, and it will help your app in production to have higher availability and better observability.
Part IV: Practical Implementation Roadmap
A Tiered Adoption Strategy
Integrating a comprehensive ESLint configuration into an existing codebase can seem daunting. A phased, tiered approach is the most effective strategy for a smooth and successful rollout. This allows the team to realize the biggest benefits first with the least amount of friction.
- Tier 1 (The Foundation - Immediate Priority): The first step is to enable all rules from Part I: Fortifying the Foundation. These rules are non-negotiable as they prevent demonstrable bugs and runtime errors. The process should be:
- Enable all rules in the
eslint:recommended
configuration. - Add any other rules from that section that are not in the recommended set.
- Run
eslint. --fix
to automatically correct the large number of fixable violations. - Manually address the remaining, non-fixable errors. The stability and reliability gains from this tier are immediate and substantial.
- Enable all rules in the
- Tier 2 (The Modernization Push - High Priority): The next step is to adopt the rules from the subsection on Enforcing Modern JavaScript Idioms. This group (
no-var
,prefer-const
,prefer-template
, etc.) provides the single largest boost to code quality, maintainability, and developer experience. It aligns the codebase with current best practices. Many of these rules are also auto-fixable, easing the transition. - Tier 3 (The Craftsmanship Polish - Medium Priority): This tier involves a team-wide discussion about the more subjective rules from Part II: Enhancing Craftsmanship. This includes rules governing readability (
curly
,no-else-return
), complexity (complexity
,max-lines-per-function
), and anti-patterns (no-param-reassign
,no-shadow
). The team should collectively decide which rules align with its development philosophy and agree on configuration options. This process is crucial for building a shared sense of ownership over the codebase's quality standards. - Tier 4 (The Final Polish - Low Priority): Finally, implement the rules from Part III: Process & Governance. While rules like
unicode-bom
are a simple change, they solidify the foundation for a hassle-free development and deployment environment.
A Phased Rollout Plan
A safe adoption plan combines the tiered strategy with a gradual enforcement mechanism.
- Start with readability/safety rules: Enable rules like
error-message
,no-invalid-fetch-options
, andprefer-string-starts-ends-with
. These provide high value with low churn. - Add immutability rules: Introduce rules like
no-array-sort
andno-array-reverse
to prevent side effects. - Layer modern platform rules: Gradually enable rules for modern DOM, Node.js protocol imports, and modern global APIs.
- Use
expiring-todo-comments
: Use this rule to keep the backlog of technical debt honest and actionable. - Gate rollouts with CI: Treat high-churn or highly opinionated rules as warnings (
warn
) first to avoid blocking developers. Promote them to errors (error
) once the codebase is clean. Use--fix
locally to minimize manual effort.
Workflow Integration
To maximize the effectiveness of ESLint, it must be deeply integrated into the daily workflow of the development team. Relying on manual runs is insufficient.
- Editor Integration: This is the first and most important line of defense. Integrating ESLint into the code editor (e.g., via the ESLint extension for VS Code) provides real-time feedback to developers as they type. This catches errors at the moment of creation, making them trivial to fix and creating a tight feedback loop that naturally teaches developers the team's coding standards.
- Pre-commit Hooks: The second line of defense is a pre-commit hook (using tools like Husky and lint-staged). This automatically runs ESLint on staged files before a commit is allowed to be created. This ensures that no code violating the team's standards can ever enter the version control history, keeping the main branch perpetually clean.
- CI/CD Pipeline: The final and ultimate gatekeeper is the Continuous Integration (CI) pipeline (e.g., in GitHub Actions, Jenkins, GitLab CI). The CI server should run an ESLint check on every pull request. This step guarantees that even if a developer manages to bypass the local checks, no non-compliant code can be merged and deployed to production. A failing lint check should be treated as a failing test—a hard blocker for the merge.
Configuration Snippets and Baselines
Example .eslintrc.js
Here is a baseline configuration example that fails builds on critical problems, warns on important suggestions, and sets reasonable complexity limits.
// Fail builds on Possible Problems
module.exports = {
extends: ["eslint:recommended"], // ✅
rules: {
// Throughput & correctness
"no-await-in-loop": "error",
"array-callback-return": ["error", { allowImplicit: false }],
"no-unsafe-finally": "error",
"no-constant-condition": ["error", { checkLoops: true }],
"no-unreachable": "error",
"no-prototype-builtins": "error",
"require-atomic-updates": "error",
// Modern JS
"no-var": "error",
"prefer-const": ["error", { destructuring: "all" }],
"prefer-object-has-own": "error",
"prefer-rest-params": "error",
"prefer-spread": "warn",
// Safety & hygiene
"no-implied-eval": "error",
"no-debugger": "error",
"no-console": ["warn", { allow: ["warn", "error"] }],
"no-unused-vars": ["warn", { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }],
// Regex & parsing
"prefer-named-capture-group": "warn",
"require-unicode-regexp": "warn",
// Limits (start as warn)
"complexity": ["warn", 12],
"max-lines-per-function": ["warn", { max: 100, skipBlankLines: true, skipComments: true }],
}
}
Code Refactor Snippets
These snippets provide concrete examples of how ESLint rules guide code toward better patterns.
Safer async loop (no-await-in-loop)
// Before
for (const id of ids) {
await fetch(`/items/${id}`); // serialized
}
// After
await Promise.all(ids.map(id => fetch(`/items/${id}`)));
Atomic update (require-atomic-updates)
// Before
count = count + await getDelta(); // race
// After
const delta = await getDelta();
count += delta;
Prototype-safe hasOwn (prefer-object-has-own)
if (Object.hasOwn(obj, key)) { /* ... */ }
A Living Document
This report provides a comprehensive foundation for building a robust linting strategy. However, an ESLint configuration should not be a static, write-once artifact. It is a living document that reflects the team's evolving understanding of quality and best practices. As new versions of JavaScript and ESLint are released, as new patterns emerge, and as the needs of the project change, the team should periodically revisit its .eslintrc
file. These rules and configurations should be a topic of discussion in team meetings and retrospectives. By treating the linter configuration as a dynamic representation of the team's commitment to quality, it remains a powerful and relevant tool for building exceptional software.
Appendix: Master Rule Index
ESLint Unicorn Rules (Alphabetical)
- better-regex — regexes are shorter/consistent/safer.
- catch-error-name — consistent catch param.
- consistent-assert — consistent
node:assert
style. - consistent-date-clone — clone dates via constructor.
- consistent-destructuring — prefer destructured locals.
- consistent-empty-array-spread — keep ternary spreads type‑consistent.
- consistent-existence-index-check — one style for existence via index.
- consistent-function-scoping — hoist fn defs to widest scope.
- custom-error-definition — correct
Error
subclassing. - empty-brace-spaces — no spaces inside
{}
. - error-message — require error messages.
- escape-case — consistent hex/unicode escape case.
- expiring-todo-comments — add expiry/issue to TODOs.
- explicit-length-check — compare
.length
/.size
explicitly. - filename-case — enforce file name casing.
- import-style — per‑module import form.
- new-for-builtins —
new
for builtins (except primitives wrappers). - no-abusive-eslint-disable — forbid blanket
eslint-disable
. - no-accessor-recursion — guard against recursive accessor calls.
- no-anonymous-default-export — name your default exports.
- no-array-callback-reference — disallow bare refs with wrong arity/signature.
- no-array-for-each — prefer
for…of
. - no-array-method-this-argument — disallow
thisArg
in array methods. - no-array-reduce — discourage
reduce
. - no-array-reverse — prefer
toReversed()
. - no-array-sort — prefer
toSorted()
. - no-await-expression-member — avoid
(await x).y
. - no-await-in-promise-methods — don’t
await
insidePromise.*
calls. - no-console-spaces — no stray spaces in console params.
- no-document-cookie — avoid direct cookie string.
- no-empty-file — ban empty files.
- no-for-loop — prefer
for…of
when possible. - no-hex-escape — prefer Unicode escapes.
- no-instanceof-builtins — avoid
instanceof
on wrappers. - no-invalid-fetch-options — forbid invalid
fetch
/Request
options. - no-invalid-remove-event-listener — remove with the same reference.
- no-keyword-prefix — ban identifiers starting with
new
/class
. - no-lonely-if — avoid
if
as sole branch underif
. - no-magic-array-flat-depth — avoid magic flatten depth.
- no-named-default — don’t name default import/export.
- no-negated-condition — avoid
if (!cond)
style. - no-negation-in-equality-check — avoid
!a === b
antipatterns. - no-nested-ternary — flatten complex ternaries.
- no-new-array — avoid
new Array()
. - no-new-buffer — use
Buffer.from/alloc
. - no-null — avoid
null
. - no-object-as-default-parameter — no object default args.
- no-process-exit — avoid
process.exit()
. - no-single-promise-in-promise-methods — don’t wrap a single promise in
Promise.*
arrays. - no-static-only-class — avoid classes with only statics.
- no-thenable — disallow
then
property non‑Promises. - no-this-assignment — don’t reassign
this
. - no-typeof-undefined — don’t
typeof x === 'undefined'
. - no-unnecessary-array-flat-depth — avoid
flat(1)
. - no-unnecessary-array-splice-count — avoid
.length
/Infinity
for splice counts. - no-unnecessary-await — don’t await non‑promises.
- no-unnecessary-polyfills — don’t polyfill what exists.
- no-unnecessary-slice-end — avoid
.length
/Infinity
as slice end. - no-unreadable-array-destructuring — prefer readable patterns.
- no-unreadable-iife — improve IIFE readability.
- no-unused-properties — remove unused object props.
- no-useless-error-capture-stack-trace — remove redundant captures.
- no-useless-fallback-in-spread — remove useless fallback in spreads.
- no-useless-length-check — remove redundant length checks.
- no-useless-promise-resolve-reject — avoid wrapping in
Promise.resolve/reject
in async contexts. - no-useless-spread — remove unnecessary spreads.
- no-useless-switch-case — remove unreachable/useless cases.
- no-useless-undefined — remove useless
undefined
. - number-literal-case — normalize numeric literal case.
- numeric-separators-style — enforce digit grouping.
- prefer-add-event-listener — prefer standard event API.
- prefer-array-find — prefer
find/findLast
overfilter()[0/last]
. - prefer-array-flat — prefer
flat()
. - prefer-array-flat-map — prefer
flatMap()
. - prefer-array-index-of — prefer index methods for indices.
- prefer-array-some — prefer
some()
overfilter().length
etc. - prefer-at — prefer
.at()
. - prefer-blob-reading-methods — prefer modern Blob readers.
- prefer-class-fields — prefer class fields.
- prefer-date-now — prefer
Date.now()
. - prefer-default-parameters — use default params.
- prefer-dom-node-append — prefer
append()
. - prefer-dom-node-dataset — prefer
.dataset
. - prefer-dom-node-remove — prefer
node.remove()
. - prefer-dom-node-text-content — prefer
.textContent
. - prefer-event-target — prefer
EventTarget
. - prefer-export-from — prefer
export … from
. - prefer-global-this — prefer
globalThis
. - prefer-import-meta-properties — prefer
import.meta.*
. - prefer-includes — prefer
.includes()
. - prefer-json-parse-buffer — read JSON as buffer.
- prefer-keyboard-event-key — prefer
event.key
. - prefer-logical-operator-over-ternary — simplify boolean ternaries.
- prefer-math-min-max — prefer
Math.min/max
. - prefer-math-trunc — prefer
Math.trunc
. - prefer-modern-dom-apis — prefer modern DOM methods.
- prefer-modern-math-apis — prefer modern math helpers.
- prefer-module — prefer ESM.
- prefer-native-coercion-functions — prefer coercion fns.
- prefer-negative-index — prefer negative indices.
- prefer-node-protocol — use
node:
protocol imports. - prefer-number-properties — prefer
Number.*
statics. - prefer-object-from-entries — prefer
Object.fromEntries
. - prefer-optional-catch-binding — omit unused catch param.
- prefer-prototype-methods — borrow from prototype.
- prefer-query-selector — prefer querySelector APIs.
- prefer-reflect-apply — prefer
Reflect.apply
. - prefer-regexp-test — prefer
RegExp#test
. - prefer-set-has — prefer
Set#has
overArray#includes
. - prefer-set-size — prefer
Set#size
. - prefer-single-call — merge repeated calls.
- prefer-spread — prefer spread over legacy methods.
- prefer-string-raw — prefer
String.raw
. - prefer-string-replace-all — prefer
replaceAll
. - prefer-string-slice — prefer
slice()
. - prefer-string-starts-ends-with — prefer
startsWith/endsWith
. - prefer-string-trim-start-end — prefer
trimStart/End
. - prefer-structured-clone — use
structuredClone
. - prefer-switch — prefer
switch
over longif-else
chains. - prefer-ternary — use ternary for simple
if/else
. - prefer-top-level-await — prefer top‑level
await
. - prefer-type-error — throw
TypeError
for type mismatches. - prevent-abbreviations — discourage abbreviations in identifiers.
- relative-url-style — enforce consistent relative URL format.
- require-array-join-separator — require explicit
join
separator. - require-module-specifiers — forbid empty import/export specifiers.
- require-number-to-fixed-digits-argument — require
toFixed(digits)
. - require-post-message-target-origin — require
targetOrigin
. - switch-case-braces — consistent braces for
case
. - template-indent — fix template literal indentation.
- text-encoding-identifier-case — consistent encoding identifiers.
- throw-new-error — require
new
when constructing errors.