Scenic background of green grass and blue sky

eslint-config-ts-prefixer

A zero-config TypeScript ESLint configuration with Prettier integration

Installation

Install the package using your preferred package manager:

pnpm add -D eslint-config-ts-prefixer@latest
npm install --save-dev eslint-config-ts-prefixer@latest
yarn add -D eslint-config-ts-prefixer

Configuration

Add to your eslint.config.js:

import { defineConfig } from 'eslint/config'
import tsPrefixer from 'eslint-config-ts-prefixer'

export default defineConfig([...tsPrefixer])

Add lint scripts to your package.json:

{
  "scripts": {
    "lint": "eslint .",
    "lint:fix": "eslint . --fix"
  }
}

Configured Rules

Below is a comprehensive list of all ESLint rules configured by this package:

eqeqeq
Built-in
It is considered good practice to use the type-safe equality operators `===` and `!==` instead of their regular counterparts `==` and `!=`.

The reason for this is that == and != do type coercion which follows the rather obscure Abstract Equality Comparison Algorithm. For instance, the following statements are all considered true:

  • [] == false
  • [] == ![]
  • 3 == "03"

If one of those occurs in an innocent-looking statement such as a == b the actual problem is very difficult to spot.

Rule Details

This rule is aimed at eliminating the type-unsafe equality operators.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint eqeqeq: "error"*/

if (x == 42) {
}

if ('' == text) {
}

if (obj.getStuff() != undefined) {
}

The --fix option on the command line automatically fixes some problems reported by this rule. A problem is only fixed if one of the operands is a typeof expression, or if both operands are literals with the same type.

Options

always

The "always" option (default) enforces the use of === and !== in every situation (except when you opt-in to more specific handling of null [see below]).

Examples of incorrect code for the "always" option:

❌ Incorrect
/*eslint eqeqeq: ["error", "always"]*/

a == b
foo == true
bananas != 1
value == undefined
typeof foo == 'undefined'
'hello' != 'world'
0 == 0
true == true
foo == null

Examples of correct code for the "always" option:

✅ Correct
/*eslint eqeqeq: ["error", "always"]*/

a === b
foo === true
bananas !== 1
value === undefined
typeof foo === 'undefined'
'hello' !== 'world'
0 === 0
true === true
foo === null

This rule optionally takes a second argument, which should be an object with the following supported properties:

  • "null": Customize how this rule treats null literals. Possible values:
    • always (default) - Always use === or !==.
    • never - Never use === or !== with null.
    • ignore - Do not apply this rule to null.

smart

The "smart" option enforces the use of === and !== except for these cases:

  • Comparing two literal values.
  • Evaluating the value of typeof.
  • Comparing against null.

Examples of incorrect code for the "smart" option:

❌ Incorrect
/*eslint eqeqeq: ["error", "smart"]*/

// comparing two variables requires ===
a == b

// only one side is a literal
foo == true
bananas != 1

// comparing to undefined requires ===
value == undefined

Examples of correct code for the "smart" option:

✅ Correct
/*eslint eqeqeq: ["error", "smart"]*/

typeof foo == 'undefined'
'hello' != 'world'
0 == 0
true == true
foo == null

allow-null

Deprecated: Instead of using this option use "always" and pass a "null" option property with value "ignore". This will tell ESLint to always enforce strict equality except when comparing with the null literal.

;['error', 'always', { null: 'ignore' }]

When Not To Use It

If you don't want to enforce a style for using equality operators, then it's safe to disable this rule.

Built-in
problem
Comparisons which will always evaluate to true or false and logical expressions (`||`, `&&`, `??`) which either always short-circuit or never short-circuit are both likely indications of programmer error.

These errors are especially common in complex expressions where operator precedence is easy to misjudge. For example:

// One might think this would evaluate as `a + (b ?? c)`:
const x = a + b ?? c

// But it actually evaluates as `(a + b) ?? c`. Since `a + b` can never be null,
// the `?? c` has no effect.

Additionally, this rule detects comparisons to newly constructed objects/arrays/functions/etc. In JavaScript, where objects are compared by reference, a newly constructed object can never === any other value. This can be surprising for programmers coming from languages where objects are compared by value.

// Programmers coming from a language where objects are compared by value might expect this to work:
const isEmpty = x === []

// However, this will always result in `isEmpty` being `false`.

Rule Details

This rule identifies == and === comparisons which, based on the semantics of the JavaScript language, will always evaluate to true or false.

It also identifies ||, && and ?? logical expressions which will either always or never short-circuit.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-constant-binary-expression: "error"*/

const value1 = +x == null

const value2 = condition ? x : {} || DEFAULT

const value3 = !foo == null

const value4 = new Boolean(foo) === true

const objIsEmpty = someObj === {}

const arrIsEmpty = someArr === []

const shortCircuit1 = condition1 && false && condition2

const shortCircuit2 = condition1 || true || condition2

const shortCircuit3 = condition1 ?? 'non-nullish' ?? condition2

Examples of correct code for this rule:

✅ Correct
/*eslint no-constant-binary-expression: "error"*/

const value1 = x == null

const value2 = (condition ? x : {}) || DEFAULT

const value3 = !(foo == null)

const value4 = Boolean(foo) === true

const objIsEmpty = Object.keys(someObj).length === 0

const arrIsEmpty = someArr.length === 0
Built-in
problem
A constant expression (for example, a literal) as a test condition might be a typo or development trigger for a specific behavior. For example, the following code looks as if it is not ready for production.
if (false) {
  doSomethingUnfinished()
}

Rule Details

This rule disallows constant expressions in the test condition of:

  • if, for, while, or do...while statement
  • ?: ternary expression

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-constant-condition: "error"*/

if (false) {
  doSomethingUnfinished()
}

if (void x) {
  doSomethingUnfinished()
}

if ((x &&= false)) {
  doSomethingNever()
}

if (class {}) {
  doSomethingAlways()
}

if (new Boolean(x)) {
  doSomethingAlways()
}

if (Boolean(1)) {
  doSomethingAlways()
}

if (undefined) {
  doSomethingUnfinished()
}

if ((x ||= true)) {
  doSomethingAlways()
}

for (; -2; ) {
  doSomethingForever()
}

while (typeof x) {
  doSomethingForever()
}

do {
  doSomethingForever()
} while ((x = -1))

const result = 0 ? a : b

if (input === 'hello' || 'bye') {
  output(input)
}

Examples of correct code for this rule:

✅ Correct
/*eslint no-constant-condition: "error"*/

if (x === 0) {
  doSomething()
}

for (;;) {
  doSomethingForever()
}

while (typeof x === 'undefined') {
  doSomething()
}

do {
  doSomething()
} while (x)

const result = x !== 0 ? a : b

if (input === 'hello' || input === 'bye') {
  output(input)
}

Options

checkLoops

This is a string option having following values:

  • "all" - Disallow constant expressions in all loops.
  • "allExceptWhileTrue" (default) - Disallow constant expressions in all loops except while loops with expression true.
  • "none" - Allow constant expressions in loops.

Or instead you can set the checkLoops value to booleans where true is same as "all" and false is same as "none".

Examples of incorrect code for when checkLoops is "all" or true:

❌ Incorrect
/*eslint no-constant-condition: ["error", { "checkLoops": "all" }]*/

while (true) {
  doSomething()
}

for (; true; ) {
  doSomething()
}
❌ Incorrect
/*eslint no-constant-condition: ["error", { "checkLoops": true }]*/

while (true) {
  doSomething()
}

do {
  doSomething()
} while (true)

Examples of correct code for when checkLoops is "all" or true:

✅ Correct
/*eslint no-constant-condition: ["error", { "checkLoops": "all" }]*/

while (a === b) {
  doSomething()
}
✅ Correct
/*eslint no-constant-condition: ["error", { "checkLoops": true }]*/

for (let x = 0; x <= 10; x++) {
  doSomething()
}

Example of correct code for when checkLoops is "allExceptWhileTrue":

✅ Correct
/*eslint no-constant-condition: "error"*/

while (true) {
  doSomething()
}

Examples of correct code for when checkLoops is "none" or false:

✅ Correct
/*eslint no-constant-condition: ["error", { "checkLoops": "none" }]*/

while (true) {
  doSomething()
  if (condition()) {
    break
  }
}

do {
  doSomething()
  if (condition()) {
    break
  }
} while (true)
✅ Correct
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/

while (true) {
  doSomething()
  if (condition()) {
    break
  }
}

for (; true; ) {
  doSomething()
  if (condition()) {
    break
  }
}
no-dupe-args
Built-in
problem
If more than one parameter has the same name in a function definition, the last occurrence "shadows" the preceding occurrences. A duplicated name might be a typing error.

Rule Details

This rule disallows duplicate parameter names in function declarations or expressions. It does not apply to arrow functions or class methods, because the parser reports the error.

If ESLint parses code in strict mode, the parser (instead of this rule) reports the error.

Examples of incorrect code for this rule:

::: incorrect { "sourceType": "script" }

/*eslint no-dupe-args: "error"*/

function foo(a, b, a) {
  console.log('value of the second a:', a)
}

const bar = function (a, b, a) {
  console.log('value of the second a:', a)
}

Examples of correct code for this rule:

::: correct { "sourceType": "script" }

/*eslint no-dupe-args: "error"*/

function foo(a, b, c) {
  console.log(a, b, c)
}

const bar = function (a, b, c) {
  console.log(a, b, c)
}
no-dupe-keys
Built-in
problem
Multiple properties with the same key in object literals can cause unexpected behavior in your application.
const foo = {
  bar: 'baz',
  bar: 'qux',
}

Rule Details

This rule disallows duplicate keys in object literals.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-dupe-keys: "error"*/

const foo = {
  bar: 'baz',
  bar: 'qux',
}

const bar = {
  bar: 'baz',
  bar: 'qux',
}

const baz = {
  0x1: 'baz',
  1: 'qux',
}

Examples of correct code for this rule:

✅ Correct
/*eslint no-dupe-keys: "error"*/

const foo = {
  bar: 'baz',
  quxx: 'qux',
}

const obj = {
  __proto__: baz, // defines object's prototype
  ['__proto__']: qux, // defines a property named "__proto__"
}
Built-in
problem
When using destructuring, it's possible to create a pattern that has no effect. This happens when empty curly braces are used to the right of an embedded object destructuring pattern, such as:
// doesn't create any variables
const {
  a: {},
} = foo

In this code, no new variables are created because a is just a location helper while the {} is expected to contain the variables to create, such as:

// creates variable b
const {
  a: { b },
} = foo

In many cases, the empty object pattern is a mistake where the author intended to use a default value instead, such as:

// creates variable a
const { a = {} } = foo

The difference between these two patterns is subtle, especially because the problematic empty pattern looks just like an object literal.

Rule Details

This rule aims to flag any empty patterns in destructured objects and arrays, and as such, will report a problem whenever one is encountered.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-empty-pattern: "error"*/

const {} = foo
const [] = foo
const {
  a: {},
} = foo
const {
  a: [],
} = foo
function foo({}) {}
function bar([]) {}
function baz({ a: {} }) {}
function qux({ a: [] }) {}

Examples of correct code for this rule:

✅ Correct
/*eslint no-empty-pattern: "error"*/

const { a = {} } = foo
const { b = [] } = foo
function foo({ a = {} }) {}
function bar({ a = [] }) {}

Options

This rule has an object option for exceptions:

allowObjectPatternsAsParameters

Set to false by default. Setting this option to true allows empty object patterns as function parameters.

Note: This rule doesn't allow empty array patterns as function parameters.

Examples of incorrect code for this rule with the {"allowObjectPatternsAsParameters": true} option:

❌ Incorrect
/*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/

function foo({ a: {} }) {}
const bar = function ({ a: {} }) {}
const qux = ({ a: {} }) => {}
const quux = ({} = bar) => {}
const item = ({} = { bar: 1 }) => {}

function baz([]) {}

Examples of correct code for this rule with the {"allowObjectPatternsAsParameters": true} option:

✅ Correct
/*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/

function foo({}) {}
const bar = function ({}) {}
const qux = ({}) => {}

function baz({} = {}) {}
Built-in
In contexts such as an `if` statement's test where the result of the expression will already be coerced to a Boolean, casting to a Boolean via double negation (`!!`) or a `Boolean` call is unnecessary. For example, these `if` statements are equivalent:
if (!!foo) {
  // ...
}

if (Boolean(foo)) {
  // ...
}

if (foo) {
  // ...
}

Rule Details

This rule disallows unnecessary boolean casts.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-extra-boolean-cast: "error"*/

const foo = !!!bar

const foo1 = !!bar ? baz : bat

const foo2 = Boolean(!!bar)

const foo3 = new Boolean(!!bar)

if (!!foo) {
  // ...
}

if (Boolean(foo)) {
  // ...
}

while (!!foo) {
  // ...
}

do {
  // ...
} while (Boolean(foo))

for (; !!foo; ) {
  // ...
}

Examples of correct code for this rule:

✅ Correct
/*eslint no-extra-boolean-cast: "error"*/

const foo = !!bar
const foo1 = Boolean(bar)

function qux() {
  return !!bar
}

foo = bar ? !!baz : !!bat

Options

This rule has an object option:

  • "enforceForInnerExpressions" when set to true, in addition to checking default contexts, checks whether extra boolean casts are present in expressions whose result is used in a boolean context. See examples below. Default is false, meaning that this rule by default does not warn about extra booleans cast inside inner expressions.

