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 ReferenceErrors. 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 TypeErrors. 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 TypeErrors 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 native Array.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 disallows Array.prototype.reduce(). The rationale is that reduce can often lead to complex, hard-to-read code. If you use no-array-reduce for cases where a developer implements a complex data transformation inside a reduce callback, by encouraging a simpler for...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 like if (str.indexOf('prefix') === 0), by auto-fixing it to if (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 consistent slice() method over the legacy and sometimes confusing substr() and substring().
  • prefer-string-replace-all prefers the modern replaceAll() over using a global regex for simple, non-regex replacements, improving readability and performance.
  • prefer-string-trim-start-end prefers the standard trimStart() and trimEnd() methods over the non-standard aliases trimLeft() and trimRight().

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: Replaces on- functions (e.g., onclick) with the more flexible addEventListener, 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: Prefers child.remove() over the more verbose parent.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 accessing data-* attributes over getAttribute() and setAttribute().
  • prefer-query-selector: Prefers .querySelector() and .querySelectorAll() over older methods like getElementById or getElementsByTagName 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.

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 TypeErrors 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:
    1. Enable all rules in the eslint:recommended configuration.
    2. Add any other rules from that section that are not in the recommended set.
    3. Run eslint. --fix to automatically correct the large number of fixable violations.
    4. Manually address the remaining, non-fixable errors. The stability and reliability gains from this tier are immediate and substantial.
  • 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.

  1. Start with readability/safety rules: Enable rules like error-message, no-invalid-fetch-options, and prefer-string-starts-ends-with. These provide high value with low churn.
  2. Add immutability rules: Introduce rules like no-array-sort and no-array-reverse to prevent side effects.
  3. Layer modern platform rules: Gradually enable rules for modern DOM, Node.js protocol imports, and modern global APIs.
  4. Use expiring-todo-comments: Use this rule to keep the backlog of technical debt honest and actionable.
  5. 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 inside Promise.* 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 under if.
  • 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 over filter()[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() over filter().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 over Array#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 long if-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.