eslint-config-ts-prefixer
A zero-config TypeScript ESLint configuration with Prettier integration
Install the package using your preferred package manager:
pnpm add -D eslint-config-ts-prefixer@latestnpm install --save-dev eslint-config-ts-prefixer@latestyarn add -D eslint-config-ts-prefixerAdd 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"
}
}Below is a comprehensive list of all ESLint rules configured by this package:
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.
This rule is aimed at eliminating the type-unsafe equality operators.
Examples of incorrect code for this rule:
/*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.
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:
/*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:
/*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 treatsnullliterals. Possible values:always(default) - Always use===or!==.never- Never use===or!==withnull.ignore- Do not apply this rule tonull.
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:
/*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:
/*eslint eqeqeq: ["error", "smart"]*/
typeof foo == 'undefined'
'hello' != 'world'
0 == 0
true == true
foo == 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' }]
If you don't want to enforce a style for using equality operators, then it's safe to disable this rule.
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`.
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:
/*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:
/*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
if (false) {
doSomethingUnfinished()
}
This rule disallows constant expressions in the test condition of:
if,for,while, ordo...whilestatement?:ternary expression
Examples of incorrect code for this rule:
/*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:
/*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)
}
This is a string option having following values:
"all"- Disallow constant expressions in all loops."allExceptWhileTrue"(default) - Disallow constant expressions in all loops exceptwhileloops with expressiontrue."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:
/*eslint no-constant-condition: ["error", { "checkLoops": "all" }]*/
while (true) {
doSomething()
}
for (; true; ) {
doSomething()
}
/*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:
/*eslint no-constant-condition: ["error", { "checkLoops": "all" }]*/
while (a === b) {
doSomething()
}
/*eslint no-constant-condition: ["error", { "checkLoops": true }]*/
for (let x = 0; x <= 10; x++) {
doSomething()
}
Example of correct code for when checkLoops is "allExceptWhileTrue":
/*eslint no-constant-condition: "error"*/
while (true) {
doSomething()
}
Examples of correct code for when checkLoops is "none" or false:
/*eslint no-constant-condition: ["error", { "checkLoops": "none" }]*/
while (true) {
doSomething()
if (condition()) {
break
}
}
do {
doSomething()
if (condition()) {
break
}
} while (true)
/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/
while (true) {
doSomething()
if (condition()) {
break
}
}
for (; true; ) {
doSomething()
if (condition()) {
break
}
}
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:
/*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:
/*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)
}
const foo = {
bar: 'baz',
bar: 'qux',
}
This rule disallows duplicate keys in object literals.
Examples of incorrect code for this rule:
/*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:
/*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__"
}
// 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.
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:
/*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:
/*eslint no-empty-pattern: "error"*/
const { a = {} } = foo
const { b = [] } = foo
function foo({ a = {} }) {}
function bar({ a = [] }) {}
This rule has an object option for exceptions:
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:
/*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:
/*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/
function foo({}) {}
const bar = function ({}) {}
const qux = ({}) => {}
function baz({} = {}) {}
if (!!foo) {
// ...
}
if (Boolean(foo)) {
// ...
}
if (foo) {
// ...
}
This rule disallows unnecessary boolean casts.
Examples of incorrect code for this rule:
/*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:
/*eslint no-extra-boolean-cast: "error"*/
const foo = !!bar
const foo1 = Boolean(bar)
function qux() {
return !!bar
}
foo = bar ? !!baz : !!bat
This rule has an object option:
"enforceForInnerExpressions"when set totrue, 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 isfalse, 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.
Examples of incorrect code for this rule with "enforceForInnerExpressions" option set to true:
/*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:
/*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))
This rule is aimed at eliminating variables that have multiple declarations in the same scope.
Examples of incorrect code for this rule:
/*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:
/*eslint no-redeclare: "error"*/
var a = 3
a = 10
class C {
foo() {
var b = 3
b = 10
}
static {
var c = 3
c = 10
}
}
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...
The "builtinGlobals" option will check for redeclaration of built-in globals in global scope.
Examples of incorrect code for the { "builtinGlobals": true } option:
/*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.
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:
/*eslint no-undef: "error"*/
const foo = someFunction()
const bar = a + 1
Examples of correct code for this rule with global declaration:
/*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.
typeofset to true will warn for variables used inside typeof check (Default false).
Examples of correct code for the default { "typeof": false } option:
/*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:
/*eslint no-undef: ["error", { "typeof": true }] */
if (typeof a === 'string') {
}
Examples of correct code for the { "typeof": true } option with global declaration:
/*global a*/
/*eslint no-undef: ["error", { "typeof": true }] */
if (typeof a === 'string') {
}
If explicit declaration of global variables is not to your taste.
This rule provides compatibility with treatment of global variables in JSHint and JSLint.
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)
This rule disallow ternary operators when simpler alternatives exist.
Examples of incorrect code for this rule:
/*eslint no-unneeded-ternary: "error"*/
const a = x === 2 ? true : false
const b = x ? true : false
Examples of correct code for this rule:
/*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.
This rule has an object option:
"defaultAssignment": true(default) allows the conditional expression as a default assignment pattern"defaultAssignment": falsedisallows the conditional expression as a default assignment pattern
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:
/*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).
You can turn this rule off if you are not concerned with unnecessary complexity in conditional expressions.
This rule disallows negating the left operand of the following relational operators:
Examples of incorrect code for this rule:
/*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:
/*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
}
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:
/*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:
/*eslint no-unsafe-negation: "error"*/
if ((!foo) in object) {
// this is not an allowed exception
}
This rule has an object option:
"enforceForOrderingRelations": false(default) allows negation of the left-hand side of ordering relational operators (<,>,<=,>=)"enforceForOrderingRelations": truedisallows negation of the left-hand side of ordering relational operators
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:
/*eslint no-unsafe-negation: ["error", { "enforceForOrderingRelations": true }]*/
if (!a < b) {
}
while (!a > b) {}
foo = !a <= b
foo = !a >= b
If you don't want to notify unsafe logical negations, then it's safe to disable this rule.
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.
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.
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:
allowShortCircuitset totruewill allow you to use short circuit evaluations in your expressions (Default:false).allowTernaryset totruewill enable you to use ternary operators in your expressions similarly to short circuit evaluations (Default:false).allowTaggedTemplatesset totruewill enable you to use tagged template literals in your expressions (Default:false).enforceForJSXset totruewill flag unused JSX element expressions (Default:false).ignoreDirectivesset totruewill prevent directives from being reported as unused expressions when linting withecmaVersion: 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:
/*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:
/*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:
/*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:
/*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
}
}
Examples of incorrect code for the { "allowShortCircuit": true } option:
/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true }]*/
a || b
Examples of correct code for the { "allowShortCircuit": true } option:
/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true }]*/
a && b()
a() || (b = c)
Examples of incorrect code for the { "allowTernary": true } option:
/*eslint no-unused-expressions: ["error", { "allowTernary": true }]*/
a ? b : 0
a ? b : c()
Examples of correct code for the { "allowTernary": true } option:
/*eslint no-unused-expressions: ["error", { "allowTernary": true }]*/
a ? b() : c()
a ? (b = c) : d()
Examples of correct code for the { "allowShortCircuit": true, "allowTernary": true } options:
/*eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }]*/
a ? b() || (c = d) : e()
Examples of incorrect code for the { "allowTaggedTemplates": true } option:
/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/
;`some untagged template string`
Examples of correct code for the { "allowTaggedTemplates": true } option:
/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/
tag`some tagged template string`
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:
/*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/
;<MyComponent />
;<></>
Examples of correct code for the { "enforceForJSX": true } option:
/*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/
const myComponentPartial = <MyComponent />
const myFragment = <></>
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:
/*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:
/*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'
}
This rule supports TypeScript-specific expressions and follows these guidelines:
- Directives (like
'use strict') are allowed in module and namespace declarations - 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>)
- Type assertions (
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:
/* 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:
/* eslint no-unused-expressions: "error" */
// Standalone type expressions
Set<number>
1 as number
window!
// Expressions inside namespaces
namespace Bar {
123
}
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:
/*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:
/*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
}
}
If you don't want to be notified about unused private class members, you can safely turn this rule off.
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:
/*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:
/*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'])
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.sourceTypeismodule(default) orcommonjs - when
languageOptions.parserOptions.ecmaFeatures.globalReturnistrue
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:
/*eslint no-unused-vars: "error"*/
/* exported global_var */
var global_var = 42
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
}
]
}
}
The vars option has two settings:
allchecks all variables for usage, including those in the global scope. However, it excludes variables targeted by other options likeargsandcaughtErrors. This is the default setting.localchecks only that locally-declared variables are used but will allow global variables to be unused.
Examples of correct code for the { "vars": "local" } option:
/*eslint no-unused-vars: ["error", { "vars": "local" }]*/
/*global some_unused_var */
some_unused_var = 42
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:
/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "[iI]gnored" }]*/
const firstVarIgnored = 1
const secondVar = 2
console.log(secondVar)
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.
Examples of incorrect code for the default { "args": "after-used" } option:
/*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:
/*eslint no-unused-vars: ["error", {"args": "after-used"}]*/
;(function (foo, bar, baz, qux) {
return qux
})()
Examples of incorrect code for the { "args": "all" } option:
/*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
})()
Examples of correct code for the { "args": "none" } option:
/*eslint no-unused-vars: ["error", { "args": "none" }]*/
;(function (foo, bar, baz) {
return bar
})()
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:
/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/
function foo(x, _y) {
return x + 1
}
foo()
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.
Not specifying this option is equivalent of assigning it to all.
Examples of incorrect code for the { "caughtErrors": "all" } option:
/*eslint no-unused-vars: ["error", { "caughtErrors": "all" }]*/
// 1 error
// "err" is defined but never used
try {
//...
} catch (err) {
console.error('errors')
}
Examples of correct code for the { "caughtErrors": "none" } option:
/*eslint no-unused-vars: ["error", { "caughtErrors": "none" }]*/
try {
//...
} catch (err) {
console.error('errors')
}
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:
/*eslint no-unused-vars: ["error", { "caughtErrors": "all", "caughtErrorsIgnorePattern": "^ignore" }]*/
try {
//...
} catch (ignoreErr) {
console.error('errors')
}
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:
/*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
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:
/*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)
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
/*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
/*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/
class Foo {
static {
let bar = 'some string'
console.log(bar)
}
}
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:
/*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:
/*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/
const firstVar = 1
const secondVar = 2
console.log(firstVar, secondVar)
If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off.
const declaration tells readers, "this variable is never reassigned," reducing cognitive load and improving maintainability.
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:
/*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:
/*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)
{
"prefer-const": [
"error",
{
"destructuring": "any",
"ignoreReadBeforeAssign": false
}
]
}
The kind of the way to address variables in destructuring. There are 2 values:
"any"(default) - If any variables in destructuring should beconst, this rule warns for those variables."all"- If all variables in destructuring should beconst, this rule warns the variables. Otherwise, ignores them.
Examples of incorrect code for the default {"destructuring": "any"} option:
/*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:
/*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:
/*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:
/*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
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:
/*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:
/*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": false}]*/
const timer = setInterval(initialize, 100)
function initialize() {
if (foo()) {
clearInterval(timer)
}
}
If you don't want to be notified about variables that are never reassigned after initial assignment, you can safely disable this rule.
This rule aims to ensure that Promises are only rejected with Error objects.
This rule takes one optional object argument:
allowEmptyReject: true(falseby default) allows calls toPromise.reject()with no arguments.
Examples of incorrect code for this rule:
/*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:
/*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:
/*eslint prefer-promise-reject-errors: ["error", {"allowEmptyReject": true}]*/
Promise.reject()
new Promise(function (resolve, reject) {
reject()
})
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.
If you're using custom non-error values as Promise rejection reasons, you can turn off this rule.
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.
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.
There are two options for this rule:
"always"enforces providing a radix (default)"as-needed"disallows providing the10radix
Examples of incorrect code for the default "always" option:
/*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:
/*eslint radix: "error"*/
const num = parseInt('071', 10)
const num1 = parseInt('071', 8)
const num2 = parseFloat(someValue)
Examples of incorrect code for the "as-needed" option:
/*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:
/*eslint radix: ["error", "as-needed"]*/
const num = parseInt('071')
const num1 = parseInt('071', 8)
const num2 = parseFloat(someValue)
If you don't want to enforce either presence or omission of the 10 radix value you can turn this rule off.
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)
})
This rule aims to report assignments to variables or properties in cases where the assignments may be based on outdated values.
This rule reports an assignment to a variable when it detects the following execution flow in a generator or async function:
- The variable is read.
- A
yieldorawaitpauses the function. - 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:
/* 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:
/* 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
}
This rule reports an assignment to a property through a variable when it detects the following execution flow in a generator or async function:
- The variable or object property is read.
- A
yieldorawaitpauses the function. - 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:
/* eslint require-atomic-updates: error */
async function foo(obj) {
if (!obj.done) {
obj.something = await getSomething()
}
}
Example of correct code for this rule:
/* eslint require-atomic-updates: error */
async function foo(obj) {
if (!obj.done) {
const tmp = await getSomething()
if (!obj.done) {
obj.something = tmp
}
}
}
This rule has an object option:
"allowProperties": When set totrue, the rule does not report assignments to properties. Default isfalse.
Example of correct code for this rule with the { "allowProperties": true } option:
/* eslint require-atomic-updates: ["error", { "allowProperties": true }] */
async function foo(obj) {
if (!obj.done) {
obj.something = await getSomething()
}
}
If you don't use async or generator functions, you don't need to enable this rule.
This rule enforces comparing typeof expressions to valid string literals.
Examples of incorrect code for this rule:
/*eslint valid-typeof: "error"*/
typeof foo === 'strnig'
typeof foo == 'undefimed'
typeof bar != 'nunber'
typeof bar !== 'fucntion'
Examples of correct code for this rule:
/*eslint valid-typeof: "error"*/
typeof foo === 'string'
typeof bar == 'undefined'
typeof foo === baz
typeof bar === typeof qux
This rule has an object option:
"requireStringLiterals": trueallows the comparison oftypeofexpressions with only string literals or othertypeofexpressions, and disallows comparisons to any other value. Default isfalse.
Examples of incorrect code with the { "requireStringLiterals": true } option:
/*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:
/*eslint valid-typeof: ["error", { "requireStringLiterals": true }]*/
typeof foo === 'undefined'
typeof bar == 'object'
typeof baz === 'string'
typeof bar === typeof qux
You may want to turn this rule off if you will be using the typeof operator on host objects.
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.
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
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.
- Lee Byron's ES7 export proposal
import/ignoresettingjsnext:main(Rollup)
Reports funny business with exports, like repeated exports of names or defaults.
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.
- Lee Byron's ES7 export proposal
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.
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'
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'
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.
import/ignoresettingjsnext:maindeprecationpkg.module(Rollup)
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.
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.
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.
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.
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.
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.
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.
- Original inspiring issue
- Rule to detect that module imports itself:
no-self-import import/external-module-folderssetting
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:
- 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.
./fooand./foo.js) - this version distinguishes Flow
typeimports from standard imports. (#334)
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.
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'
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'
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.
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.
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
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
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
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
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.
- Resolver plugins
- Node resolver (default)
- Webpack resolver
import/ignoreglobal setting
Use this rule to prevent unnecessary path segments in import and require statements.
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'
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.
When set to true, this rule checks CommonJS imports. Default to false.
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.
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
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')
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
This rule supports the following options (none of which are required):
groupspathGroupspathGroupsExcludedImportTypesdistinctGroupnewlines-betweenalphabetizenamedwarnOnUnassignedImportssortTypesGroupnewlines-between-typesconsolidateIslands
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.
{
"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
],
},
],
}
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:
- If the import has no corresponding identifiers (e.g.
import './my/thing.js'), is otherwise "unassigned," or is an unsupported use ofrequire(), andwarnOnUnassignedImportsis disabled, it will be ignored entirely since the order of these imports may be important for their side-effects - 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 normalrequire()s and import-exports (e.g.export import w = y;) are ignored entirely - If the import is type-only,
"type"is ingroups, andsortTypesGroupis disabled, it will be considered type (with additional implications if usingpathGroupsand"type"is inpathGroupsExcludedImportTypes) - If the import's specifier matches
import/internal-regex, it will be considered internal - If the import's specifier is an absolute path, it will be considered unknown
- If the import's specifier has the name of a Node.js core module (using is-core-module), it will be considered builtin
- If the import's specifier matches
import/core-modules, it will be considered builtin - 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 - If the import's specifier is one of
['.', './', './index', './index.js'], it will be considered index - If the import's specifier is a path relative to its containing file (e.g. starts with
./), it will be considered sibling - 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
- If the import's specifier matches
import/external-module-folders(defaults to matching anything pointing to files within the current package'snode_modulesdirectory), it will be considered external - 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
- If the import's specifier has a name that looks like a scoped package (e.g.
@scoped/package-name), it will be considered external - If the import's specifier has a name that starts with a word character, it will be considered external
- 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.
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 furtherpathGroupsmatching unless they are removed frompathGroupsExcludedImportTypes.
| property | required | type | description |
|---|---|---|---|
pattern | ☑️ | string | Minimatch pattern for specifier matching |
patternOptions | object | Minimatch options; default: {nocomment: true} | |
group | ☑️ | predefined group | One 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) |
{
"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",
},
],
},
],
}
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,pathsintsconfig.json, etc) that end up grouped as"builtin"or"external"imports, remove them frompathGroupsExcludedImportTypesto ensure they are ordered correctly.
{
"import/order": [
"error",
{
"pathGroups": [
{
"pattern": "@app/**",
"group": "external",
"position": "after",
},
],
"pathGroupsExcludedImportTypes": ["builtin"],
},
],
}
Valid values: boolean
Default: true
[!CAUTION]
Currently,
distinctGroupdefaults totrue. However, in a later update, the default will change tofalse.
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".
{
"import/order": [
"error",
{
"distinctGroup": false,
"newlines-between": "always",
"pathGroups": [
{
"pattern": "@app/**",
"group": "external",
"position": "after",
},
],
},
],
}
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-linesrule, or a tool like Prettier, can be used.
-
If set to
always-and-inside-groups, it will act likealwaysexcept new lines are allowed inside import groups -
If set to
never, no new lines are allowed in the entire import section
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 './'
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 afterconst z = require('a');whenalphabetizeis 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: usetrueto ignore case andfalseto consider case when sorting
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'
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 firsttypes-last: forces type-only identifiers to occur lastmixed: sorts all identifiers in alphabetical order
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'
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
--fixsince unassigned imports might be used for their side-effects, and changing the order of such imports cannot be done safely.
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'
Valid values: boolean
Default: false
[!NOTE]
This setting is only meaningful when
"type"is included ingroups.
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.
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.
Valid values: "ignore" | "always" | "always-and-inside-groups" | "never"
Default: the value of newlines-between
[!NOTE]
This setting is only meaningful when
sortTypesGroupis 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.
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: truewill take precedence overnewlines-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 './'
Valid values: "inside-groups" | "never"
Default: "never"
[!NOTE]
This setting is only meaningful when
newlines-betweenand/ornewlines-between-typesis 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:
sortTypesGroupis set totrueconsolidateIslandsis set to"inside-groups"newlines-betweenis set to"always-and-inside-groups"whennewlines-between-typesis set to"never"(or vice-versa)Then
newlines-between/newlines-between-typeswill yield toconsolidateIslandsand 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.
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
pathGroupsExcludedImportTypesin 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 frompathGroupsmatching by default.The fix is to remove
"external"(and, in this example, the others) frompathGroupsExcludedImportTypes.
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'
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.
await 'value'
const createValue = () => 'value'
await createValue()
await Promise.resolve('value')
const createValue = async () => 'value'
await createValue()
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.
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)
}
}
This rule also inspects await using statements.
If the disposable being used is not async-disposable, an await using statement is unnecessary.
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()
}
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 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.
Valid values for prefer are:
type-importswill enforce that you always useimport type Foo from '...'except referenced by metadata of decorators. It is the default.no-type-importswill enforce that you always useimport Foo from '...'.
Examples of correct code with {prefer: 'type-imports'}, and incorrect code with {prefer: 'no-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'}.
import { Foo } from 'Foo'
import Bar from 'Bar'
type T = Foo
const x: Bar = 1
Valid values for fixStyle are:
separate-type-importswill add the type keyword after the import keywordimport type { A } from '...'. It is the default.inline-type-importswill inline the type keywordimport { 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
import type { Foo } from 'Foo'
import type Bar from 'Bar'
type T = Foo
const x: Bar = 1
import { type Foo } from 'Foo'
import type Bar from 'Bar'
type T = Foo
const x: Bar = 1
Examples of incorrect code with {disallowTypeAnnotations: true}:
type T = import('Foo').Foo
const x: import('Bar') = 1
:::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.
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:
| Situation | consistent-type-imports (ESLint) | verbatimModuleSyntax (TypeScript) |
|---|---|---|
| Unused imports | Ignored (consider using @typescript-eslint/no-unused-vars) | Type error |
Usage with emitDecoratorMetadata & experimentalDecorations | Ignores files that contain decorators | Reports on files that contain decorators |
| Failures detected | Does not fail tsc build; can be auto-fixed with --fix | Fails 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.
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.
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.
declare class C {
new(): C
}
interface I {
new (): I
constructor(): void
}
declare class C {
constructor()
}
interface I {
new (): C
}
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.
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.
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).
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:
Disables checking an asynchronous function passed as argument where the parameter type expects a function that returns void.
Disables checking an asynchronous function passed as a JSX attribute expected to be a function that returns void.
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.
Disables checking an asynchronous function passed as an object property expected to be a function that returns void.
Disables checking an asynchronous function returned in a function whose return type is a function that returns void.
Disables checking an asynchronous function used as a variable whose return type is a function that returns void.
If you don't want to check object spreads, you can add this configuration:
{
"@typescript-eslint/no-misused-promises": [
"error",
{
"checksSpreads": false
}
]
}
Examples of code for this rule with 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
}
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
}
Examples of code for this rule with 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()
}
}
// 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()
}
}
Examples of code for this rule with checksSpreads: true:
const getData = () => fetch('/')
console.log({ foo: 42, ...getData() })
const awaitData = async () => {
await fetch('/')
}
console.log({ foo: 42, ...awaitData() })
const getData = () => fetch('/')
console.log({ foo: 42, ...(await getData()) })
const awaitData = async () => {
await fetch('/')
}
console.log({ foo: 42, ...(await awaitData()) })
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.
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.
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! ?? ''
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.
Type assertions should communicate information that TypeScript cannot infer on its own. This preset reports redundant assertions as errors because they add visual noise and can hide misunderstandings about the inferred type.
This rule requires type information.
const message: string = 'hello'
const redundantMessage = message as string
function getCount(): number {
return 1
}
const redundantCount = getCount() as number
const input: unknown = 'hello'
const message = input as string
const element = document.getElementById('email') as HTMLInputElement
'@typescript-eslint/no-unnecessary-type-assertion': 'error'
For Oxlint's type-aware preset, the equivalent rule is typescript/no-unnecessary-type-assertion.
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>;
- Assertion expressions:
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.
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'
}
It adds support for TypeScript features, such as types.
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
varsIgnorePatternoption 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 } ] } }
- For example, the
- 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:
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() {}
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 automaticallyaswith 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.
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' }
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.
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
Promiseand non-Promisetypes 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 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')
}
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 }:
const returnsAny = () => ({}) as any
const returnsAny = async () => ({}) as any
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"] }:
class Bluebird {}
const returnsBluebird = () => new Bluebird(() => {})
class Bluebird {}
const returnsBluebird = async () => new Bluebird(() => {})
This rule can be difficult to enable on projects that use APIs which require functions to always be async.
CWE: CWE-598: Use of GET Request with Sensitive Query Strings
OWASP Mobile: OWASP Mobile Top 10 M1: Improper Credential Usage
ESLint Rule: no-credentials-in-query-params. This rule is part of eslint-plugin-browser-security.
| Aspect | Details |
|---|---|
| Severity | High (Credential Exposure) |
| Auto-Fix | ❌ No (requires moving data to Body) |
| Category | Security |
| ESLint MCP | ✅ Optimized for ESLint MCP integration |
| Best For | Web and Mobile applications using APIs |
Vulnerability: Passing sensitive information (like passwords, API keys, or session tokens) within URL query parameters (GET requests).
Risk: URL query parameters are frequently stored in browser history, server logs, and web analytics platforms. They are also visible in the Referer header sent to third-party sites. An attacker with access to these logs or history can easily recover user credentials.
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
🔒 CWE-598 OWASP:M1 | Credentials in Query Params detected | HIGH [CredExposure]
Fix: Do not pass sensitive data in URL; use POST request body or headers | https://cwe.mitre.org/data/definitions/598.html
| Component | Purpose | Example |
|---|---|---|
| Risk Standards | Security benchmarks | CWE-598 OWASP:M1 |
| Issue Description | Specific vulnerability | Credentials in Query Params detected |
| Severity & Compliance | Impact assessment | HIGH [CredExposure] |
| Fix Instruction | Actionable remediation | Use POST request body or headers |
| Technical Truth | Official reference | Sensitive Query Strings |
This rule flags strings and template literals that contain common credential keywords (e.g., password, token, secret, apikey) followed by an assignment in a URL-like structure (query parameters).
| Issue | Impact | Solution |
|---|---|---|
| 🕵️ Leakage | Credentials stored in logs/history | Send sensitive data in request body only |
| 🚀 Exfiltration | Referer header leaks tokens | Use Authorization: Bearer <token> header |
| 🔒 Compliance | Failure to meet security benchmarks | Implement strict transport security and data encapsulation |
This rule has no configuration options in the current version.
// Hardcoded credentials in a query string
const url = '/api/login?username=user&password=pass123'
// Passing a token in the URL for a search query
fetch('/api/search?token=abc123&query=test')
// Safe URL without credentials
const url = '/api/users?page=1&limit=10'
// Passing sensitive data in the request body (Secure)
fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ username: 'user', password: 'password' }),
})
// Passing credentials in an Authorization header (Secure)
fetch('/api/data', {
headers: {
Authorization: 'Bearer abc123',
},
})
The following patterns are not detected due to static analysis limitations:
Why: This rule performs simple string matching on literals. It does not trace values stored in variables.
// ❌ NOT DETECTED
const pName = 'password'
const pVal = 'secret'
const url = `/api?${pName}=${pVal}`
Mitigation: Always use a URL builder library or a secure request utility.
Why: If URLs are built using non-literal methods (e.g., URLSearchParams object), they might be missed.
// ❌ NOT DETECTED
const params = new URLSearchParams()
params.append('password', '123')
const url = '/api?' + params.toString()
Mitigation: Standardize on a secure API caller that automatically handles sensitive data.
| Option | Type | Default | Description |
|---|---|---|---|
bodyProperties | string[] | ["body","data","form","formData"] | Property names whose value is a request body rather than a URL |
A string in one of these positions is form-encoded payload, not a query string, and credentials belong there. RFC 6749 §2.3.1 prescribes exactly this for OAuth 2.0 token introspection and the client-credentials grant:
fetch(introspectUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: `client_id=${id}&client_secret=${secret}&token=${token}`,
})
The defaults cover fetch/undici (body), axios (data), got and request
(form, formData); extend the list for a client that names its payload
something else.
The same string in a URL still reports — only the position is exempt.
CWE: CWE-95
OWASP Mobile: OWASP Mobile Top 10
Detects dangerous eval() and similar code execution patterns. This rule is part of eslint-plugin-browser-security.
| Aspect | Details |
|---|---|
| CWE Reference | CWE-94 (Code Injection) |
| Severity | 🔴 Critical |
| Auto-Fix | ❌ No (requires refactoring) |
| Category | Security |
| Best For | All JavaScript applications |
Vulnerability: eval() and similar functions execute arbitrary code, allowing attackers to run malicious scripts if they can control the input.
Risk: Code injection can lead to:
- Complete application compromise
- Data theft
- Remote code execution
- Cryptocurrency mining
// Direct eval - CRITICAL
eval(userInput)
eval('console.log("' + userData + '")')
// Function constructor - CRITICAL
const fn = new Function(userCode)
const fn = new Function('a', 'b', userExpression)
// setTimeout/setInterval with strings - VULNERABLE
setTimeout('doSomething(' + userId + ')', 1000)
setInterval(userAction, 500)
// Use JSON.parse for data
const data = JSON.parse(jsonString)
// Use proper function references
setTimeout(() => doSomething(userId), 1000)
setInterval(processQueue, 500)
// Use a safe expression parser for calculators
import { Parser } from 'expr-eval'
const parser = new Parser()
const result = parser.evaluate(expression)
Set deferDynamicPayloads: true.
'browser-security/no-eval': ['error', { deferDynamicPayloads: true }]
node-security/detect-eval-with-expression covers dynamic eval() payloads and
classifies them (json / math / template / object), which is more than this rule
can say. Without the option both rules report the same line — measured: two
findings for eval(userInput) and for new Function(body)().
The default is false, so this rule covers eval() on its own. That is
deliberate: eslint-plugin-browser-security does not depend on
eslint-plugin-node-security, and until 2026-08-16 the deferral was
unconditional — which left anyone installing this plugin alone with no eval
coverage at all, while eval("2 + 2") still reported at CVSS 9.8.
Between the two failure modes, a duplicate finding is visible and one config line away; a silent hole in code-injection coverage is neither.
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | false | Skip this rule in *.test.* / *.spec.* files |
allowFunctionConstructor | boolean | false | Allow new Function(...) while still reporting eval() |
deferDynamicPayloads | boolean | false | Let node-security/detect-eval-with-expression own dynamic eval() payloads. Enable when both plugins are installed, to avoid one line being reported twice. Off by default so browser-security covers eval() on its own. |
{
"rules": {
"browser-security/no-eval": "error"
}
}
| Use Case | Instead of eval | Use This |
|---|---|---|
| JSON parsing | eval(jsonStr) | JSON.parse(jsonStr) |
| Math expressions | eval(expr) | expr-eval or mathjs |
| Dynamic property | eval('obj.' + prop) | obj[prop] |
| Template rendering | eval(template) | Template literals, Handlebars |
| Config objects | eval(configStr) | JSON.parse() or YAML parser |
no-innerhtml- XSS via innerHTML
The following patterns are not detected due to static analysis limitations:
Why: eval assigned to a variable is not traced.
// ❌ NOT DETECTED - Aliased eval
const execute = eval
execute(userInput)
Mitigation: Never alias eval. Use strict mode.
Why: Window property access may not be detected.
// ❌ NOT DETECTED - Indirect via window
window['eval'](userInput)
Mitigation: Avoid dynamic eval invocation.
Why: Dynamic import with user input is different but still dangerous.
// ❌ NOT DETECTED - Dynamic import
import(userControlledPath)
Mitigation: Validate import paths. Use allowlist.
Why: eval in Worker context may not be recognized.
// ❌ NOT DETECTED - Worker eval
new Worker(`data:,${userCode}`)
Mitigation: Review Worker creation patterns.
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
🔒 CWE-94 OWASP:A05 CVSS:9.8 | Code Injection detected | CRITICAL [SOC2,PCI-DSS,ISO27001]
Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/
| Component | Purpose | Example |
|---|---|---|
| Risk Standards | Security benchmarks | CWE-94 OWASP:A05 CVSS:9.8 |
| Issue Description | Specific vulnerability | Code Injection detected |
| Severity & Compliance | Impact assessment | CRITICAL [SOC2,PCI-DSS,ISO27001] |
| Fix Instruction | Actionable remediation | Follow the remediation steps below |
| Technical Truth | Official reference | OWASP Top 10 |
CWE: CWE-693
OWASP Mobile: OWASP Mobile Top 10
Detects dangerous innerHTML/outerHTML assignments that can lead to Cross-Site Scripting (XSS). This rule is part of eslint-plugin-browser-security.
| Aspect | Details |
|---|---|
| CWE Reference | CWE-79 (Cross-site Scripting) |
| Severity | 🔴 Critical |
| Auto-Fix | ✅ Yes (suggests DOMPurify) |
| Category | Security |
| Best For | Frontend apps, React/Vue/Angular, DOM manipulation |
Vulnerability: Assigning unsanitized user input to innerHTML or outerHTML allows attackers to inject malicious scripts.
Risk: XSS attacks can:
- Steal session cookies and authentication tokens
- Perform actions as the victim user
- Redirect to phishing sites
- Install keyloggers
// Direct assignment of user input - CRITICAL XSS
element.innerHTML = userInput
// Template literal with user data - VULNERABLE
element.innerHTML = `<div>${userData.name}</div>`
// Function result without sanitization - VULNERABLE
element.innerHTML = getUserContent()
element.outerHTML = fetchedData
// React dangerouslySetInnerHTML (different rule, same concept)
;<div dangerouslySetInnerHTML={{ __html: userContent }} />
// Use textContent for text - SAFE
element.textContent = userInput
// Sanitize with DOMPurify - SAFE
import DOMPurify from 'dompurify'
element.innerHTML = DOMPurify.sanitize(userInput)
// Literal strings are safe
element.innerHTML = '<p>Static content</p>'
// Create elements programmatically - SAFE
const div = document.createElement('div')
div.textContent = userInput
container.appendChild(div)
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | false | Skip this rule in *.test.* / *.spec.* files |
trustedSanitizers | string[] | ["DOMPurify.sanitize","sanitize","sanitizeHtml","xss","purify","escapeHtml","escapeHTML","escape"] | Extra function names to treat as sanitizers |
allowLiteralStrings | boolean | true | Allow innerHTML with literal strings |
{
"rules": {
"browser-security/no-innerhtml": [
"error",
{
"trustedSanitizers": ["DOMPurify.sanitize", "myCustomSanitizer"],
"allowLiteralStrings": true
}
]
}
}
import DOMPurify from 'dompurify'
function renderUserContent(html) {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
})
}
element.innerHTML = renderUserContent(userInput)
// For plain text, always use textContent
element.textContent = userData.name // Safe - no HTML parsing
// Build DOM programmatically
function createComment(user, text) {
const div = document.createElement('div')
div.className = 'comment'
const author = document.createElement('span')
author.textContent = user.name // Safe
const content = document.createElement('p')
content.textContent = text // Safe
div.appendChild(author)
div.appendChild(content)
return div
}
no-eval- Detects dangerous eval usageno-sensitive-localstorage- Detects sensitive data in localStorage
The following patterns are not detected due to static analysis limitations:
Why: Content assigned to variables is not traced.
// ❌ NOT DETECTED - Content from variable
const html = userInput
element.innerHTML = html
Mitigation: Always sanitize before assignment to any variable.
Why: Non-standard sanitizer names may not be detected.
// ❌ NOT DETECTED - Custom sanitizer
element.innerHTML = myCustomEscape(userInput)
Mitigation: Configure trustedSanitizers with custom function names.
Why: Framework-specific binding may not be recognized.
// ❌ NOT DETECTED - jQuery html()
$element.html(userInput);
// ❌ NOT DETECTED - Angular [innerHTML]
<div [innerHTML]="userContent"></div>
Mitigation: Use framework-specific security rules.
Why: Dynamic property access is not analyzed.
// ❌ NOT DETECTED - Dynamic property
const prop = 'innerHTML'
element[prop] = userInput
Mitigation: Avoid dynamic property assignment for DOM manipulation.
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
⚠️ CWE-79 OWASP:A05 CVSS:6.1 | Cross-site Scripting (XSS) detected | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001]
Fix: Review and apply the recommended fix | https://owasp.org/Top10/A05_2021/
| Component | Purpose | Example |
|---|---|---|
| Risk Standards | Security benchmarks | CWE-79 OWASP:A05 CVSS:6.1 |
| Issue Description | Specific vulnerability | Cross-site Scripting (XSS) detected |
| Severity & Compliance | Impact assessment | MEDIUM [SOC2,PCI-DSS,GDPR,ISO27001] |
| Fix Instruction | Actionable remediation | Follow the remediation steps below |
| Technical Truth | Official reference | OWASP Top 10 |
CWE: CWE-693
OWASP Mobile: OWASP Mobile Top 10
ESLint Rule: no-insecure-redirects. This rule is part of eslint-plugin-browser-security.
| Aspect | Details |
|---|---|
| CWE Reference | CWE-601 (Open Redirect) |
| Severity | Medium (security vulnerability) |
| Auto-Fix | ❌ No |
| Category | Security |
| ESLint MCP | ✅ Optimized for ESLint MCP integration |
| Best For | Web applications with redirection logic |
Vulnerability: Insecure redirects (also known as Open Redirects) occur when an application redirects the user to a URL specified by untrusted user input without validation.
Risk: Attackers can redirect users to phishing sites (to steal credentials) or malicious sites (to download malware), leveraging the trust the user has in the original domain.
| Issue | Impact | Solution |
|---|---|---|
| 🔒 Security/Code Quality | [Specific issue] | [Solution approach] |
| 🐛 Maintainability | [Impact] | [Fix] |
| ⚡ Performance | [Impact] | [Optimization] |
No configuration options available.
// Example of incorrect usage
// Example of correct usage
// eslint.config.mjs
export default [
{
rules: {
'browser-security/no-insecure-redirects': 'error',
},
},
]
🚨 no insecure redirects | Description | MEDIUM
Fix: Suggestion | Reference
rule-name- Description
The following patterns are not detected due to static analysis limitations:
Why: Values stored in variables are not traced.
// ❌ NOT DETECTED - Value from variable
const value = userInput
dangerousOperation(value)
Mitigation: Validate all user inputs.
Why: Custom wrappers not recognized.
// ❌ NOT DETECTED - Wrapper
myWrapper(userInput) // Uses dangerous API internally
Mitigation: Apply rule to wrapper implementations.
Why: Dynamic calls not analyzed.
// ❌ NOT DETECTED - Dynamic
obj[method](userInput)
Mitigation: Avoid dynamic method invocation.
- OWASP Unvalidated Redirects Cheat Sheet - Protection guide
- CWE-601: URL Redirection to Untrusted Site - Official CWE entry
| Option | Type | Default | Description |
|---|---|---|---|
ignoreInTests | boolean | true | Skip this rule in *.test.* / *.spec.* files |
allowedDomains | string[] | [] | Redirect target domains treated as safe |
🔒 Disallow storing JWT tokens in localStorage or sessionStorage
CWE: CWE-311
OWASP Mobile: M9: Insecure Data Storage
This rule prevents storing JWT tokens in browser storage (localStorage/sessionStorage). JWTs stored in these locations are fully accessible to JavaScript, making them vulnerable to XSS attacks.
When you store JWTs in browser storage:
- Any XSS attack can read the token via
localStorage.getItem('token') - Tokens can be exfiltrated to attacker-controlled servers
- Attackers can impersonate users with stolen tokens
// Storing JWT in localStorage
localStorage.setItem('token', jwtToken)
localStorage.setItem('accessToken', response.access_token)
localStorage.setItem('jwt', authResult.jwt)
// Storing in sessionStorage (equally vulnerable)
sessionStorage.setItem('refreshToken', refreshToken)
sessionStorage.setItem('id_token', idToken)
// Direct assignment
localStorage['bearer'] = bearerToken
localStorage.access_token = token
// Even with obfuscated keys - if value is a JWT, it's detected
localStorage.setItem('data', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...')
// Non-sensitive data is fine
localStorage.setItem('theme', 'dark')
localStorage.setItem('locale', 'en-US')
sessionStorage.setItem('searchHistory', JSON.stringify(history))
// Token counters and metadata (not actual tokens)
localStorage.setItem('tokenCount', '5')
localStorage.setItem('tokenExpiry', '1234567890')
// Use HttpOnly cookies instead (set by server)
// Server: res.cookie('token', jwt, { httpOnly: true, secure: true, sameSite: 'strict' })
// Or use in-memory only (cleared on page refresh)
let token = response.jwt // Not persisted
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | true | Skip this rule in *.test.* / *.spec.* files |
bearerPatterns | string[] | ["jwt","token","bearer","auth","authorization","session","sid","credential","credentials"] | Whole words that name a bearer credential. Replaces the default vocabulary. |
{
"browser-security/no-jwt-in-storage": [
"error",
{
"allowInTests": true
}
]
}
Default: ["jwt", "token", "bearer", "auth", "authorization", "session", "sid", "credential", "credentials"]
Matched whole-word after the key is split on _, -, ., case boundaries
and URL punctuation, and after regular plurals are folded — so token matches
access_token and accessToken but not tokenizer, and auth matches
auth_state but not author. A key that names a fact about a credential
(tokenCount, sessionTimeout) is excluded separately.
This list used to live inside utils/sensitive-value-evidence.ts with no option
surface at all, one level below the rules that report from it: a user whose
credential cookie was called handle could not add it, and a user whose harmless
key collided with the list could not remove it.
Keeping the partition. The five medium rules (
no-sensitive-localstorage,no-sensitive-sessionstorage,no-sensitive-indexeddb,no-sensitive-data-in-cache,no-sensitive-cookie-js) defer to this rule with the SAME vocabulary, and they read the default because a rule cannot see another rule's options. If you customisebearerPatterns, mirror the change in those rules'sensitivePatterns— otherwise a key you added here is reported by this rule and by the medium rule that no longer defers.
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | true | Skip checking in test files (_.test.ts, _.spec.ts) |
The rule detects JWT storage through two mechanisms:
Keys matching these patterns are flagged:
jwt,token,bearer(exact)access_token,accessToken,access-tokenrefresh_token,refreshTokenid_token,idTokenauth_token,authToken- Any key ending with
token(e.g.,userToken,apiToken)
Values that look like JWTs are detected:
- Matches pattern:
eyJ[...].eyJ[...].[...](base64.base64.signature)
These patterns are explicitly excluded:
tokenCount,tokenLength,tokenSizetokenLimit,tokenMax,tokenMintokenIndex,tokenPosition
You may disable this rule if:
- Your application doesn't handle authentication
- You're building a demo/prototype without security requirements
- You have specific requirements that mandates browser storage (not recommended)
However, always prefer HttpOnly cookies for JWT storage in production.
// Server-side (Express.js)
res.cookie('token', jwtToken, {
httpOnly: true, // Not accessible via JavaScript
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 3600000, // 1 hour
})
// Client-side - token is automatically sent with requests
fetch('/api/protected', {
credentials: 'include',
})
// Token lives only in memory - cleared on page refresh
class TokenService {
private token: string | null = null;
setToken(jwt: string) { this.token = jwt; }
getToken() { return this.token; }
clearToken() { this.token = null; }
}
- CWE-922: Insecure Storage of Sensitive Information
- Auth0: Token Storage
browser-security/no-sensitive-localstorage- General sensitive data detection
The following patterns are not detected due to static analysis limitations:
Why: Token values from variables are not traced.
// ❌ NOT DETECTED - Value from variable
const value = response.jwt
localStorage.setItem('data', value)
Mitigation: Never store JWTs in localStorage.
Why: Storage wrappers not recognized.
// ❌ NOT DETECTED - Custom wrapper
myStorage.save('token', jwt) // Uses localStorage internally
Mitigation: Apply rule to wrapper implementations.
Why: Key patterns may not match.
// ❌ NOT DETECTED - Obfuscated key
localStorage.setItem('_t', jwt) // Not in key patterns
Mitigation: Configure additional key patterns.
Why: Encrypted JWTs don't match pattern.
// ❌ NOT DETECTED - Encrypted
localStorage.setItem('data', encrypt(jwt)) // Pattern broken
Mitigation: Still avoid localStorage for auth data.
| Category | ID |
|---|---|
| OWASP Top 10 2021 | A02:2021 - Cryptographic Failures |
| CWE | CWE-922 |
| CVSS | 8.1 (High) |
🔒 Disallow using wildcard (*) as targetOrigin in postMessage calls
CWE: CWE-693
OWASP Mobile: OWASP Mobile Top 10
This rule prevents using "*" as the targetOrigin parameter in postMessage() calls. Using a wildcard allows any window to receive the message, potentially leaking sensitive data to malicious sites.
When you use postMessage(data, "*"):
- Any window that has a reference to your window can receive the message
- Attackers can embed your site in an iframe and listen for messages
- Sensitive data (tokens, user info) can be exfiltrated
// Wildcard allows any window to receive the message
window.postMessage(userData, '*')
// Also dangerous with iframes
iframe.contentWindow.postMessage(authToken, '*')
// Parent window communication
parent.postMessage({ type: 'auth', token: jwt }, '*')
// Window opener
window.opener.postMessage(sensitiveData, '*')
// Options object with wildcard
window.postMessage(data, { targetOrigin: '*' })
// Specify the exact origin
window.postMessage(userData, 'https://trusted-domain.com')
// iframe with known origin
iframe.contentWindow.postMessage(authToken, 'https://embed.myapp.com')
// Parent window with specific origin
parent.postMessage({ type: 'auth', token: jwt }, 'https://parent.myapp.com')
// Use "/" for same-origin communication
window.postMessage(data, '/')
// Options object with specific origin
window.postMessage(data, { targetOrigin: 'https://trusted-domain.com' })
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | true | Skip this rule in *.test.* / *.spec.* files |
{
"browser-security/no-postmessage-wildcard-origin": [
"error",
{
"allowInTests": true
}
]
}
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | true | Skip checking in test files (_.test.ts, _.spec.ts) |
You may disable this rule if:
- Your application only sends non-sensitive public data via postMessage
- You're building a development tool where security isn't a concern
- You have other mechanisms to prevent cross-origin message interception
However, it's generally recommended to always specify the exact target origin.
- CWE-346: Origin Validation Error
- MDN: postMessage Security Concerns
browser-security/require-postmessage-origin-check- Validates origin on message receipt
The following patterns are not detected due to static analysis limitations:
Why: Origin stored in variables is not analyzed.
// ❌ NOT DETECTED - Origin from variable
const origin = '*'
window.postMessage(data, origin)
Mitigation: Never assign '*' to origin variables.
Why: Computed origins are not traced.
// ❌ NOT DETECTED - Dynamic construction
const target = getTargetOrigin() // May return '*'
iframe.contentWindow.postMessage(data, target)
Mitigation: Validate origin before postMessage.
Why: Custom postMessage wrappers not recognized.
// ❌ NOT DETECTED - Wrapper function
sendMessage(data, '*') // Uses postMessage internally
Mitigation: Apply rule to wrapper implementations.
| Category | ID |
|---|---|
| OWASP Top 10 2021 | A01:2021 - Broken Access Control |
| CWE | CWE-346 |
| CVSS | 7.5 (High) |
CWE: CWE-359
OWASP Mobile: M9: Insecure Data Storage
Detects storage of sensitive data (tokens, passwords, PII) in localStorage. This rule is part of eslint-plugin-browser-security.
| Aspect | Details |
|---|---|
| CWE Reference | CWE-922 (Insecure Storage of Sensitive Information) |
| Severity | 🔴 High |
| Auto-Fix | ❌ No (requires architecture change) |
| Category | Security |
| Best For | SPAs, frontend apps handling authentication |
Vulnerability: localStorage is accessible to any JavaScript running on the page. If an XSS vulnerability exists, attackers can steal tokens.
Risk:
- XSS attacks can steal all localStorage data
- Tokens persist beyond session (unlike cookies)
- No protection against malicious browser extensions
- Shared across tabs (potential for leakage)
// Storing tokens - VULNERABLE to XSS
localStorage.setItem('token', jwtToken)
localStorage.setItem('accessToken', response.accessToken)
localStorage.setItem('refreshToken', response.refreshToken)
// Storing sensitive user data - VULNERABLE
localStorage.setItem(
'user',
JSON.stringify({
email: 'user@example.com',
ssn: '123-45-6789',
password: 'secret',
}),
)
// Session storage has same issues
sessionStorage.setItem('authToken', token)
// Use httpOnly cookies for tokens (set by server)
// Server response:
// Set-Cookie: token=xxx; HttpOnly; Secure; SameSite=Strict
// Store only non-sensitive preferences
localStorage.setItem('theme', 'dark')
localStorage.setItem('language', 'en')
// For tokens that must be in JS, use memory-only storage
class TokenStore {
#token = null // Private field, not persisted
setToken(token) {
this.#token = token
}
getToken() {
return this.#token
}
}
// Or use a closure
const tokenStore = (() => {
let token = null
return {
set: (t) => {
token = t
},
get: () => token,
}
})()
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | false | Skip this rule in *.test.* / *.spec.* files |
sensitivePatterns | string[] | ["password","passwd","pwd","passphrase","secret","api key","apikey","private key","secret key","encryption key","signing key","client secret","credit card","card number","cvv","cvc","ssn","social security","seed phrase","mnemonic","recovery code"] | Key substrings considered sensitive |
checkSessionStorage | boolean | false | Also flag sessionStorage writes |
{
"rules": {
"browser-security/no-sensitive-localstorage": [
"error",
{
"sensitivePatterns": ["token", "password", "apiKey", "secret"]
}
]
}
}
// Backend sets the cookie
res.cookie('accessToken', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 3600000,
})
// Frontend just makes requests (cookie sent automatically)
fetch('/api/protected', {
credentials: 'include',
})
// Token disappears on page refresh (good for security)
let accessToken = null
function setToken(token) {
accessToken = token
}
function getToken() {
return accessToken
}
| Method | XSS Safe? | Persists? | Use For |
|---|---|---|---|
| httpOnly Cookie | ✅ Yes | ✅ Yes | Auth tokens |
| Memory variable | ✅ Yes | ❌ No | Temporary tokens |
| sessionStorage | ❌ No | Tab only | Non-sensitive data |
| localStorage | ❌ No | ✅ Yes | Only user preferences |
no-innerhtml- Prevent XSS that could access localStoragerequire-postmessage-origin-check- Cross-origin security
The following patterns are not detected due to static analysis limitations:
Why: Computed key names are not analyzed.
// ❌ NOT DETECTED - Dynamic key
const key = 'accessToken'
localStorage.setItem(key, value)
Mitigation: Use consistent naming, or add the substring to sensitivePatterns.
Why: Sensitive data inside objects may not be detected.
// ❌ NOT DETECTED - Nested sensitive data
localStorage.setItem('user', JSON.stringify({ token: jwt }))
Mitigation: Never store objects containing tokens.
Why: Custom storage wrappers are not recognized.
// ❌ NOT DETECTED - Wrapper function
storageHelper.save('token', jwt) // Uses localStorage internally
Mitigation: Apply rule to wrapper implementations.
Why: Different API not covered by this rule.
// ❌ NOT DETECTED - IndexedDB
db.put({ token: jwt })
Mitigation: Use no-sensitive-indexeddb rule.
Require Cookie Secure Attrs
Require Secure and SameSite attributes on cookies.
| Property | Value |
|---|---|
| CWE | CWE-614: Sensitive Cookie in HTTPS Session Without 'Secure' Attribute |
| OWASP | A05:2021 - Security Misconfiguration |
| CVSS | 6.5 (Medium) |
| Severity | MEDIUM |
Cookies without Secure can be transmitted over HTTP (man-in-the-middle attacks). Cookies without SameSite are vulnerable to CSRF attacks.
// Missing both attributes
document.cookie = 'name=value'
// Missing SameSite
document.cookie = 'name=value; Secure'
// Missing Secure
document.cookie = 'name=value; SameSite=Strict'
// Both attributes present
document.cookie = 'name=value; Secure; SameSite=Strict'
// Lax SameSite (allows top-level GET)
document.cookie = 'name=value; Secure; SameSite=Lax'
// Server-side (preferred)
res.cookie('name', 'value', {
secure: true,
sameSite: 'strict',
})
| Option | Type | Default | Description |
|---|---|---|---|
allowInTests | boolean | true | Skip this rule in *.test.* / *.spec.* files |
{
"rules": {
"browser-security/require-cookie-secure-attrs": [
"error",
{
"allowInTests": true
}
]
}
}
The following patterns are not detected due to static analysis limitations:
Why: Cookie values from variables not traced.
// ❌ NOT DETECTED - Cookie from variable
const cookie = 'name=value' // Missing attrs
document.cookie = cookie
Mitigation: Build cookie strings with attributes inline.
Why: Library methods not recognized.
// ❌ NOT DETECTED - Library wrapper
Cookies.set('name', 'value') // May not set Secure
Mitigation: Review cookie library configurations.
Why: Dynamic conditions not evaluated.
// ❌ NOT DETECTED - Conditional attributes
const attrs = isDev ? ' : '; Secure';
document.cookie = 'name=value' + attrs;
Mitigation: Always use secure attributes in production.
The rule provides LLM-optimized error messages (Compact 2-line format) with actionable security guidance:
⚠️ CWE-614 OWASP:A02 CVSS:5.3 | Sensitive Cookie in HTTPS without Secure detected | MEDIUM
Fix: Review and apply the recommended fix | https://owasp.org/Top10/A02_2021/
| Component | Purpose | Example |
|---|---|---|
| Risk Standards | Security benchmarks | CWE-614 OWASP:A02 CVSS:5.3 |
| Issue Description | Specific vulnerability | Sensitive Cookie in HTTPS without Secure detected |
| Severity & Compliance | Impact assessment | MEDIUM |
| Fix Instruction | Actionable remediation | Follow the remediation steps below |
| Technical Truth | Official reference | OWASP Top 10 |