Deprecated: The object property enforceForLogicalOperands is deprecated (eslint#18222). Please use enforceForInnerExpressions instead.

enforceForInnerExpressions

Examples of incorrect code for this rule with "enforceForInnerExpressions" option set to true:

❌ Incorrect
/*eslint no-extra-boolean-cast: ["error", {"enforceForInnerExpressions": true}]*/

if (!!foo || bar) {
  //...
}

while (!!foo && bar) {
  //...
}

if ((!!foo || bar) && !!baz) {
  //...
}

const foo = new Boolean(!!bar || baz)

foo && Boolean(bar) ? baz : bat

const ternaryBranches = Boolean(bar ? !!baz : bat)

const nullishCoalescingOperator = Boolean(bar ?? Boolean(baz))

const commaOperator = Boolean((bar, baz, !!bat))

// another comma operator example
for (let i = 0; console.log(i), Boolean(i < 10); i++) {
  // ...
}

Examples of correct code for this rule with "enforceForInnerExpressions" option set to true:

✅ Correct
/*eslint no-extra-boolean-cast: ["error", {"enforceForInnerExpressions": true}]*/

// Note that `||` and `&&` alone aren't a boolean context for either operand
// since the resultant value need not be a boolean without casting.
const foo = !!bar || baz

if (foo || bar) {
  //...
}

while (foo && bar) {
  //...
}

if ((foo || bar) && baz) {
  //...
}

const foo1 = new Boolean(bar || baz)

foo && bar ? baz : bat

const ternaryBranches = Boolean(bar ? baz : bat)

const nullishCoalescingOperator = Boolean(bar ?? baz)

const commaOperator = Boolean((bar, baz, bat))

// another comma operator example
for (let i = 0; console.log(i), i < 10; i++) {
  // ...
}

// comma operator in non-final position
Boolean((Boolean(bar), baz, bat))
no-redeclare
Built-in
In JavaScript, it's possible to redeclare the same variable name using `var`. This can lead to confusion as to where the variable is actually declared and initialized.

Rule Details

This rule is aimed at eliminating variables that have multiple declarations in the same scope.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-redeclare: "error"*/

var a = 3
var a = 10

class C {
  foo() {
    var b = 3
    var b = 10
  }

  static {
    var c = 3
    var c = 10
  }
}

Examples of correct code for this rule:

✅ Correct
/*eslint no-redeclare: "error"*/

var a = 3
a = 10

class C {
  foo() {
    var b = 3
    b = 10
  }

  static {
    var c = 3
    c = 10
  }
}

Options

This rule takes one optional argument, an object with a boolean property "builtinGlobals". It defaults to true. If set to true, this rule also checks redeclaration of built-in globals, such as Object, Array, Number...

builtinGlobals

The "builtinGlobals" option will check for redeclaration of built-in globals in global scope.

Examples of incorrect code for the { "builtinGlobals": true } option:

::: incorrect { "sourceType": "script" }

/*eslint no-redeclare: ["error", { "builtinGlobals": true }]*/

var Object = 0

Note that when using sourceType: "commonjs" (or ecmaFeatures.globalReturn, if using the default parser), the top scope of a program is not actually the global scope, but rather a "module" scope. When this is the case, declaring a variable named after a builtin global is not a redeclaration, but rather a shadowing of the global variable. In that case, the no-shadow rule with the "builtinGlobals" option should be used.

Built-in
It is NOT recommended to use the `no-return-await` rule anymore because:
  • return await on a promise will not result in an extra microtask.
  • return await yields a better stack trace for debugging.

Historical context: When promises were first introduced, calling return await introduced an additional microtask, one for the await and one for the return value of the async function. Each extra microtask delays the computation of a result and so this rule was added to help avoid this performance trap. Later, ECMA-262 changed the way return await worked so it would create a single microtask, which means this rule is no longer necessary.

Rule Details

This rule warns on any usage of return await except in try blocks.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-return-await: "error"*/

async function foo() {
  return await bar()
}

Examples of correct code for this rule:

✅ Correct
/*eslint no-return-await: "error"*/

async function foo1() {
  return bar()
}

async function foo2() {
  await bar()
  return
}

// This is essentially the same as `return await bar();`, but the rule checks only `await` in `return` statements
async function foo3() {
  const x = await bar()
  return x
}

// In this example the `await` is necessary to be able to catch errors thrown from `bar()`
async function foo4() {
  try {
    return await bar()
  } catch (error) {}
}

When Not To Use It

You should not use this rule. There is no reason to avoid return await.

no-undef
Built-in
problem
This rule can help you locate potential ReferenceErrors resulting from misspellings of variable and parameter names, or accidental implicit globals (for example, from forgetting the `var` keyword in a `for` loop initializer).

Rule Details

Any reference to an undeclared variable causes a warning, unless the variable is explicitly mentioned in a /*global ...*/ comment, or specified in the globals key in the configuration file. A common use case for these is if you intentionally use globals that are defined elsewhere (e.g. in a script sourced from HTML).

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-undef: "error"*/

const foo = someFunction()
const bar = a + 1

Examples of correct code for this rule with global declaration:

✅ Correct
/*global someFunction, a*/
/*eslint no-undef: "error"*/

const foo = someFunction()
const bar = a + 1

Note that this rule does not disallow assignments to read-only global variables. See no-global-assign if you also want to disallow those assignments.

This rule also does not disallow redeclarations of global variables. See no-redeclare if you also want to disallow those redeclarations.

Options

  • typeof set to true will warn for variables used inside typeof check (Default false).

typeof

Examples of correct code for the default { "typeof": false } option:

✅ Correct
/*eslint no-undef: "error"*/

if (typeof UndefinedIdentifier === 'undefined') {
  // do something ...
}

You can use this option if you want to prevent typeof check on a variable which has not been declared.

Examples of incorrect code for the { "typeof": true } option:

❌ Incorrect
/*eslint no-undef: ["error", { "typeof": true }] */

if (typeof a === 'string') {
}

Examples of correct code for the { "typeof": true } option with global declaration:

✅ Correct
/*global a*/
/*eslint no-undef: ["error", { "typeof": true }] */

if (typeof a === 'string') {
}

When Not To Use It

If explicit declaration of global variables is not to your taste.

Compatibility

This rule provides compatibility with treatment of global variables in JSHint and JSLint.

Built-in
It's a common mistake in JavaScript to use a conditional expression to select between two Boolean values instead of using `!` to convert the test to a Boolean.

Here are some examples:

// Bad
const isYes = answer === 1 ? true : false

// Good
const isYes = answer === 1

// Bad
const isNo = answer === 1 ? false : true

// Good
const isNo = answer !== 1

Another common mistake is using a single variable as both the conditional test and the consequent. In such cases, the logical OR can be used to provide the same functionality. Here is an example:

// Bad
foo(bar ? bar : 1)

// Good
foo(bar || 1)

Rule Details

This rule disallow ternary operators when simpler alternatives exist.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-unneeded-ternary: "error"*/

const a = x === 2 ? true : false

const b = x ? true : false

Examples of correct code for this rule:

✅ Correct
/*eslint no-unneeded-ternary: "error"*/

const a = x === 2 ? 'Yes' : 'No'

const b = x !== false

const c = x ? 'Yes' : 'No'

const d = x ? y : x

f(x ? x : 1) // default assignment - would be disallowed if defaultAssignment option set to false. See option details below.

Options

This rule has an object option:

  • "defaultAssignment": true (default) allows the conditional expression as a default assignment pattern
  • "defaultAssignment": false disallows the conditional expression as a default assignment pattern

defaultAssignment

When set to true, which it is by default, The defaultAssignment option allows expressions of the form x ? x : expr (where x is any identifier and expr is any expression).

Examples of additional incorrect code for this rule with the { "defaultAssignment": false } option:

❌ Incorrect
/*eslint no-unneeded-ternary: ["error", { "defaultAssignment": false }]*/

const a = x ? x : 1

f(x ? x : 1)

Note that defaultAssignment: false still allows expressions of the form x ? expr : x (where the identifier is on the right hand side of the ternary).

When Not To Use It

You can turn this rule off if you are not concerned with unnecessary complexity in conditional expressions.

Built-in
problem
Just as developers might type `-a + b` when they mean `-(a + b)` for the negative of a sum, they might type `!key in object` by mistake when they almost certainly mean `!(key in object)` to test that a key is not in an object. `!obj instanceof Ctor` is similar.

Rule Details

This rule disallows negating the left operand of the following relational operators:

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-unsafe-negation: "error"*/

if ((!key) in object) {
  // operator precedence makes it equivalent to (!key) in object
  // and type conversion makes it equivalent to (key ? "false" : "true") in object
}

if ((!obj) instanceof Ctor) {
  // operator precedence makes it equivalent to (!obj) instanceof Ctor
  // and it equivalent to always false since boolean values are not objects.
}

Examples of correct code for this rule:

✅ Correct
/*eslint no-unsafe-negation: "error"*/

if (!(key in object)) {
  // key is not in object
}

if (!(obj instanceof Ctor)) {
  // obj is not an instance of Ctor
}

Exception

For rare situations when negating the left operand is intended, this rule allows an exception. If the whole negation is explicitly wrapped in parentheses, the rule will not report a problem.

Examples of correct code for this rule:

✅ Correct
/*eslint no-unsafe-negation: "error"*/

if ((!foo) in object) {
  // allowed, because the negation is explicitly wrapped in parentheses
  // it is equivalent to (foo ? "false" : "true") in object
  // this is allowed as an exception for rare situations when that is the intended meaning
}

if ('' + !foo in object) {
  // you can also make the intention more explicit, with type conversion
}

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-unsafe-negation: "error"*/

if ((!foo) in object) {
  // this is not an allowed exception
}

Options

This rule has an object option:

  • "enforceForOrderingRelations": false (default) allows negation of the left-hand side of ordering relational operators (<, >, <=, >=)
  • "enforceForOrderingRelations": true disallows negation of the left-hand side of ordering relational operators

enforceForOrderingRelations

With this option set to true the rule is additionally enforced for:

  • < operator.
  • > operator.
  • <= operator.
  • >= operator.

The purpose is to avoid expressions such as ! a < b (which is equivalent to (a ? 0 : 1) < b) when what is really intended is !(a < b).

Examples of additional incorrect code for this rule with the { "enforceForOrderingRelations": true } option:

❌ Incorrect
/*eslint no-unsafe-negation: ["error", { "enforceForOrderingRelations": true }]*/

if (!a < b) {
}

while (!a > b) {}

foo = !a <= b

foo = !a >= b

When Not To Use It

If you don't want to notify unsafe logical negations, then it's safe to disable this rule.

Built-in
An unused expression which has no effect on the state of the program indicates a logic error.

For example, n + 1; is not a syntax error, but it might be a typing mistake where a programmer meant an assignment statement n += 1; instead. Sometimes, such unused expressions may be eliminated by some build tools in production environment, which possibly breaks application logic.

Rule Details

This rule aims to eliminate unused expressions which have no effect on the state of the program.

This rule does not apply to function calls or constructor calls with the new operator, because they could have side effects on the state of the program.

let i = 0
function increment() {
  i += 1
}
increment() // return value is unused, but i changed as a side effect

let nThings = 0
function Thing() {
  nThings += 1
}
new Thing() // constructed object is unused, but nThings changed as a side effect

This rule does not apply to directives (which are in the form of literal string expressions such as "use strict"; at the beginning of a script, module, or function) when using ES5+ environments. In ES3 environments, directives are treated as unused expressions by default, but this behavior can be changed using the ignoreDirectives option.

Sequence expressions (those using a comma, such as a = 1, b = 2) are always considered unused unless their return value is assigned or used in a condition evaluation, or a function call is made with the sequence expression value.

Options

This rule, in its default state, does not require any arguments. If you would like to enable one or more of the following you may pass an object with the options set as follows:

  • allowShortCircuit set to true will allow you to use short circuit evaluations in your expressions (Default: false).
  • allowTernary set to true will enable you to use ternary operators in your expressions similarly to short circuit evaluations (Default: false).
  • allowTaggedTemplates set to true will enable you to use tagged template literals in your expressions (Default: false).
  • enforceForJSX set to true will flag unused JSX element expressions (Default: false).
  • ignoreDirectives set to true will prevent directives from being reported as unused expressions when linting with ecmaVersion: 3 (Default: false).

These options allow unused expressions only if all of the code paths either directly change the state (for example, assignment statement) or could have side effects (for example, function call).

Examples of incorrect code for the default { "allowShortCircuit": false, "allowTernary": false } options:

❌ Incorrect
/*eslint no-unused-expressions: "error"*/

0

if (0) 0

{
  0
}

;(f(0), {})

a && b()
;(a, b())
;((c = a), b)

a() &&
  (function namedFunctionInExpressionContext() {
    f()
  })(function anIncompleteIIFE() {})

injectGlobal`body{ color: red; }`

Examples of correct code for the default { "allowShortCircuit": false, "allowTernary": false } options:

✅ Correct
/*eslint no-unused-expressions: "error"*/

{
} // In this context, this is a block statement, not an object literal

{
  myLabel: foo()
} // In this context, this is a block statement with a label and expression, not an object literal

function namedFunctionDeclaration() {}

;(function aGenuineIIFE() {})()

f()

a = 0

new C()

delete a.b

void a

Note that one or more string expression statements (with or without semi-colons) will only be considered as unused if they are not in the beginning of a script, module, or function (alone and uninterrupted by other statements). Otherwise, they will be treated as part of a "directive prologue", a section potentially usable by JavaScript engines. This includes "strict mode" directives.

Examples of correct code for this rule in regard to directives:

✅ Correct
/*eslint no-unused-expressions: "error"*/

'use strict'
'use asm'
'use stricter'
'use babel'
'any other strings like this in the directive prologue'
'this is still the directive prologue'

function foo() {
  'bar'
}

class Foo {
  someMethod() {
    'use strict'
  }
}

Examples of incorrect code for this rule in regard to directives:

❌ Incorrect
/*eslint no-unused-expressions: "error"*/

doSomething()
;('use strict') // this isn't in a directive prologue, because there is a non-directive statement before it

function foo() {
  'bar' + 1
}

class Foo {
  static {
    'use strict' // class static blocks do not have directive prologues
  }
}

allowShortCircuit

Examples of incorrect code for the { "allowShortCircuit": true } option:

❌ Incorrect
/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true }]*/

a || b

Examples of correct code for the { "allowShortCircuit": true } option:

✅ Correct
/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true }]*/

a && b()
a() || (b = c)

allowTernary

Examples of incorrect code for the { "allowTernary": true } option:

❌ Incorrect
/*eslint no-unused-expressions: ["error", { "allowTernary": true }]*/

a ? b : 0
a ? b : c()

Examples of correct code for the { "allowTernary": true } option:

✅ Correct
/*eslint no-unused-expressions: ["error", { "allowTernary": true }]*/

a ? b() : c()
a ? (b = c) : d()

allowShortCircuit and allowTernary

Examples of correct code for the { "allowShortCircuit": true, "allowTernary": true } options:

✅ Correct
/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }]*/

a ? b() || (c = d) : e()

allowTaggedTemplates

Examples of incorrect code for the { "allowTaggedTemplates": true } option:

❌ Incorrect
/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/

;`some untagged template string`

Examples of correct code for the { "allowTaggedTemplates": true } option:

✅ Correct
/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/

tag`some tagged template string`

enforceForJSX

JSX is most-commonly used in the React ecosystem, where it is compiled to React.createElement expressions. Though free from side-effects, these calls are not automatically flagged by the no-unused-expression rule. If you're using React, or any other side-effect-free JSX pragma, this option can be enabled to flag these expressions.

Examples of incorrect code for the { "enforceForJSX": true } option:

::: incorrect { "parserOptions": { "ecmaFeatures": { "jsx": true } } }

/*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/

;<MyComponent />
;<></>

Examples of correct code for the { "enforceForJSX": true } option:

::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } }

/*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/

const myComponentPartial = <MyComponent />

const myFragment = <></>

ignoreDirectives

When set to false (default), this rule reports directives (like "use strict") as unused expressions when linting with ecmaVersion: 3. This default behavior exists because ES3 environments do not formally support directives, meaning such strings are effectively unused expressions in that specific context.

Set this option to true to prevent directives from being reported as unused, even when ecmaVersion: 3 is specified. This option is primarily useful for projects that need to maintain a single codebase containing directives while supporting both older ES3 environments and modern (ES5+) environments.

Note: In ES5+ environments, directives are always ignored regardless of this setting.

Examples of incorrect code for the { "ignoreDirectives": false } option and ecmaVersion: 3:

::: incorrect { "ecmaVersion": 3, "sourceType": "script" }

/*eslint no-unused-expressions: ["error", { "ignoreDirectives": false }]*/

'use strict'
'use asm'
'use stricter'
'use babel'
'any other strings like this in the directive prologue'
'this is still the directive prologue'

function foo() {
  'bar'
}

Examples of correct code for the { "ignoreDirectives": true } option and ecmaVersion: 3:

::: correct { "ecmaVersion": 3, "sourceType": "script" }

/*eslint no-unused-expressions: ["error", { "ignoreDirectives": true }]*/

'use strict'
'use asm'
'use stricter'
'use babel'
'any other strings like this in the directive prologue'
'this is still the directive prologue'

function foo() {
  'bar'
}

TypeScript Support

This rule supports TypeScript-specific expressions and follows these guidelines:

  1. Directives (like 'use strict') are allowed in module and namespace declarations
  2. Type-related expressions are treated as unused if their wrapped value expressions are unused:
    • Type assertions (x as number, <number>x)
    • Non-null assertions (x!)
    • Type instantiations (Set<number>)

Note: Although type expressions never have runtime side effects (e.g., x! is equivalent to x at runtime), they can be used to assert types for testing purposes.

Examples of correct code for this rule when using TypeScript:

✅ Correct
/* eslint no-unused-expressions: "error" */

// Type expressions wrapping function calls are allowed
function getSet() {
  return Set
}
getSet()<number>
getSet() as Set<unknown>
getSet()!

// Directives in modules and namespaces
module Foo {
  'use strict'
  'hello world'
}

namespace Bar {
  'use strict'
  export class Baz {}
}

Examples of incorrect code for this rule when using TypeScript:

❌ Incorrect
/* eslint no-unused-expressions: "error" */

// Standalone type expressions
Set<number>
1 as number
window!

// Expressions inside namespaces
namespace Bar {
  123
}
Built-in
problem
Private class members that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. Such class members take up space in the code and can lead to confusion by readers.

Rule Details

This rule reports unused private class members.

  • A private field or method is considered to be unused if its value is never read.
  • A private accessor is considered to be unused if it is never accessed (read or write).

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-unused-private-class-members: "error"*/

class A {
  #unusedMember = 5
}

class B {
  #usedOnlyInWrite = 5
  method() {
    this.#usedOnlyInWrite = 42
  }
}

class C {
  #usedOnlyToUpdateItself = 5
  method() {
    this.#usedOnlyToUpdateItself++
  }
}

class D {
  #unusedMethod() {}
}

class E {
  get #unusedAccessor() {}
  set #unusedAccessor(value) {}
}

Examples of correct code for this rule:

✅ Correct
/*eslint no-unused-private-class-members: "error"*/

class A {
  #usedMember = 42
  method() {
    return this.#usedMember
  }
}

class B {
  #usedMethod() {
    return 42
  }
  anotherMethod() {
    return this.#usedMethod()
  }
}

class C {
  get #usedAccessor() {}
  set #usedAccessor(value) {}

  method() {
    this.#usedAccessor = 42
  }
}

When Not To Use It

If you don't want to be notified about unused private class members, you can safely turn this rule off.

Built-in
problem
Variables that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. Such variables take up space in the code and can lead to confusion by readers.

Rule Details

This rule is aimed at eliminating unused variables, functions, and function parameters.

A variable foo is considered to be used if any of the following are true:

  • It is called (foo()) or constructed (new foo())
  • It is read (let bar = foo)
  • It is passed into a function as an argument (doSomething(foo))
  • It is read inside of a function that is passed to another function (doSomething(function() { foo(); }))

A variable is not considered to be used if it is only ever declared (let foo = 5) or assigned to (foo = 7).

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint no-unused-vars: "error"*/
/*global some_unused_var*/

// It checks variables you have defined as global
some_unused_var = 42

let x

// Write-only variables are not considered as used.
let y = 10
y = 5

// A read for a modification of itself is not considered as used.
let z = 0
z = z + 1

// By default, unused arguments cause warnings.
;(function (foo) {
  return 5
})()

// Unused recursive functions also cause warnings.
function fact(n) {
  if (n < 2) return 1
  return n * fact(n - 1)
}

// When a function definition destructures an array, unused entries from the array also cause warnings.
function getY([x, y]) {
  return y
}
getY(['a', 'b'])

Examples of correct code for this rule:

✅ Correct
/*eslint no-unused-vars: "error"*/

const x = 10
alert(x)

// foo is considered used here
myFunc(
  function foo() {
    // ...
  }.bind(this),
)
;(function (foo) {
  return foo
})()

var myFunc
myFunc = setTimeout(function () {
  // myFunc is considered used
  myFunc()
}, 50)

// Only the second argument from the destructured array is used.
function getY([, y]) {
  return y
}
getY(['a', 'b'])

exported

In environments outside of CommonJS or ECMAScript modules, you may use var to create a global variable that may be used by other scripts. You can use the /* exported variableName */ comment block to indicate that this variable is being exported and therefore should not be considered unused.

Note that /* exported */ has no effect for any of the following:

  • when languageOptions.sourceType is module (default) or commonjs
  • when languageOptions.parserOptions.ecmaFeatures.globalReturn is true

The line comment // exported variableName will not work as exported is not line-specific.

/* exported global_var */

var global_var = 42

Examples of correct code for /* exported variableName */ operation with no-unused-vars:

::: correct { "sourceType": "script" }

/*eslint no-unused-vars: "error"*/
/* exported global_var */

var global_var = 42

Options

This rule takes one argument which can be a string or an object. The string settings are the same as those of the vars property (explained below).

By default this rule is enabled with all option for caught errors and variables, and after-used for arguments.

{
  "rules": {
    "no-unused-vars": [
      "error",
      {
        "vars": "all",
        "args": "after-used",
        "caughtErrors": "all",
        "ignoreRestSiblings": false,
        "reportUsedIgnorePattern": false
      }
    ]
  }
}

vars

The vars option has two settings:

  • all checks all variables for usage, including those in the global scope. However, it excludes variables targeted by other options like args and caughtErrors. This is the default setting.
  • local checks only that locally-declared variables are used but will allow global variables to be unused.

vars: local

Examples of correct code for the { "vars": "local" } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "vars": "local" }]*/
/*global some_unused_var */

some_unused_var = 42

varsIgnorePattern

The varsIgnorePattern option specifies exceptions not to check for usage: variables whose names match a regexp pattern. For example, variables whose names contain ignored or Ignored. However, it excludes variables targeted by other options like argsIgnorePattern and caughtErrorsIgnorePattern.

Examples of correct code for the { "varsIgnorePattern": "[iI]gnored" } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "[iI]gnored" }]*/

const firstVarIgnored = 1
const secondVar = 2
console.log(secondVar)

args

The args option has three settings:

  • after-used - unused positional arguments that occur before the last used argument will not be checked, but all named arguments and all positional arguments after the last used argument will be checked.
  • all - all named arguments must be used.
  • none - do not check arguments.

args: after-used

Examples of incorrect code for the default { "args": "after-used" } option:

❌ Incorrect
/*eslint no-unused-vars: ["error", { "args": "after-used" }]*/

// 2 errors, for the parameters after the last used parameter (bar)
// "baz" is defined but never used
// "qux" is defined but never used
;(function (foo, bar, baz, qux) {
  return bar
})()

Examples of correct code for the default { "args": "after-used" } option:

✅ Correct
/*eslint no-unused-vars: ["error", {"args": "after-used"}]*/

;(function (foo, bar, baz, qux) {
  return qux
})()

args: all

Examples of incorrect code for the { "args": "all" } option:

❌ Incorrect
/*eslint no-unused-vars: ["error", { "args": "all" }]*/

// 2 errors
// "foo" is defined but never used
// "baz" is defined but never used
;(function (foo, bar, baz) {
  return bar
})()

args: none

Examples of correct code for the { "args": "none" } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "args": "none" }]*/

;(function (foo, bar, baz) {
  return bar
})()

argsIgnorePattern

The argsIgnorePattern option specifies exceptions not to check for usage: arguments whose names match a regexp pattern. For example, variables whose names begin with an underscore.

Examples of correct code for the { "argsIgnorePattern": "^_" } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/

function foo(x, _y) {
  return x + 1
}
foo()

caughtErrors

The caughtErrors option is used for catch block arguments validation.

It has two settings:

  • all - all named arguments must be used. This is the default setting.
  • none - do not check error objects.

caughtErrors: all

Not specifying this option is equivalent of assigning it to all.

Examples of incorrect code for the { "caughtErrors": "all" } option:

❌ Incorrect
/*eslint no-unused-vars: ["error", { "caughtErrors": "all" }]*/

// 1 error
// "err" is defined but never used
try {
  //...
} catch (err) {
  console.error('errors')
}

caughtErrors: none

Examples of correct code for the { "caughtErrors": "none" } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "caughtErrors": "none" }]*/

try {
  //...
} catch (err) {
  console.error('errors')
}

caughtErrorsIgnorePattern

The caughtErrorsIgnorePattern option specifies exceptions not to check for usage: catch arguments whose names match a regexp pattern. For example, variables whose names begin with a string 'ignore'.

Examples of correct code for the { "caughtErrorsIgnorePattern": "^ignore" } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "caughtErrors": "all", "caughtErrorsIgnorePattern": "^ignore" }]*/

try {
  //...
} catch (ignoreErr) {
  console.error('errors')
}

destructuredArrayIgnorePattern

The destructuredArrayIgnorePattern option specifies exceptions not to check for usage: elements of array destructuring patterns whose names match a regexp pattern. For example, variables whose names begin with an underscore.

Examples of correct code for the { "destructuredArrayIgnorePattern": "^_" } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "destructuredArrayIgnorePattern": "^_" }]*/

const [a, _b, c] = ['a', 'b', 'c']
console.log(a + c)

const {
  x: [_a, foo],
} = bar
console.log(foo)

function baz([_c, x]) {
  x
}
baz()

function test({ p: [_q, r] }) {
  r
}
test()

let _m, n
foo.forEach((item) => {
  ;[_m, n] = item
  console.log(n)
})

let _o, p
_o = 1
;[_o, p] = foo
p

ignoreRestSiblings

The ignoreRestSiblings option is a boolean (default: false). Using a Rest Property it is possible to "omit" properties from an object, but by default the sibling properties are marked as "unused". With this option enabled the rest property's siblings are ignored.

Examples of correct code for the { "ignoreRestSiblings": true } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/

// 'foo' and 'bar' were ignored because they have a rest property sibling.
const { foo, ...rest } = data
console.log(rest)

// OR

let bar
;({ bar, ...rest } = data)

ignoreClassWithStaticInitBlock

The ignoreClassWithStaticInitBlock option is a boolean (default: false). Static initialization blocks allow you to initialize static variables and execute code during the evaluation of a class definition, meaning the static block code is executed without creating a new instance of the class. When set to true, this option ignores classes containing static initialization blocks.

Examples of incorrect code for the { "ignoreClassWithStaticInitBlock": true } option

❌ Incorrect
/*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/

class Foo {
  static myProperty = 'some string'
  static mymethod() {
    return 'some string'
  }
}

class Bar {
  static {
    let baz // unused variable
  }
}

Examples of correct code for the { "ignoreClassWithStaticInitBlock": true } option

✅ Correct
/*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/

class Foo {
  static {
    let bar = 'some string'

    console.log(bar)
  }
}

reportUsedIgnorePattern

The reportUsedIgnorePattern option is a boolean (default: false). Using this option will report variables that match any of the valid ignore pattern options (varsIgnorePattern, argsIgnorePattern, caughtErrorsIgnorePattern, or destructuredArrayIgnorePattern) if they have been used.

Examples of incorrect code for the { "reportUsedIgnorePattern": true } option:

❌ Incorrect
/*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/

const firstVarIgnored = 1
const secondVar = 2
console.log(firstVarIgnored, secondVar)

Examples of correct code for the { "reportUsedIgnorePattern": true } option:

✅ Correct
/*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/

const firstVar = 1
const secondVar = 2
console.log(firstVar, secondVar)

When Not To Use It

If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off.

prefer-const
Built-in
If a variable is never reassigned, using the `const` declaration is better.

const declaration tells readers, "this variable is never reassigned," reducing cognitive load and improving maintainability.

Rule Details

This rule is aimed at flagging variables that are declared using let keyword, but never reassigned after the initial assignment.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint prefer-const: "error"*/

// it's initialized and never reassigned.
let a = 3
console.log(a)

let b
b = 0
console.log(b)

class C {
  static {
    let a
    a = 0
    console.log(a)
  }
}

// `i` is redefined (not reassigned) on each loop step.
for (let i in [1, 2, 3]) {
  console.log(i)
}

// `a` is redefined (not reassigned) on each loop step.
for (let a of [1, 2, 3]) {
  console.log(a)
}

Examples of correct code for this rule:

✅ Correct
/*eslint prefer-const: "error"*/

// using const.
const a = 0

// it's never initialized.
let b
console.log(b)

// it's reassigned after initialized.
let c
c = 0
c = 1
console.log(c)

// it's initialized in a different block from the declaration.
let d
if (true) {
  d = 0
}
console.log(d)

// it's initialized in a different scope.
let e
class C {
  #x
  static {
    e = (obj) => obj.#x
  }
}

// it's initialized at a place that we cannot write a variable declaration.
let f
if (true) f = 0
console.log(f)

// `i` gets a new binding each iteration
for (const i in [1, 2, 3]) {
  console.log(i)
}

// `a` gets a new binding each iteration
for (const a of [1, 2, 3]) {
  console.log(a)
}

// `end` is never reassigned, but we cannot separate the declarations without modifying the scope.
for (let i = 0, end = 10; i < end; ++i) {
  console.log(i)
}

// `predicate` is only assigned once but cannot be separately declared as `const`
let predicate
;[object.type, predicate] = foo()

// `g` is only assigned once but cannot be separately declared as `const`
let g
const h = {}
;({ g, c: h.c } = func())

// suggest to use `no-var` rule.
var i = 3
console.log(i)

Options

{
  "prefer-const": [
    "error",
    {
      "destructuring": "any",
      "ignoreReadBeforeAssign": false
    }
  ]
}

destructuring

The kind of the way to address variables in destructuring. There are 2 values:

  • "any" (default) - If any variables in destructuring should be const, this rule warns for those variables.
  • "all" - If all variables in destructuring should be const, this rule warns the variables. Otherwise, ignores them.

Examples of incorrect code for the default {"destructuring": "any"} option:

❌ Incorrect
/*eslint prefer-const: "error"*/

let { a, b } = obj /*error 'b' is never reassigned, use 'const' instead.*/
a = a + 1

Examples of correct code for the default {"destructuring": "any"} option:

✅ Correct
/*eslint prefer-const: "error"*/

// using const.
const { a: a0, b } = obj
const a = a0 + 1

// all variables are reassigned.
let { c, d } = obj
c = c + 1
d = d + 1

Examples of incorrect code for the {"destructuring": "all"} option:

❌ Incorrect
/*eslint prefer-const: ["error", {"destructuring": "all"}]*/

// all of `a` and `b` should be const, so those are warned.
let { a, b } = obj /*error 'a' is never reassigned, use 'const' instead.
                             'b' is never reassigned, use 'const' instead.*/

Examples of correct code for the {"destructuring": "all"} option:

✅ Correct
/*eslint prefer-const: ["error", {"destructuring": "all"}]*/

// 'b' is never reassigned, but all of `a` and `b` should not be const, so those are ignored.
let { a, b } = obj
a = a + 1

ignoreReadBeforeAssign

This is an option to avoid conflicting with no-use-before-define rule (without "nofunc" option). If true is specified, this rule will ignore variables that are read between the declaration and the first assignment. Default is false.

Examples of correct code for the {"ignoreReadBeforeAssign": true} option:

✅ Correct
/*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": true}]*/

let timer
function initialize() {
  if (foo()) {
    clearInterval(timer)
  }
}
timer = setInterval(initialize, 100)

Examples of correct code for the default {"ignoreReadBeforeAssign": false} option:

✅ Correct
/*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": false}]*/

const timer = setInterval(initialize, 100)
function initialize() {
  if (foo()) {
    clearInterval(timer)
  }
}

When Not To Use It

If you don't want to be notified about variables that are never reassigned after initial assignment, you can safely disable this rule.

Built-in
It is considered good practice to only pass instances of the built-in `Error` object to the `reject()` function for user-defined errors in Promises. `Error` objects automatically store a stack trace, which can be used to debug an error by determining where it came from. If a Promise is rejected with a non-`Error` value, it can be difficult to determine where the rejection occurred.

Rule Details

This rule aims to ensure that Promises are only rejected with Error objects.

Options

This rule takes one optional object argument:

  • allowEmptyReject: true (false by default) allows calls to Promise.reject() with no arguments.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint prefer-promise-reject-errors: "error"*/

Promise.reject('something bad happened')

Promise.reject(5)

Promise.reject()

new Promise(function (resolve, reject) {
  reject('something bad happened')
})

new Promise(function (resolve, reject) {
  reject()
})

Examples of correct code for this rule:

✅ Correct
/*eslint prefer-promise-reject-errors: "error"*/

Promise.reject(new Error('something bad happened'))

Promise.reject(new TypeError('something bad happened'))

new Promise(function (resolve, reject) {
  reject(new Error('something bad happened'))
})

const foo = getUnknownValue()
Promise.reject(foo)

Examples of correct code for this rule with the allowEmptyReject: true option:

✅ Correct
/*eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}]*/

Promise.reject()

new Promise(function (resolve, reject) {
  reject()
})

Known Limitations

Due to the limits of static analysis, this rule cannot guarantee that you will only reject Promises with Error objects. While the rule will report cases where it can guarantee that the rejection reason is clearly not an Error, it will not report cases where there is uncertainty about whether a given reason is an Error. For more information on this caveat, see the similar limitations in the no-throw-literal rule.

To avoid conflicts between rules, this rule does not report non-error values used in throw statements in async functions, even though these lead to Promise rejections. To lint for these cases, use the no-throw-literal rule.

When Not To Use It

If you're using custom non-error values as Promise rejection reasons, you can turn off this rule.

radix
Built-in
When using the `parseInt()` function it is common to omit the second argument, the radix, and let the function try to determine from the first argument what type of number it is. By default, `parseInt()` will autodetect decimal and hexadecimal (via `0x` prefix). Prior to ECMAScript 5, `parseInt()` also autodetected octal literals, which caused problems because many developers assumed a leading `0` would be ignored.

This confusion led to the suggestion that you always use the radix parameter to parseInt() to eliminate unintended consequences. So instead of doing this:

const num = parseInt('071') // 57

Do this:

const num = parseInt('071', 10) // 71

ECMAScript 5 changed the behavior of parseInt() so that it no longer autodetects octal literals and instead treats them as decimal literals. However, the differences between hexadecimal and decimal interpretation of the first parameter causes many developers to continue using the radix parameter to ensure the string is interpreted in the intended way.

On the other hand, if the code is targeting only ES5-compliant environments passing the radix 10 may be redundant. In such a case you might want to disallow using such a radix.

Rule Details

This rule is aimed at preventing the unintended conversion of a string to a number of a different base than intended or at preventing the redundant 10 radix if targeting modern environments only.

Options

There are two options for this rule:

  • "always" enforces providing a radix (default)
  • "as-needed" disallows providing the 10 radix

always

Examples of incorrect code for the default "always" option:

❌ Incorrect
/*eslint radix: "error"*/

const num = parseInt('071')

const num1 = parseInt(someValue)

const num2 = parseInt('071', 'abc')

const num3 = parseInt('071', 37)

const num4 = parseInt()

Examples of correct code for the default "always" option:

✅ Correct
/*eslint radix: "error"*/

const num = parseInt('071', 10)

const num1 = parseInt('071', 8)

const num2 = parseFloat(someValue)

as-needed

Examples of incorrect code for the "as-needed" option:

❌ Incorrect
/*eslint radix: ["error", "as-needed"]*/

const num = parseInt('071', 10)

const num1 = parseInt('071', 'abc')

const num2 = parseInt()

Examples of correct code for the "as-needed" option:

✅ Correct
/*eslint radix: ["error", "as-needed"]*/

const num = parseInt('071')

const num1 = parseInt('071', 8)

const num2 = parseFloat(someValue)

When Not To Use It

If you don't want to enforce either presence or omission of the 10 radix value you can turn this rule off.

Built-in
problem
When writing asynchronous code, it is possible to create subtle race condition bugs. Consider the following example:
let totalLength = 0

async function addLengthOfSinglePage(pageNum) {
  totalLength += await getPageLength(pageNum)
}

Promise.all([addLengthOfSinglePage(1), addLengthOfSinglePage(2)]).then(() => {
  console.log('The combined length of both pages is', totalLength)
})

This code looks like it will sum the results of calling getPageLength(1) and getPageLength(2), but in reality the final value of totalLength will only be the length of one of the two pages. The bug is in the statement totalLength += await getPageLength(pageNum);. This statement first reads an initial value of totalLength, then calls getPageLength(pageNum) and waits for that Promise to fulfill. Finally, it sets the value of totalLength to the sum of await getPageLength(pageNum) and the initial value of totalLength. If the totalLength variable is updated in a separate function call during the time that the getPageLength(pageNum) Promise is pending, that update will be lost because the new value is overwritten without being read.

One way to fix this issue would be to ensure that totalLength is read at the same time as it's updated, like this:

async function addLengthOfSinglePage(pageNum) {
  const lengthOfThisPage = await getPageLength(pageNum)

  totalLength += lengthOfThisPage
}

Another solution would be to avoid using a mutable variable reference at all:

Promise.all([getPageLength(1), getPageLength(2)]).then((pageLengths) => {
  const totalLength = pageLengths.reduce(
    (accumulator, length) => accumulator + length,
    0,
  )

  console.log('The combined length of both pages is', totalLength)
})

Rule Details

This rule aims to report assignments to variables or properties in cases where the assignments may be based on outdated values.

Variables

This rule reports an assignment to a variable when it detects the following execution flow in a generator or async function:

  1. The variable is read.
  2. A yield or await pauses the function.
  3. After the function is resumed, a value is assigned to the variable from step 1.

The assignment in step 3 is reported because it may be incorrectly resolved because the value of the variable from step 1 may have changed between steps 2 and 3. In particular, if the variable can be accessed from other execution contexts (for example, if it is not a local variable and therefore other functions can change it), the value of the variable may have changed elsewhere while the function was paused in step 2.

Note that the rule does not report the assignment in step 3 in any of the following cases:

  • If the variable is read again between steps 2 and 3.
  • If the variable cannot be accessed while the function is paused (for example, if it's a local variable).

Examples of incorrect code for this rule:

❌ Incorrect
/* eslint require-atomic-updates: error */

let result

async function foo() {
  result += await something
}

async function bar() {
  result = result + (await something)
}

async function baz() {
  result = result + doSomething(await somethingElse)
}

async function qux() {
  if (!result) {
    result = await initialize()
  }
}

function* generator() {
  result += yield
}

Examples of correct code for this rule:

✅ Correct
/* eslint require-atomic-updates: error */

let result

async function foobar() {
  result = (await something) + result
}

async function baz() {
  const tmp = doSomething(await somethingElse)
  result += tmp
}

async function qux() {
  if (!result) {
    const tmp = await initialize()
    if (!result) {
      result = tmp
    }
  }
}

async function quux() {
  let localVariable = 0
  localVariable += await something
}

function* generator() {
  result = (yield) + result
}

Properties

This rule reports an assignment to a property through a variable when it detects the following execution flow in a generator or async function:

  1. The variable or object property is read.
  2. A yield or await pauses the function.
  3. After the function is resumed, a value is assigned to a property.

This logic is similar to the logic for variables, but stricter because the property in step 3 doesn't have to be the same as the property in step 1. It is assumed that the flow depends on the state of the object as a whole.

Example of incorrect code for this rule:

❌ Incorrect
/* eslint require-atomic-updates: error */

async function foo(obj) {
  if (!obj.done) {
    obj.something = await getSomething()
  }
}

Example of correct code for this rule:

✅ Correct
/* eslint require-atomic-updates: error */

async function foo(obj) {
  if (!obj.done) {
    const tmp = await getSomething()
    if (!obj.done) {
      obj.something = tmp
    }
  }
}

Options

This rule has an object option:

  • "allowProperties": When set to true, the rule does not report assignments to properties. Default is false.

allowProperties

Example of correct code for this rule with the { "allowProperties": true } option:

✅ Correct
/* eslint require-atomic-updates: ["error", { "allowProperties": true }] */

async function foo(obj) {
  if (!obj.done) {
    obj.something = await getSomething()
  }
}

When Not To Use It

If you don't use async or generator functions, you don't need to enable this rule.

valid-typeof
Built-in
problem
For a vast majority of use cases, the result of the `typeof` operator is one of the following string literals: `"undefined"`, `"object"`, `"boolean"`, `"number"`, `"string"`, `"function"`, `"symbol"`, and `"bigint"`. It is usually a typing mistake to compare the result of a `typeof` operator to other string literals.

Rule Details

This rule enforces comparing typeof expressions to valid string literals.

Examples of incorrect code for this rule:

❌ Incorrect
/*eslint valid-typeof: "error"*/

typeof foo === 'strnig'
typeof foo == 'undefimed'
typeof bar != 'nunber'
typeof bar !== 'fucntion'

Examples of correct code for this rule:

✅ Correct
/*eslint valid-typeof: "error"*/

typeof foo === 'string'
typeof bar == 'undefined'
typeof foo === baz
typeof bar === typeof qux

Options

This rule has an object option:

  • "requireStringLiterals": true allows the comparison of typeof expressions with only string literals or other typeof expressions, and disallows comparisons to any other value. Default is false.

requireStringLiterals

Examples of incorrect code with the { "requireStringLiterals": true } option:

❌ Incorrect
/*eslint valid-typeof: ["error", { "requireStringLiterals": true }]*/

typeof foo === undefined
typeof bar == Object
typeof baz === 'strnig'
typeof qux === 'some invalid type'
typeof baz === anotherVariable
typeof foo == 5

Examples of correct code with the { "requireStringLiterals": true } option:

✅ Correct
/*eslint valid-typeof: ["error", { "requireStringLiterals": true }]*/

typeof foo === 'undefined'
typeof bar == 'object'
typeof baz === 'string'
typeof bar === typeof qux

When Not To Use It

You may want to turn this rule off if you will be using the typeof operator on host objects.

default
eslint-plugin-import
💼 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`.

If a default import is requested, this rule will report if there is no default export in the imported module.

For ES7, reports if a default is named and exported but is not found in the referenced module.

Note: for packages, the plugin will find exported names from jsnext:main, if present in package.json. Redux's npm module includes this key, and thereby is lintable, for example.

A module path that is ignored or not unambiguously an ES module will not be reported when imported.

Rule Details

Given:

// ./foo.js
export default function () {
  return 42
}

// ./bar.js
export function bar() {
  return null
}

// ./baz.js
module.exports = function () {
  /* ... */
}

// node_modules/some-module/index.js
exports.sharedFunction = function shared() {
  /* ... */
}

The following is considered valid:

import foo from './foo'

// assuming 'node_modules' are ignored (true by default)
import someModule from 'some-module'

...and the following cases are reported:

import bar from './bar' // no default export found in ./bar
import baz from './baz' // no default export found in ./baz

When Not To Use It

If you are using CommonJS and/or modifying the exported namespace of any module at runtime, you will likely see false positives with this rule.

This rule currently does not interpret module.exports = ... as a default export, either, so such a situation will be reported in the importing module.

Further Reading

export
eslint-plugin-import
💼 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`.

Reports funny business with exports, like repeated exports of names or defaults.

Rule Details

export default class MyClass { /*...*/ } // Multiple default exports.

function makeClass() { return new MyClass(...arguments) }

export default makeClass // Multiple default exports.

or

export const foo = function () {
  /*...*/
} // Multiple exports of name 'foo'.

function bar() {
  /*...*/
}
export { bar as foo } // Multiple exports of name 'foo'.

In the case of named/default re-export, all n re-exports will be reported, as at least n-1 of them are clearly mistakes, but it is not clear which one (if any) is intended. Could be the result of copy/paste, code duplication with intent to rename, etc.

Further Reading

  • Lee Byron's ES7 export proposal
named
eslint-plugin-import
💼🚫 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`. This rule is _disabled_ in the ⌨️ `typescript` config.

Verifies that all named imports are part of the set of named exports in the referenced module.

For export, verifies that all named exports exist in the referenced module.

Note: for packages, the plugin will find exported names from jsnext:main (deprecated) or module, if present in package.json. Redux's npm module includes this key, and thereby is lintable, for example.

A module path that is ignored or not unambiguously an ES module will not be reported when imported. Note that type imports and exports, as used by Flow, are always ignored.

Rule Details

Given:

// ./foo.js
export const foo = "I'm so foo"

The following is considered valid:

// ./bar.js
import { foo } from './foo'

// ES7 proposal
export { foo as bar } from './foo'

// node_modules without jsnext:main are not analyzed by default
// (import/ignore setting)
import { SomeNonsenseThatDoesntExist } from 'react'

...and the following are reported:

// ./baz.js
import { notFoo } from './foo'

// ES7 proposal
export { notFoo as defNotBar } from './foo'

// will follow 'jsnext:main', if available
import { dontCreateStore } from 'redux'

Settings

import/ignore can be provided as a setting to ignore certain modules (node_modules, CoffeeScript, CSS if using Webpack, etc.).

Given:

# .eslintrc (YAML)
---
settings:
  import/ignore:
    - node_modules  # included by default, but replaced if explicitly configured
    - *.coffee$     # can't parse CoffeeScript (unless a custom polyglot parser was configured)

and

# ./whatever.coffee
exports.whatever = (foo) -> console.log foo

then the following is not reported:

// ./foo.js

// can't be analyzed, and ignored, so not reported
import { notWhatever } from './whatever'

When Not To Use It

If you are using CommonJS and/or modifying the exported namespace of any module at runtime, you will likely see false positives with this rule.

Further Reading

no-cycle
eslint-plugin-import
<!-- end auto-generated rule header -->

Ensures that there is no resolvable path back to this module via its dependencies.

This includes cycles of depth 1 (imported module imports me) to "∞" (or Infinity), if the maxDepth option is not set.

// dep-b.js
import './dep-a.js'

export function b() {
  /* ... */
}
// dep-a.js
import { b } from './dep-b.js' // reported: Dependency cycle detected.

This rule does not detect imports that resolve directly to the linted module; for that, see no-self-import.

This rule ignores type-only imports in Flow and TypeScript syntax (import type and import typeof), which have no runtime effect.

Rule Details

Options

By default, this rule only detects cycles for ES6 imports, but see the no-unresolved options as this rule also supports the same commonjs and amd flags. However, these flags only impact which import types are linted; the import/export infrastructure only registers import statements in dependencies, so cycles created by require within imported modules may not be detected.

maxDepth

There is a maxDepth option available to prevent full expansion of very deep dependency trees:

/*eslint import/no-cycle: [2, { maxDepth: 1 }]*/

// dep-c.js
import './dep-a.js'
// dep-b.js
import './dep-c.js'

export function b() {
  /* ... */
}
// dep-a.js
import { b } from './dep-b.js' // not reported as the cycle is at depth 2

This is not necessarily recommended, but available as a cost/benefit tradeoff mechanism for reducing total project lint time, if needed.

ignoreExternal

An ignoreExternal option is available to prevent the cycle detection to expand to external modules:

/*eslint import/no-cycle: [2, { ignoreExternal: true }]*/

// dep-a.js
import 'module-b/dep-b.js'

export function a() {
  /* ... */
}
// node_modules/module-b/dep-b.js
import { a } from './dep-a.js' // not reported as this module is external

Its value is false by default, but can be set to true for reducing total project lint time, if needed.

allowUnsafeDynamicCyclicDependency

This option disable reporting of errors if a cycle is detected with at least one dynamic import.

// bar.js
import { foo } from './foo'
export const bar = foo

// foo.js
export const foo = 'Foo'
export function getBar() {
  return import('./bar')
}

Cyclic dependency are always a dangerous anti-pattern as discussed extensively in #2265. Please be extra careful about using this option.

disableScc

This option disables a pre-processing step that calculates Strongly Connected Components, which are used for avoiding unnecessary work checking files in different SCCs for cycles.

However, under some configurations, this pre-processing may be more expensive than the time it saves.

When this option is true, we don't calculate any SCC graph, and check all files for cycles (leading to higher time-complexity). Default is false.

When Not To Use It

This rule is comparatively computationally expensive. If you are pressed for lint time, or don't think you have an issue with dependency cycles, you may not want this rule enabled.

Further Reading

no-duplicates
eslint-plugin-import
⚠️ This rule _warns_ in the following configs: ☑️ `recommended`, 🚸 `warnings`.

🔧 This rule is automatically fixable by the --fix CLI option.

Reports if a resolved path is imported more than once.

ESLint core has a similar rule (no-duplicate-imports), but this version is different in two key ways:

  1. the paths in the source code don't have to exactly match, they just have to point to the same module on the filesystem. (i.e. ./foo and ./foo.js)
  2. this version distinguishes Flow type imports from standard imports. (#334)

Rule Details

Valid:

import SomeDefaultClass, * as names from './mod'
// Flow `type` import from same module is fine
import type SomeType from './mod'

...whereas here, both ./mod imports will be reported:

import SomeDefaultClass from './mod'

// oops, some other import separated these lines
import foo from './some-other-mod'

import * as names from './mod'

// will catch this too, assuming it is the same target module
import { something } from './mod.js'

The motivation is that this is likely a result of two developers importing different names from the same module at different times (and potentially largely different locations in the file.) This rule brings both (or n-many) to attention.

Query Strings

By default, this rule ignores query strings (i.e. paths followed by a question mark), and thus imports from ./mod?a and ./mod?b will be considered as duplicates. However you can use the option considerQueryString to handle them as different (primarily because browsers will resolve those imports differently).

Config:

"import/no-duplicates": ["error", {"considerQueryString": true}]

And then the following code becomes valid:

import minifiedMod from './mod?minify'
import noCommentsMod from './mod?comments=0'
import originalMod from './mod'

It will still catch duplicates when using the same module and the exact same query string:

import SomeDefaultClass from './mod?minify'

// This is invalid, assuming `./mod` and `./mod.js` are the same target:
import * from './mod.js?minify'

Inline Type imports

TypeScript 4.5 introduced a new feature that allows mixing of named value and type imports. In order to support fixing to an inline type import when duplicate imports are detected, prefer-inline can be set to true.

Config:

"import/no-duplicates": ["error", {"prefer-inline": true}]

❌ Invalid ["error", {"prefer-inline": true}]

import { AValue, type AType } from './mama-mia'
import type { BType } from './mama-mia'

import { CValue } from './papa-mia'
import type { CType } from './papa-mia'

✅ Valid with ["error", {"prefer-inline": true}]

import { AValue, type AType, type BType } from './mama-mia'

import { CValue, type CType } from './papa-mia'

When Not To Use It

If the core ESLint version is good enough (i.e. you're not using Flow and you are using import/extensions), keep it and don't use this.

If you like to split up imports across lines or may need to import a default and a namespace, you may not want to enable this rule.

no-unresolved
eslint-plugin-import
💼 This rule is enabled in the following configs: ❗ `errors`, ☑️ `recommended`.

Ensures an imported module can be resolved to a module on the local filesystem, as defined by standard Node require.resolve behavior.

See settings for customization options for the resolution (i.e. additional filetypes, NODE_PATH, etc.)

This rule can also optionally report on unresolved modules in CommonJS require('./foo') calls and AMD require(['./foo'], function (foo) {...}) and define(['./foo'], function (foo) {...}).

To enable this, send { commonjs: true/false, amd: true/false } as a rule option. Both are disabled by default.

If you are using Webpack, see the section on resolvers.

Rule Details

Options

By default, only ES6 imports will be resolved:

/*eslint import/no-unresolved: 2*/
import x from './foo' // reports if './foo' cannot be resolved on the filesystem

If {commonjs: true} is provided, single-argument require calls will be resolved:

/*eslint import/no-unresolved: [2, { commonjs: true }]*/
const { default: x } = require('./foo') // reported if './foo' is not found

require(0) // ignored
require(['x', 'y'], function (x, y) {
  /*...*/
}) // ignored

Similarly, if { amd: true } is provided, dependency paths for define and require calls will be resolved:

/*eslint import/no-unresolved: [2, { amd: true }]*/
define(['./foo'], function (foo) {
  /*...*/
}) // reported if './foo' is not found
require(['./foo'], function (foo) {
  /*...*/
}) // reported if './foo' is not found

const { default: x } = require('./foo') // ignored

Both may be provided, too:

/*eslint import/no-unresolved: [2, { commonjs: true, amd: true }]*/
const { default: x } = require('./foo') // reported if './foo' is not found
define(['./foo'], function (foo) {
  /*...*/
}) // reported if './foo' is not found
require(['./foo'], function (foo) {
  /*...*/
}) // reported if './foo' is not found

ignore

This rule has its own ignore list, separate from import/ignore. This is because you may want to know whether a module can be located, regardless of whether it can be parsed for exports: node_modules, CoffeeScript files, etc. are all good to resolve properly, but will not be parsed if configured as such via import/ignore.

To suppress errors from files that may not be properly resolved by your resolver settings, you may add an ignore key with an array of RegExp pattern strings:

/*eslint import/no-unresolved: [2, { ignore: ['\\.img$'] }]*/

import { x } from './mod' // may be reported, if not resolved to a module

import coolImg from '../../img/coolImg.img' // will not be reported, even if not found

caseSensitive

By default, this rule will report paths whose case do not match the underlying filesystem path, if the FS is not case-sensitive. To disable this behavior, set the caseSensitive option to false.

/*eslint import/no-unresolved: [2, { caseSensitive: true (default) | false }]*/
const { default: x } = require('./foo') // reported if './foo' is actually './Foo' and caseSensitive: true

caseSensitiveStrict

The caseSensitive option does not detect case for the current working directory. The caseSensitiveStrict option allows checking cwd in resolved path. By default, the option is disabled.

/*eslint import/no-unresolved: [2, { caseSensitiveStrict: true }]*/

// Absolute paths
import Foo from `/Users/fOo/bar/file.js` // reported, /Users/foo/bar/file.js
import Foo from `d:/fOo/bar/file.js` // reported, d:/foo/bar/file.js

// Relative paths, cwd is Users/foo/
import Foo from `./../fOo/bar/file.js` // reported

When Not To Use It

If you're using a module bundler other than Node or Webpack, you may end up with a lot of false positive reports of missing dependencies.

Further Reading

eslint-plugin-import
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

Use this rule to prevent unnecessary path segments in import and require statements.

Rule Details

Given the following folder structure:

my-project
├── app.js
├── footer.js
├── header.js
└── helpers.js
└── helpers
    └── index.js
├── index.js
└── pages
    ├── about.js
    ├── contact.js
    └── index.js

The following patterns are considered problems:

/**
 *  in my-project/app.js
 */

import './../my-project/pages/about.js' // should be "./pages/about.js"
import './../my-project/pages/about' // should be "./pages/about"
import '../my-project/pages/about.js' // should be "./pages/about.js"
import '../my-project/pages/about' // should be "./pages/about"
import './pages//about' // should be "./pages/about"
import './pages/' // should be "./pages"
import './pages/index' // should be "./pages" (except if there is a ./pages.js file)
import './pages/index.js' // should be "./pages" (except if there is a ./pages.js file)

The following patterns are NOT considered problems:

/**
 *  in my-project/app.js
 */

import './header.js'
import './pages'
import './pages/about'
import '.'
import '..'
import fs from 'fs'

Options

noUselessIndex

If you want to detect unnecessary /index or /index.js (depending on the specified file extensions, see below) imports in your paths, you can enable the option noUselessIndex. By default it is set to false:

"import/no-useless-path-segments": ["error", {
  noUselessIndex: true,
}]

Additionally to the patterns described above, the following imports are considered problems if noUselessIndex is enabled:

// in my-project/app.js
import './helpers/index' // should be "./helpers/" (not auto-fixable to `./helpers` because this would lead to an ambiguous import of `./helpers.js` and `./helpers/index.js`)
import './pages/index' // should be "./pages" (auto-fixable)
import './pages/index.js' // should be "./pages" (auto-fixable)

Note: noUselessIndex only avoids ambiguous imports for .js files if you haven't specified other resolved file extensions. See Settings: import/extensions for details.

commonjs

When set to true, this rule checks CommonJS imports. Default to false.

order
eslint-plugin-import
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).

Enforce a convention in the order of require() / import statements.

With the groups option set to ["builtin", "external", "internal", "parent", "sibling", "index", "object", "type"] the order is as shown in the following example:

// 1. node "builtin" modules
import fs from 'fs'
import path from 'path'
// 2. "external" modules
import _ from 'lodash'
import chalk from 'chalk'
// 3. "internal" modules
// (if you have configured your path or webpack to handle your internal paths differently)
import foo from 'src/foo'
// 4. modules from a "parent" directory
import foo from '../foo'
import qux from '../../foo/qux'
// 5. "sibling" modules from the same or a sibling's directory
import bar from './bar'
import baz from './bar/baz'
// 6. "index" of the current directory
import main from './'
// 7. "object"-imports (only available in TypeScript)
import log = console.log
// 8. "type" imports (only available in Flow and TypeScript)
import type { Foo } from 'foo'

See here for further details on how imports are grouped.

Fail

import _ from 'lodash'
import path from 'path' // `path` import should occur before import of `lodash`

// -----

var _ = require('lodash')
var path = require('path') // `path` import should occur before import of `lodash`

// -----

var path = require('path')
import foo from './foo' // `import` statements must be before `require` statement

Pass

import path from 'path'
import _ from 'lodash'

// -----

var path = require('path')
var _ = require('lodash')

// -----

// Allowed as ̀`babel-register` is not assigned.
require('babel-register')
var path = require('path')

// -----

// Allowed as `import` must be before `require`
import foo from './foo'
var path = require('path')

Limitations of --fix

Unbound imports are assumed to have side effects, and will never be moved/reordered. This can cause other imports to get "stuck" around them, and the fix to fail.

import b from 'b'
import 'format.css' // This will prevent --fix from working.
import a from 'a'

As a workaround, move unbound imports to be entirely above or below bound ones.

import 'format1.css' // OK
import b from 'b'
import a from 'a'
import 'format2.css' // OK

Options

This rule supports the following options (none of which are required):


groups

Valid values: ("builtin" | "external" | "internal" | "unknown" | "parent" | "sibling" | "index" | "object" | "type")[]
Default: ["builtin", "external", "parent", "sibling", "index"]

Determines which imports are subject to ordering, and how to order them. The predefined groups are: "builtin", "external", "internal", "unknown", "parent", "sibling", "index", "object", and "type".

The import order enforced by this rule is the same as the order of each group in groups. Imports belonging to groups omitted from groups are lumped together at the end.

Example

{
  "import/order": [
    "error",
    {
      "groups": [
        // Imports of builtins are first
        "builtin",
        // Then sibling and parent imports. They can be mingled together
        ["sibling", "parent"],
        // Then index file imports
        "index",
        // Then any arcane TypeScript imports
        "object",
        // Then the omitted imports: internal, external, type, unknown
      ],
    },
  ],
}

How Imports Are Grouped

An import (a ImportDeclaration, TSImportEqualsDeclaration, or require() CallExpression) is grouped by its type ("require" vs "import"), its specifier, and any corresponding identifiers.

import { identifier1, identifier2 } from 'specifier1'
import type { MyType } from 'specifier2'
const identifier3 = require('specifier3')

Roughly speaking, the grouping algorithm is as follows:

  1. If the import has no corresponding identifiers (e.g. import './my/thing.js'), is otherwise "unassigned," or is an unsupported use of require(), and warnOnUnassignedImports is disabled, it will be ignored entirely since the order of these imports may be important for their side-effects
  2. If the import is part of an arcane TypeScript declaration (e.g. import log = console.log), it will be considered object. However, note that external module references (e.g. import x = require('z')) are treated as normal require()s and import-exports (e.g. export import w = y;) are ignored entirely
  3. If the import is type-only, "type" is in groups, and sortTypesGroup is disabled, it will be considered type (with additional implications if using pathGroups and "type" is in pathGroupsExcludedImportTypes)
  4. If the import's specifier matches import/internal-regex, it will be considered internal
  5. If the import's specifier is an absolute path, it will be considered unknown
  6. If the import's specifier has the name of a Node.js core module (using is-core-module), it will be considered builtin
  7. If the import's specifier matches import/core-modules, it will be considered builtin
  8. If the import's specifier is a path relative to the parent directory of its containing file (e.g. starts with ../), it will be considered parent
  9. If the import's specifier is one of ['.', './', './index', './index.js'], it will be considered index
  10. If the import's specifier is a path relative to its containing file (e.g. starts with ./), it will be considered sibling
  11. If the import's specifier is a path pointing to a file outside the current package's root directory (determined using package-up), it will be considered external
  12. If the import's specifier matches import/external-module-folders (defaults to matching anything pointing to files within the current package's node_modules directory), it will be considered external
  13. If the import's specifier is a path pointing to a file within the current package's root directory (determined using package-up), it will be considered internal
  14. If the import's specifier has a name that looks like a scoped package (e.g. @scoped/package-name), it will be considered external
  15. If the import's specifier has a name that starts with a word character, it will be considered external
  16. If this point is reached, the import will be ignored entirely

At the end of the process, if they co-exist in the same file, all top-level require() statements that haven't been ignored are shifted (with respect to their order) below any ES6 import or similar declarations. Finally, any type-only declarations are potentially reorganized according to sortTypesGroup.

pathGroups

Valid values: PathGroup[]
Default: []

Sometimes the predefined groups are not fine-grained enough, especially when using import aliases. pathGroups defines one or more PathGroups relative to a predefined group. Imports are associated with a PathGroup based on path matching against the import specifier (using minimatch).

[!IMPORTANT]

Note that, by default, imports grouped as "builtin", "external", or "object" will not be considered for further pathGroups matching unless they are removed from pathGroupsExcludedImportTypes.

PathGroup

propertyrequiredtypedescription
pattern☑️stringMinimatch pattern for specifier matching
patternOptionsobjectMinimatch options; default: {nocomment: true}
group☑️predefined groupOne of the predefined groups to which matching imports will be positioned relatively
position"after" | "before"Where, in relation to group, matching imports will be positioned; default: same position as group (neither before or after)

Example

{
  "import/order": [
    "error",
    {
      "pathGroups": [
        {
          // Minimatch pattern used to match against specifiers
          "pattern": "~/**",
          // The predefined group this PathGroup is defined in relation to
          "group": "external",
          // How matching imports will be positioned relative to "group"
          "position": "after",
        },
      ],
    },
  ],
}

pathGroupsExcludedImportTypes

Valid values: ("builtin" | "external" | "internal" | "unknown" | "parent" | "sibling" | "index" | "object" | "type")[]
Default: ["builtin", "external", "object"]

By default, imports in certain groups are excluded from being matched against pathGroups to prevent overeager sorting. Use pathGroupsExcludedImportTypes to modify which groups are excluded.

[!TIP]

If using imports with custom specifier aliases (e.g. you're using eslint-import-resolver-alias, paths in tsconfig.json, etc) that end up grouped as "builtin" or "external" imports, remove them from pathGroupsExcludedImportTypes to ensure they are ordered correctly.

Example

{
  "import/order": [
    "error",
    {
      "pathGroups": [
        {
          "pattern": "@app/**",
          "group": "external",
          "position": "after",
        },
      ],
      "pathGroupsExcludedImportTypes": ["builtin"],
    },
  ],
}

distinctGroup

Valid values: boolean
Default: true

[!CAUTION]

Currently, distinctGroup defaults to true. However, in a later update, the default will change to false.

This changes how PathGroup.position affects grouping, and is most useful when newlines-between is set to always and at least one PathGroup has a position property set.

When newlines-between is set to always and an import matching a specific PathGroup.pattern is encountered, that import is added to a sort of "sub-group" associated with that PathGroup. Thanks to newlines-between, imports in this "sub-group" will have a new line separating them from the rest of the imports in PathGroup.group.

This behavior can be undesirable when using PathGroup.position to order imports within PathGroup.group instead of creating a distinct "sub-group". Set distinctGroup to false to disable the creation of these "sub-groups".

Example

{
  "import/order": [
    "error",
    {
      "distinctGroup": false,
      "newlines-between": "always",
      "pathGroups": [
        {
          "pattern": "@app/**",
          "group": "external",
          "position": "after",
        },
      ],
    },
  ],
}

newlines-between

Valid values: "ignore" | "always" | "always-and-inside-groups" | "never"
Default: "ignore"

Enforces or forbids new lines between import groups.

  • If set to ignore, no errors related to new lines between import groups will be reported

  • If set to always, at least one new line between each group will be enforced, and new lines inside a group will be forbidden

[!TIP]

To prevent multiple lines between imports, the no-multiple-empty-lines rule, or a tool like Prettier, can be used.

  • If set to always-and-inside-groups, it will act like always except new lines are allowed inside import groups

  • If set to never, no new lines are allowed in the entire import section

Example

With the default groups setting, the following will fail the rule check:

/* eslint import/order: ["error", {"newlines-between": "always"}] */
import fs from 'fs'
import path from 'path'
import sibling from './foo'
import index from './'
/* eslint import/order: ["error", {"newlines-between": "always-and-inside-groups"}] */
import fs from 'fs'

import path from 'path'
import sibling from './foo'
import index from './'
/* eslint import/order: ["error", {"newlines-between": "never"}] */
import fs from 'fs'
import path from 'path'

import sibling from './foo'

import index from './'

While this will pass:

/* eslint import/order: ["error", {"newlines-between": "always"}] */
import fs from 'fs'
import path from 'path'

import sibling from './foo'

import index from './'
/* eslint import/order: ["error", {"newlines-between": "always-and-inside-groups"}] */
import fs from 'fs'

import path from 'path'

import sibling from './foo'

import index from './'
/* eslint import/order: ["error", {"newlines-between": "never"}] */
import fs from 'fs'
import path from 'path'
import sibling from './foo'
import index from './'

alphabetize

Valid values: { order?: "asc" | "desc" | "ignore", orderImportKind?: "asc" | "desc" | "ignore", caseInsensitive?: boolean }
Default: { order: "ignore", orderImportKind: "ignore", caseInsensitive: false }

Determine the sort order of imports within each predefined group or PathGroup alphabetically based on specifier.

[!NOTE]

Imports will be alphabetized based on their specifiers, not by their identifiers. For example, const a = require('z'); will come after const z = require('a'); when alphabetize is set to { order: "asc" }.

Valid properties and their values include:

  • order: use "asc" to sort in ascending order, "desc" to sort in descending order, or "ignore" to prevent sorting

  • orderImportKind: use "asc" to sort various import kinds, e.g. type-only and typeof imports, in ascending order, "desc" to sort them in descending order, or "ignore" to prevent sorting

  • caseInsensitive: use true to ignore case and false to consider case when sorting

Example

Given the following settings:

{
  "import/order": [
    "error",
    {
      "alphabetize": {
        "order": "asc",
        "caseInsensitive": true,
      },
    },
  ],
}

This will fail the rule check:

import React, { PureComponent } from 'react'
import aTypes from 'prop-types'
import { compose, apply } from 'xcompose'
import * as classnames from 'classnames'
import blist from 'BList'

While this will pass:

import blist from 'BList'
import * as classnames from 'classnames'
import aTypes from 'prop-types'
import React, { PureComponent } from 'react'
import { compose, apply } from 'xcompose'

named

Valid values: boolean | { enabled: boolean, import?: boolean, export?: boolean, require?: boolean, cjsExports?: boolean, types?: "mixed" | "types-first" | "types-last" }
Default: false

Enforce ordering of names within imports and exports.

If set to true or { enabled: true }, all named imports must be ordered according to alphabetize. If set to false or { enabled: false }, named imports can occur in any order.

If set to { enabled: true, ... }, and any of the properties import, export, require, or cjsExports are set to false, named ordering is disabled with respect to the following kind of expressions:

  • import:
import { Readline } from 'readline'
  • export:
export { Readline }
// and
export { Readline } from 'readline'
  • require:
const { Readline } = require('readline')
  • cjsExports:
module.exports.Readline = Readline
// and
module.exports = { Readline }

Further, the named.types option allows you to specify the order of import identifiers with inline type qualifiers (or "type-only" identifiers/names), e.g. import { type TypeIdentifier1, normalIdentifier2 } from 'specifier';.

named.types accepts the following values:

  • types-first: forces type-only identifiers to occur first
  • types-last: forces type-only identifiers to occur last
  • mixed: sorts all identifiers in alphabetical order

Example

Given the following settings:

{
  "import/order": [
    "error",
    {
      "named": true,
      "alphabetize": {
        "order": "asc",
      },
    },
  ],
}

This will fail the rule check:

import { compose, apply } from 'xcompose'

While this will pass:

import { apply, compose } from 'xcompose'

warnOnUnassignedImports

Valid values: boolean
Default: false

Warn when "unassigned" imports are out of order. Unassigned imports are imports with no corresponding identifiers (e.g. import './my/thing.js' or require('./side-effects.js')).

[!NOTE]

These warnings are not fixable with --fix since unassigned imports might be used for their side-effects, and changing the order of such imports cannot be done safely.

Example

Given the following settings:

{
  "import/order": [
    "error",
    {
      "warnOnUnassignedImports": true,
    },
  ],
}

This will fail the rule check:

import fs from 'fs'
import './styles.css'
import path from 'path'

While this will pass:

import fs from 'fs'
import path from 'path'
import './styles.css'

sortTypesGroup

Valid values: boolean
Default: false

[!NOTE]

This setting is only meaningful when "type" is included in groups.

Sort type-only imports separately from normal non-type imports.

When enabled, the intragroup sort order of type-only imports will mirror the intergroup ordering of normal imports as defined by groups, pathGroups, etc.

Example

Given the following settings:

{
  "import/order": [
    "error",
    {
      "groups": ["type", "builtin", "parent", "sibling", "index"],
      "alphabetize": { "order": "asc" },
    },
  ],
}

This will fail the rule check even though it's logically ordered as we expect (builtins come before parents, parents come before siblings, siblings come before indices), the only difference is we separated type-only imports from normal imports:

import type A from 'fs'
import type B from 'path'
import type C from '../foo.js'
import type D from './bar.js'
import type E from './'

import a from 'fs'
import b from 'path'
import c from '../foo.js'
import d from './bar.js'
import e from './'

This happens because type-only imports are considered part of one global "type" group by default. However, if we set sortTypesGroup to true:

{
  "import/order": [
    "error",
    {
      "groups": ["type", "builtin", "parent", "sibling", "index"],
      "alphabetize": { "order": "asc" },
      "sortTypesGroup": true,
    },
  ],
}

The same example will pass.

newlines-between-types

Valid values: "ignore" | "always" | "always-and-inside-groups" | "never"
Default: the value of newlines-between

[!NOTE]

This setting is only meaningful when sortTypesGroup is enabled.

newlines-between-types is functionally identical to newlines-between except it only enforces or forbids new lines between type-only import groups, which exist only when sortTypesGroup is enabled.

In addition, when determining if a new line is enforceable or forbidden between the type-only imports and the normal imports, newlines-between-types takes precedence over newlines-between.

Example

Given the following settings:

{
  "import/order": [
    "error",
    {
      "groups": ["type", "builtin", "parent", "sibling", "index"],
      "sortTypesGroup": true,
      "newlines-between": "always",
    },
  ],
}

This will fail the rule check:

import type A from 'fs'
import type B from 'path'
import type C from '../foo.js'
import type D from './bar.js'
import type E from './'

import a from 'fs'
import b from 'path'

import c from '../foo.js'

import d from './bar.js'

import e from './'

However, if we set newlines-between-types to "ignore":

{
  "import/order": [
    "error",
    {
      "groups": ["type", "builtin", "parent", "sibling", "index"],
      "sortTypesGroup": true,
      "newlines-between": "always",
      "newlines-between-types": "ignore",
    },
  ],
}

The same example will pass.

Note the new line after import type E from './'; but before import a from "fs";. This new line separates the type-only imports from the normal imports. Its existence is governed by newlines-between-types and not newlines-between.

[!IMPORTANT]

In certain situations, consolidateIslands: true will take precedence over newlines-between-types: "never", if used, when it comes to the new line separating type-only imports from normal imports.

The next example will pass even though there's a new line preceding the normal import and newlines-between is set to "never":

{
  "import/order": [
    "error",
    {
      "groups": ["type", "builtin", "parent", "sibling", "index"],
      "sortTypesGroup": true,
      "newlines-between": "never",
      "newlines-between-types": "always",
    },
  ],
}
import type A from 'fs'

import type B from 'path'

import type C from '../foo.js'

import type D from './bar.js'

import type E from './'

import a from 'fs'
import b from 'path'
import c from '../foo.js'
import d from './bar.js'
import e from './'

While the following fails due to the new line between the last type import and the first normal import:

{
  "import/order": [
    "error",
    {
      "groups": ["type", "builtin", "parent", "sibling", "index"],
      "sortTypesGroup": true,
      "newlines-between": "always",
      "newlines-between-types": "never",
    },
  ],
}
import type A from 'fs'
import type B from 'path'
import type C from '../foo.js'
import type D from './bar.js'
import type E from './'

import a from 'fs'

import b from 'path'

import c from '../foo.js'

import d from './bar.js'

import e from './'

consolidateIslands

Valid values: "inside-groups" | "never"
Default: "never"

[!NOTE]

This setting is only meaningful when newlines-between and/or newlines-between-types is set to "always-and-inside-groups".

When set to "inside-groups", this ensures imports spanning multiple lines are separated from other imports with a new line while single-line imports are grouped together (and the space between them consolidated) if they belong to the same group or pathGroups.

[!IMPORTANT]

When all of the following are true:

Then newlines-between/newlines-between-types will yield to consolidateIslands and allow new lines to separate multi-line imports regardless of the "never" setting.

This configuration is useful, for instance, to keep single-line type-only imports stacked tightly together at the bottom of your import block to preserve space while still logically organizing normal imports for quick and pleasant reference.

Example

Given the following settings:

{
  "import/order": [
    "error",
    {
      "newlines-between": "always-and-inside-groups",
      "consolidateIslands": "inside-groups",
    },
  ],
}

This will fail the rule check:

var fs = require('fs')
var path = require('path')
var { util1, util2, util3 } = require('util')
var async = require('async')
var relParent1 = require('../foo')
var { relParent21, relParent22, relParent23, relParent24 } = require('../')
var relParent3 = require('../bar')
var { sibling1, sibling2, sibling3 } = require('./foo')
var sibling2 = require('./bar')
var sibling3 = require('./foobar')

While this will succeed (and is what --fix would yield):

var fs = require('fs')
var path = require('path')
var { util1, util2, util3 } = require('util')

var async = require('async')

var relParent1 = require('../foo')

var { relParent21, relParent22, relParent23, relParent24 } = require('../')

var relParent3 = require('../bar')

var { sibling1, sibling2, sibling3 } = require('./foo')

var sibling2 = require('./bar')
var sibling3 = require('./foobar')

Note the intragroup "islands" of grouped single-line imports, as well as multi-line imports, are surrounded by new lines. At the same time, note the typical new lines separating different groups are still maintained thanks to newlines-between.

The same holds true for the next example; when given the following settings:

{
  "import/order": [
    "error",
    {
      "alphabetize": { "order": "asc" },
      "groups": ["external", "internal", "index", "type"],
      "pathGroups": [
        {
          "pattern": "dirA/**",
          "group": "internal",
          "position": "after",
        },
        {
          "pattern": "dirB/**",
          "group": "internal",
          "position": "before",
        },
        {
          "pattern": "dirC/**",
          "group": "internal",
        },
      ],
      "newlines-between": "always-and-inside-groups",
      "newlines-between-types": "never",
      "pathGroupsExcludedImportTypes": [],
      "sortTypesGroup": true,
      "consolidateIslands": "inside-groups",
    },
  ],
}

[!IMPORTANT]

Pay special attention to the value of pathGroupsExcludedImportTypes in this example's settings. Without it, the successful example below would fail. This is because the imports with specifiers starting with "dirA/", "dirB/", and "dirC/" are all considered part of the "external" group, and imports in that group are excluded from pathGroups matching by default.

The fix is to remove "external" (and, in this example, the others) from pathGroupsExcludedImportTypes.

This will fail the rule check:

import c from 'Bar'
import d from 'bar'
import { aa, bb, cc, dd, ee, ff, gg } from 'baz'
import { hh, ii, jj, kk, ll, mm, nn } from 'fizz'
import a from 'foo'
import b from 'dirA/bar'
import index from './'
import type { AA, BB, CC } from 'abc'
import type { Z } from 'fizz'
import type { A, B } from 'foo'
import type { C2 } from 'dirB/Bar'
import type { D2, X2, Y2 } from 'dirB/bar'
import type { E2 } from 'dirB/baz'
import type { C3 } from 'dirC/Bar'
import type { D3, X3, Y3 } from 'dirC/bar'
import type { E3 } from 'dirC/baz'
import type { F3 } from 'dirC/caz'
import type { C1 } from 'dirA/Bar'
import type { D1, X1, Y1 } from 'dirA/bar'
import type { E1 } from 'dirA/baz'
import type { F } from './index.js'
import type { G } from './aaa.js'
import type { H } from './bbb'

While this will succeed (and is what --fix would yield):

import c from 'Bar'
import d from 'bar'

import { aa, bb, cc, dd, ee, ff, gg } from 'baz'

import { hh, ii, jj, kk, ll, mm, nn } from 'fizz'

import a from 'foo'

import b from 'dirA/bar'

import index from './'

import type { AA, BB, CC } from 'abc'

import type { Z } from 'fizz'

import type { A, B } from 'foo'

import type { C2 } from 'dirB/Bar'

import type { D2, X2, Y2 } from 'dirB/bar'

import type { E2 } from 'dirB/baz'
import type { C3 } from 'dirC/Bar'

import type { D3, X3, Y3 } from 'dirC/bar'

import type { E3 } from 'dirC/baz'
import type { F3 } from 'dirC/caz'
import type { C1 } from 'dirA/Bar'

import type { D1, X1, Y1 } from 'dirA/bar'

import type { E1 } from 'dirA/baz'
import type { F } from './index.js'
import type { G } from './aaa.js'
import type { H } from './bbb'
@typescript-eslint
Disallow awaiting a value that is not a Thenable.

A "Thenable" value is an object which has a then method, such as a Promise. The await keyword is generally used to retrieve the result of calling a Thenable's then method.

If the await keyword is used on a value that is not a Thenable, the value is directly resolved, but will still pause execution until the next microtask. While doing so is valid JavaScript, it is often a programmer error, such as forgetting to add parenthesis to call a function that returns a Promise.

Examples

await 'value'

const createValue = () => 'value'
await createValue()
await Promise.resolve('value')

const createValue = async () => 'value'
await createValue()

Async Iteration (for await...of Loops)

This rule also inspects for await...of statements, and reports if the value being iterated over is not async-iterable.

:::info[Why does the rule report on for await...of loops used on an array of Promises?]

While for await...of can be used with synchronous iterables, and it will await each promise produced by the iterable, it is inadvisable to do so. There are some tiny nuances that you may want to consider.

The biggest difference between using for await...of and using for...of (apart from awaiting each result yourself) is error handling. When an error occurs within the loop body, for await...of does not close the original sync iterable, while for...of does. For detailed examples of this, see the MDN documentation on using for await...of with sync-iterables.

Also consider whether you need sequential awaiting at all. Using for await...of may obscure potential opportunities for concurrent processing, such as those reported by no-await-in-loop. Consider instead using one of the promise concurrency methods for better performance.

Examples

async function syncIterable() {
  const arrayOfValues = [1, 2, 3]
  for await (const value of arrayOfValues) {
    console.log(value)
  }
}

async function syncIterableOfPromises() {
  const arrayOfPromises = [
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3),
  ]
  for await (const promisedValue of arrayOfPromises) {
    console.log(promisedValue)
  }
}
async function syncIterable() {
  const arrayOfValues = [1, 2, 3]
  for (const value of arrayOfValues) {
    console.log(value)
  }
}

async function syncIterableOfPromises() {
  const arrayOfPromises = [
    Promise.resolve(1),
    Promise.resolve(2),
    Promise.resolve(3),
  ]
  for (const promisedValue of await Promise.all(arrayOfPromises)) {
    console.log(promisedValue)
  }
}

async function validUseOfForAwaitOnAsyncIterable() {
  async function* yieldThingsAsynchronously() {
    yield 1
    await new Promise((resolve) => setTimeout(resolve, 1000))
    yield 2
  }

  for await (const promisedValue of yieldThingsAsynchronously()) {
    console.log(promisedValue)
  }
}

Explicit Resource Management (await using Statements)

This rule also inspects await using statements. If the disposable being used is not async-disposable, an await using statement is unnecessary.

Examples

function makeSyncDisposable(): Disposable {
  return {
    [Symbol.dispose](): void {
      // Dispose of the resource
    },
  }
}

async function shouldNotAwait() {
  await using resource = makeSyncDisposable()
}
function makeSyncDisposable(): Disposable {
  return {
    [Symbol.dispose](): void {
      // Dispose of the resource
    },
  }
}

async function shouldNotAwait() {
  using resource = makeSyncDisposable()
}

function makeAsyncDisposable(): AsyncDisposable {
  return {
    async [Symbol.asyncDispose](): Promise<void> {
      // Dispose of the resource asynchronously
    },
  }
}

async function shouldAwait() {
  await using resource = makeAsyncDisposable()
}

When Not To Use It

If you want to allow code to await non-Promise values. For example, if your framework is in transition from one style of asynchronous code to another, it may be useful to include awaits unnecessarily. This is generally not preferred but can sometimes be useful for visual consistency.

@typescript-eslint
Enforce consistent usage of type imports.

TypeScript allows specifying a type keyword on imports to indicate that the export exists only in the type system, not at runtime. This allows transpilers to drop imports without knowing the types of the dependencies.

See Blog > Consistent Type Exports and Imports: Why and How for more details.

Options

prefer

Valid values for prefer are:

  • type-imports will enforce that you always use import type Foo from '...' except referenced by metadata of decorators. It is the default.
  • no-type-imports will enforce that you always use import Foo from '...'.

Examples of correct code with {prefer: 'type-imports'}, and incorrect code with {prefer: 'no-type-imports'}.

{ "prefer": "type-imports" }
import type { Foo } from 'Foo'
import type Bar from 'Bar'
type T = Foo
const x: Bar = 1

Examples of incorrect code with {prefer: 'type-imports'}, and correct code with {prefer: 'no-type-imports'}.

{ "prefer": "type-imports" }
import { Foo } from 'Foo'
import Bar from 'Bar'
type T = Foo
const x: Bar = 1

fixStyle

Valid values for fixStyle are:

  • separate-type-imports will add the type keyword after the import keyword import type { A } from '...'. It is the default.
  • inline-type-imports will inline the type keyword import { type A } from '...' and is only available in TypeScript 4.5 and onwards. See documentation here.
import { Foo } from 'Foo'
import Bar from 'Bar'
type T = Foo
const x: Bar = 1
{ "fixStyle": "separate-type-imports" }
import type { Foo } from 'Foo'
import type Bar from 'Bar'
type T = Foo
const x: Bar = 1
{ "fixStyle": "inline-type-imports" }
import { type Foo } from 'Foo'
import type Bar from 'Bar'
type T = Foo
const x: Bar = 1

disallowTypeAnnotations

Examples of incorrect code with {disallowTypeAnnotations: true}:

{ "disallowTypeAnnotations": true }
type T = import('Foo').Foo
const x: import('Bar') = 1

Caveat: @decorators + experimentalDecorators: true + emitDecoratorMetadata: true

:::note If you are using experimentalDecorators: false (eg TypeScript v5.0's stable decorators) then the rule will always report errors as expected. This caveat only applies to experimentalDecorators: true

The rule will not report any errors in files that contain decorators when both experimentalDecorators and emitDecoratorMetadata are turned on.

See Blog > Changes to consistent-type-imports when used with legacy decorators and decorator metadata for more details.

If you are using type-aware linting then we will automatically infer your setup from your tsconfig and you should not need to configure anything. Otherwise you can explicitly tell our tooling to analyze your code as if the compiler option was turned on by setting both parserOptions.emitDecoratorMetadata = true and parserOptions.experimentalDecorators = true.

Comparison with importsNotUsedAsValues / verbatimModuleSyntax

verbatimModuleSyntax was introduced in TypeScript v5.0 (as a replacement for importsNotUsedAsValues). This rule and verbatimModuleSyntax mostly behave in the same way. There are a few behavior differences:

Situationconsistent-type-imports (ESLint)verbatimModuleSyntax (TypeScript)
Unused importsIgnored (consider using @typescript-eslint/no-unused-vars)Type error
Usage with emitDecoratorMetadata & experimentalDecorationsIgnores files that contain decoratorsReports on files that contain decorators
Failures detectedDoes not fail tsc build; can be auto-fixed with --fixFails tsc build; cannot be auto-fixed on the command-line
import { type T } from 'T';TypeScript will emit nothing (it "elides" the import)TypeScript emits import {} from 'T'

Because there are some differences, using both this rule and verbatimModuleSyntax at the same time can lead to conflicting errors. As such we recommend that you only ever use one or the other -- never both.

When Not To Use It

If you specifically want to use both import kinds for stylistic reasons, or don't wish to enforce one style over the other, you can avoid this rule.

However, keep in mind that inconsistent style can harm readability in a project. We recommend picking a single option for this rule that works best for your project.

@typescript-eslint
Enforce valid definition of `new` and `constructor`.

JavaScript classes may define a constructor method that runs when a class instance is newly created. TypeScript allows interfaces that describe a static class object to define a new() method (though this is rarely used in real world code). Developers new to JavaScript classes and/or TypeScript interfaces may sometimes confuse when to use constructor or new.

This rule reports when a class defines a method named new or an interface defines a method named constructor.

Examples

declare class C {
  new(): C
}

interface I {
  new (): I
  constructor(): void
}
declare class C {
  constructor()
}

interface I {
  new (): C
}

When Not To Use It

If you intentionally want a class with a new method, and you're confident nobody working in your code will mistake it with a constructor, you might not want this rule.

@typescript-eslint
Disallow Promises in places not designed to handle them.

This rule forbids providing Promises to logical locations such as if statements in places where the TypeScript compiler allows them but they are not handled properly. These situations can often arise due to a missing await keyword or just a misunderstanding of the way async functions are handled/awaited.

:::tip no-misused-promises only detects code that provides Promises to incorrect logical locations. See no-floating-promises for detecting unhandled Promise statements.

Options

checksConditionals

If you don't want to check conditionals, you can configure the rule with "checksConditionals": false:

{
  "@typescript-eslint/no-misused-promises": [
    "error",
    {
      "checksConditionals": false
    }
  ]
}

Doing so prevents the rule from looking at code like if (somePromise).

checksVoidReturn

Likewise, if you don't want to check functions that return promises where a void return is expected, your configuration will look like this:

{
  "@typescript-eslint/no-misused-promises": [
    "error",
    {
      "checksVoidReturn": false
    }
  ]
}

You can disable selective parts of the checksVoidReturn option by providing an object that disables specific checks. For example, if you don't mind that passing a () => Promise<void> to a () => void parameter or JSX attribute can lead to a floating unhandled Promise:

{
  "@typescript-eslint/no-misused-promises": [
    "error",
    {
      "checksVoidReturn": {
        "arguments": false,
        "attributes": false
      }
    }
  ]
}

The following sub-options are supported:

arguments

Disables checking an asynchronous function passed as argument where the parameter type expects a function that returns void.

attributes

Disables checking an asynchronous function passed as a JSX attribute expected to be a function that returns void.

inheritedMethods

Disables checking an asynchronous method in a type that extends or implements another type expecting that method to return void.

:::note For now, no-misused-promises only checks named methods against extended/implemented types: that is, call/construct/index signatures are ignored. Call signatures are not required in TypeScript to be consistent with one another, and construct signatures cannot be async in the first place. Index signature checking may be implemented in the future.

properties

Disables checking an asynchronous function passed as an object property expected to be a function that returns void.

returns

Disables checking an asynchronous function returned in a function whose return type is a function that returns void.

variables

Disables checking an asynchronous function used as a variable whose return type is a function that returns void.

checksSpreads

If you don't want to check object spreads, you can add this configuration:

{
  "@typescript-eslint/no-misused-promises": [
    "error",
    {
      "checksSpreads": false
    }
  ]
}

Examples

checksConditionals

Examples of code for this rule with checksConditionals: true:

{ "checksConditionals": true }
const promise = Promise.resolve('value')

if (promise) {
  // Do something
}

const val = promise ? 123 : 456

;[1, 2, 3].filter(() => promise)

while (promise) {
  // Do something
}
{ "checksConditionals": true }
const promise = Promise.resolve('value')

// Always `await` the Promise in a conditional
if (await promise) {
  // Do something
}

const val = (await promise) ? 123 : 456

const returnVal = await promise
;[1, 2, 3].filter(() => returnVal)

while (await promise) {
  // Do something
}

checksVoidReturn

Examples of code for this rule with checksVoidReturn: true:

{ "checksVoidReturn": true }
;[1, 2, 3].forEach(async (value) => {
  await fetch(`/${value}`)
})

new Promise<void>(async (resolve, reject) => {
  await fetch('/')
  resolve()
})

document.addEventListener('click', async () => {
  console.log('synchronous call')
  await fetch('/')
  console.log('synchronous call')
})

interface MySyncInterface {
  setThing(): void
}
class MyClass implements MySyncInterface {
  async setThing(): Promise<void> {
    this.thing = await fetchThing()
  }
}
{ "checksVoidReturn": true }
// for-of puts `await` in outer context
for (const value of [1, 2, 3]) {
  await doSomething(value)
}

// If outer context is not `async`, handle error explicitly
Promise.all(
  [1, 2, 3].map(async (value) => {
    await doSomething(value)
  }),
).catch(handleError)

// Use an async IIFE wrapper
new Promise((resolve, reject) => {
  // combine with `void` keyword to tell `no-floating-promises` rule to ignore unhandled rejection
  void (async () => {
    await doSomething()
    resolve()
  })()
})

// Name the async wrapper to call it later
document.addEventListener('click', () => {
  const handler = async () => {
    await doSomething()
    otherSynchronousCall()
  }

  try {
    synchronousCall()
  } catch (err) {
    handleSpecificError(err)
  }

  handler().catch(handleError)
})

interface MyAsyncInterface {
  setThing(): Promise<void>
}
class MyClass implements MyAsyncInterface {
  async setThing(): Promise<void> {
    this.thing = await fetchThing()
  }
}

checksSpreads

Examples of code for this rule with checksSpreads: true:

{ "checksSpreads": true }
const getData = () => fetch('/')

console.log({ foo: 42, ...getData() })

const awaitData = async () => {
  await fetch('/')
}

console.log({ foo: 42, ...awaitData() })
{ "checksSpreads": true }
const getData = () => fetch('/')

console.log({ foo: 42, ...(await getData()) })

const awaitData = async () => {
  await fetch('/')
}

console.log({ foo: 42, ...(await awaitData()) })

When Not To Use It

This rule can be difficult to enable on large existing projects that set up many misused Promises. Alternately, if you're not worried about crashes from floating or misused Promises -such as if you have global unhandled Promise handlers registered- then in some cases it may be safe to not use this rule.

Further Reading

@typescript-eslint
Disallow non-null assertions in the left operand of a nullish coalescing operator.

The ?? nullish coalescing runtime operator allows providing a default value when dealing with null or undefined. Using a ! non-null assertion type operator in the left operand of a nullish coalescing operator is redundant, and likely a sign of programmer error or confusion over the two operators.

Examples

foo! ?? bar
foo.bazz! ?? bar
foo!.bazz! ?? bar
foo()! ?? bar

let x!: string
x! ?? ''

let x: string
x = foo()
x! ?? ''
foo ?? bar
foo ?? bar!
foo!.bazz ?? bar
foo!.bazz ?? bar!
foo() ?? bar

// This is considered correct code because there's no way for the user to satisfy it.
let x: string
x! ?? ''

When Not To Use It

If your project's types don't yet fully describe whether certain values may be nullable, such as if you're transitioning to strictNullChecks, this rule might create many false reports.

Further Reading

@typescript-eslint
Disallow unused expressions.

It supports TypeScript-specific expressions:

  • Marks directives in modules declarations ("use strict", etc.) as not unused
  • Marks the following expressions as unused if their wrapped value expressions are unused:
    • Assertion expressions: x as number;, x!;, <number>x;
    • Instantiation expressions: Set<number>;

Although the type expressions never have runtime side effects (that is, x!; is the same as x;), they can be used to assert types for testing purposes.

Examples

Set<number>
1 as number
window!
function getSet() {
  return Set
}

// Funtion calls are allowed, so type expressions that wrap function calls are allowed
getSet()<number>
getSet() as Set<unknown>
getSet()!

// Namespaces can have directives
namespace A {
  'use strict'
}
@typescript-eslint
Disallow unused variables.

It adds support for TypeScript features, such as types.

Options

FAQs

What benefits does this rule have over TypeScript?

TypeScript provides noUnusedLocals and noUnusedParameters compiler options that can report errors on unused local variables or parameters, respectively. Those compiler options can be convenient to use if you don't want to set up ESLint and typescript-eslint. However:

  • These lint rules are more configurable than TypeScript's compiler options.
    • For example, the varsIgnorePattern option can customize what names are always allowed to be exempted. TypeScript hardcodes its exemptions to names starting with _. If you would like to emulate the TypeScript style of exempting names starting with _, you can use this configuration (this includes errors as well):
      {
        "rules": {
          "@typescript-eslint/no-unused-vars": [
            "error",
            {
              "args": "all",
              "argsIgnorePattern": "^_",
              "caughtErrors": "all",
              "caughtErrorsIgnorePattern": "^_",
              "destructuredArrayIgnorePattern": "^_",
              "varsIgnorePattern": "^_",
              "ignoreRestSiblings": true
            }
          ]
        }
      }
      
  • ESLint can be configured within lines, files, and folders. TypeScript compiler options are linked to their TSConfig file.
  • Many projects configure TypeScript's reported errors to block builds more aggressively than ESLint complaints. Blocking builds on unused variables can be inconvenient.

We generally recommend using @typescript-eslint/no-unused-vars to flag unused locals and parameters instead of TypeScript.

:::tip Editors such as VS Code will still generally "grey out" unused variables even if noUnusedLocals and noUnusedParameters are not enabled in a project.

Also see similar rules provided by ESLint:

Why does this rule report variables used only for types?

This rule does not count type-only uses when determining whether a variable is used. Declaring variables only to use them for types adds code and runtime complexity. The variables are never actually used at runtime. They can be misleading to readers of the code.

For example, if a variable is only used for typeof, this rule will report:

const box = {
  //  ~~~
  //  'box' is assigned a value but only used as a type.
  value: 123,
}

export type Box = typeof box

Instead, it's often cleaner and less code to write out the types directly:

export interface Box {
  value: number
}

For example, if a Zod schema variable is only used for typeof, this rule will report:

import { z } from 'zod'

const schema = z.object({
  //  ~~~~~~
  //  'schema' is assigned a value but only used as a type.
  value: z.number(),
})

export type Box = z.infer<typeof schema>

Instead, it's often cleaner and less code to write out the types directly:

export interface Box {
  value: number
}

If you find yourself writing runtime values only for types, consider refactoring your code to declare types directly.

JSDoc references are not supported by typescript-eslint. You can use a rule such as jsdoc/no-undefined-types to resolve variables as used in JSDoc comments.

import type { Box } from './Box'
//            ~~~
//            'Box' is defined but never used.

/**
 * @see {@link Box}
 */
export function getBox() {}
@typescript-eslint
Enforce the use of `as const` over literal type.

There are two common ways to tell TypeScript that a literal value should be interpreted as its literal type (e.g. 2) rather than general primitive type (e.g. number);

  • as const: telling TypeScript to infer the literal type automatically
  • as with the literal type: explicitly telling the literal type to TypeScript

as const is generally preferred, as it doesn't require re-typing the literal value. This rule reports when an as with an explicit literal type can be replaced with an as const.

Examples

let bar: 2 = 2
let foo = <'bar'>'bar'
let foo = { bar: 'baz' as 'baz' }
let foo = 'bar'
let foo = 'bar' as const
let foo: 'bar' = 'bar' as const
let bar = 'bar' as string
let foo = <string>'bar'
let foo = { bar: 'baz' }

When Not To Use It

If you don't care about which style of literals assertions is used in your code, then you will not need this rule.

However, keep in mind that inconsistent style can harm readability in a project. We recommend picking a single option for this rule that works best for your project.

@typescript-eslint
Require any function or method that returns a Promise to be marked async.

Ensures that each function is only capable of:

  • returning a rejected promise, or
  • throwing an Error object.

In contrast, non-async, Promise-returning functions are technically capable of either. Code that handles the results of those functions will often need to handle both cases, which can get complex. This rule's practice removes a requirement for creating code to handle both cases.

When functions return unions of Promise and non-Promise types implicitly, it is usually a mistake—this rule flags those cases. If it is intentional, make the return type explicitly to allow the rule to pass.

Examples

Examples of code for this rule

const arrowFunctionReturnsPromise = () => Promise.resolve('value')

function functionReturnsPromise() {
  return Promise.resolve('value')
}

function functionReturnsUnionWithPromiseImplicitly(p: boolean) {
  return p ? 'value' : Promise.resolve('value')
}
const arrowFunctionReturnsPromise = async () => Promise.resolve('value')

async function functionReturnsPromise() {
  return Promise.resolve('value')
}

// An explicit return type that is not Promise means this function cannot be made async, so it is ignored by the rule
function functionReturnsUnionWithPromiseExplicitly(
  p: boolean,
): string | Promise<string> {
  return p ? 'value' : Promise.resolve('value')
}

async function functionReturnsUnionWithPromiseImplicitly(p: boolean) {
  return p ? 'value' : Promise.resolve('value')
}

Options

allowAny

If you want additional safety, consider turning this option off, as it makes the rule less able to catch incorrect Promise behaviors.

Examples of code with { "allowAny": false }:

{ "allowAny": false }
const returnsAny = () => ({}) as any
{ "allowAny": false }
const returnsAny = async () => ({}) as any

allowedPromiseNames

For projects that use constructs other than the global built-in Promise for asynchronous code. This option allows specifying string names of classes or interfaces that cause a function to be checked as well.

Examples of code with { "allowedPromiseNames": ["Bluebird"] }:

{ "allowedPromiseNames": ["Bluebird"] }
class Bluebird {}

const returnsBluebird = () => new Bluebird(() => {})
{ "allowedPromiseNames": ["Bluebird"] }
class Bluebird {}

const returnsBluebird = async () => new Bluebird(() => {})

checkArrowFunctions

checkFunctionDeclarations

checkFunctionExpressions

checkMethodDeclarations

When Not To Use It

This rule can be difficult to enable on projects that use APIs which require functions to always be async.