Lint rules
Every rule built into bun lint
bun lint has 1,021 rules built in. Turn one on in bunfig.toml, or read about it in the terminal with bun lint --explain <rule>.
ESLint 218 rules
accessor-pairs
Require a getter for every setter in objects and classes
const user = {
set nameGetter is not present for setter name.(value) {
this._name = value.trim();
},
};array-callback-returnhas suggestions
Require callbacks of array methods such as map and filter to return a value
const names = users.map(user =>Array.prototype.map() expects a return value from arrow function. {
user.name.trim();
});arrow-body-stylefixable
Enforce one style for the braces around arrow function bodies
const double = items.map(item => {Unexpected block statement surrounding arrow body; move the returned value immediately after the =>.
return item * 2;
});block-scoped-var
Disallow using a var outside the block it is declared in
function label(user) {
if (user.admin) {
var prefix = "Admin: ";
}
return prefixprefix declared on line 3 column 9 is used outside of binding context. + user.name; // "undefinedAda" for everyone else
}callback-returndeprecated
Require return when calling a callback
function load(path, callback) {
readFile(path, (err, text) => {
if (err) {
callback(err)Expected return with your callback function.;
}
callback(null, JSON.parse(text));
});
}camelcase
Require camelCase names for variables, functions and properties
const first_nameIdentifier first_name is not in camel case. = "Ada";
function full_nameIdentifier full_name is not in camel case.(user) {
return { display_nameIdentifier display_name is not in camel case.: user.first + " " + user.last };
}capitalized-commentsfixable
Require comments to start with a capital letter, or forbid it
// retry once, then give upComments should not begin with a lowercase character.
await send(message);class-methods-use-this
Require class methods to use this
class Cart {
formatPriceExpected this to be used by class method formatPrice.(cents) {
return "$" + (cents / 100).toFixed(2);
}
}complexity
Enforce a maximum number of branches in a function
function shippingFunction shipping has a complexity of 4. Maximum allowed is 3.(order) {
if (order.total > 100) {
return 0;
} else if (order.express) {
return 15;
} else if (order.country !== "US") {
return 25;
}
return 5;
}consistent-return
Require every return in a function to have a value, or none of them
function findUser(id) {
if (!id) {
return;
}
return users.get(id);Function findUser expected no return value.
}consistent-this
Enforce one name for variables that hold this
function Timer() {
const self = thisUnexpected alias self for this.;
setInterval(function () {
self.tick();
}, 1000);
}constructor-superon by default
Require super() to be called exactly once in the constructor of a derived class
class Admin extends User {
constructor(name) {Expected to call super().
this.name = name;
}
}curlyfixable
Require braces around the bodies of if, else and loops
if (!user) redirect("/login");Expected { after if condition.
for (const item of items) total += item.price;Expected { after for-of.default-case
Require a default case in every switch
switch (status) {Expected a default case.
case "open":
show();
break;
case "closed":
hide();
break;
}default-case-last
Require default to be the last clause of a switch
switch (level) {
default:Default clause should be the last clause.
log(message);
break;
case "error":
alert(message);
break;
}default-param-last
Require parameters with a default value to come after the required ones
function createUser(role = "member"Default parameters should be last., name) {
return { name, role };
}dot-notationfixable
Require user.name instead of user["name"]
const name = user["name"[name] is better written in dot notation.];eqeqeqfixable
Require === and !== instead of == and !=
if (count ==Expected === and instead saw ==. 0) {
reset();
}for-directionon by default
Require the update clause of a for loop to move the counter toward the end of the loop
for (let i = 0; i < items.length; i--)The update clause in this loop moves the variable in the wrong direction. {
render(items[i]);
}func-name-matching
Require a function expression's name to match the variable or property it is assigned to
const loadUser = function fetchUser(id) {Function name fetchUser should match variable name loadUser.
return db.users.get(id);
};
api.remove = function destroy(id) {}Function name destroy should match property name remove.;
const handlers = {
onSave: function save() {}Function name save should match property name onSave.,
};func-names
Require or disallow names for function expressions
button.addEventListener("click", function Unexpected unnamed function.() {
save();
});func-style
Enforce one way to define functions: declarations, or expressions assigned to variables
function formatName(user) {Expected a function expression.
return `${user.first} ${user.last}`;
}getter-returnon by default
Require getters to return a value
const user = {
get fullNameExpected to return a value in getter fullName.() {
`${this.first} ${this.last}`;
},
};global-requiredeprecated
Require require() calls to be at the top level of the module
function readConfig(path) {
const fs = require("fs")Unexpected require().;
return fs.readFileSync(path, "utf8");
}grouped-accessor-pairs
Require the getter and setter of a property to be next to each other
class User {
get name() {
return this.first;
}
save() {}
set nameAccessor pair getter name and setter name should be grouped.(value) {
this.first = value;
}
}guard-for-in
Require the body of a for...in loop to be guarded by an if
for (const key in settings) {The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.
apply(key, settings[key]);
}handle-callback-errdeprecated
Require callbacks to use their error parameter
fs.readFile(path, "utf8", function (err, text) {Expected error to be handled.
render(text);
});id-blacklistdeprecated
Disallow the identifier names you list (old name of id-denylist)
const dataIdentifier data is restricted. = loadUser(id);
function save(user, callbackIdentifier callback is restricted.) {}
const response = { dataIdentifier data is restricted.: 1 };
response.dataIdentifier data is restricted. = 2;id-denylist
Disallow the identifier names you list
const dataIdentifier data is restricted. = loadUser(id);
function save(user, callbackIdentifier callback is restricted.) {}
const response = { dataIdentifier data is restricted.: 1 };
response.dataIdentifier data is restricted. = 2;id-length
Enforce a minimum and a maximum length for identifier names
const nIdentifier name n is too short (< 2). = items.length;
const prices = items.map((xIdentifier name x is too short (< 2).) => x.price);
try {
save();
} catch (eIdentifier name e is too short (< 2).) {
report(e);
}
const point = { yIdentifier name y is too short (< 2).: 0 };id-match
Require identifier names to match a regular expression
const user_nameIdentifier user_name does not match the pattern ^[^_]+$. = "Ada";
function load_userIdentifier load_user does not match the pattern ^[^_]+$.(user_idIdentifier user_id does not match the pattern ^[^_]+$.) {}init-declarations
Require or disallow a value in variable declarations
let totalVariable total should be initialized on declaration.;
if (cart.items.length > 0) {
total = sum(cart.items);
}logical-assignment-operatorsfixable
Require or disallow the logical assignment operators ||=, &&= and ??=
options = options || {}Assignment (=) can be replaced with operator assignment (||=).;
cache = cache ?? new Map()Assignment (=) can be replaced with operator assignment (??=).;
enabled = enabled && isReady()Assignment (=) can be replaced with operator assignment (&&=).;
title || (title = "Untitled")Logical expression can be replaced with an assignment (||=).;
user.name = user.name || "guest"Assignment (=) can be replaced with operator assignment (||=).;max-classes-per-file
Enforce a maximum number of classes per file
class User {}File has too many classes (2). Maximum allowed is 1.
class Session {}max-depth
Enforce a maximum depth that blocks can be nested
function notify(users) {
for (const user of users) {
if (user.active) {
ifBlocks are nested too deeply (3). Maximum allowed is 2. (user.email) {
send(user.email);
}
}
}
}max-lines
Enforce a maximum number of lines per file
import { readFile } from "node:fs/promises";
const text = await readFile("notes.txt", "utf8");
console.log(text.split("\n").length);File has too many lines (7). Maximum allowed is 4.max-lines-per-function
Enforce a maximum number of lines in a function
function checkoutFunction checkout has too many lines (7). Maximum allowed is 5.(cart) {
let total = 0;
for (const item of cart.items) {
total += item.price * item.count;
}
charge(cart.user, total);
}max-nested-callbacks
Enforce a maximum depth that callbacks can be nested
readConfig((config) => {
connect(config, (db) => {
db.query("select 1", (rows) =>Too many nested callbacks (3). Maximum allowed is 2. {
console.log(rows);
});
});
});max-params
Enforce a maximum number of parameters in function definitions
function createUserFunction createUser has too many parameters (4). Maximum allowed is 3.(name, email, role, team) {
return { name, email, role, team };
}max-statements
Enforce a maximum number of statements in a function
function registerFunction register has too many statements (4). Maximum allowed is 3.(form) {
const user = parse(form);
validate(user);
save(user);
sendWelcomeMail(user);
}new-cap
Require constructor names to begin with a capital letter
const user = new personA constructor name should not start with a lowercase letter.("Ada");
const team = TeamA function with a name starting with an uppercase letter should only be used as a constructor.("core");no-alert
Disallow alert, confirm, and prompt
alert("Saved")Unexpected alert.;
if (window.confirm("Delete this file?")Unexpected confirm.) {
remove(file);
}no-array-constructorfixable
Disallow Array constructors in favor of array literals
const empty = new Array()The array literal notation [] is preferable.;
const sizes = Array(10, 20, 30)The array literal notation [] is preferable.;no-async-promise-executoron by default
Disallow an async function as a Promise executor
const user = new Promise(asyncPromise executor functions should not be async. (resolve) => {
const response = await fetch("/api/user");
resolve(await response.json());
});no-await-in-loop
Disallow await inside of loops
async function loadAll(urls) {
const pages = [];
for (const url of urls) {
pages.push(await fetch(url)Unexpected await inside a loop.);
}
return pages;
}no-bitwise
Disallow bitwise operators
if (user.active & user.verifiedUnexpected use of &.) {
grant(user);
}no-buffer-constructordeprecated
Disallow the deprecated Buffer() constructor
const header = new Buffer(16)new Buffer() is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead.;
const name = Buffer("Ada", "utf8")Buffer() is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead.;no-caller
Disallow arguments.caller and arguments.callee
const factorial = function (n) {
return n <= 1 ? 1 : n * arguments.calleeAvoid arguments.callee.(n - 1);
};no-case-declarationson by defaulthas suggestions
Disallow let, const, function and class declarations directly in a case clause
switch (action.type) {
case "add":
const item = action.item;Unexpected lexical declaration in case block.
list.push(item);
break;
default:
list.length = 0;
}no-catch-shadowdeprecated
Disallow a catch parameter with the name of a variable in the scope around it
const error = loadLastError();
try {
save();
} catch (error) {Value of error may be overwritten in IE 8 and earlier.
console.log(error);
}no-class-assignon by default
Disallow assigning to the name of a class
class Logger {}
LoggerLogger is a class. = createLogger();no-compare-neg-zeroon by defaulthas suggestions
Disallow comparing against -0
if (offset === -0Do not use the === operator to compare against -0.) {
flipDirection();
}no-cond-assignon by default
Disallow assignments in the test of if, loops and ? :
if (user.role = "admin"Expected a conditional expression and instead saw an assignment.) {
showSettings();
}no-consolehas suggestions
Disallow the use of console
console.logUnexpected console statement.("loaded", items.length);
items.forEach(console.warnUnexpected console statement.);no-const-assignon by default
Disallow reassigning const, using and await using variables
const retries = 0;
function onFailure() {
retriesretries is constant. += 1;
}
function reset() {
retriesretries is constant. = 0;
}no-constant-binary-expressionon by default
Disallow comparisons and &&, ||, ?? that always give the same result
const label = "Hello, " + user.nameUnexpected constant nullishness on the left-hand side of a ?? expression. ?? "guest";
if (items === []Unexpected comparison to newly constructed object. These two values can never be equal.) {
showEmptyState();
}
const isMissing = !user.emailUnexpected constant binary expression. Compares constantly with the right-hand side of the ===. === undefined;no-constant-conditionon by default
Disallow conditions that are always truthy or always falsy
if (role === "admin" || "owner"Unexpected constant condition.) {
showSettings();
}
const title = ""Unexpected constant condition. ? "Draft" : "Published";no-constructor-return
Disallow returning a value from a constructor
class Connection {
constructor(url) {
if (cache.has(url)) {
return cache.get(url);Unexpected return statement in constructor.
}
this.url = url;
}
}no-continue
Disallow continue statements
for (const user of users) {
if (!user.active) {
continue;Unexpected use of continue statement.
}
notify(user);
}no-control-regexon by default
Disallow control characters in regular expressions
const lineBreak = /\x0A/Unexpected control character(s) in regular expression: \x0a.;
const separator = new RegExp("\x1F"Unexpected control character(s) in regular expression: \x1f.);no-debuggeron by default
Disallow debugger statements
function total(items) {
debugger;Unexpected debugger statement.
return items.reduce((sum, item) => sum + item.price, 0);
}no-delete-varon by default
Disallow delete on variables
var session = createSession();
delete sessionVariables should not be deleted.;no-div-regexfixable
Disallow regular expressions that start with =
const value = line.replace(/=\s*/A regular expression literal can be confused with /=., "");no-dupe-argson by default
Disallow two parameters with the same name in a function
function resize(width, height, width)Duplicate param width. {
return width * height;
}no-dupe-class-memberson by default
Disallow two class members with the same name
class Cart {
total() {
return this.items.length;
}
totalDuplicate name total.() {
return this.items.reduce((sum, item) => sum + item.price, 0);
}
}no-dupe-else-ifon by default
Disallow an else if with a condition that an earlier branch already covers
if (status === "active") {
enable();
} else if (status === "paused") {
pause();
} else if (status === "active"This branch can never execute. Its condition is a duplicate or covered by previous conditions in the if-else-if chain.) {
archive();
}no-dupe-keyson by default
Disallow two keys with the same name in an object literal
const options = {
timeout: 1000,
retries: 3,
timeoutDuplicate key timeout.: 5000,
};no-duplicate-caseon by default
Disallow two case labels with the same test in a switch
switch (event.key) {
case "ArrowUp":
moveUp();
break;
case "ArrowDown":
moveDown();
break;
case "ArrowUp":Duplicate case label.
selectPrevious();
break;
}no-duplicate-imports
Disallow importing from the same module more than once in a file
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { writeFile } from "node:fs/promises";node:fs/promises import is duplicated.no-else-returnfixable
Disallow else after an if branch that always returns
function label(user) {
if (user.admin) {
return "Admin";
} else {Unnecessary else after return.
return "Member";
}
}no-emptyon by defaulthas suggestions
Disallow empty block statements
try {
cache.clear();
} catch (error) {}Empty block statement.no-empty-character-classon by default
Disallow empty character classes [] in regular expressions
const isHexColor = /^#[]{6}$/Empty class..test(color);no-empty-functionhas suggestions
Disallow functions with an empty body
function onClose() {}Unexpected empty function onClose.
const handlers = {
onError: () => {}Unexpected empty method onError.,
};no-empty-patternon by default
Disallow destructuring patterns that bind nothing
const { settings: {}Unexpected empty object pattern. } = user;
const []Unexpected empty array pattern. = items;
function render([]Unexpected empty array pattern.) {
return "<div></div>";
}no-empty-static-blockon by defaulthas suggestions
Disallow empty static {} blocks in classes
class Config {
static values = new Map();
static {}Unexpected empty static block.
}no-eq-null
Disallow comparing with null using == or !=
if (user == nullUse === to compare with null.) {
redirect("/login");
}
while (node != nullUse === to compare with null.) {
node = node.next;
}no-eval
Disallow eval()
const settings = evaleval can be harmful.("(" + text + ")");no-ex-assignon by default
Disallow assigning to the error parameter of a catch clause
try {
upload(file);
} catch (error) {
errorDo not assign to the exception parameter. = new Error("Upload failed");
report(error);
}no-extend-native
Disallow adding properties to the prototypes of built-in objects
Array.prototype.last = function () {Array prototype is read only, properties should not be added.
return this[this.length - 1];
};
Object.defineProperty(String.prototype, "shout", {String prototype is read only, properties should not be added.
value() {
return this.toUpperCase() + "!";
},
});no-extra-bindfixable
Disallow .bind() on functions that do not use this
const greet = function () {
return "Hello";
}.bindThe function binding is unnecessary.(user);
const log = (() => {
console.log(this.name);
}).bindThe function binding is unnecessary.(user);no-extra-boolean-caston by defaultfixable
Disallow !! and Boolean() where the value is converted to a boolean anyway
if (!!userRedundant double negation.) {
greet(user);
}
const label = Boolean(count)Redundant Boolean call. ? "some" : "none";
const isEmpty = !Boolean(items.length)Redundant Boolean call.;no-extra-labelfixable
Disallow labels on break and continue that name the innermost loop or switch
search: for (const item of items) {
if (item.id === id) {
select(item);
break searchThis label search is unnecessary.;
}
}no-fallthroughon by default
Disallow case clauses that run on into the next one
switch (post.status) {
case "draft":
publish(post);
case "published":Expected a break statement before case.
notify(post);
break;
}no-func-assignon by default
Disallow assigning to the name of a function declaration
function format(value) {
return String(value);
}
formatformat is a function. = value => value.toFixed(2);no-global-assignon by default
Disallow assigning to built-in and other read-only global variables
PromiseRead-only global Promise should not be modified. = loadPromiseLibrary();
windowRead-only global window should not be modified. = createFakeWindow();
statusRead-only global status should not be modified. = "ready";no-implicit-coercionfixable
Require Boolean(), Number() and String() instead of operator tricks like !!x and +x
const hasItems = !!items.lengthUnexpected implicit coercion encountered. Use Boolean(items.length) instead.;
const isMember = ~names.indexOf(user.name)Unexpected implicit coercion encountered. Use names.indexOf(user.name) !== -1 instead.;
const quantity = +input.valueUnexpected implicit coercion encountered. Use Number(input.value) instead.;
const price = 1 * row.priceUnexpected implicit coercion encountered. Use Number(row.price) instead.;
const id = "" + user.idUnexpected implicit coercion encountered. Use String(user.id) instead.;
label += ""Unexpected implicit coercion encountered. Use label = String(label) instead.;no-implicit-globals
Disallow declarations and assignments that create global variables by accident
var config = { debug: false }Unexpected var declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.;
function start() {Unexpected function declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.
console.log(config.debug);
}no-implied-eval
Disallow passing a string of code to setTimeout(), setInterval() or execScript()
setTimeout("refresh()", 1000)Implied eval. Consider passing a function instead of a string.;
setInterval("poll('" + url + "')", 5000)Implied eval. Consider passing a function instead of a string.;
window.setTimeout(`save(${id})`, 0)Implied eval. Consider passing a function instead of a string.;no-import-assignon by default
Disallow assigning to imported bindings
import config, { retries } from "./config.js";
import * as settings from "./settings.js";
config = {}config is read-only.;
retries += 1retries is read-only.;
settings.debug = trueThe members of settings are read-only.;
delete settings.debugThe members of settings are read-only.;
Object.assign(settings, { debug: true })The members of settings are read-only.;no-inline-comments
Disallow comments on the same line as code
const retries = 3; // the network is flakyUnexpected comment inline with code.
const timeout = /* seconds */Unexpected comment inline with code. 30;
/* legacy */Unexpected comment inline with code. connect(retries, timeout);no-inner-declarations
Disallow function and, optionally, var declarations in nested blocks
if (process.env.DEBUG) {
function log(message) {Move function declaration to program root.
console.log(message);
}
}
log("ready"); // TypeError unless DEBUG is setno-invalid-regexpon by default
Disallow invalid patterns and flags in RegExp() and new RegExp()
const letters = new RegExp("[a-z")Invalid regular expression: /[a-z/: Unterminated character class.;
const digits = RegExp("(\\d+")Invalid regular expression: /(\d+/: Unterminated group.;
const twice = new RegExp("\\d+", "gg")Duplicate flags (g) supplied to RegExp constructor.;
const unknown = new RegExp("\\d+", "x")Invalid flags supplied to RegExp constructor x.;no-invalid-this
Disallow this where its value is undefined
export const root = thisUnexpected this.;
function describe() {
return thisUnexpected this..name;
}
class Cart {
total = 0;
add(items) {
items.forEach(function (item) {
thisUnexpected this..total += item.price;
});
}
}no-irregular-whitespaceon by default
Disallow whitespace characters other than the ordinary space, tab and line break
const total = Irregular whitespace not allowed.price * quantity;no-iterator
Disallow the __iterator__ property
Playlist.prototype.__iterator__Reserved name __iterator__. = function () {
return new SongIterator(this);
};
const iterate = playlist["__iterator__"]Reserved name __iterator__.;no-label-var
Disallow labels that share a name with a variable
const rows = table.rows;
rows: for (const row of rows) {Found identifier with same name as label.
for (const cell of row.cells) {
if (cell.isEmpty) continue rows;
}
print(row);
}no-labels
Disallow labeled statements
let found;
search: for (const row of rows) {Unexpected labeled statement.
for (const cell of row.cells) {
if (cell.value === target) {
found = cell;
break search;Unexpected labeled statement.Unexpected label in break statement.
}
}
}no-lone-blocks
Disallow blocks that do nothing
{Block is redundant.
loadConfig();
}
function start() {
connect();
{Nested block is redundant.
listen();
}
}no-lonely-iffixable
Require else if instead of an if alone in an else block
if (user.isAdmin) {
showAdminPage();
} else {
if (user.isMember) {Unexpected if as the only statement in an else block.
showMemberPage();
} else {
showLogin();
}
}no-loop-func
Disallow functions in loops that use a variable the loop changes
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function () {Function declared in a loop contains unsafe references to variable(s) i.
select(i); // always buttons.length
};
}no-loss-of-precisionon by default
Disallow number literals that cannot be stored exactly as written
const orderId = 9007199254740993This number literal will lose precision at runtime.;
const mask = 0x20000000000001This number literal will lose precision at runtime.;
const huge = 1e400This number literal will lose precision at runtime.;
const tiny = 1e-400This number literal will lose precision at runtime.;no-magic-numbers
Require numbers to be given a name instead of being used directly
function priceWithTax(price) {
return price * 1.2No magic number: 1.2.;
}
setInterval(refresh, 60000No magic number: 60000.);no-misleading-character-classon by defaulthas suggestions
Disallow characters made of several code points inside a regular expression character class
const isThumbsUp = /^[👍Unexpected surrogate pair in character class. Use u flag.]$/;no-mixed-requiresdeprecated
Disallow require calls mixed with other variables in one declaration
const fs = require("fs"),Do not mix require and other declarations.
retries = 3;no-multi-assign
Disallow chained assignments such as a = b = c
const width = height = 100Unexpected chained assignment.;no-multi-str
Disallow strings that continue on the next line with a backslash
const help = "Usage: deploy <target> \Multiline support is limited to browsers supporting ES5 only.
--dry-run";no-native-reassigndeprecated
Disallow assignments to built-in and other read-only global variables
PromiseRead-only global Promise should not be modified. = require("bluebird");no-negated-condition
Disallow negated conditions in if/else and in ternaries
if (!user) {Unexpected negated condition.
redirect("/login");
} else {
render(user);
}no-negated-in-lhsdeprecated
Disallow negating the left operand of in
if (!key in cacheThe in expression's left operand is negated.) {
cache[key] = load(key);
}no-nested-ternary
Disallow a ternary inside the branch of another ternary
const size = width > 1200 ? "large" : width > 600 ? "medium" : "small"Do not nest ternary expressions.;no-new
Disallow new when the created object is thrown away
new Router(routes);Do not use new for side effects.no-new-func
Disallow creating functions from strings with Function
const add = new Function("a", "b", "return a + b")The Function constructor is eval.;no-new-native-nonconstructoron by default
Disallow new Symbol() and new BigInt()
const id = new SymbolSymbol cannot be called as a constructor.("id");no-new-objectdeprecated
Disallow new Object() in favor of {}
const headers = new Object()The object literal notation {} is preferable.;
headers.accept = "text/html";no-new-requiredeprecated
Disallow new require(..)
const server = new require("./server")Unexpected use of new with require.;no-new-symboldeprecated
Disallow new Symbol()
const id = new SymbolSymbol cannot be called as a constructor.("id");no-new-wrappers
Disallow new String(), new Number() and new Boolean()
const enabled = new Boolean(false)Do not use Boolean as a constructor.;
if (enabled) {
start();
}no-nonoctal-decimal-escapeon by defaulthas suggestions
Disallow the \8 and \9 escapes in strings
const version = "v\8Don't use \8 escape sequence..0";no-obj-callson by default
Disallow calling Math, JSON, Reflect, Atomics, Intl and Temporal as functions
const random = Math()Math is not a function.;no-object-constructorhas suggestions
Disallow Object() and new Object() without an argument
const cache = new Object()The object literal notation {} is preferable.;
const totals = Object()The object literal notation {} is preferable.;no-octalon by default
Disallow number literals that start with 0 followed by a digit
const mode = 0755Octal literals should not be used.;
const month = 09Octal literals should not be used.;no-octal-escape
Disallow octal escape sequences such as \251 in strings
const notice = "Copyright \251 2026"Don't use octal: \251. Use \u.... instead.;no-param-reassign
Disallow assigning to function parameters
function total(price, count) {
priceAssignment to function parameter price. = price * 1.2;
countAssignment to function parameter count.++;
return price * count;
}no-path-concatdeprecated
Disallow building paths with + from __dirname or __filename
const configPath = __dirname + "/config.json"Use path.join() or path.resolve() instead of + to create paths.;no-plusplus
Disallow the ++ and -- operators
let retries = 0;
retries++Unary operator ++ used.;
const next = queue[--positionUnary operator -- used.];no-process-envdeprecated
Disallow process.env
function connect() {
return open(process.envUnexpected use of process.env..DATABASE_URL);
}no-process-exitdeprecated
Disallow calls to process.exit()
function loadConfig(path) {
if (!exists(path)) {
console.error("No config file");
process.exit(1)Don't use process.exit(); throw an error instead.;
}
return read(path);
}no-promise-executor-returnhas suggestions
Disallow returning a value from the function passed to new Promise
function readCached(key) {
return new Promise((resolve, reject) => {
if (cache.has(key)) {
return cache.get(key);Return values from promise executor functions cannot be read.
}
load(key, resolve, reject);
});
}
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)Return values from promise executor functions cannot be read.);no-proto
Disallow the __proto__ property
const parent = user.__proto__The __proto__ property is deprecated.;
user["__proto__"]The __proto__ property is deprecated. = admin;no-prototype-builtinson by defaulthas suggestions
Disallow calling hasOwnProperty and other Object.prototype methods on the object itself
const data = JSON.parse(body);
if (data.hasOwnPropertyDo not access Object.prototype method hasOwnProperty from target object.("id")) {
update(data.id);
}
const isChild = base.isPrototypeOfDo not access Object.prototype method isPrototypeOf from target object.(data);no-redeclareon by default
Disallow declaring the same variable twice in one scope
function summarize(items, label) {
var total = items.length;
var labellabel is already defined. = label || "items";
for (const item of items) {
var totaltotal is already defined. = item.price;
}
return `${total} ${label}`;
}no-regex-spaceson by defaultfixable
Disallow several spaces in a row in a regular expression
const line = /error code/Spaces are hard to count. Use {3}.;
const header = new RegExp("name value")Spaces are hard to count. Use {2}.;no-restricted-exports
Disallow the export names you list, or default exports
export function thenthen is restricted from being used as an exported name.(callback) {
callback("ready");
}no-restricted-globals
Disallow the global variables you list
button.addEventListener("click", () => {
fetchUnexpected use of fetch. Use the client in api.js.("/track?id=" + eventUnexpected use of event..target.id);
});no-restricted-imports
Disallow importing the modules, or the names from them, that you list
import moment from "moment";moment import is restricted from being used.
export { merge } from "lodash";lodash import is restricted from being used. Use lodash-es.no-restricted-modulesdeprecated
Disallow loading the modules you list with require()
const fs = require("fs")fs module is restricted from being used.;
const request = require("request")request module is restricted from being used. Use fetch().;no-restricted-properties
Disallow the object properties you list
const area = Math.powMath.pow is restricted from being used. Use the ** operator.(side, 2);
const volume = Math["pow"]Math.pow is restricted from being used. Use the ** operator.(side, 3);
const { pow }Math.pow is restricted from being used. Use the ** operator. = Math;no-restricted-syntax
Disallow the syntax you describe with selectors
for (const key in settings) {Using ForInStatement is not allowed.
apply(key, settings[key]);
}no-return-assign
Disallow assignments in return statements
function setName(user, name) {
return user.name = name;Return statement should not contain assignment.
}
const select = (item) => selected = itemArrow function should not return assignment.;no-return-awaithas suggestionsdeprecated
Disallow return await where the await changes nothing for the caller
async function getUser(id) {
return await db.find(id)Redundant use of await on a return value.;
}
const getItems = async () => await db.all()Redundant use of await on a return value.;no-script-url
Disallow javascript: URLs
link.href = "javascript:void(0)"Script URL is a form of eval.;
location.href = `JavaScript:save()`Script URL is a form of eval.;no-self-assignon by default
Disallow assigning a variable or a property to itself
function rename(user, name) {
name = namename is assigned to itself.;
user.name = user.nameuser.name is assigned to itself.;
[user, name] = [useruser is assigned to itself., "guest"];
}no-self-compare
Disallow comparing a value with itself
function isSameUser(user, other) {
return user.id === user.idComparing to itself is potentially pointless.;
}no-sequences
Disallow the comma operator
count++,Unexpected use of comma operator. save();
while (item = queue.shift(),Unexpected use of comma operator. item) {
handle(item);
}no-setter-returnon by default
Disallow returning a value from a setter
class User {
set name(value) {
this._name = value;
return this;Setter cannot return a value.
}
}
Object.defineProperty(config, "port", {
set(value) {
return Number(value);Setter cannot return a value.
},
});no-shadow
Disallow a variable with the same name as one in an outer scope
const user = currentUser();
function greet(useruser is already declared in the upper scope on line 1 column 7.) {
return `Hello, ${user.name}`;
}no-shadow-restricted-nameson by default
Disallow variables named undefined, NaN, Infinity, eval, arguments or globalThis
const undefinedShadowing of global property undefined. = "none";
function toNumber(text, NaNShadowing of global property NaN.) {
return text === "" ? NaN : Number(text);
}no-sparse-arrayson by default
Disallow array literals with holes
const colors = ["red",,Unexpected comma in middle of array. "blue"];no-syncdeprecated
Disallow methods with a name that ends in Sync
import fs from "node:fs";
function handle(request) {
const page = fs.readFileSyncUnexpected sync method: readFileSync.("index.html", "utf8");
return new Response(page);
}no-template-curly-in-string
Disallow ${} placeholders in strings with ordinary quotes
const greeting = "Hello, ${user.name}!"Unexpected template string expression.;no-ternary
Disallow the ? : operator
const label = count === 1 ? "item" : "items"Ternary operator used.;no-this-before-superon by default
Disallow this and super.x before super() in a constructor
class Admin extends User {
constructor(name) {
thisthis is not allowed before super()..role = "admin";
supersuper is not allowed before super()..validate(name);
super(name);
}
}no-throw-literal
Require throw to be given something that can be an Error
function find(id) {
if (!id) throw "id is required";Expected an error object to be thrown.
if (id < 0) throw { code: 400 };Expected an error object to be thrown.
if (id > max) throw "no such id: " + id;Expected an error object to be thrown.
throw undefined;Do not throw undefined.
}no-unassigned-varson by default
Disallow let and var variables that are read but never assigned
let statusstatus is always undefined because it's never assigned.;
if (status === "ready") {
start();
}no-undefon by default
Disallow variables that are not declared anywhere
function total(items) {
return items.reduce((sum, item) => sum + item.price, 0) * taxRatetaxRate is not defined.;
}no-undef-initfixable
Disallow initializing a variable to undefined
let selected = undefinedIt's not necessary to initialize selected to undefined.;no-undefined
Disallow the use of undefined as a variable
let selected = undefinedUnexpected use of undefined.;
if (selected === undefinedUnexpected use of undefined.) {
selected = first();
}no-underscore-dangle
Disallow names that start or end with an underscore
const _cache = new Map()Unexpected dangling _ in _cache.;
function load_(id) {Unexpected dangling _ in load_.
return user._idUnexpected dangling _ in load_.Unexpected dangling _ in _id. === id;
}no-unexpected-multilineon by default
Disallow lines that look like a new statement but continue the previous one
const name = getName()
[Unexpected newline between object and [ of property access.first, last].forEach(print)no-unmodified-loop-condition
Disallow loop conditions that test variables the loop never changes
let node = list.head;
while (nodenode is not modified in this loop.) {
print(node.value);
list.next();
}no-unneeded-ternaryfixable
Disallow ? : where the condition alone says the same
const isAdult = age >= 18 ? true : falseUnnecessary use of boolean literals in conditional expression.;
const isGuest = user ? false : trueUnnecessary use of boolean literals in conditional expression.;no-unreachableon by default
Disallow code after return, throw, break, and continue that can never run
function price(item) {
return item.price * item.quantity;
console.log("computed", item.id);Unreachable code.
}no-unreachable-loop
Disallow loops with a body that can only run once
function firstError(results) {
for (const result of results) {Invalid loop. Its body allows only one iteration.
if (result.ok) {
return null;
} else {
return result.error;
}
}
}no-unsafe-finallyon by default
Disallow return, throw, break, and continue in finally blocks
function load() {
try {
return readConfig();
} finally {
return {};Unsafe usage of ReturnStatement.
}
}no-unsafe-negationon by defaulthas suggestions
Disallow negating the left operand of in and instanceof
if (!keyUnexpected negating the left operand of in operator. in cache) {
cache[key] = load(key);
}
if (!errorUnexpected negating the left operand of instanceof operator. instanceof TypeError) {
throw error;
}no-unsafe-optional-chainingon by default
Disallow optional chaining where undefined throws an error
const { name } = user?.profileUnsafe usage of optional chaining. If it short-circuits with undefined the evaluation will throw TypeError.;
for (const item of order?.itemsUnsafe usage of optional chaining. If it short-circuits with undefined the evaluation will throw TypeError.) {
ship(item);
}
const all = [...order?.itemsUnsafe usage of optional chaining. If it short-circuits with undefined the evaluation will throw TypeError.];
const data = (await response?.json()Unsafe usage of optional chaining. If it short-circuits with undefined the evaluation will throw TypeError.).data;no-unused-expressions
Disallow statements that compute a value and throw it away
server.close;Expected an assignment or function call and instead saw an expression.
count === 0;Expected an assignment or function call and instead saw an expression.
`Hello, ${name}`;Expected an assignment or function call and instead saw an expression.no-unused-labelson by defaultfixable
Disallow labels that no break or continue refers to
rowsrows: is defined but never used.: for (const row of table) {
for (const cell of row) {
if (cell.isEmpty) break;
}
}no-unused-private-class-memberson by defaulthas suggestions
Disallow #private class members that are never used
class Counter {
#count#count is defined but never used. = 0;
#step#step is defined but never used. = 1;
#log#log is defined but never used.() {
console.log("changed");
}
increment() {
this.#count++;
}
}no-unused-varson by defaulthas suggestions
Disallow variables, functions, imports, and parameters that are never used
import { readFilereadFile is defined but never used., writeFile } from "node:fs/promises";
let savedsaved is assigned a value but never used. = 0;
async function savesave is defined but never used.(path, data, optionsoptions is defined but never used.) {
await writeFile(path, data);
saved++;
}no-use-before-define
Disallow using a variable, function, or class above the line that declares it
console.log(limitlimit was used before it was defined.);
const limit = 10;
startstart was used before it was defined.();
function start() {}
const total = totaltotal was used before it was defined. + 1;no-useless-assignmenton by default
Disallow assigning a value to a variable that is never read afterwards
function label(user) {
let nameThe value assigned to name is not used in subsequent statements. = "guest";
name = user.name;
return name;
}no-useless-backreferenceon by default
Disallow regular expression backreferences that always match the empty string
const quoted = /\1.*?(["'])/Backreference \1 will be ignored. It references group (["])' which appears later in the pattern.;
const either = /(a)|\1b/Backreference \1 will be ignored. It references group (a) which is in another alternative.;
const doubled = new RegExp("(\\w\\1)")Backreference \1 will be ignored. It references group (\w\1) from within that group.;
const notQuoted = /(?!(["']))\w+\1/Backreference \1 will be ignored. It references group (["])' which is in a negative lookaround.;no-useless-call
Disallow .call() and .apply() where a normal call does the same
format.call(undefined, value, locale)Unnecessary .call().;
format.apply(null, [value, locale])Unnecessary .apply().;
logger.write.call(logger, message)Unnecessary .call().;no-useless-catchon by default
Disallow catch clauses that only rethrow the error
try {Unnecessary try/catch wrapper.
await save(user);
} catch (error) {
throw error;
}no-useless-computed-keyfixable
Disallow brackets around a property key that is a plain string or number
const headers = { ["content-type"]: "text/plain"Unnecessarily computed property [content-type] found., [0]: "first"Unnecessarily computed property [0] found. };
const { ["content-type"]: typeUnnecessarily computed property [content-type] found. } = headers;
class Cache {
["clear"]() {}Unnecessarily computed property [clear] found.
}no-useless-concat
Disallow joining two string literals with + on the same line
const greeting = "Hello, " +Unexpected string concatenation of literals. "world";
const path = base + "/api" +Unexpected string concatenation of literals. `/users`;no-useless-constructorhas suggestions
Disallow constructors that do what the default constructor does
class Store {
constructorUseless constructor.() {}
}
class UserStore extends Store {
constructorUseless constructor.(name, options) {
super(name, options);
}
}no-useless-escapeon by defaulthas suggestions
Disallow backslashes that do not change the meaning of a string or regular expression
const quote = "It\Unnecessary escape character: \'.'s here";
const digits = new RegExp("\Unnecessary escape character: \d.d+");
const price = `\Unnecessary escape character: \#.#${amount}`;
const email = /[\w\Unnecessary escape character: \...]+\Unnecessary escape character: \@.@example\.com/;no-useless-renamefixable
Disallow renaming an import, export, or destructured value to the same name
import { load as loadImport load unnecessarily renamed. } from "./store.js";
const { id: idDestructuring assignment id unnecessarily renamed. } = load();
export { id as idExport id unnecessarily renamed. };no-useless-returnfixable
Disallow a return without a value where the function ends anyway
function reset(form) {
form.clear();
return;Unnecessary return statement.
}
function notify(user) {
if (user.muted) {
return;Unnecessary return statement.
} else {
send(user);
}
}no-varon by defaultfixable
Require let or const instead of var
for (var i = 0Unexpected var, use let or const instead.; i < 3; i++) {
setTimeout(() => console.log(i)); // 3, 3, 3
}no-void
Disallow the void operator
let selected = void 0Expected undefined and instead saw void.;
button.onclick = () => void save()Expected undefined and instead saw void.;no-warning-comments
Disallow comments that start with TODO, FIXME, or another term you choose
// TODO: handle an empty listUnexpected todo comment: TODO: handle an empty list.
function total(items) {
/* fixme: rounding */Unexpected fixme comment: fixme: rounding.
return items.length * 1.1;
}no-withon by default
Disallow with statements
withUnexpected use of with statement. (point) {
distance = Math.sqrt(x * x + y * y);
}object-shorthandfixable
Require or disallow the short forms { name } and { save() {} } in object literals
const user = {
name: nameExpected property shorthand.,
save: function () {Expected method shorthand.
store(this);
},
};one-varfixable
Require variables to be declared in one statement, or in one statement each
function area(shape) {
const width = shape.width;
const height = shape.height;Combine this with the previous const statement.
return width * height;
}operator-assignmentfixable
Require or disallow shorthand assignment operators such as +=
total = total + priceAssignment (=) can be replaced with operator assignment (+=).;
user.visits = user.visits + 1Assignment (=) can be replaced with operator assignment (+=).;
area = width * areaAssignment (=) can be replaced with operator assignment (*=).;prefer-arrow-callbackfixable
Require arrow functions for callbacks
const prices = items.map(function (item) {Unexpected function expression.
return item.price;
});
button.addEventListener("click", function () {Unexpected function expression.
this.save();
}.bind(this));prefer-conston by defaultfixable
Require const for variables that are never reassigned
let totaltotal is never reassigned. Use const instead. = items.length;
console.log(total);
for (let itemitem is never reassigned. Use const instead. of items) {
console.log(item);
}
let label;
labellabel is never reassigned. Use const instead. = "Total";prefer-destructuringfixable
Require destructuring to read a property or an element into a variable
const name = user.nameUse object destructuring.;
const first = items[0]Use array destructuring.;
email = user.emailUse object destructuring.;prefer-exponentiation-operatorfixable
Require the ** operator instead of Math.pow()
const area = Math.PI * Math.pow(radius, 2)Use the ** operator instead of Math.pow.;
const distance = Math.sqrt(Math.pow(dx, 2)Use the ** operator instead of Math.pow. + Math.pow(dy, 2)Use the ** operator instead of Math.pow.);prefer-named-capture-grouphas suggestions
Require capture groups in regular expressions to have a name
const date = /(\d{4})-(\d{2})/Capture group (\d{4}) should be converted to a named or non-capturing group.Capture group (\d{2}) should be converted to a named or non-capturing group..exec(input);
const year = date[1];
const version = new RegExp("v([0-9]+)")Capture group ([0-9]+) should be converted to a named or non-capturing group.;prefer-numeric-literalsfixable
Require binary, octal and hexadecimal literals instead of parseInt() on a fixed string
const mask = parseInt("1010", 2)Use binary literals instead of parseInt().;
const mode = Number.parseInt("644", 8)Use octal literals instead of Number.parseInt().;
const color = parseInt("ff8800", 16)Use hexadecimal literals instead of parseInt().;prefer-object-has-ownfixable
Require Object.hasOwn() instead of Object.prototype.hasOwnProperty.call()
if (Object.prototype.hasOwnProperty.call(user, "email")Use Object.hasOwn() instead of Object.prototype.hasOwnProperty.call().) {
send(user.email);
}
const isSet = {}.hasOwnProperty.call(settings, key)Use Object.hasOwn() instead of Object.prototype.hasOwnProperty.call().;
const isCached = Object.hasOwnProperty.call(cache, id)Use Object.hasOwn() instead of Object.prototype.hasOwnProperty.call().;prefer-object-spreadfixable
Require object spread instead of Object.assign() into a new object
const settings = Object.assign({}, defaults, options)Use an object spread instead of Object.assign eg: { ...foo }.;
const admin = Object.assign({ role: "admin" }, user)Use an object spread instead of Object.assign eg: { ...foo }.;
const point = Object.assign({ x: 0, y: 0 })Use an object literal instead of Object.assign. eg: { foo: bar }.;prefer-promise-reject-errors
Require an Error object as the reason of a promise rejection
Promise.reject("User not found")Expected the Promise rejection reason to be an Error.;
new Promise((resolve, reject) => {
reject({ code: 404 })Expected the Promise rejection reason to be an Error.;
});prefer-reflectdeprecated
Require Reflect methods instead of their older Object and Function forms
save.apply(user, args)Avoid using Function.prototype.apply, instead use Reflect.apply.;
save.call(user, "draft")Avoid using Function.prototype.call, instead use Reflect.apply.;
Object.defineProperty(user, "id", { value: 1 })Avoid using Object.defineProperty, instead use Reflect.defineProperty.;
delete user.emailAvoid using the delete keyword, instead use Reflect.deleteProperty.;prefer-regex-literalshas suggestions
Require regular expression literals instead of RegExp() with a fixed pattern
const digits = new RegExp("\\d+")Use a regular expression literal instead of the RegExp constructor.;
const word = RegExp("hello", "gi")Use a regular expression literal instead of the RegExp constructor.;
const path = new RegExp(String.raw`\.tsx?$`)Use a regular expression literal instead of the RegExp constructor.;prefer-rest-paramson by default
Require rest parameters instead of arguments
function log(level) {
const parts = Array.prototype.slice.call(argumentsUse the rest parameters instead of arguments., 1);
console.log(level, parts.join(" "));
}prefer-spreadon by default
Require spread syntax instead of .apply() to pass an array as arguments
const highest = Math.max.apply(Math, values)Use the spread operator instead of .apply().;
notify.apply(null, listeners)Use the spread operator instead of .apply().;
logger.info.apply(logger, parts)Use the spread operator instead of .apply().;prefer-templatefixable
Require template literals instead of string concatenation
const greeting = "Hello, " + user.name + "!"Unexpected string concatenation.;
const url = base + "/users/" + idUnexpected string concatenation.;preserve-caught-erroron by defaulthas suggestions
Require the caught error as the cause of a new error thrown in catch
try {
loadConfig();
} catch (error) {
throw new Error("Could not load the config");There is no cause attached to the symptom error being thrown.
}radixhas suggestions
Require the radix argument of parseInt()
const page = parseInt(query.page)Missing radix parameter.;
const size = Number.parseInt(query.size)Missing radix parameter.;
const level = parseInt(input, 1)Invalid radix parameter, must be an integer between 2 and 36.;
const depth = parseInt(input, "10")Invalid radix parameter, must be an integer between 2 and 36.;require-atomic-updates
Disallow assignments that use a value read before an await or a yield
let total = 0;
async function addPrice(id) {
total += await fetchPrice(id)Possible race condition: total might be reassigned based on an outdated value of total.;
}require-awaithas suggestions
Disallow async functions that never use await
async function getUserAsync function getUser has no await expression.(id) {
return cache.get(id);
}require-unicode-regexphas suggestions
Require the u or v flag on regular expressions
const oneCharacter = /^.$/Use the u flag.;
const word = new RegExp("\\w+", "g")Use the u flag.;require-yieldon by default
Require generator functions to contain yield
function* idsThis generator function does not have yield.() {
return nextId();
}sort-importsfixable
Enforce sorted import declarations and sorted names inside them
import { writeFile, readFileMember readFile of the import declaration should be sorted alphabetically. } from "node:fs/promises";sort-keys
Require the keys of object literals to be sorted
const user = {
name: "Ada",
emailExpected object keys to be in ascending order. email should be before name.: "ada@example.com",
role: "admin",
};sort-varsfixable
Require the variables of one declaration to be sorted
let width, heightVariables within the same declaration block should be sorted alphabetically., depthVariables within the same declaration block should be sorted alphabetically.;strictfixable
Require or disallow "use strict" directives
"use strict";'use strict' is unnecessary inside of modules.
export function run() {
save();
}symbol-description
Require a description when creating a Symbol
const userId = Symbol()Expected Symbol to have a description.;unicode-bomfixable
Require or disallow a Unicode byte order mark at the start of a file
cExpected Unicode BOM (Byte Order Mark).onst port = 3000;use-isnanon by defaulthas suggestions
Require Number.isNaN() instead of a comparison with NaN
const price = Number(input);
if (price === NaNUse the isNaN function to compare with NaN.) {
showError();
}valid-typeofon by defaulthas suggestions
Require typeof to be compared with a valid type name
if (typeof name === "strnig"Invalid typeof comparison value.) {
trim(name);
}
if (typeof callback === undefinedInvalid typeof comparison value.) {
callback = noop;
}
if (typeof user === nullInvalid typeof comparison value.) {
signIn();
}vars-on-top
Require var declarations at the top of their function or file
function total(items) {
var sum = 0;
for (var i = 0All var declarations must be at the top of the function scope.; i < items.length; i++) {
var price = items[i].price;All var declarations must be at the top of the function scope.
sum += price;
}
return sum;
}yodafixable
Require or disallow comparisons with the literal on the left
if ("red" === colorExpected literal to be on the right side of ===.) {
stop();
}
if (0 < countExpected literal to be on the right side of <.) {
render();
}typescript-eslint 137 rules
@typescript-eslint/adjacent-overload-signatures
Require the overloads of a function to be next to each other
interface Storage {
get(key: string): string;
clear(): void;
get(key: string, fallback: string): string;All get signatures should be adjacent.
}@typescript-eslint/array-typefixable
Require either T[] or Array<T> for array types, consistently
const names: Array<string>Array type using Array<string> is forbidden. Use string[] instead. = [];
const ids: ReadonlyArray<number>Array type using ReadonlyArray<number> is forbidden. Use readonly number[] instead. = [1, 2];@typescript-eslint/await-thenablehas suggestionsneeds types
Disallow await on a value that is not a promise
function readConfig(): string {
return "{}";
}
const config = await readConfig()Unexpected await of a non-Promise (non-"Thenable") value.;@typescript-eslint/ban-ts-commenton by defaulthas suggestions
Disallow @ts-ignore and @ts-nocheck, and require a reason after @ts-expect-error
// @ts-ignoreUse @ts-expect-error instead of @ts-ignore, as @ts-ignore will do nothing if the following line is error-free.
const port: number = process.env.PORT;@typescript-eslint/ban-tslint-commentfixable
Disallow // tslint:disable and // tslint:enable comments
/* tslint:disable */tslint comment detected: /* tslint:disable */
const port = 3000;
console.log(port); // tslint:disable-line:no-consoletslint comment detected: // tslint:disable-line:no-console@typescript-eslint/ban-types
Disallow String, Number, Object, Function, {} and similar types
function greet(name: StringDo not use String as a type. Use string instead, onDone: FunctionDon't use Function as a type) {
onDone(`Hello, ${name}`);
}@typescript-eslint/class-literal-property-stylehas suggestions
Require constants on a class to be all readonly fields or all getters
class Server {
get hostLiterals should be exposed using readonly fields.() {
return "localhost";
}
}@typescript-eslint/class-methods-use-this
Require class methods to use this
class Invoice {
total = 0;
formatExpected this to be used by class method format.(amount: number) {
return amount.toFixed(2);
}
round = Expected this to be used by class method round.(amount: number) => Math.round(amount);
}@typescript-eslint/consistent-generic-constructorsfixable
Require type arguments on the constructor call or on the type annotation, consistently
const scores: Map<string, number> = new Map()The generic type arguments should be specified as part of the constructor type arguments.;
class Game {
players: Set<string> = new Set();The generic type arguments should be specified as part of the constructor type arguments.
}
function start(seen: Set<string> = new Set()The generic type arguments should be specified as part of the constructor type arguments.) {}@typescript-eslint/consistent-indexed-object-stylefixable
Require either Record<K, V> or an index signature, consistently
type Scores = { [player: string]: number }A record is preferred over an index signature.;
interface Prices {A record is preferred over an index signature.
[product: string]: number;
}@typescript-eslint/consistent-returnneeds types
Require every return in a function to return a value, or none of them
function findUser(id: number) {
if (id < 0) {
return;
}
return { id };Function findUser expected no return value.
}@typescript-eslint/consistent-type-assertionsfixable
Require one style of type assertion: value as T, <T>value, or none
const input = <HTMLInputElement>document.querySelector("input")Use 'as HTMLInputElement' instead of <HTMLInputElement>.;@typescript-eslint/consistent-type-definitionsfixable
Require either interface or type for object types, consistently
type UserUse an interface instead of a type. = {
name: string;
email: string;
};@typescript-eslint/consistent-type-exportsfixableneeds types
Require export type for exports that are only types
interface User {
name: string;
}
export { User };All exports in the declaration are only used as types. Use export type.@typescript-eslint/consistent-type-importsfixable
Require import type for imports that are only used as types
import { User } from "./models";All imports in the declaration are only used as types. Use import type.
export function greet(user: User) {
return `Hello, ${user.name}`;
}@typescript-eslint/default-param-last
Require parameters with a default value, and optional parameters, to come last
function connect(port = 3000Default parameters should be last., host: string) {}
connect(undefined, "localhost");@typescript-eslint/dot-notationfixableneeds types
Require user.name instead of user["name"]
const user = { name: "Ada" };
console.log(user["name"[name] is better written in dot notation.]);@typescript-eslint/explicit-function-return-type
Require a return type on functions and class methods
function totalMissing return type on function.(prices: number[]) {
return prices.reduce((sum, price) => sum + price, 0);
}
const isEmpty = (items: string[]) =>Missing return type on function. items.length === 0;
class Cart {
sizeMissing return type on function.() {
return 0;
}
}@typescript-eslint/explicit-member-accessibilityfixable
Require public, private or protected on class members
class Account {
balanceMissing accessibility modifier on class property balance. = 0;
constructorMissing accessibility modifier on method definition constructor.(readonly ownerMissing accessibility modifier on parameter property owner.: string) {}
}@typescript-eslint/explicit-module-boundary-types
Require parameter and return types on exported functions and the methods of exported classes
export function parsePriceMissing return type on function.(textArgument text should be typed.) {
return Number(text.replace("$", ""));
}
export function log(value: anyArgument value should be typed with a non-any type.): void {
console.log(value);
}@typescript-eslint/init-declarations
Require or disallow a value in variable declarations
let discountVariable discount should be initialized on declaration.: number;@typescript-eslint/max-params
Enforce a maximum number of parameters in function definitions
function createUserFunction createUser has too many parameters (4). Maximum allowed is 3.(name: string, email: string, isAdmin: boolean, isActive: boolean) {
return { name, email, isAdmin, isActive };
}@typescript-eslint/member-ordering
Require a consistent order for the members of classes, interfaces and type literals
class Timer {
start(): void {}
constructor() {}Member constructor should be declared before all public instance method definitions.
private elapsed = 0;Member elapsed should be declared before all public instance method definitions.
}@typescript-eslint/method-signature-stylefixable
Enforce one way to write a method in an interface or type literal
interface Store {
save(user: string): void;Shorthand method signature is forbidden. Use a function property instead.
}@typescript-eslint/naming-convention
Enforce naming conventions for variables, functions, types, members and imports
const user_nameVariable name user_name must match one of the following formats: camelCase, UPPER_CASE = "ada";
function Load_userFunction name Load_user must match one of the following formats: camelCase() {}
interface user_recordInterface name user_record must match one of the following formats: PascalCase {}@typescript-eslint/no-array-constructoron by defaultfixable
Disallow Array() and new Array() in favor of array literals
const sizes = new Array(10, 20, 30)The array literal notation [] is preferable.;
const names = Array()The array literal notation [] is preferable.;@typescript-eslint/no-array-deletehas suggestionsneeds types
Disallow delete on an element of an array
const items = ["a", "b", "c"];
delete items[1]Using the delete operator with an array expression is unsafe.; // ["a", <empty>, "c"], length 3@typescript-eslint/no-base-to-stringneeds types
Disallow turning an object into a string when the result is [object Object]
const user = { name: "Ada" };
console.log(`Hello, ${useruser will use Object's default stringification format ([object Object]) when stringified.}`);
console.log("Hello, " + useruser will use Object's default stringification format ([object Object]) when stringified.);
console.log([user]Using join() for [user] will use Object's default stringification format ([object Object]) when stringified..join(", "));@typescript-eslint/no-confusing-non-null-assertionhas suggestions
Disallow a non-null assertion ! right before =, ==, ===, in or instanceof
declare let user: string | undefined;
declare const cache: Record<string, number>;
const isSame = user! == "ada"Confusing combination of non-null assertion and equality test like a! == b, which looks very similar to a !== b.;
user! = "grace"Confusing combination of non-null assertion and assignment like a! = b, which looks very similar to a != b.;
const isCached = user! in cacheConfusing combination of non-null assertion and in operator like a! in b, which might be misinterpreted as !(a in b).;@typescript-eslint/no-confusing-void-expressionfixableneeds types
Require expressions of type void to stand alone as statements
declare function log(message: string): void;
const result = log("saved")Placing a void expression inside another expression is forbidden. Move it to its own statement instead.;
const onSave = () => log("saved")Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.;
function onError(error: Error) {
return log(error.message)Returning a void expression from a function is forbidden. Please remove the return statement.;
}@typescript-eslint/no-deprecatedneeds types
Disallow using code marked as @deprecated
/** @deprecated Use `fetchUser` instead. */
function getUser(id: number) {}
interface Options {
/** @deprecated */
legacy?: boolean;
}
getUsergetUser is deprecated. Use fetchUser instead.(1);
function start(options: Options) {
return options.legacylegacy is deprecated.;
}@typescript-eslint/no-dupe-class-members
Disallow two class members with the same name
class Store {
save() {
return "first";
}
saveDuplicate name save.() {
return "second";
}
}@typescript-eslint/no-duplicate-enum-valueson by default
Disallow two enum members with the same value
enum Status {
Active = 1,
Enabled = 1Duplicate enum member value 1.,
}
enum Color {
Red = "red",
Crimson = "red"Duplicate enum member value red.,
}@typescript-eslint/no-duplicate-type-constituentsfixableneeds types
Disallow the same type twice in a union or intersection
type Id = string | number | stringUnion type constituent is duplicated with string.;
type Name = string;
type Label = Name | stringUnion type constituent is duplicated with Name.;
type Point = { x: number } & { x: number }Intersection type constituent is duplicated with { x: number }.;
function greet(name?: string | undefinedExplicit undefined is unnecessary on an optional parameter.) {}@typescript-eslint/no-dynamic-delete
Disallow delete with a computed property key
const sessions: Record<string, Date> = {};
function logout(userId: string) {
delete sessions[userIdDo not delete dynamically computed property keys.];
}@typescript-eslint/no-empty-functionhas suggestions
Disallow functions with an empty body
function onClose() {}Unexpected empty function onClose.
class Logger {
flush() {}Unexpected empty method flush.
}@typescript-eslint/no-empty-interfacefixabledeprecated
Disallow interfaces that declare no members
interface OptionsAn empty interface is equivalent to {}. {}
interface User {
name: string;
}
interface AdminAn interface declaring no members is equivalent to its supertype. extends User {}@typescript-eslint/no-empty-object-typeon by defaulthas suggestions
Disallow the {} type and empty interfaces
let settings: {}The {} ("empty object") type allows any non-nullish value, including literals like 0 and "".If that's what you want, disable this lint rule with an inline comment or configure the allowObjectTypes rule option.If you want a type meaning "any object", you probably want object instead.If you want a type meaning "any value", you probably want unknown instead. = 42; // no type error
interface PropsAn empty interface declaration allows any non-nullish value, including literals like 0 and "".If that's what you want, disable this lint rule with an inline comment or configure the allowInterfaces rule option.If you want a type meaning "any object", you probably want object instead.If you want a type meaning "any value", you probably want unknown instead. {}@typescript-eslint/no-explicit-anyon by defaultfixable
Disallow the any type
function parse(text: string): anyUnexpected any. Specify a different type. {
return JSON.parse(text);
}
parse("{}").user.nmae; // no type error@typescript-eslint/no-extra-non-null-assertionon by defaultfixable
Disallow a non-null assertion that has no effect
function nameOf(user?: { name: string }) {
return user!Forbidden extra non-null assertion.!.name;
}
function lengthOf(items?: string[]) {
return items!Forbidden extra non-null assertion.?.length;
}@typescript-eslint/no-extraneous-class
Disallow classes that are empty, only static, or only a constructor
export class MathUtilsUnexpected class with only static properties. {
static readonly TAU = Math.PI * 2;
static clamp(value: number, min: number, max: number) {
return Math.min(Math.max(value, min), max);
}
}@typescript-eslint/no-floating-promiseshas suggestionsneeds types
Require promises to be awaited, returned, or handled
async function save(data: string): Promise<void> {}
function onClick() {
save("draft");Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the void operator.
}@typescript-eslint/no-for-in-arrayneeds types
Disallow for...in over an array
const prices = [5, 10, 20];
for (const i in prices)For-in loops over arrays skips holes, returns indices as strings, and may visit the prototype chain or other enumerable properties. Use a more robust iteration method such as for-of or array.forEach instead. {
console.log(i + 1); // "01", "11", "21"
}@typescript-eslint/no-generated-empty-object-typeneeds types
Disallow type operations that produce the {} type
type User = { name: string };
type Rest = Omit<User, "name">This type resolves to {}, the empty object type. This was likely not intentional.;
const rest: Rest = 42; // no type error@typescript-eslint/no-implied-evalneeds types
Disallow passing code as a string to setTimeout, setInterval and new Function
setTimeout("refresh()"Implied eval. Consider passing a function., 1000);
function schedule(code: string) {
globalThis.setInterval(codeImplied eval. Consider passing a function., 1000);
}
const add = new Function("a", "b", "return a + b")Implied eval. Do not use the Function constructor to create functions.;@typescript-eslint/no-import-type-side-effectsfixable
Require import type when every name in an import is marked type
import { type User, type Role } from "./models";TypeScript will only remove the inline type specifiers which will leave behind a side effect import at runtime. Convert this to a top-level type qualifier to properly remove the entire import.
export function describe(user: User, role: Role) {}@typescript-eslint/no-inferrable-typesfixable
Disallow type annotations that repeat what the initial value already says
const retries: number = 3Type number trivially inferred from a number literal, remove type annotation.;
const pattern: RegExp = /\d+/Type RegExp trivially inferred from a RegExp literal, remove type annotation.;
function greet(name: string = "world"Type string trivially inferred from a string literal, remove type annotation.) {}
class Cart {
isOpen: boolean = false;Type boolean trivially inferred from a boolean literal, remove type annotation.
}@typescript-eslint/no-invalid-this
Disallow this where its value is undefined
const button = {
label: "Save",
onClick() {
setTimeout(function () {
console.log(thisUnexpected this..label);
}, 100);
},
};@typescript-eslint/no-invalid-void-type
Disallow void outside of return types and generic type arguments
let result: voidvoid is only valid as a return type or generic type argument.;
function log(message: string | voidvoid is not valid as a constituent in a union type) {}
type Listener = (event: voidvoid is only valid as a return type or generic type argument.) => void;@typescript-eslint/no-loop-funcdeprecated
Disallow functions in loops that use a variable the loop changes
const handlers: Array<() => void> = [];
for (var i = 0; i < 3; i++) {
handlers.push(() => console.log(i)Function declared in a loop contains unsafe references to variable(s) i.); // 3, 3, 3
}@typescript-eslint/no-loss-of-precisiondeprecated
Disallow number literals that cannot be stored exactly as written
const orderId = 9007199254740993This number literal will lose precision at runtime.; // stored as 9007199254740992
const limit = 2e999This number literal will lose precision at runtime.; // stored as Infinity@typescript-eslint/no-magic-numbers
Disallow numbers that are used without a name
function total(price: number) {
return price + price * 0.2No magic number: 0.2.;
}@typescript-eslint/no-meaningless-void-operatorfixableneeds types
Disallow void in front of an expression that has no value to discard
function log(message: string): void {
console.log(message);
}
void log("saved")void operator shouldn't be used on void; it should convey that a return value is being ignored;
const count = 3;
void countvoid operator is useless here; it should only discard a call's return value;@typescript-eslint/no-misused-newon by default
Disallow new in a class and constructor in an interface
declare class Client {
new(): Client;Class cannot have method named new.
}
interface Session {
new (): Session;Interfaces cannot be constructed, only classes.
constructor(): void;Interfaces cannot be constructed, only classes.
}@typescript-eslint/no-misused-promisesneeds types
Disallow promises where the code around them cannot handle one
declare function isAdmin(id: string): Promise<boolean>;
declare function remove(id: string): Promise<void>;
declare function load(id: string): Promise<{ name: string }>;
function cleanUp(ids: string[]) {
if (isAdmin(ids[0])Expected non-Promise value in a boolean conditional.) {
ids.forEach(async id => {Promise returned in function argument where a void return was expected.
await remove(id);
});
}
return { id: ids[0], ...load(ids[0])Expected a non-Promise value to be spread in an object. };
}@typescript-eslint/no-misused-spreadhas suggestionsneeds types
Disallow ... on values that do not spread the way they look
declare function loadUser(): Promise<{ name: string }>;
const roles = new Map([["admin", true]]);
const tags = ["new", "sale"];
const user = { ...loadUser()Using the spread operator on Promise in an object can cause unexpected behavior. Did you forget to await the promise? };
const access = { ...rolesUsing the spread operator on a Map in an object will result in an empty object. Did you mean to use Object.fromEntries(map) instead? };
const labels = { ...tagsUsing the spread operator on an array in an object will result in a list of indices. };@typescript-eslint/no-mixed-enumsneeds types
Disallow enums that have both number and string members
enum Status {
Active,
Archived = "archived"Mixing number and string enums can be confusing.,
}@typescript-eslint/no-namespaceon by default
Disallow TypeScript namespace and module blocks
namespace Format {ES2015 module syntax is preferred over namespaces.
export function price(cents: number) {
return `$${cents / 100}`;
}
}
Format.price(1999);@typescript-eslint/no-non-null-asserted-nullish-coalescinghas suggestions
Disallow ! on the left side of ??
declare const settings: { theme?: string };
declare function findUser(id: string): { name: string } | null;
const theme = settings.theme!The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed. ?? "light";
const user = findUser("42")!The nullish coalescing operator is designed to handle undefined and null - using a non-null assertion is not needed. ?? { name: "guest" };@typescript-eslint/no-non-null-asserted-optional-chainon by defaulthas suggestions
Disallow ! after an optional chain
declare const user: { name: string; address?: { city: string } } | undefined;
const name: string = user?.name!Optional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong.;
const city: string = (user?.address?.cityOptional chain expressions can return undefined by design - using a non-null assertion is unsafe and wrong.)!;@typescript-eslint/no-non-null-assertionhas suggestions
Disallow the non-null assertion value!
const users = new Map<string, { name: string }>();
function greet(id: string) {
const form = document.querySelector("form")!Forbidden non-null assertion.;
form.title = users.get(id)!Forbidden non-null assertion..name;
}@typescript-eslint/no-redeclare
Disallow declaring the same name twice in one scope
function price(items: number[]) {
var total = items.length;
for (const item of items) {
var totaltotal is already defined. = item;
}
return total;
}@typescript-eslint/no-redundant-type-constituentsneeds types
Disallow members of unions and intersections that change nothing or swallow the rest
type Status = "draft"draft is overridden by string in this union type. | "published"published is overridden by string in this union type. | string;
type Payload = { id: number } | anyany overrides all other types in this union type.;
type Result = string | nevernever is overridden by other types in this union type.;
function send(body: unknownunknown overrides all other types in this union type. | string) {}@typescript-eslint/no-require-imports
Disallow require() in favor of import
const path = require("node:path")A require() style import is forbidden.;
import fs = require("node:fs")A require() style import is forbidden.;
console.log(path.join("a", "b"), fs.existsSync("a"));@typescript-eslint/no-restricted-importsdeprecated
Disallow imports of the modules you list, with an exception for type imports
import moment from "moment";moment import is restricted from being used. Use Temporal instead.
import legacy = require("moment");moment import is restricted from being used. Use Temporal instead.
export { duration } from "moment";moment import is restricted from being used. Use Temporal instead.@typescript-eslint/no-restricted-typesfixable
Disallow the types you list
interface LegacyUser {
name: string;
}
function greet(user: LegacyUserDon't use LegacyUser as a type. Use Account instead.) {
return user.name;
}@typescript-eslint/no-shadow
Disallow a declaration that hides one with the same name in an outer scope
const timeout = 1000;
function retry(action: () => void) {
const timeouttimeout is already declared in the upper scope on line 1 column 7. = 50;
setTimeout(action, timeout);
}@typescript-eslint/no-this-aliason by default
Disallow storing this in a variable
class Timer {
seconds = 0;
start() {
const selfUnexpected aliasing of this to local variable. = this;
setInterval(function () {
self.seconds++;
}, 1000);
}
}@typescript-eslint/no-type-aliasdeprecated
Disallow type aliases, or allow only some kinds of them
type UserId = stringType aliases are not allowed.;
type User = {Type literals are not allowed.
id: string;
};
type OnSave = (user: User) => voidType callbacks are not allowed.;@typescript-eslint/no-unnecessary-boolean-literal-comparefixableneeds types
Disallow comparing a boolean with true or false
function canEdit(isOwner: boolean, isLocked: boolean) {
return isOwner === trueThis expression unnecessarily compares a boolean value to a boolean instead of using it directly. && isLocked === falseThis expression unnecessarily compares a boolean value to a boolean instead of using it directly.;
}@typescript-eslint/no-unnecessary-conditionhas suggestionsneeds types
Disallow conditions that the types say are always true or always false
interface User {
name: string;
tags: string[];
}
function label(user: User) {
if (user.tagsUnnecessary conditional, value is always truthy.) {
console.log(user.tags.length);
}
const name = user.nameUnnecessary conditional, expected left-hand side of ?? operator to be possibly null or undefined. ?? "anonymous";
return user?.Unnecessary optional chain on a non-nullish value.name + name;
}@typescript-eslint/no-unnecessary-parameter-property-assignment
Disallow assigning a constructor parameter property to itself
class User {
constructor(public name: string) {
this.name = nameThis assignment is unnecessary since it is already assigned by a parameter property.;
}
}@typescript-eslint/no-unnecessary-qualifierfixableneeds types
Disallow naming the enclosing namespace or enum where the short name works
namespace Shapes {
export interface Circle {
radius: number;
}
export const unit: ShapesQualifier is unnecessary since Circle is in scope..Circle = { radius: 1 };
}
enum Level {
Low = 1,
Default = LevelQualifier is unnecessary since Low is in scope..Low,
}@typescript-eslint/no-unnecessary-template-expressionfixableneeds types
Disallow ${} in a template literal where plain text or the value itself does the same
function describe(name: string, width: number) {
const title = `${name}Template literal expression is unnecessary and can be simplified.`;
const size = `${width}${"px"}Template literal expression is unnecessary and can be simplified.`;
const retries = `retries: ${3}Template literal expression is unnecessary and can be simplified.`;
return [title, size, retries];
}
type Size = `${number}${"px"}Template literal expression is unnecessary and can be simplified.`;@typescript-eslint/no-unnecessary-type-argumentsfixableneeds types
Disallow a type argument that is the same as the default of its type parameter
class Store<Value = string> {
items: Value[] = [];
}
const labels: Store<stringThis is the default value for this type parameter, so it can be omitted.> = new Store<stringThis is the default value for this type parameter, so it can be omitted.>();
class TagStore extends Store<stringThis is the default value for this type parameter, so it can be omitted.> {}@typescript-eslint/no-unnecessary-type-assertionfixableneeds types
Disallow as, <T> and ! assertions that do not change the type
interface User {
name: string;
email?: string;
}
function show(user: User) {
const name = user.name as stringThis assertion is unnecessary since it does not change the type of the expression.;
const length = user.name!This assertion is unnecessary since it does not change the type of the expression..length;
const email: string | undefined = user.email!This assertion is unnecessary since the receiver accepts the original type of the expression.;
return [name, length, email];
}@typescript-eslint/no-unnecessary-type-constrainton by defaulthas suggestions
Disallow extends any and extends unknown on a type parameter
function first<Item extends unknownConstraining the generic type Item to unknown does nothing and is unnecessary.>(items: Item[]) {
return items[0];
}
class Box<Value extends anyConstraining the generic type Value to any does nothing and is unnecessary.> {
constructor(public value: Value) {}
}
type Pair<Left, Right extends unknownConstraining the generic type Right to unknown does nothing and is unnecessary.> = [Left, Right];@typescript-eslint/no-unnecessary-type-conversionhas suggestionsneeds types
Disallow converting a value to the type it already has
function format(name: string, count: number, isOpen: boolean) {
const label = StringPassing a string to String() does not change the type or value of the string.(name);
const title = name.toString()Calling a string's .toString() method does not change the type or value of the string. + ":";
const total = +Using the unary + operator on a number does not change the type or value of the number.count;
const open = !!Using !! on a boolean does not change the type or value of the boolean.isOpen;
return [label, title, total, open];
}@typescript-eslint/no-unnecessary-type-parametershas suggestionsneeds types
Disallow a type parameter that is used only once in a signature
function log<MessageType parameter Message is used only once in the function signature.>(message: Message): void {
console.log(message);
}
function size<Item extends { length: number }Type parameter Item is used only once in the function signature.>(item: Item): number {
return item.length;
}
function load<DataType parameter Data is used only once in the function signature.>(key: string): Data {
return JSON.parse(localStorage.getItem(key) ?? "null");
}@typescript-eslint/no-unsafe-argumentneeds types
Disallow passing a value of type any as an argument
declare function resize(width: number, height: number): void;
declare function addTags(tags: Set<string>): void;
const config = JSON.parse("{}");
resize(config.widthUnsafe argument of type any assigned to a parameter of type number., 100);
resize(...config.sizeUnsafe spread of an any type.);
addTags(new Set<any>()Unsafe argument of type Set<any> assigned to a parameter of type Set<string>.);@typescript-eslint/no-unsafe-assignmentneeds types
Disallow assigning a value of type any to a variable or a property
const config = JSON.parse("{}")Unsafe assignment of an any value.;
const port: number = JSON.parse("8080")Unsafe assignment of an any value.;
const { hostUnsafe object destructuring of a property with an any value. } = JSON.parse("{}") as { host: any };
const tags: Set<string> = new Set<any>()Unsafe assignment of type Set<any> to a variable of type Set<string>.;@typescript-eslint/no-unsafe-callneeds types
Disallow calling a value of type any
function run(plugin: any, handler: Function) {
pluginUnsafe call of an any typed value.();
plugin.setupUnsafe call of an any typed value.();
new plugin.Worker()Unsafe construction of an any typed value.;
handlerUnsafe call of a Function typed value.();
}@typescript-eslint/no-unsafe-declaration-mergingon by default
Disallow a class and an interface with the same name
interface UserUnsafe declaration merging between classes and interfaces. {
name: string;
}
class UserUnsafe declaration merging between classes and interfaces. {}
// No compile error, but it throws when it runs.
console.log(new User().name.length);@typescript-eslint/no-unsafe-enum-assignmentneeds types
Disallow giving a plain number or string to a place typed as an enum
enum Status {
Open,
Closed,
}
declare function setStatus(next: Status): void;
declare const labels: { [key in Status]: string };
let current: Status = 1The value assigned here does not have a shared enum type with the expected enum Status.;
current++This mutation can produce a value outside of the expected enum Status.;
setStatus(0The argument passed here does not have a shared enum type with the expected enum Status.);
console.log(labels[0The computed key used here does not have a shared enum type with the expected enum Status.]);
const parsed = Number("1") as StatusThe value asserted here does not have a shared enum type with the expected enum Status.;
const all: Status[] = [Status.Open, 1The value assigned here does not have a shared enum type with the expected enum Status.];@typescript-eslint/no-unsafe-enum-comparisonhas suggestionsneeds types
Disallow comparing an enum value with a plain number or string
enum Status {
Open,
Closed,
}
enum Color {
Red = "red",
}
function describe(status: Status, color: Color) {
if (status === 1The two values in this comparison do not have a shared enum type.) {
return "closed";
}
if (color === "red"The two values in this comparison do not have a shared enum type.) {
return "red";
}
switch (status) {
case 0:The case statement does not have a shared enum type with the switch predicate.
return "open";
}
}@typescript-eslint/no-unsafe-function-typeon by default
Disallow the built-in Function type
function onChange(listener: FunctionThe Function type accepts any function-like value.Prefer explicitly defining any function parameters and return type.) {
listener("name", 1, 2, 3);
}@typescript-eslint/no-unsafe-member-accessneeds types
Disallow reading a property of a value of type any
function readName(response: any) {
return response.userUnsafe member access .user on an any value..name;
}@typescript-eslint/no-unsafe-returnneeds types
Disallow returning a value of type any from a function
function loadConfig(text: string) {
return JSON.parse(text);Unsafe return of a value of type any.
}@typescript-eslint/no-unsafe-type-assertionneeds types
Disallow type assertions that narrow a type
function toPort(value: string | number) {
return value as numberUnsafe type assertion: type number is more narrow than the original type.;
}@typescript-eslint/no-unsafe-unary-minusneeds types
Require the operand of unary - to be a number or a bigint
function refund(amount: string) {
return -amountArgument of unary negation should be assignable to number | bigint but is string instead.;
}@typescript-eslint/no-unused-expressionson by default
Disallow statements that compute a value and do nothing with it
declare const user: { name?: string; save(): void };
user.save;Expected an assignment or function call and instead saw an expression.
user.name === "admin";Expected an assignment or function call and instead saw an expression.
user?.name;Expected an assignment or function call and instead saw an expression.
user.name!;Expected an assignment or function call and instead saw an expression.@typescript-eslint/no-unused-private-class-members
Disallow private class members that are never used
class Cache {
#sizePrivate class member #size is defined but never used. = 0;
private hitsPrivate class member hits is defined but never used. = 0;
constructor(private limit: numberPrivate class member limit is defined but never used.) {}
add() {
this.hits += 1;
}
}@typescript-eslint/no-unused-varson by defaultfixable
Disallow variables, functions, parameters, imports, and types that are never used
import { readFilereadFile is defined but never used. } from "node:fs/promises";
interface OptionsOptions is defined but never used. {
retries: number;
}
let attempts = 0;
attemptsattempts is assigned a value but never used. = attempts + 1;
function retryretry is defined but never used.() {}@typescript-eslint/no-use-before-define
Disallow using a variable, function, class, enum, or type above the line that declares it
console.log(limitlimit was used before it was defined.);
const limit = 10;
startstart was used before it was defined.(ModeMode was used before it was defined..Fast);
function start(mode: Mode) {}
enum Mode {
Fast,
Slow,
}@typescript-eslint/no-useless-constructorhas suggestions
Disallow constructors that do what the default constructor does
class Store {
constructorUseless constructor.() {}
}
class HttpError extends Error {
constructorUseless constructor.(message: string) {
super(message);
}
}@typescript-eslint/no-useless-default-assignmenthas suggestionsneeds types
Disallow default values that can never be used
function greet({ name = "guest"Default value is useless because the property is not optional. }: { name: string }) {
return `Hello, ${name}`;
}@typescript-eslint/no-useless-empty-exportfixable
Disallow export {} in a file that is already a module
export const port = 3000;
export {};Empty export does nothing and can be removed.@typescript-eslint/no-var-requiresdeprecated
Disallow const x = require() in favor of import
const fs = require("node:fs")Require statement not part of import statement.;
const join = require("node:path")Require statement not part of import statement..join;
register(require("./plugin")Require statement not part of import statement.);@typescript-eslint/no-wrapper-object-typeson by defaultfixable
Disallow String, Number, Boolean, BigInt, Symbol, and Object as types
function repeat(text: StringPrefer using the primitive string as a type name, rather than the upper-cased String., times: NumberPrefer using the primitive number as a type name, rather than the upper-cased Number.): StringPrefer using the primitive string as a type name, rather than the upper-cased String. {
return text.repeat(times.valueOf());
}@typescript-eslint/non-nullable-type-assertion-stylefixableneeds types
Require value! instead of value as T when the assertion only removes null and undefined
function nameOf(names: Map<string, string>, id: string) {
return names.get(id) as stringUse a ! assertion to more succinctly remove null and undefined from the type.;
}@typescript-eslint/only-throw-errorneeds types
Disallow throwing values that are not Error objects
function load(path: string, status: number) {
if (!path) throw "path is required"Expected an error object to be thrown.;
if (status === 404) throw { status }Expected an error object to be thrown.;
throw undefinedDo not throw undefined.;
}@typescript-eslint/parameter-properties
Require or disallow parameter properties in class constructors
class User {
constructor(private name: stringProperty name should be declared as a class property.) {}
}@typescript-eslint/prefer-as-conston by defaultfixable
Require as const instead of repeating a literal as its own type
let theme = "dark" as "dark"Expected a const instead of a literal type assertion.;
let retries: 3Expected a const assertion instead of a literal type annotation. = 3;
class Request {
method: "GET"Expected a const assertion instead of a literal type annotation. = "GET";
}@typescript-eslint/prefer-destructuringfixableneeds types
Require destructuring to read a property or an element into a variable
declare const user: { name: string; email: string };
declare const items: string[];
let email: string;
const name = user.nameUse object destructuring.;
const first = items[0]Use array destructuring.;
email = user.emailUse object destructuring.;@typescript-eslint/prefer-enum-initializershas suggestions
Require every enum member to have an explicit value
enum Status {
DraftThe value of the member Draft should be explicitly defined.,
PublishedThe value of the member Published should be explicitly defined.,
ArchivedThe value of the member Archived should be explicitly defined.,
}@typescript-eslint/prefer-findhas suggestionsneeds types
Require .find() instead of .filter()[0] to get the first match
declare const users: { name: string; admin: boolean }[];
const admin = users.filter(user => user.admin)[0]Prefer .find(...) instead of .filter(...)[0].;
const guest = users.filter(user => !user.admin).at(0)Prefer .find(...) instead of .filter(...)[0].;@typescript-eslint/prefer-for-of
Require for...of instead of a for loop that only uses its index to read the array
for (let i = 0; i < items.length; i++) {Expected a for-of loop instead of a for loop with this simple iteration.
console.log(items[i]);
}@typescript-eslint/prefer-function-typefixable
Require a function type instead of an interface or object type with only a call signature
interface Handler {
(event: Event): void;Interface only has a call signature, you should use a function type instead.
}
function listen(callback: { (event: Event): voidType literal only has a call signature, you should use a function type instead. }) {}@typescript-eslint/prefer-includesfixableneeds types
Require .includes() instead of comparing .indexOf() with -1
function check(tags: string[], path: string) {
if (tags.indexOf("draft") !== -1Use includes() method instead.) return false;
if (path.indexOf("/private/") >= 0Use includes() method instead.) return false;
return /admin/.test(path)Use String#includes() method with a string instead.;
}@typescript-eslint/prefer-literal-enum-member
Require enum values to be literals, not computed expressions
const prefix = "app";
enum Key {
SessionExplicit enum value must only be a literal value (string or number). = `${prefix}:session`,
LengthExplicit enum value must only be a literal value (string or number). = "session".length,
TotalExplicit enum value must only be a literal value (string or number). = 1 + 2,
}@typescript-eslint/prefer-namespace-keywordon by defaultfixable
Require namespace instead of module to declare a namespace
module Shapes {Use namespace instead of module to declare custom TypeScript modules.
export const sides = 4;
}
declare module Vendor {Use namespace instead of module to declare custom TypeScript modules.
function init(): void;
}@typescript-eslint/prefer-nullish-coalescinghas suggestionsneeds types
Require ?? instead of || or a ternary to give a default for null or undefined
function connect(options: { port?: number; host: string | null; name?: string; tags?: string[] }) {
const port = options.port ||Prefer using nullish coalescing operator (??) instead of a logical or (||), as it is a safer operator. 3000;
const host = options.host !== null ? options.host : "localhost"Prefer using nullish coalescing operator (??) instead of a ternary expression, as it is simpler to read.;
options.name ||=Prefer using nullish coalescing operator (??=) instead of a logical assignment (||=), as it is a safer operator. "default";
if (!options.tags) {Prefer using nullish coalescing operator (??=) instead of an assignment expression, as it is simpler to read.
options.tags = [];
}
}@typescript-eslint/prefer-optional-chainfixableneeds types
Require a?.b instead of a && a.b, !a || !a.b, or (a || {}).b
type User = { profile?: { city?: string; refresh?: () => void } };
function show(user: User | undefined) {
const city = user && user.profile && user.profile.cityPrefer using an optional chain expression instead, as it's more concise and easier to read.;
if (!user || !user.profilePrefer using an optional chain expression instead, as it's more concise and easier to read.) return;
return (user.profile || {}).refreshPrefer using an optional chain expression instead, as it's more concise and easier to read.;
}@typescript-eslint/prefer-promise-reject-errorsneeds types
Require an Error as the reason when a promise is rejected
function load(url: string, reason: string) {
if (!url) return Promise.reject("missing url")Expected the Promise rejection reason to be an Error.;
if (url === "/") return Promise.reject()Expected the Promise rejection reason to be an Error.;
return new Promise((resolve, reject) => {
reject(reason)Expected the Promise rejection reason to be an Error.;
});
}@typescript-eslint/prefer-readonlyfixableneeds types
Require readonly on private members that are only assigned in the constructor
class Cache {
private entriesMember entries is never reassigned; mark it as readonly. = new Map<string, string>();
#limitMember #limit is never reassigned; mark it as readonly. = 100;
private nameMember name is never reassigned; mark it as readonly.: string;
constructor(name: string, private ttlMember ttl: number is never reassigned; mark it as readonly.: number) {
this.name = name;
}
set(key: string, value: string) {
this.entries.set(key, value);
}
}@typescript-eslint/prefer-readonly-parameter-typesneeds types
Require the types of function parameters to be read-only
function total(prices: number[]Parameter should be a read only type.) {}
function rename(user: { name: string }Parameter should be a read only type.) {}
function names(users: readonly { name: string }[]Parameter should be a read only type.) {}@typescript-eslint/prefer-reduce-type-parameterfixableneeds types
Require a type argument on reduce instead of a type assertion on the initial value
function count(words: string[]) {
return words.reduce((counts, word) => {
counts[word] = (counts[word] ?? 0) + 1;
return counts;
}, {} as Record<string, number>Unnecessary assertion: Array#reduce accepts a type parameter for the default value.);
}
function lengths(words: string[]) {
return words.reduce((result, word) => result.concat(word.length), [] as number[]Unnecessary assertion: Array#reduce accepts a type parameter for the default value.);
}@typescript-eslint/prefer-regexp-execfixableneeds types
Require pattern.exec(text) instead of text.match(pattern) when there is no g flag
function version(text: string) {
return text.matchUse the RegExp#exec() method instead.(/v(\d+)/) ?? text.matchUse the RegExp#exec() method instead.("latest");
}@typescript-eslint/prefer-return-this-typefixableneeds types
Require the return type this for a method that returns this
class Query {
where(condition: string): QueryUse this type instead. {
return this;
}
limit = (count: number): QueryUse this type instead. => this;
}@typescript-eslint/prefer-string-starts-ends-withfixableneeds types
Require startsWith and endsWith instead of other ways to test the start or end of a string
function isApiRoute(path: string) {
return path.indexOf("/api") === 0Use String#startsWith method instead.;
}
function isHidden(name: string) {
return name[0] === "."Use String#startsWith method instead.;
}
function isTypeScript(file: string) {
return file.slice(-3) === ".ts"Use the String#endsWith method instead. || /\.tsx$/.test(file)Use the String#endsWith method instead.;
}@typescript-eslint/prefer-ts-expect-errorfixabledeprecated
Require @ts-expect-error instead of @ts-ignore
// @ts-ignore: `retries` is missing from the package's typesUse @ts-expect-error to ensure an error is actually being suppressed.
client.retries = 3;@typescript-eslint/promise-function-asyncfixableneeds types
Require functions that return a promise to be async
function loadUserFunctions that return promises must be async.(id: string) {
if (!id) throw new Error("missing id");
return fetch(`/users/${id}`);
}
const cached = (hit?: string) =>Functions that return promises must be async. Consider adding an explicit return type annotation if the function is intended to return a union of promise and non-promise types. hit ?? Promise.resolve("miss");@typescript-eslint/related-getter-setter-pairsneeds types
Require the type of a getter to be assignable to the type of its setter
interface Field {
get value(): string | undefinedget() type should be assignable to its equivalent set() type.;
set value(next: string);
}@typescript-eslint/require-array-sort-compareneeds types
Require a compare function in calls to sort and toSorted on arrays
const prices = [5, 100, 20];
prices.sort()Require compare argument.; // [100, 20, 5]
const sorted = prices.toSorted()Require compare argument.;@typescript-eslint/require-awaithas suggestionsneeds types
Disallow async functions that never await and do not return a promise
async function totalAsync function total has no await expression.(prices: number[]) {
return prices.reduce((sum, price) => sum + price, 0);
}
const log = async (message: string) =>Async arrow function log has no await expression. {
console.log(message);
};@typescript-eslint/restrict-plus-operandsneeds types
Require both operands of + to be numbers, bigints, or strings
function describe(user: { name: string }, tags: string[], size: bigint) {
const label = "User: " + userInvalid operand for a + operation. Operands must each be a number or string, allowing a string + any of: any, boolean, null, RegExp, undefined. Got { name: string; }.;
const list = "Tags: " + tagsInvalid operand for a + operation. Operands must each be a number or string, allowing a string + any of: any, boolean, null, RegExp, undefined. Got string[].;
const next = size + 1Numeric + operations must either be both bigints or both numbers. Got bigint + number.;
}@typescript-eslint/restrict-template-expressionsneeds types
Disallow values in template literals that do not turn into a useful string
async function loadName(): Promise<string> {
return "Ada";
}
const user = { name: "Ada" };
console.log(`Hello ${userInvalid type { name: string; } of template literal expression.}`); // Hello [object Object]
console.log(`Hello ${loadName()Invalid type Promise<string> of template literal expression.}`); // Hello [object Promise]@typescript-eslint/return-awaitfixableneeds types
Enforce consistent use of await when an async function returns a promise
async function save(data: string): Promise<void> {}
async function trySave(data: string) {
try {
return save(data)Returning an awaited promise is required in this context.;
} catch (error) {
console.error("not reached when save() rejects", error);
}
}
async function saveDraft() {
return await save("draft")Returning an awaited promise is not allowed in this context.;
}@typescript-eslint/sort-type-constituentsfixabledeprecated
Require the members of union and intersection types to be sorted
type Status = "open" | "closed" | "merged"Union type Status constituents must be sorted.;
type Id = null | string | UserIdUnion type Id constituents must be sorted.;
type Props = WithStyle & WithChildrenIntersection type Props constituents must be sorted.;@typescript-eslint/strict-boolean-expressionshas suggestionsneeds types
Disallow nullable numbers, strings, and booleans, any, and always-true values in conditions
function render(width?: number, title?: string, items: string[] = []) {
if (widthUnexpected nullable number value in conditional. Please handle the nullish/zero/NaN cases explicitly.) {
resize(width); // skipped for 0
}
const heading = titleUnexpected nullable string value in conditional. Please handle the nullish/empty cases explicitly. || "Untitled"; // replaces ""
if (itemsUnexpected object value in conditional. The condition is always true.) {
draw(items); // always runs, even for []
}
}
declare function resize(width: number): void;
declare function draw(items: string[]): void;@typescript-eslint/strict-void-returnhas suggestionsneeds types
Disallow passing a function that returns a value where a void function is expected
declare function onChange(listener: (value: string) => void): void;
declare function save(value: string): Promise<void>;
const history: string[] = [];
onChange(async value =>Async function used in a context where a void function is expected. {
await save(value);
});
onChange(value => history.push(value)Value returned in a context where a void return is expected.);
const stop: () => void = () => {
returnValue returned in a context where a void return is expected. history.length;
};@typescript-eslint/switch-exhaustiveness-checkhas suggestionsneeds types
Require a switch on a union or an enum to have a case for every member
type Status = "open" | "closed" | "merged";
function label(status: Status) {
switch (statusSwitch is not exhaustive. Cases not matched: merged) {
case "open":
return "Open";
case "closed":
return "Closed";
}
}@typescript-eslint/triple-slash-referenceon by default
Disallow /// <reference /> comments where an import does the job
/// <reference path="./globals.d.ts" />Do not use a triple slash reference for ./globals.d.ts, use import style instead.
/// <reference types="lodash" />Do not use a triple slash reference for lodash, use import style instead.
import { chunk } from "lodash";
export const pages = chunk([1, 2, 3, 4], 2);@typescript-eslint/typedefdeprecated
Require type annotations in the places you choose
function greet(nameExpected name to have a type annotation.) {
return `Hello ${name}`;
}
const double = valueExpected value to have a type annotation. => value * 2;@typescript-eslint/unbound-methodneeds types
Disallow taking a method off its object without binding it
class User {
name = "Ada";
greet() {
return `Hello ${this.name}`;
}
}
const user = new User();
const greet = user.greetA method that is not declared with this: void may cause unintentional scoping of this when separated from its object.Consider using an arrow function or explicitly .bind()ing the method to avoid calling the method with an unintended this value. If a function does not access this, it can be annotated with this: void.;
const { greetA method that is not declared with this: void may cause unintentional scoping of this when separated from its object.Consider using an arrow function or explicitly .bind()ing the method to avoid calling the method with an unintended this value. If a function does not access this, it can be annotated with this: void.: hello } = user;
setTimeout(user.greetA method that is not declared with this: void may cause unintentional scoping of this when separated from its object.Consider using an arrow function or explicitly .bind()ing the method to avoid calling the method with an unintended this value. If a function does not access this, it can be annotated with this: void., 100);@typescript-eslint/unified-signatures
Disallow overloads that can be written as one signature
function find(id: string): void;
function find(id: numberThese overloads can be combined into one signature taking string | number.): void;
function find(id: string | number) {}
interface Logger {
log(message: string): void;
log(message: string, level: numberThese overloads can be combined into one signature with an optional parameter.): void;
}@typescript-eslint/use-unknown-in-catch-callback-variablehas suggestionsneeds types
Require the parameter of a promise rejection callback to be typed unknown
declare function save(): Promise<void>;
save().catch(errorPrefer the safe : unknown for a catch callback variable. => {
console.error(error.message);
});
save().catch((error: ErrorPrefer the safe : unknown for a catch callback variable.) => {
console.error(error.message);
});
save().then(
() => console.log("saved"),
({ message }Prefer the safe : unknown for a then rejection callback variable. The thrown error may be nullable, or may not have the expected shape.) => console.error(message),
);React 135 rules
react/boolean-prop-naming
Enforce a naming pattern such as isOpen or hasIcon for boolean props
type Props = { open: boolean;Prop name open doesn’t match rule ^(is|has)[A-Z]([A-Za-z0-9]?)+ title: string };
function Dialog(props: Props) {
return <dialog open={props.open}>{props.title}</dialog>;
}react/button-has-type
Require an explicit, valid type on every button element
<form>
<button onClick={openMenu}>Options</button>Missing an explicit type attribute for button
</form>;react-hooks/capitalized-calls
Disallow calling a function with a capitalized name from a component or a hook
import Avatar from "./Avatar";
function Profile({ user }) {
return <section>{Avatar({ user })Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler configAvatar may be a component.}</section>;
}react/checked-requires-onchange-or-readonly
Require onChange or readOnly on an input that has checked
<input type="checkbox" checked={isSubscribed} />checked should be used with either onChange or readOnly.;react-hooks/component-hook-factoriesdeprecated
Deprecated: reports nothing, and is kept so that older configs still load
react-hooks/config
Report an invalid configuration of the React Compiler
react/default-props-match-prop-types
Require every entry of defaultProps to have a prop type that is not required
function Greeting({ name, greeting }) {
return <p>{greeting}, {name}</p>;
}
Greeting.propTypes = {
name: PropTypes.string.isRequired,
greeting: PropTypes.string,
};
Greeting.defaultProps = {
name: "friend"defaultProp name defined for isRequired propType., // required, so never used
greting: "Hello"defaultProp greting has no corresponding propTypes declaration., // no such prop
};react/destructuring-assignmentfixable
Enforce one style for reading props, state, and context: destructured or not
function Badge(props) {
return <span className={props.colorMust use destructuring props assignment}>{props.labelMust use destructuring props assignment}</span>;
}react/display-name
Require every component to have a name that React can show
export default () => <nav>Menu</nav>Component definition is missing display name;react-hooks/error-boundaries
Disallow JSX inside a try block, where catch cannot see rendering errors
function Dashboard({ data }) {
try {
return <Chart data={data} />Error: Avoid constructing JSX within try/catchReact does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary).;
} catch {
return <p>Could not draw the chart.</p>;
}
}react-hooks/exhaustive-depson by defaultfixable
Require the dependency array of a hook to list every value its callback reads
function Profile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
loadUser(userId).then(setUser);
}, []React Hook useEffect has a missing dependency: userId. Either include it or remove the dependency array.); // keeps showing the first user when userId changes
return <h1>{user?.name}</h1>;
}react-hooks/exhaustive-effect-dependencieshas suggestions
Require the dependency array of an effect to match what the effect reads
import { useEffect } from "react";
function Room({ roomId }) {
useEffect(() => {
connect(roomIdError: Found missing effect dependenciesMissing dependencies can cause an effect to fire less often than it should.);
}, []); // stays in the first room
}react-hooks/fbt
Reserved for problems with fbt translations in the React Compiler: reports nothing
react/forbid-component-props
Disallow chosen props, by default className and style, on components
<Button className="wide"Prop className is forbidden on Components style={{ color: "red" }}Prop style is forbidden on Components>
Delete
</Button>;react/forbid-dom-props
Disallow the props you choose on DOM elements such as div
<div id="sidebar"Prop id is forbidden on DOM Nodes />;react/forbid-elements
Disallow the elements and components you choose
<button<button> is forbidden onClick={save}>Save</button>;react/forbid-foreign-prop-types
Disallow reading the propTypes of another component
import Button from "./Button";
function IconButton(props) {
return <Button {...props} />;
}
IconButton.propTypes = {
...Button.propTypesUsing propTypes from another component is not safe because they may be removed in production builds,
icon: PropTypes.string,
};react/forbid-prop-types
Disallow vague prop types such as any, array, and object
import PropTypes from "prop-types";
function UserList({ users }) {
return <ul>{users.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
}
UserList.propTypes = {
users: PropTypes.array.isRequiredProp type array is forbidden,
};react/forward-ref-uses-refhas suggestions
Require a component wrapped in forwardRef to take a ref parameter
import { forwardRef } from "react";
const TextInput = forwardRef(props => {forwardRef is used with this component but no ref parameter is set
return <input type="text" value={props.value} />;
});react/function-component-definitionfixable
Enforce one way to write function components: declaration, expression, or arrow function
const Greeting = props => {Function component is not a function declaration
return <h1>Hello, {props.name}</h1>;
};
const Footer = function (props) {Function component is not a function declaration
return <footer>{props.text}</footer>;
};react-hooks/gating
Validate the directive that puts a compiled component behind a feature flag
react-hooks/globals
Disallow reassigning variables declared outside a component or hook during render
let renderCount = 0;
function Counter() {
renderCountError: Cannot reassign variables declared outside of the component/hookVariable renderCount is declared outside of the component/hook. Reassigning this value during render is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect. (react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render). = renderCount + 1;
return <div>Rendered {renderCount} times</div>;
}react/hook-use-statehas suggestions
Require useState to be destructured into a [value, setValue] pair
import React, { useState } from "react";
function Picker() {
const color = useState("red")useState call is not destructured into value + setter pair;
const [size, updateSize]useState call is not destructured into value + setter pair = React.useState(1);
const [open]useState call is not destructured into value + setter pair = useState(false);
return <div />;
}react-hooks/hooks
Require hooks to be called at the top level of a component or hook, never conditionally
import { useState } from "react";
function Profile({ user }) {
if (!user) {
return null;
}
const [name, setName] = useStateError: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (react.dev/warnings/invalid-hook-call-warning)(user.name);
return <input value={name} onChange={event => setName(event.target.value)} />;
}react/iframe-missing-sandbox
Require a valid sandbox attribute on iframe elements
const map = <iframe src="https://maps.example.com" />An iframe element is missing a sandbox attribute;
const video = <iframe src="https://video.example.com" sandbox="allow-everything" />An iframe element defines a sandbox attribute with invalid value allow-everything;
const widget = <iframe src="/widget" sandbox="allow-scripts allow-same-origin" />An iframe element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid;react-hooks/immutability
Disallow modifying props, state, and other values that React expects to stay unchanged
function Price({ product }) {
productError: This value cannot be modifiedModifying component props or hook arguments is not allowed. Consider using a local variable instead..price = product.price / 2;
return <span>{product.price}</span>;
}react-hooks/incompatible-library
Report calls to library APIs that the React Compiler cannot memoize safely
import { useForm } from "react-hook-form";
function Signup() {
const { register, watch } = useForm();
const email = watchCompilation Skipped: Use of incompatible libraryThis API returns functions which cannot be memoized without leading to stale UI. To prevent this, by default React Compiler will skip memoizing this component/hook. However, you may see issues if values from this API are passed to other components/hooks that are memoized.("email");
return <input {...register("email")} title={email} />;
}react-hooks/invariant
Report code that makes the React Compiler fail one of its internal checks
function Results(props) {
let rows = [];
let total = 0;
({ rows, totalInvariant: Expected consistent kind for destructuringOther places were Const but store total$37{reactive} is reassigned. } = props.load());
const logRows = () => console.log(rows);
return <div onClick={logRows}>{total}</div>;
}react/jsx-boolean-valuefixable
Enforce one way to write boolean attributes in JSX: disabled or disabled={true}
const field = <input type="checkbox" checked={true}Value must be omitted for boolean attribute checked />;react/jsx-child-element-spacing
Disallow a line break between text and an inline element in JSX, where a space may be lost
const help = (
<p>
Read the
<Ambiguous spacing before next element aa href="/docs">docs</a>
</p>
);react/jsx-closing-bracket-locationfixable
Enforce where the closing bracket of a multiline JSX tag goes
const button = (
<Button
label="Save"
onClick={save} /The closing bracket must be aligned with the opening tag (expected column 3 on the next line)>
);react/jsx-closing-tag-locationfixable
Enforce that the closing tag of a multiline JSX element lines up with its opening tag
const card = (
<Card>
Welcome back</Card>Closing tag of a multiline JSX expression must be on its own line.
);react/jsx-curly-brace-presencefixable
Disallow curly braces around plain strings in JSX, or require them
const button = <Button label={"Save"}Curly braces are unnecessary here. title={`Save`}Curly braces are unnecessary here.>{"Save"}Curly braces are unnecessary here.</Button>;
const panel = <Panel>{<Spinner />}Curly braces are unnecessary here.</Panel>;react/jsx-curly-newlinefixable
Enforce consistent line breaks inside the curly braces of JSX expressions
const list = (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)
}Unexpected newline before }.
</ul>
);react/jsx-curly-spacingfixable
Enforce or disallow spaces inside the curly braces of JSX attributes and children
const field = <Input value={There should be no space after { name }There should be no space before } {There should be no space after { ...rest }There should be no space before } />;react/jsx-equals-spacingfixable
Enforce or disallow spaces around the = of JSX attributes
const field = <input name =There should be no space before =There should be no space after = "email" />;react/jsx-filename-extension
Restrict the file extensions that may contain JSX
export function Button({ label }) {
return <button>{label}</button>JSX not allowed in files with extension .js;
}react/jsx-first-prop-new-linefixable
Enforce the position of the first prop of a JSX element
const field = (
<Input name="email"Property should be placed on a new line
value={email}
/>
);react/jsx-fragmentsfixable
Enforce <> or <React.Fragment> for React fragments
import React, { Fragment } from "react";
const name = (
<React.Fragment>Prefer fragment shorthand over React.Fragment
<First />
<Last />
</React.Fragment>
);
const address = (
<Fragment>Prefer fragment shorthand over React.Fragment
<Street />
<City />
</Fragment>
);react/jsx-handler-names
Enforce naming conventions for event handlers and the props they are passed to
class Form extends React.Component {
render() {
return <Input onChange={this.updateName}Handler function for onChange prop key must be a camelCase name beginning with handle only blur={this.handleBlur}Prop key for handleBlur must begin with on />;
}
}react/jsx-indentfixable
Enforce consistent indentation of JSX elements and their children
const page = (
<main>
<Header />Expected indentation of 8 space characters but found 6.
<p>Expected indentation of 8 space characters but found 12.Welcome</p>
</main>
);react/jsx-indent-propsfixable
Enforce consistent indentation of JSX props
const field = (
<Input
name="email"Expected indentation of 8 space characters but found 6.
value={email}Expected indentation of 8 space characters but found 12.
/>
);react/jsx-keyon by default
Require a key prop on elements in arrays and iterators
const list = (
<ul>
{users.map(user => <li>{user.name}</li>Missing key prop for element in iterator)}
</ul>
);
const tabs = <nav>{[<Tab key="home" />, <Tab />Missing key prop for element in array]}</nav>;
const stars = Array.from({ length: 5 }, () => <Star />Missing key prop for element in iterator);react/jsx-max-depth
Enforce a maximum depth of nested JSX elements
const page = (
<main>
<section>
<ul>
<li>First</li>Expected the depth of nested jsx elements to be <= 2, but found 3.
</ul>
</section>
</main>
);react/jsx-max-props-per-linefixable
Enforce a maximum number of props on one line of a JSX tag
const field = <Input name="email" value={email}Prop value must be placed on a new line />;react/jsx-newlinefixable
Require or disallow an empty line between JSX elements and expressions
const page = (
<main>
<Header />
<Content />JSX element should start in a new line
{footer}JSX element should start in a new line
</main>
);react/jsx-no-bind
Disallow .bind() and functions created during render as JSX props
function Toolbar({ onSave, doc }) {
const handleReset = () => doc.reset();
return (
<div>
<Button onClick={() => onSave(doc)}JSX props should not use arrow functions />
<Button onClick={doc.print.bind(doc)}JSX props should not use .bind() />
<Button onClick={handleReset}JSX props should not use arrow functions />
</div>
);
}react/jsx-no-comment-textnodes
Disallow // and /* */ comments written as text between JSX tags
const header = (
<header>Comments inside children section of tag should be placed inside braces
// TODO: add the logo
<h1>Welcome</h1>Comments inside children section of tag should be placed inside braces
/* the subtitle goes here */
</header>
);react/jsx-no-constructed-context-values
Disallow context provider values that are created anew on every render
function App({ user, children }) {
return (
<UserContext.Provider value={{ user, isAdmin: user.role === "admin" }The object passed as the value prop to the Context provider (at line 3) changes every render. To fix this consider wrapping it in a useMemo hook.}>
{children}
</UserContext.Provider>
);
}react/jsx-no-duplicate-props
Disallow the same prop twice on one JSX element
const field = <Input name="email" type="text" name="username"No duplicate props allowed />;react/jsx-no-leaked-renderfixable
Disallow && conditions in JSX that can render 0 or NaN
function Cart({ items, note }) {
return (
<Panel footer={note && <Note text={note} />Potential leaked value that might cause unintentionally rendered values or rendering crashes}>
{items.length && <List items={items} />Potential leaked value that might cause unintentionally rendered values or rendering crashes}
</Panel>
);
}react/jsx-no-literals
Disallow text written directly in JSX
function Greeting() {
return <p>Welcome backMissing JSX expression container around literal string: "Welcome back"</p>;
}react/jsx-no-script-url
Disallow javascript: URLs in links
const close = <a href="javascript:closeDialog()"A future version of React will block javascript: URLs as a security precaution. Use event handlers instead if you can. If you need to generate unsafe HTML, try using dangerouslySetInnerHTML instead.>Close</a>;react/jsx-no-target-blankfixable
Require rel="noreferrer" on links that open in a new tab
const docs = <a href="https://example.com/docs" target="_blank">Using target="_blank" without rel="noreferrer" (which implies rel="noopener") is a security risk in older browsers: see mathiasbynens.github.io/rel-noopener/#recommendationsDocs</a>;react/jsx-no-undef
Disallow components in JSX that are not declared or imported
export function Page() {
return <HeaderHeader is not defined. title="Home" />;
}react/jsx-no-useless-fragmentfixable
Disallow fragments that wrap a single child or sit directly in an HTML element
function Avatar({ user }) {
return (
<>Fragments should contain more than one child - otherwise, there’s no need for a Fragment at all.
<img src={user.photo} alt={user.name} />
</>
);
}react/jsx-one-expression-per-linefixable
Require each child of a JSX element to be on its own line
const greeting = <p>HelloHello must be placed on a new line</p>;react/jsx-pascal-case
Require component names in JSX to be in PascalCase
const page = (
<User_Card>Imported JSX component User_Card must be in PascalCase
<NAVBAR />Imported JSX component NAVBAR must be in PascalCase
</User_Card>
);react/jsx-props-no-multi-spacesfixable
Disallow more than one space, or an empty line, between JSX props
const button = <Button type="submit"Expected only one space between “Button” and “type” disabledExpected only one space between “type” and “disabled” />;react/jsx-props-no-spread-multifixable
Disallow spreading the same variable twice in one JSX element
function SaveButton(props) {
return <Button {...props} type="submit" {...props}Spreading the same expression multiple times is forbidden />;
}react/jsx-props-no-spreading
Disallow {...props} in JSX
function Avatar(props) {
return <img {...props}Prop spreading is forbidden />;
}react/jsx-sort-default-propsdeprecated
Require the keys of defaultProps to be sorted alphabetically
function Button({ label, size }) {
return <button className={size}>{label}</button>;
}
Button.defaultProps = {
size: "medium",
label: "OK"Default prop types declarations should be sorted alphabetically,
};react/jsx-sort-propsfixable
Require JSX props to be sorted alphabetically
const button = <Button type="submit" disabledProps should be sorted alphabetically />;react/jsx-tag-spacingfixable
Enforce spacing around the <, / and > of JSX tags
const small = <Avatar/A space is required before closing bracket>;
const medium = < A space is forbidden after opening bracketAvatar />;
const large = <Avatar / >Whitespace is forbidden between / and >; write />;
const label = <p>Profile< /Whitespace is forbidden between < and /; write </p>;react/jsx-uses-reacton by default
Count React as used in files that contain JSX
react/jsx-uses-vars
Count a variable as used when it is the name of a JSX tag
react/jsx-wrap-multilinesfixable
Require parentheses around JSX that spans several lines
function Card({ title }) {
return <section>Missing parentheses around multilines JSX
<h2>{title}</h2>
</section>;
}react-hooks/memo-dependencieshas suggestions
Require the dependency list of useMemo and useCallback to match what the callback reads
import { useMemo } from "react";
function Results({ items, query }) {
const visible = useMemo(() => items.filter(item => item.name.includes(queryError: Found missing memoization dependenciesMissing dependencies can cause a value to update less often than it should, resulting in stale UI.)), [items]);
return <List items={visible} />;
}react-hooks/memoized-effect-dependencies
Require the dependencies of an effect to be memoized
react/no-access-state-in-setstate
Disallow reading this.state in the argument of this.setState()
class Counter extends React.Component {
increment() {
this.setState({ count: this.stateUse callback in setState when referencing the previous state..count + 1 });
}
}react/no-adjacent-inline-elements
Disallow inline elements that touch each other without whitespace between them
const nav = (
<div>Child elements which render as inline HTML elements should be separated by a space or wrapped in block level elements.
<a href="/login">Log in</a><a href="/signup">Sign up</a>
</div>
);react/no-array-index-key
Disallow the index of an array item as its key
const list = todos.map((todo, index) => <Todo key={indexDo not use Array index in keys} {...todo} />);react/no-arrow-function-lifecyclefixable
Disallow arrow functions in class fields as React lifecycle methods
class Clock extends React.Component {
componentDidMount = () => {componentDidMount is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.
this.timer = setInterval(this.tick, 1000);
};
render = () => <time>{this.state.now}</time>;render is a React lifecycle method, and should not be an arrow function or in a class field. Use an instance method instead.
}react/no-children-prop
Disallow passing children as a prop
const card = <Card children={<Avatar user={user} />}Do not pass children as props. Instead, nest children between the opening and closing tags. />;react/no-clone-element
Disallow React.cloneElement
import React, { Children, cloneElement } from "react";
function List({ children, selected }) {
return Children.map(children, (child, i) => cloneElementReact.cloneElement should not be used.(child, { isSelected: i === selected }));
}
const disabled = React.cloneElementReact.cloneElement should not be used.(<Button />, { disabled: true });react/no-danger
Disallow the dangerouslySetInnerHTML prop
const comment = <div dangerouslySetInnerHTML={{ __html: post.body }}Dangerous property dangerouslySetInnerHTML found />;react/no-danger-with-children
Disallow dangerouslySetInnerHTML on an element that also has children
const article = <div dangerouslySetInnerHTML={{ __html: post.body }}>Loading</div>Only set one of children or props.dangerouslySetInnerHTML;
const summary = <div dangerouslySetInnerHTML={{ __html: post.summary }} children="Loading" />Only set one of children or props.dangerouslySetInnerHTML;react/no-deprecated
Disallow React APIs that are deprecated
import ReactDOM from "react-dom";
ReactDOM.renderReactDOM.render is deprecated since React 18.0.0, use createRoot instead, see reactjs.org/link/switch-to-createroot(<App />, document.getElementById("root"));react-hooks/no-deriving-state-in-effects
Disallow effects that only compute state from props or other state
import { useEffect, useState } from "react";
function Greeting({ firstName, lastName }) {
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullNameError: Values derived from props and state should be calculated during render, not in an effect. (react.dev/learn/you-might-not-need-an-effect#updating-state-based-on-props-or-state)(firstName + " " + lastName);
}, [firstName, lastName]);
return <h1>{fullName}</h1>;
}react/no-did-mount-set-state
Disallow this.setState() in componentDidMount
class Profile extends React.Component {
componentDidMount() {
this.setStateDo not use setState in componentDidMount({ name: this.props.name.toUpperCase() });
}
}react/no-did-update-set-state
Disallow this.setState() in componentDidUpdate
class Cart extends React.Component {
componentDidUpdate(previous) {
if (previous.items !== this.props.items) {
this.setStateDo not use setState in componentDidUpdate({ total: sum(this.props.items) });
}
}
}react/no-direct-mutation-state
Disallow assigning to this.state outside of the constructor
class Counter extends React.Component {
increment() {
this.stateDo not mutate state directly. Use setState()..count++;
}
rename(name) {
this.state.userDo not mutate state directly. Use setState()..name = name;
}
}react/no-find-dom-node
Disallow findDOMNode
import { findDOMNode } from "react-dom";
class SearchBox extends React.Component {
componentDidMount() {
findDOMNodeDo not use findDOMNode. It doesn’t work with function components and is deprecated in StrictMode. See reactjs.org/docs/react-dom.html#finddomnode(this).focus();
}
render() {
return <input type="search" />;
}
}react/no-invalid-html-attributehas suggestions
Disallow values of the rel attribute that HTML does not define
const docs = <a href="https://example.com" rel="noopenner"“noopenner” is never a valid “rel” attribute value.>Docs</a>;
const home = <a href="/" rel="stylesheet"“stylesheet” is not a valid “rel” attribute value for <a>.>Home</a>;
const logo = <img src="/logo.png" relThe ”rel“ attribute only has meaning on the tags: <link>, <a>, <area>, <form>="preload" />;react/no-is-mounted
Disallow this.isMounted()
class Profile extends React.Component {
componentDidMount() {
loadUser(this.props.id).then(user => {
if (this.isMountedDo not use isMounted()) {
this.setState({ user });
}
});
}
}react/no-multi-comp
Disallow more than one React component in a file
function Avatar({ user }) {
return <img src={user.photo} alt={user.name} />;
}
function Profile({ user }) {Declare only one React component per file
return <Avatar user={user} />;
}react/no-namespace
Disallow namespaced element names such as <svg:circle>
const dot = <svg:circle r="4" />React component svg:circle must not be in a namespace, as React does not support them;
const line = React.createElement("svg:line")React component svg:line must not be in a namespace, as React does not support them;react/no-object-type-as-default-prop
Disallow objects, arrays, and functions as default values of destructured props
function UserList({ users = []users has a/an array literal as default prop. This could lead to potential infinite render loop in React. Use a variable reference instead of array literal., onSelect = () => {}onSelect has a/an arrow function as default prop. This could lead to potential infinite render loop in React. Use a variable reference instead of arrow function. }) {
return <List items={users} onSelect={onSelect} />;
}react/no-react-children
Disallow the React.Children methods
import { Children } from "react";
function Menu({ children }) {
return (
<ul>
{Children.mapReact.Children should not be used.(children, child => (
<li>{child}</li>
))}
</ul>
);
}react/no-redundant-should-component-update
Disallow shouldComponentUpdate in a class that extends React.PureComponent
class Price extends React.PureComponent {Price does not need shouldComponentUpdate when extending React.PureComponent.
shouldComponentUpdate(nextProps) {
return nextProps.amount !== this.props.amount;
}
render() {
return <span>{this.props.amount}</span>;
}
}react/no-render-return-value
Disallow using the return value of ReactDOM.render
const app = ReactDOM.renderDo not depend on the return value from ReactDOM.render(<App />, document.body);react/no-set-state
Disallow this.setState in class components
class Counter extends React.Component {
increment() {
this.setStateDo not use setState({ count: this.state.count + 1 });
}
render() {
return <button onClick={() => this.increment()}>{this.state.count}</button>;
}
}react/no-string-refs
Disallow strings as the value of the ref attribute
class SearchBox extends React.Component {
componentDidMount() {
this.refs.input.focus();
}
render() {
return <input ref="input"Using string literals in ref attributes is deprecated. />;
}
}react/no-this-in-sfc
Disallow this in function components
function Greeting(props) {
return <h1>Hello, {this.propsStateless functional components should not use this.name}</h1>;
}react/no-typos
Disallow misspelled lifecycle methods, static properties, and prop types
class Clock extends React.Component {
static DefaultPropsTypo in static class property declaration = { interval: 1000 };
getDerivedStateFromProps(props) {Lifecycle method should be static: getDerivedStateFromProps
return { interval: props.interval };
}
componentDidmount() {Typo in component lifecycle method declaration: componentDidmount should be componentDidMount
this.start();
}
}react/no-unescaped-entitieshas suggestions
Disallow " and ' as plain characters in JSX text
const label = <label htmlFor="name">"" can be escaped with ", “, ", ”. Your name</label>;react/no-unknown-propertyfixable
Disallow attributes on DOM elements that React does not know
const field = (
<label class="field"Unknown property class found, use className instead for="email"Unknown property for found, use htmlFor instead>
Email <input id="email" tabindex="1"Unknown property tabindex found, use tabIndex instead />
</label>
);react/no-unsafe
Disallow the UNSAFE_ lifecycle methods of class components
class Feed extends React.Component {
UNSAFE_componentWillMount() {UNSAFE_componentWillMount is unsafe for use in async rendering. Update the component to use componentDidMount instead. See reactjs.org/blog/2018/03/27/update-on-async-rendering.html.
this.subscription = subscribe(this.props.channel);
}
}react/no-unstable-nested-components
Disallow defining a component inside another component
function UserList({ users }) {
function Row({ user }) {Do not define components during render. React will see a new component type on every render and destroy the entire subtree’s DOM nodes and state (reactjs.org/docs/reconciliation.html#elements-of-different-types). Instead, move this component definition out of the parent component “UserList” and pass data as props.
return <li>{user.name}</li>;
}
return (
<ul>
{users.map(user => (
<Row key={user.id} user={user} />
))}
</ul>
);
}react/no-unused-class-component-methods
Disallow methods and properties of a class component that the class never uses
class SearchBox extends React.Component {
timeoutUnused method or property timeout of class SearchBox = 300;
handleChangeUnused method or property handleChange of class SearchBox(event) {
this.props.onSearch(event.target.value);
}
render() {
return <input />;
}
}react/no-unused-prop-types
Disallow props that are declared but never used in the component
function Avatar({ name }) {
return <span>{name}</span>;
}
Avatar.propTypes = {
name: PropTypes.string,
sizesize PropType is defined but prop is never used: PropTypes.number,
};react/no-unused-state
Disallow fields of a class component's state that are never read
class Counter extends React.Component {
state = { count: 0, lastClick: nullUnused state field: lastClick };
render() {
return <button>{this.state.count}</button>;
}
}react/no-will-update-set-state
Disallow this.setState in componentWillUpdate
class Price extends React.Component {
componentWillUpdate(nextProps) {
this.setStateDo not use setState in componentWillUpdate({ total: nextProps.amount * nextProps.quantity });
}
render() {
return <span>{this.state.total}</span>;
}
}react/only-export-components
Require files with components to export only components, so Fast Refresh works
export function formatPriceFast refresh only works when a file only exports components. Use a new file to share constants or functions between components.(cents) {
return `$${(cents / 100).toFixed(2)}`;
}
export function Price({ cents }) {
return <span>{formatPrice(cents)}</span>;
}react/prefer-es6-class
Enforce one way to write class components: class or createReactClass
const Greeting = createReactClass({Component should use es6 class instead of createClass
render() {
return <h1>Hello {this.props.name}</h1>;
},
});react/prefer-exact-props
Require propTypes to be wrapped in a function that rejects unknown props
function Button({ label }) {
return <button>{label}</button>;
}
Button.propTypesComponent propTypes should be exact by using exact. = {
label: PropTypes.string,
};react/prefer-function-component
Require function components instead of class components
class CounterClass component should be written as a function component. extends React.Component {
state = { count: 0 };
render() {
return <button onClick={() => this.setState({ count: this.state.count + 1 })}>{this.state.count}</button>;
}
}react/prefer-read-only-propsfixable
Require the props in a component's type to be readonly
type Props = {
name: string;Prop name should be read-only.
age?: number;Prop age should be read-only.
};
function Profile(props: Props) {
return <p>{props.name} ({props.age})</p>;
}react/prefer-stateless-function
Require class components that only render from props to be functions
class Greeting extends React.Component {Component should be written as a pure function
render() {
return <h1>Hello {this.props.name}</h1>;
}
}react-hooks/preserve-manual-memoization
Disallow useMemo and useCallback calls that React Compiler cannot keep memoized
import { useMemo } from "react";
function Table({ rows }) {
const columns = ["name"];
const isWide = useIsWide();
if (isWide) {
columns.push("email");
}
const header = useMemo(() => columns.map(name => name.toUpperCase()), [columns])Compilation Skipped: Existing memoization could not be preservedReact Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.;
return <Grid header={header} rows={rows} />;
}react/prop-types
Require every prop that a component uses to be declared
function Greeting({ namename is missing in props validation }) {
return <h1>Hello {name}</h1>;
}react-hooks/purity
Disallow calls such as Math.random() and Date.now() while a component renders
function Clock() {
const now = Date.now()Error: Cannot call impure function during renderDate.now is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent).;
return <time>{now}</time>;
}react/react-in-jsx-scope
Require React to be in scope in files that use JSX
export function App() {
return <h1>React must be in scope when using JSXHello</h1>;
}react-hooks/refs
Disallow reading or writing ref.current while a component renders
import { useRef } from "react";
function Counter() {
const renders = useRef(0);
renders.currentError: Cannot access refs during renderReact refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the current property) during render can cause your component not to update as expected (react.dev/reference/react/useRef). = renders.currentError: Cannot access refs during renderReact refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the current property) during render can cause your component not to update as expected (react.dev/reference/react/useRef). + 1;
return <p>Rendered {renders.currentError: Cannot access refs during renderReact refs are values that are not needed for rendering. Refs should only be accessed outside of render, such as in event handlers or effects. Accessing a ref value (the current property) during render can cause your component not to update as expected (react.dev/reference/react/useRef).} times</p>;
}react/require-default-props
Require a default value for every prop that is not required
function Greeting({ name, punctuation }) {
return <h1>Hello {name}{punctuation}</h1>;
}
Greeting.propTypes = {
name: PropTypes.string.isRequired,
punctuation: PropTypes.stringpropType punctuation is not required, but has no corresponding defaultProps declaration.,
};react/require-optimization
Require class components to define shouldComponentUpdate or extend PureComponent
class Row extends React.Component {Component is not optimized. Please add a shouldComponentUpdate method.
render() {
return <li>{this.props.label}</li>;
}
}react/require-render-return
Require the render method of a class component to return something
class Greeting extends React.Component {
render() {Your render method should have a return statement
<h1>Hello {this.props.name}</h1>;
}
}react-hooks/rule-suppression
Disallow comments that turn off rules-of-hooks or exhaustive-deps
import { useEffect } from "react";
function Title({ text }) {
useEffect(() => {
document.title = text;
// eslint-disable-next-line react-hooks/exhaustive-depsReact rule suppression prevents optimization
}, []);
return <h1>{text}</h1>;
}react-hooks/rules-of-hookson by default
Require hooks to be called at the top level of a component or a custom hook
function Profile({ user }) {
if (user) {
const [editing, setEditing] = useStateReact Hook useState is called conditionally. React Hooks must be called in the exact same order in every component render.(false);
}
return <p>{user?.name}</p>;
}react/self-closing-compfixable
Require elements without children to be self-closing
const avatar = <Avatar user={user}>Empty components are self-closing</Avatar>;
const spacer = <div className="spacer">Empty components are self-closing</div>;
const list = (
<UserList users={users}>Empty components are self-closing
</UserList>
);react-hooks/set-state-in-effect
Disallow calling a state setter directly in the body of an effect
import { useEffect, useState } from "react";
function FullName({ first, last }) {
const [fullName, setFullName] = useState("");
useEffect(() => {
setFullNameError: Calling setState synchronously within an effect can trigger cascading rendersEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:Update external systems with the latest state from React.Subscribe for updates from some external system, calling setState in a callback function when external state changes.Calling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (react.dev/learn/you-might-not-need-an-effect).(first + " " + last);
}, [first, last]);
return <span>{fullName}</span>;
}react-hooks/set-state-in-render
Disallow calling a state setter unconditionally while a component renders
import { useState } from "react";
function Cart({ items }) {
const [count, setCount] = useState(0);
setCountError: Cannot call setState during renderCalling setState during render may trigger an infinite loop.To reset state when other state/props change, store the previous value in state and update conditionally: react.dev/reference/react/useState#storing-information-from-previous-rendersTo derive data from other state/props, compute the derived data during render without using state.(items.length);
return <span>{count}</span>;
}react/sort-comp
Enforce the order of methods and properties in a class component
class Profile extends React.Component {
render() {render should be placed after handleClick
return <button onClick={this.handleClick}>{this.props.name}</button>;
}
handleClick() {
this.props.onSelect();
}
componentDidMount() {
this.props.load();
}
}react/sort-default-props
Require the keys of defaultProps to be sorted alphabetically
function Button({ label, size }) {
return <button className={size}>{label}</button>;
}
Button.defaultProps = {
size: "medium",
label: "OK"Default prop types declarations should be sorted alphabetically,
};react/sort-prop-typesfixable
Require the keys of propTypes to be sorted alphabetically
function Button({ label, size }) {
return <button className={size}>{label}</button>;
}
Button.propTypes = {
size: PropTypes.string,
label: PropTypes.stringProp types declarations should be sorted alphabetically,
};react/state-in-constructor
Enforce where a class component sets its initial state: in the constructor or in a class field
class Counter extends React.Component {
state = { count: 0 };State initialization should be in a constructor
render() {
return <span>{this.state.count}</span>;
}
}react-hooks/static-components
Disallow components that are created while another component renders
function Form({ label }) {
const Field = () => <input placeholder={label} />;
return <FieldError: Cannot create components during renderComponents created during render will reset their state each time they are created. Declare components outside of render. />;
}react/static-property-placement
Enforce where a class component declares propTypes, defaultProps and other static properties
class Button extends React.Component {
static get defaultProps() {defaultProps should be declared as a static class property.
return { label: "OK" };
}
render() {
return <button>{this.props.label}</button>;
}
}
Button.propTypespropTypes should be declared as a static class property. = {
label: PropTypes.string,
};react/style-prop-object
Require the value of the style prop to be an object
const title = <h1 style="color: red"Style prop value must be an object>Settings</h1>;
const css = "margin-top: 8px";
const body = <p style={cssStyle prop value must be an object}>Saved</p>;
const footer = React.createElement("footer", { style: "padding: 4px"Style prop value must be an object });react-hooks/syntaxhas suggestions
Disallow invalid JavaScript in components and hooks, such as assigning to a const
function Total({ items }) {
const total = 0;
totalError: Cannot reassign a const variabletotal is declared as const. = items.length;
return <span>{total}</span>;
}react-hooks/todo
Report valid syntax that the React Compiler does not support yet
function Report({ load }) {
let rows = [];
try {Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
rows = load();
} finally {
log("loaded");
}
return <Table rows={rows} />;
}react-hooks/unsupported-syntax
Disallow syntax that the React Compiler cannot compile, such as eval()
function Calculator({ formula }) {
const result = evalCompilation Skipped: The eval function is not supportedEval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler.(formula);
return <output>{result}</output>;
}react-hooks/use-memo
Require useMemo() callbacks to take no parameters, be synchronous, and not reassign outer variables
import { useMemo } from "react";
function Report({ id }) {
const rows = useMemo(async () => {Error: useMemo() callbacks may not be async or generator functionsuseMemo() callbacks are called once and must synchronously return a value.
return await loadRows(id);
}, [id]);
return <Table rows={rows} />;
}react/void-dom-elements-no-children
Disallow children on void DOM elements such as <img /> and <br />
const field = <input name="email">Email</input>Void DOM element <input /> cannot receive children.;
const photo = <img src={url} children="A photo" />Void DOM element <img /> cannot receive children.;
const rule = <hr dangerouslySetInnerHTML={{ __html: "Section" }} />Void DOM element <hr /> cannot receive children.;
const lineBreak = React.createElement("br", {}, "Next line")Void DOM element <br /> cannot receive children.;react-hooks/void-use-memo
Require useMemo() callbacks to return a value, and the value to be used
import { useMemo } from "react";
function Report({ rows }) {
const visible = useMemo(() => {Error: useMemo() callbacks must return a valueThis useMemo() callback doesn't return a value. useMemo() is for computing and caching values, not for arbitrary side effects.
rows.filter(row => !row.hidden);
}, [rows]);
return <Table rows={visible} />;
}React performance 4 rules
react-perf/jsx-no-jsx-as-prop
Disallow JSX elements created during render as the value of a JSX prop
function Toolbar() {
return <Button icon={<SaveIcon />JSX attribute values should not contain other JSX.} />;
}react-perf/jsx-no-new-array-as-prop
Disallow arrays created during render as the value of a JSX prop
function Settings() {
return <Select options={["light", "dark"]JSX attribute values should not contain Arrays created in the same scope.} />;
}react-perf/jsx-no-new-function-as-prop
Disallow functions created during render as the value of a JSX prop
function Editor({ save }) {
return <Button onClick={() => save("draft")JSX attribute values should not contain functions created in the same scope.} />;
}react-perf/jsx-no-new-object-as-prop
Disallow objects created during render as the value of a JSX prop
function Profile({ user }) {
return <Avatar user={user} style={{ width: 32 }JSX attribute values should not contain objects created in the same scope.} />;
}Accessibility (jsx-a11y) 36 rules
jsx-a11y/alt-text
Require alternative text on img, area, object and input type="image"
const avatar = <img src={user.photo} />Missing alt attribute.;
const divider = <img src="/line.png" role="presentation" />ARIA used where native HTML could suffice.;
const search = <input type="image" src="/search.png" />Missing alternative text.;
const report = <object data="/report.pdf" alt="Annual report" />Missing alternative text.;jsx-a11y/anchor-ambiguous-text
Disallow link text such as "click here" that says nothing about the target
const pricing = <p>To see our prices, <a href="/pricing">click here</a>Ambiguous text within anchor, screen reader users rely on link text for context..</p>;
const docs = <a href="/docs">Learn more!</a>Ambiguous text within anchor, screen reader users rely on link text for context.;
const home = <a href="/"><img src="/home.png" alt="link" /></a>Ambiguous text within anchor, screen reader users rely on link text for context.;jsx-a11y/anchor-has-contentfixable
Require links to have content that a screen reader can read
const profile = <a href="/profile" />Missing accessible content when using a elements.;
const settings = <a href="/settings"><span aria-hidden>Settings</span></a>Missing accessible content when using a elements.;jsx-a11y/anchor-is-valid
Require links to have a real href, and buttons to be button elements
const home = <aMissing href attribute for the a element.>Home</a>;
const top = <aUse of incorrect href for the a element. href="#">Back to top</a>;
const save = <aThe a element has href and onClick. href="#" onClick={saveDraft}>Save</a>;jsx-a11y/aria-activedescendant-has-tabindex
Require elements with aria-activedescendant to be focusable
const list = (
<divElements with aria-activedescendant must be tabbable. role="listbox" aria-activedescendant={activeId}>
{options}
</div>
);jsx-a11y/aria-propsfixable
Disallow aria-* attributes that do not exist
const name = <input aria-labeledby="name-label"aria-labeledby is not a valid ARIA attribute. />;
const menu = <button aria-open={isOpen}aria-open is not a valid ARIA attribute.>Menu</button>;jsx-a11y/aria-proptypes
Require aria-* attributes to have a value of the right kind
const toggle = <button aria-expanded="yes"This is not a valid ARIA state and property value for aria-expanded.>Menu</button>;
const title = <div role="heading" aria-level="two"This is not a valid ARIA state and property value for aria-level.>Billing</div>;
const column = <th aria-sort="up"This is not a valid ARIA state and property value for aria-sort.>Name</th>;
const status = <div aria-live="loud"This is not a valid ARIA state and property value for aria-live.>{message}</div>;jsx-a11y/aria-role
Require role to be a real ARIA role that is not abstract
const picker = <div role="datepicker"Elements with ARIA roles must use a valid, non-abstract ARIA role.>{days}</div>;
const slider = <div role="range"Elements with ARIA roles must use a valid, non-abstract ARIA role. />;
const panel = <div role=""Elements with ARIA roles must use a valid, non-abstract ARIA role.>{children}</div>;
const toggle = <div role="switch toggle"Elements with ARIA roles must use a valid, non-abstract ARIA role. aria-checked={isOn} tabIndex={0} />;jsx-a11y/aria-unsupported-elementsfixable
Disallow role and aria-* attributes on elements that do not support them
const head = (
<head>
<meta charSet="utf-8" aria-hidden="true"This element does not support ARIA roles, states, or properties. />
<title role="heading"This element does not support ARIA roles, states, or properties.>Dashboard</title>
</head>
);jsx-a11y/autocomplete-valid
Require autoComplete on an input to be a value that browsers know
const email = <input type="email" autoComplete="mail"mail is not a valid value for autocomplete. />;
const password = <input type="password" autoComplete="password"password is not a valid value for autocomplete. />;jsx-a11y/click-events-have-key-events
Require a keyboard handler next to onClick on elements that are not controls
const row = <div onClick={openDetails}>Enforce a clickable non-interactive element has at least one keyboard event listener.{item.name}</div>;
const more = <a onClick={loadMore}>Enforce a clickable non-interactive element has at least one keyboard event listener.Load more</a>;jsx-a11y/control-has-associated-label
Require buttons, links and other controls to have a text label
const close = <button onClick={closeDialog}>A control must be associated with a text label.<img src="/x.svg" /></button>;
const next = <div role="button" tabIndex={0} onClick={goNext} />A control must be associated with a text label.;jsx-a11y/heading-has-content
Require headings to have content that a screen reader can read
const title = <h1 className="hero" />Headings must have content and the content must be accessible by a screen reader.;
const section = <h2>Headings must have content and the content must be accessible by a screen reader.<span aria-hidden>Pricing</span></h2>;jsx-a11y/html-has-lang
Require a lang attribute on the html element
function Layout({ children }) {
return <htmlMissing lang attribute.><body>{children}</body></html>;
}
function EmptyLang({ children }) {
return <html lang="">Missing value for lang attribute<body>{children}</body></html>;
}jsx-a11y/iframe-has-title
Require a title on iframe elements
const map = <iframeMissing title attribute for the iframe element. src={mapUrl} />;
const video = <iframeMissing title attribute for the iframe element. src={videoUrl} title="" />;
const widget = <iframeMissing title attribute for the iframe element. {...frameProps} />;jsx-a11y/img-redundant-alt
Disallow the words "image", "photo" and "picture" in alt text
const cover = <img src="/lighthouse.jpg" altRedundant alt attribute.="Photo of a lighthouse" />;
const avatar = <img src={user.photo} altRedundant alt attribute.={`Picture of ${user.name}`} />;jsx-a11y/interactive-supports-focushas suggestions
Require elements with an interactive role and an event handler to be focusable
const save = <div role="button" onClick={saveDraft}>Elements with the button interactive role must be tabbable.Save</div>;
const tab = <span role="tab" onClick={showBilling}>Elements with the tab interactive role must be focusable.Billing</span>;jsx-a11y/label-has-associated-control
Require every <label> to have text and a form control it belongs to
const form = (
<form>
<label>A form label must be associated with a control.Email</label>
<input type="email" id="email" />
<label htmlFor="password" />A form label must have accessible text.
<input type="password" id="password" />
</form>
);jsx-a11y/lang
Require the lang attribute of <html> to be a valid language tag
const wrong = <html lang="english"lang attribute must have a valid value. />;
const empty = <html lang=""lang attribute must have a valid value. />;
const missing = <htmllang attribute must have a valid value. />;jsx-a11y/media-has-caption
Require <audio> and <video> to have a <track> with captions
const player = <video src="/intro.mp4" controls />Missing <track> element with captions inside <audio> or <video> element;jsx-a11y/mouse-events-have-key-events
Require onFocus with onMouseOver and onBlur with onMouseOut
const help = (
<button onMouseOver={showTooltip}onMouseOver must be accompanied by onFocus for accessibility. onMouseOut={hideTooltip}onMouseOut must be accompanied by onBlur for accessibility.>
Help
</button>
);jsx-a11y/no-access-keyhas suggestions
Disallow the accessKey attribute
const save = <button accessKey="s"No access key attribute allowed.>Save</button>;
const open = <MenuItem accesskey="o"No access key attribute allowed. />;jsx-a11y/no-autofocushas suggestions
Disallow the autoFocus attribute
const search = <input type="search" autoFocusThe autoFocus attribute is found here, which can cause usability issues for sighted and non-sighted users. />;jsx-a11y/no-distracting-elements
Disallow <marquee> and <blink>
const banner = <marqueeDo not use <marquee> elements as they can create visual accessibility issues and are deprecated.>Free shipping this week</marquee>;
const warning = <blinkDo not use <blink> elements as they can create visual accessibility issues and are deprecated.>Sale ends today</blink>;jsx-a11y/no-interactive-element-to-noninteractive-role
Disallow non-interactive ARIA roles on interactive elements
const title = <button role="heading"Interactive elements should not be assigned non-interactive roles. onClick={toggle}>Details</button>;
const item = <a href="/settings" role="listitem"Interactive elements should not be assigned non-interactive roles.>Settings</a>;jsx-a11y/no-noninteractive-element-interactions
Disallow mouse and keyboard handlers on non-interactive elements
const item = <liNon-interactive elements should not be assigned mouse or keyboard event listeners. onClick={() => select(user)}>{user.name}</li>;
const card = <divNon-interactive elements should not be assigned mouse or keyboard event listeners. role="article" onKeyDown={openCard} />;jsx-a11y/no-noninteractive-element-to-interactive-role
Disallow interactive ARIA roles on non-interactive elements
const title = <h2 role="button"Non-interactive elements should not be assigned interactive roles. onClick={toggle}>Details</h2>;
const avatar = <img src={user.photo} alt={user.name} role="link"Non-interactive elements should not be assigned interactive roles. />;jsx-a11y/no-noninteractive-tabindex
Disallow tabIndex of 0 or more on non-interactive elements
const summary = <article tabIndex={0}tabIndex should only be declared on interactive elements.>{post.summary}</article>;
const box = <div tabIndex="0"tabIndex should only be declared on interactive elements. />;jsx-a11y/no-redundant-rolesfixable
Disallow a role that the element already has
const save = <button role="button"The button element has an implicit role of button. Defining this explicitly is redundant and should be avoided.>Save</button>;
const items = <ul role="list"The ul element has an implicit role of list. Defining this explicitly is redundant and should be avoided. />;
const volume = <input type="range" role="slider"The input element has an implicit role of slider. Defining this explicitly is redundant and should be avoided. />;jsx-a11y/no-static-element-interactions
Require a role on <div>, <span> and similar elements that have event handlers
const close = <divStatic HTML elements with event handlers require a role. className="close" onClick={closeDialog}>Close</div>;
const more = <aStatic HTML elements with event handlers require a role. onClick={loadMore}>Show more</a>;jsx-a11y/prefer-tag-over-role
Require the HTML element instead of a role that imitates it
const save = <div role="button"Prefer button over role attribute button. onClick={saveDraft}>Save</div>;
const menu = <div role="navigation"Prefer nav over role attribute navigation. />;
const title = <span role="heading"Prefer h1, h2, h3, h4, h5, h6 over role attribute heading. aria-level={2}>Settings</span>;
const post = <Card role="article"Prefer article over role attribute article. />;jsx-a11y/role-has-required-aria-props
Require the aria-* attributes that an element's role needs
const dark = <span role="switch"switch role is missing required aria props aria-checked. tabIndex={0} onClick={toggleTheme} />;
const title = <div role="heading"heading role is missing required aria props aria-level.>Settings</div>;jsx-a11y/role-supports-aria-props
Disallow aria-* attributes that the element's role does not support
const filter = <a href="/sale" aria-checked={onSale}The attribute aria-checked is not supported by the role link. This role is implicit on the element a.>On sale</a>;
const items = <ul aria-expanded={isOpen}The attribute aria-expanded is not supported by the role list. This role is implicit on the element ul. />;
const save = <div role="button" aria-required="true"The attribute aria-required is not supported by the role button. />;jsx-a11y/scopefixable
Disallow the scope attribute on elements other than <th>
const row = (
<tr>
<td scope="row"The scope prop can only be used on <th> elements>Total</td>
<td>{total}</td>
</tr>
);jsx-a11y/tabindex-no-positivehas suggestions
Disallow tabIndex values greater than 0
const form = (
<form>
<input name="email" tabIndex={2}Avoid positive integer values for tabIndex. />
<input name="name" tabIndex="1"Avoid positive integer values for tabIndex. />
</form>
);Next.js 21 rules
@next/next/google-font-display
Require a display parameter other than auto, block or fallback on Google Fonts URLs
const font = <linkA font-display parameter is missing (adding &display=optional is recommended). href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />;@next/next/google-font-preconnect
Require rel="preconnect" on <link> elements that point to fonts.gstatic.com
const hint = <linkrel="preconnect" is missing from Google Font. href="https://fonts.gstatic.com" />;@next/next/inline-script-id
Require an id on next/script components that have inline content
import Script from "next/script";
export default function Page() {
return <Scriptnext/script components with inline content must specify an id attribute.>{`window.startedAt = Date.now();`}</Script>;
}@next/next/next-script-for-ga
Require next/script instead of <script> to load Google Analytics
export default function Analytics() {
return <scriptPrefer next/script component when using the inline script for Google Analytics. async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX" />;
}@next/next/no-assign-module-variable
Disallow declaring a variable named module
const moduleDo not assign to the variable module. = await import("./charts.js");
module.render();@next/next/no-async-client-component
Disallow async components in files that start with "use client"
"use client";
export default async function ProfilePrevent client components from being async functions.({ id }) {
const user = await fetchUser(id);
return <h1>{user.name}</h1>;
}@next/next/no-before-interactive-script-outside-document
Disallow the beforeInteractive strategy of next/script outside pages/_document.js
import Script from "next/script";
export default function Home() {
return <Script src="https://example.com/consent.js" strategy="beforeInteractive"next/script's beforeInteractive strategy should not be used outside of pages/_document.js />;
}@next/next/no-css-tags
Disallow <link rel="stylesheet"> for your own CSS files
export default function Layout({ children }) {
return (
<div>
<linkDo not include stylesheets manually. rel="stylesheet" href="/styles/site.css" />
{children}
</div>
);
}@next/next/no-document-import-in-page
Disallow importing next/document outside pages/_document.js
import { Head } from "next/document";<Document /> from next/document should not be imported outside of pages/_document.js. See: nextjs.org/docs/messages/no-document-import-in-page
export default function Home() {
return (
<Head>
<meta name="theme-color" content="#111111" />
</Head>
);
}@next/next/no-duplicate-head
Disallow more than one <Head> element in a file
import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html>
<HeadDo not include multiple instances of <Head/>>
<meta name="theme-color" content="#111111" />
</Head>
<Head>
<link rel="icon" href="/favicon.ico" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}@next/next/no-head-element
Disallow the <head> element in favor of Head from next/head
export default function About() {
return (
<div>
<head>Do not use <head> element. Use <Head /> from next/head instead.
<title>About us</title>
</head>
<h1>About us</h1>
</div>
);
}@next/next/no-head-import-in-document
Disallow importing next/head in pages/_document.js
import { Html, Main, NextScript } from "next/document";
import Head from "next/head";Prevent usage of next/head in pages/_document.js.
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}@next/next/no-html-link-for-pages
Disallow <a> elements that link to another page of the app
export default function Nav() {
return <a href="/about">Do not use <a> elements to navigate between Next.js pages.About</a>;
}@next/next/no-img-element
Disallow the <img> element in favor of Image from next/image
export default function Avatar({ user }) {
return <imgUsing <img> could result in slower LCP and higher bandwidth. src="/avatars/default.png" alt={user.name} />;
}@next/next/no-page-custom-font
Disallow Google Fonts <link> elements outside pages/_document.js
import Head from "next/head";
export default function Home() {
return (
<Head>
<link href="https://fonts.googleapis.com/css2?family=Inter&display=optional" rel="stylesheet" />Custom fonts not added in pages/_document.js will only load for a single page. This is discouraged.
</Head>
);
}@next/next/no-script-component-in-head
Disallow <Script> as a child of Head from next/head
import Head from "next/head";
import Script from "next/script";
export default function Home() {
return (
<HeadPrevent usage of next/script in next/head component.>
<title>Home</title>
<Script src="https://example.com/widget.js" />
</Head>
);
}@next/next/no-styled-jsx-in-document
Disallow <style jsx> in pages/_document.js
import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<style jsx>styled-jsx should not be used in pages/_document.js{`
body {
margin: 0;
}
`}</style>
</body>
</Html>
);
}@next/next/no-sync-scripts
Disallow <script src> without async or defer
export default function Checkout() {
return <scriptPrevent synchronous scripts. src="https://example.com/payments.js" />;
}@next/next/no-title-in-document-head
Disallow <title> in the Head of next/document
import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html>
<HeadPrevent usage of <title> with Head component from next/document.>
<title>My shop</title>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}@next/next/no-typoshas suggestions
Disallow misspellings of getStaticProps, getStaticPaths and getServerSideProps
export async function getStaticPropgetStaticProp may be a typo. Did you mean getStaticProps?() {
return { props: { posts: await loadPosts() } };
}
export default function Posts({ posts }) {
return <PostList posts={posts} />;
}@next/next/no-unwanted-polyfillio
Disallow scripts from polyfill.io, and polyfills that Next.js already ships
export default function Polyfills() {
return <script src="https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver"Using polyfill.io is a security risk due to a supply chain attack in 2024. />;
}import 46 rules
import/consistent-type-specifier-stylefixable
Enforce one way to mark type-only imports: import type { A } or import { type A }
import { type User, type Session } from "./models";Prefer using a top-level type-only import instead of inline type specifiers.import/default
Require a default import to match a default export in the imported file
import saveNo default export found in imported module ./storage.js. from "./storage.js";
save({ id: "draft" });import/dynamic-import-chunknamehas suggestions
Require a webpackChunkName comment in every dynamic import()
const settings = await import("./pages/settings.js")dynamic imports require a leading comment with the webpack chunkname;import/enforce-node-protocol-usagefixable
Enforce either node:fs or fs when importing Node.js built-in modules
import fs from "fs"Prefer node:fs over fs.;
export { join } from "path"Prefer node:path over path.;
const os = require("os"Prefer node:os over os.);
const crypto = await import("crypto"Prefer node:crypto over crypto.);import/export
Disallow exporting the same name twice, and export * from a file with no named exports
export * from "./dates.js";Multiple exports of name format.
export * from "./numbers.js";Multiple exports of name format.import/exports-last
Require all exports to come after every other statement in the file
export const retries = 3;Export statements should appear at the end of the file
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
export { wait };import/extensions
Enforce that import paths always include the file extension, or never do
import { createUser } from "./user"Missing file extension js for ./user;import/firstfixable
Require all imports to come before every other statement in the file
import { loadConfig } from "./config.js";
loadConfig(); // runs after "./server.js" has loaded, not before
import { startServer } from "./server.js";Import in body of module; reorder to top.
startServer();import/group-exports
Require all named exports to be in a single export declaration
export const retries = 3;Multiple named export declarations; consolidate all named exports into a single export declaration
export function wait(ms) {Multiple named export declarations; consolidate all named exports into a single export declaration
return new Promise(resolve => setTimeout(resolve, ms));
}import/imports-firstfixabledeprecated
Require all imports to come before every other statement (old name of import/first)
import { loadConfig } from "./config.js";
loadConfig(); // runs after "./server.js" has loaded, not before
import { startServer } from "./server.js";Import in body of module; reorder to top.
startServer();import/max-dependencies
Enforce a maximum number of modules that a file can import
import { readFile } from "node:fs/promises";
export { parse } from "./parser.js";
const { render } = await import("./render.js"Maximum number of dependencies (2) exceeded.);import/named
Require each named import to match a named export in the imported file
import { fromatDatefromatDate not found in ./dates.js } from "./dates.js";
export { parseDateparseDate not found in ./dates.js } from "./dates.js";
console.log(fromatDate(new Date()));import/namespace
Require each property read from an import * as namespace to be exported by that file
import * as api from "./api.js";
api.removeremove not found in imported namespace api.("draft");
const { updateupdate not found in imported namespace api. } = api;
api.save = () => {}Assignment to member of namespace api.;
const action = "load";
api[actionUnable to validate computed reference to imported namespace api.]("draft");import/newline-after-importfixable
Require an empty line between the last import and the code that follows
import { readFile } from "node:fs/promises";
iExpected 1 empty line after import statement not followed by another import.mport { join } from "node:path";
const root = join(import.meta.dirname, "..");import/no-absolute-pathfixable
Disallow importing a module by an absolute file system path
import config from "/home/ana/app/src/config.js"Do not import modules using an absolute path;
const logger = require("/home/ana/app/src/logger.js"Do not import modules using an absolute path);import/no-amd
Disallow AMD define() and require() calls
define(["./logger"], function (logger) {Expected imports instead of AMD define().
logger.info("ready");
});import/no-anonymous-default-export
Disallow default exports that have no name
export default function () {Unexpected default export of anonymous function
return "ok";
}import/no-commonjs
Disallow require(), module.exports and exports.* in favor of import and export
const { parse } = requireExpected import instead of require()("./parse");
module.exportsExpected export or "export default" = { run: text => parse(text) };import/no-cycle
Disallow importing a module that imports this file back, directly or through other modules
import { currentUser } from "./session";Dependency cycle detected.
export const findUser = id => ({ id });
export const isCurrent = user => user.id === currentUser.id;import/no-default-export
Disallow default exports
export defaultPrefer named exports. function formatPrice(cents) {
return `$${(cents / 100).toFixed(2)}`;
}import/no-deprecated
Disallow importing and using names that are marked @deprecated
import { fetchUserDeprecated: Use loadUser() instead. } from "./api";
fetchUserDeprecated: Use loadUser() instead.(1);import/no-duplicatesfixable
Disallow importing the same module more than once in a file
import { readFile } from "node:fs/promises"node:fs/promises imported multiple times.;
import { join } from "node:path";
import { writeFile } from "node:fs/promises"node:fs/promises imported multiple times.;import/no-dynamic-require
Disallow require() with an argument that is not a string literal
const plugin = require(name)Calls to require() should use string literals;
const locale = require(`./locales/${language}.json`)Calls to require() should use string literals;import/no-empty-named-blocksfixable
Disallow import {} from with nothing between the braces
import {} from "./setup";Unexpected empty named import blockimport/no-extraneous-dependencies
Disallow importing a package that package.json does not list
import express from "express";
import ms from "ms";ms should be listed in the project's dependencies. Run npm i -S ms to add itimport/no-import-module-exportsfixable
Disallow import declarations in a file that exports with module.exports or exports.*
import { parse } from "./parse";Cannot use import declarations in modules that export using CommonJS (module.exports = foo or exports.bar = hi)
module.exports = { run: text => parse(text) };import/no-internal-modules
Disallow importing a file from inside another directory or package
import { listUsers } from "./api/users"Reaching to ./api/users is not allowed.;import/no-mutable-exports
Disallow exporting a variable declared with let or var
export let count = 0;Exporting mutable let binding, use const instead.
export function increment() {
count += 1;
}import/no-named-as-default
Disallow naming a default import after a named export of the same module
import ButtonUsing exported name Button as identifier for default import. from "./button";import/no-named-as-default-member
Disallow reading a property of a default import that is also a named export of the module
import config from "./config";
config.loadCaution: config also has a named export load. Check if you meant to write import {load} from './config' instead.("./app.json");
const { loadCaution: config also has a named export load. Check if you meant to write import {load} from './config' instead. } = config;import/no-named-default
Disallow import { default as name } in favor of import name
import { default as ReactUse default import syntax to import React., useState } from "react";import/no-named-export
Disallow named exports, so that a module only has a default export
export const taxRate = 0.2;Named exports are not allowed.
export function formatPrice(cents) {Named exports are not allowed.
return `$${(cents / 100).toFixed(2)}`;
}import/no-namespacefixable
Disallow import * as name in favor of named imports
import * as utilsUnexpected namespace import. from "./utils";
const width = utils.clamp(utils.sum(sizes), 0, 100);import/no-nodejs-modules
Disallow importing Node.js builtin modules such as fs and path
import fs from "fs";Do not import Node.js builtin module fs
import { join } from "node:path";Do not import Node.js builtin module node:path
const os = require("os")Do not import Node.js builtin module os;import/no-relative-packagesfixable
Disallow relative imports that reach into another package
import { Button } from "../ui/button.js"Relative import from another package is not allowed. Use @acme/ui/button.js instead of ../ui/button.js;import/no-relative-parent-imports
Disallow relative imports from parent directories
import { getUser } from "../api.js"Relative imports from parent directories are not allowed. Please either pass what you're importing through at runtime (dependency injection), move home.js to same directory as ../api.js or consider making ../api.js a package.;import/no-restricted-paths
Disallow files in one folder from importing files in another
import { query } from "../server/db.js"Unexpected path ../server/db.js imported in restricted zone.;import/no-self-import
Disallow a module from importing itself
import * as cart from "./cart.js";Module imports itself.
export function total(items) {
return items.length;
}
export function print(items) {
console.log(cart.total(items));
}import/no-unassigned-import
Disallow imports that bind no name
import "./register-globals.js";Imported module should be assigned
import {} from "lodash";Imported module should be assigned
require("./patch-console.js")Imported module should be assigned;import/no-unresolved
Require every import to point to a file or package that exists
import { formatDate } from "./utils/fromat.js"Unable to resolve path to module ./utils/fromat.js.;
import leftPad from "a-package-that-is-not-installed"Unable to resolve path to module a-package-that-is-not-installed.;import/no-unused-modules
Disallow exports that no other file imports, and files that export nothing
export function formatDate(date) {}
export function formatTime(date) {}exported declaration formatTime not used within other modulesimport/no-useless-path-segmentsfixable
Disallow path segments in imports that can be removed
import { formatDate } from "./../format.js"Useless path segments for ./../format.js, should be ../format.js;
import { Header } from "../pages/header.js"Useless path segments for ../pages/header.js, should be ./header.js;import/no-webpack-loader-syntax
Disallow webpack loader syntax such as style-loader!./app.css in imports
import styles from "style-loader!css-loader!./app.css";Unexpected ! in style-loader!css-loader!./app.css. Do not use import syntax to configure webpack loaders.
const notes = require("raw-loader!./notes.txt")Unexpected ! in raw-loader!./notes.txt. Do not use import syntax to configure webpack loaders.;import/orderfixable
Enforce a consistent order of imports, by group and optionally by name
import { formatDate } from "./format.js";
import express from "express";express import should occur before import of ./format.js
import fs from "node:fs";node:fs import should occur before import of ./format.jsimport/prefer-default-export
Require a default export in a file that exports a single name
export function formatDate(date) {Prefer default export on a file with single export.
return date.toISOString();
}import/unambiguous
Require a module to have an import or export so it cannot be read as a script
function greet(name) {This module could be parsed as a valid script.
console.log(`Hello, ${name}`);
}
greet("Ada");unicorn 138 rules
unicorn/catch-error-namefixable
Enforce one name for the error in catch clauses and promise handlers
try {
await save(user);
} catch (eThe catch parameter e should be named error) {
console.log(e.message);
}
save(user).catch(errThe catch parameter err should be named error => console.log(err.message));
save(user).then(done, exThe catch parameter ex should be named error => console.log(ex.message));unicorn/consistent-assertfixable
Require assert.ok(value) instead of assert(value) with node:assert
import assert from "node:assert";
assertInconsistent assert usage.(user.isActive);
assert.equal(user.name, "Ada");unicorn/consistent-date-clonefixable
Require new Date(date) instead of new Date(date.getTime()) to copy a date
const deadline = new Date(start.getTime()Unnecessary .getTime() call);
deadline.setDate(deadline.getDate() + 7);unicorn/consistent-empty-array-spreadhas suggestions
Require both branches of a spread ternary in an array to have the same type
const args = ["build", ...(watch ? ["--watch", "--hot"] : ""Prefer consistent types when spreading a ternary in an array literal.)];unicorn/consistent-existence-index-checkfixable
Require === -1 and !== -1 to test the result of indexOf() and findIndex()
const index = users.findIndex(user => user.id === id);
if (index < 0Prefer === -1 over < 0 to check non-existence.) {
throw new Error("No such user");
}
if (index >= 0Prefer !== -1 over >= 0 to check non-existence.) {
users.splice(index, 1);
}unicorn/consistent-function-scoping
Require functions that use nothing from the enclosing function to be moved out of it
export function totalPriceFunction withTax does not capture any variables from its parent scope(items) {
function withTax(price) {
return price * 1.2;
}
return items.reduce((sum, item) => sum + withTax(item.price), 0);
}unicorn/consistent-template-literal-escapefixable
Enforce one way to escape ${ in template literals
const usage = `Write $\{Invalid escape sequence in template literal.name} to insert the name`;
const help = `Write \$\{Invalid escape sequence in template literal.name} to insert the name`;unicorn/custom-error-definition
Enforce that subclasses of Error set name and pass the message to super()
class ValidationError extends Error {}The name property should be set to ValidationError.unicorn/empty-brace-spacesfixable
Disallow spaces and line breaks inside empty braces
const cache = { }No spaces inside empty pair of braces allowed;
function noop() {No spaces inside empty pair of braces allowed
}
class Store { }No spaces inside empty pair of braces allowedunicorn/error-message
Require a message when creating a built-in error
function parsePort(value) {
if (value === undefined) throw new Error()Pass a message to the Error constructor.;
if (value === "") throw new TypeError(""Error message should not be an empty string.);
if (value > 65_535) throw new RangeError({ value }Error message should be a string.);
return Number(value);
}unicorn/escape-casefixable
Require uppercase hexadecimal digits in escape sequences
const copyright = "\xa9 2026"Use uppercase characters for the value of the escape sequence.;
const price = `${amount}\u00a0EURUse uppercase characters for the value of the escape sequence.`;
const smile = "\u{1f600}"Use uppercase characters for the value of the escape sequence.;
const escape = /\x1b\[\d+m/Use uppercase characters for the value of the escape sequence.;unicorn/explicit-length-checkfixable
Require length and size to be compared with a number
if (items.lengthUse .length > 0 when checking length is not zero.) {
render(items);
}
if (!selected.sizeUse .size === 0 when checking size is zero.) {
disable(button);
}
const hasErrors = errors.length >= 1Use .length > 0 when checking length is not zero.;
const first = items.lengthUse .length > 0 when checking length is not zero. && items[0];unicorn/explicit-timer-delayfixable
Require or disallow an explicit delay for setTimeout() and setInterval()
setTimeout(() => input.focus())setTimeout should have an explicit delay argument.;
window.setInterval(poll)setInterval should have an explicit delay argument.;unicorn/filename-case
Enforce one case style for file names
eFilename should be in kebab-casexport function userProfile() {}unicorn/import-style
Enforce how each module is imported: named, default, namespace, or for side effects
import { join } from "node:path";Use default import for module node:path.
import util from "node:util";Use named import for module node:util.unicorn/max-nested-calls
Enforce a maximum depth of calls nested in the arguments of other calls
save(normalize(parse(read(file)Call is nested too deeply. Maximum allowed is 3.)));unicorn/new-for-builtins
Require new for built-in classes and disallow it for String, Number, Boolean, Symbol, BigInt
const slots = Array(3)Use new Array() instead of Array();
const seen = Map()Use new Map() instead of Map();
const failure = Error("Not found")Use new Error() instead of Error();
const now = Date()Use String(new Date()) instead of Date();unicorn/no-abusive-eslint-disable
Require eslint-disable comments to name the rules they turn off
console.log(user.name); // eslint-disable-lineUnexpected eslint-disable comment that does not specify any rules to disable.
// oxlint-disable-next-lineUnexpected oxlint-disable comment that does not specify any rules to disable.
console.log(user.email);
/* eslint-disable Unexpected eslint-disable comment that does not specify any rules to disable.*/
console.log(user.id);
/* eslint-enable */unicorn/no-accessor-recursion
Disallow a getter or setter that uses its own property on this
class User {
get name() {
return this.nameDisallow recursive access to this within getters.;
}
set name(value) {
this.nameDisallow recursive access to this within setters. = value.trim();
}
}unicorn/no-anonymous-default-export
Require a name for a function or class that is the default export
export default function (user) {This function default export is missing a name
return `${user.first} ${user.last}`;
}unicorn/no-array-callback-reference
Disallow passing a function by name to array methods such as map and filter
const sizes = ["10", "10", "10"].map(parseIntAvoid passing a function reference directly to iterator methods); // [10, NaN, 2]unicorn/no-array-fill-with-reference-type
Disallow filling an array with one object, array, or class instance
const grid = new Array(3).fill([]Do not use a reference value as the fill value.);
grid[0].push("x"); // [["x"], ["x"], ["x"]]unicorn/no-array-for-each
Require a for...of loop instead of .forEach()
users.forEachDo not use Array#forEach((user) => {
if (!user.active) return;
notify(user);
});unicorn/no-array-method-this-argument
Disallow the thisArg argument of array methods such as map and filter
const mine = items.filter(function (item) {Avoid using thisArg with array iteration methods
return item.owner === this.id;
}, user);unicorn/no-array-reduce
Disallow reduce() and reduceRight() in favor of a loop
const byId = users.reduceDon't use Array#reduce() and Array#reduceRight(), use for loops instead.((result, user) => {
result[user.id] = user;
return result;
}, {});unicorn/no-array-reversehas suggestions
Require toReversed() instead of reverse(), which changes the array
const newestFirst = posts.reverseUse Array#toReversed() instead of Array#reverse().();unicorn/no-array-sorthas suggestions
Require toSorted() instead of sort(), which changes the array
const ranked = players.sortUse Array#toSorted() instead of Array#sort().((a, b) => b.score - a.score);unicorn/no-await-expression-memberfixable
Disallow reading a property directly from an await expression
async function show(id) {
const name = (await getUser(id)).nameDo not access a member directly from an await expression.;
const first = (await getPosts(id))[0]Do not access a member directly from an await expression.;
render(name, first);
}unicorn/no-await-in-promise-methodshas suggestions
Disallow await on the elements of the array passed to Promise.all() and similar methods
async function loadPage(id) {
const [user, posts] = await Promise.all([awaitPromise in Promise.all() should not be awaited. loadUser(id), awaitPromise in Promise.all() should not be awaited. loadPosts(id)]);
render(user, posts);
}unicorn/no-confusing-array-with
Disallow a negative index and .length as the index of Array#with()
const updated = scores.withAvoid using a negative index with Array#with().(-1, 100);unicorn/no-console-spacesfixable
Disallow spaces at the edges of strings where console.log() already adds one
console.log("Total: Do not use trailing spaces with console.log parameters", total);
console.error(path, " was not foundDo not use leading spaces with console.error parameters");
console.info(`Saved ${count} files `Do not use trailing spaces with console.info parameters, "in", elapsed);unicorn/no-document-cookie
Disallow assigning to document.cookie
document.cookieDo not use document.cookie directly. = "theme=dark; path=/";unicorn/no-empty-file
Disallow files that contain no code
// TODO: move the date helpers hereEmpty files are not allowed.unicorn/no-hex-escapefixable
Require Unicode escapes such as \u001B instead of hexadecimal escapes such as \x1B
const notice = "\xA9 2026 Example Inc."Use Unicode escapes instead of hexadecimal escapes.;
const reset = `\x1B[0mUse Unicode escapes instead of hexadecimal escapes.`;
const controlCharacters = /[\x00Use Unicode escapes instead of hexadecimal escapes.-\x1FUse Unicode escapes instead of hexadecimal escapes.]/;unicorn/no-immediate-mutation
Disallow changing an array, object, Set, or Map in the statement right after creating it
const fruits = ["apple", "pear"];
fruits.push("plum")Do not call push() immediately after initializing an array.;
const user = { name: "Ada" };
user.role = "admin"Do not assign a property immediately after initializing an object literal.;unicorn/no-instanceof-arrayfixable
Require Array.isArray() instead of instanceof Array
function toList(value) {
return value instanceof ArrayUse Array.isArray() instead of instanceof Array. ? value : [value];
}unicorn/no-instanceof-builtinshas suggestions
Disallow instanceof with built-in constructors such as Array, String, and Function
function describe(value) {
if (value instanceof StringDo not use instanceof with built-in constructors) return "text";
if (value instanceof FunctionDo not use instanceof with built-in constructors) return "callback";
}unicorn/no-invalid-fetch-options
Disallow a body in fetch() and new Request() when the method is GET or HEAD
function createUser(user) {
return fetch("/api/users", {
bodybody is not allowed when method is GET: JSON.stringify(user),
});
}unicorn/no-invalid-remove-event-listener
Disallow passing a new function to removeEventListener()
button.addEventListener("click", () => save());
button.removeEventListenerInvalid removeEventListener call.("click", () => save());
window.addEventListener("resize", this.layout.bind(this));
window.removeEventListenerInvalid removeEventListener call.("resize", this.layout.bind(this));unicorn/no-length-as-slice-endfixable
Disallow passing .length as the end argument of .slice()
const rest = items.slicePassing length as the end argument of a slice call is unnecessary.(1, items.length);unicorn/no-lonely-if
Disallow an if as the only statement in another if when neither has an else
if (user) {
ifUnexpected if as the only statement in a if block without else. (user.isAdmin) {
showSettings();
}
}unicorn/no-magic-array-flat-depth
Disallow an unexplained number as the depth of .flat()
const cells = tables.flat(2Magic number for Array.prototype.flat depth is not allowed.);unicorn/no-negated-conditionfixable
Disallow a negated condition in an if with an else and in a ternary
if (!userUnexpected negated condition.) {
showLogin();
} else {
showProfile(user);
}
const label = count !== 1Unexpected negated condition. ? "items" : "item";unicorn/no-negation-in-equality-checkhas suggestions
Disallow ! on the left side of ===, !==, ==, and !=
if (!user.roleNegated expression is not allowed in equality check. === "admin") {
deny();
}unicorn/no-nested-ternaryfixable
Disallow deeply nested ternaries, and require parentheses around a ternary in a ternary
const size = width > 1200 ? "large" : width > 600 ? "medium" : "small"Unexpected nested ternary expression without parentheses.;unicorn/no-new-arrayhas suggestions
Disallow new Array() with a single argument
const slots = new Array(10)Do not use new Array(singleArgument).;
const list = new Array(input)Do not use new Array(singleArgument).;unicorn/no-new-bufferhas suggestions
Require Buffer.from() or Buffer.alloc() instead of the deprecated new Buffer()
const header = new BufferUse Buffer.alloc() or Buffer.from() instead of the deprecated new Buffer() constructor.(16);
const name = new BufferUse Buffer.alloc() or Buffer.from() instead of the deprecated new Buffer() constructor.("Ada", "utf8");unicorn/no-nullfixable
Disallow the null literal in favor of undefined
let selected = nullDo not use null literals;unicorn/no-object-as-default-parameter
Disallow an object literal with properties as the default value of a parameter
function connect(options = { retries: 3, secure: true }Do not use an object literal as default for parameter options.) {
return open(options.retries, options.secure);
}
connect({ retries: 5 }); // options.secure is undefinedunicorn/no-process-exit
Disallow process.exit()
function loadConfig(path) {
if (!exists(path)) {
console.error("config not found");
process.exit(1)Don't use process.exit();
}
}unicorn/no-single-promise-in-promise-methodsfixable
Disallow an array with one item in Promise.all(), Promise.any(), and Promise.race()
async function refresh() {
await Promise.allWrapping single-element array with Promise.all() is unnecessary.([saveDraft()]);
const [user] = await Promise.allWrapping single-element array with Promise.all() is unnecessary.([loadUser()]);
const posts = await Promise.raceWrapping single-element array with Promise.race() is unnecessary.([loadPosts()]);
return { user, posts };
}unicorn/no-static-only-classfixable
Disallow classes that have only static members
class Temperature {Use an object instead of a class with only static members.
static freezing = 32;
static toCelsius(fahrenheit) {
return ((fahrenheit - 32) * 5) / 9;
}
}unicorn/no-thenable
Disallow a then property on objects, classes, and module exports
const step = {
thenDo not add then to an object.(next) {
return next;
},
};
await step; // never finishes: `next` is not calledunicorn/no-this-assignment
Disallow assigning this to a variable
class Timer {
start() {
const self = thisDo not assign this to self;
setInterval(function () {
self.tick();
}, 1000);
}
}unicorn/no-typeof-undefinedfixable
Require comparing with undefined directly instead of typeof x === "undefined"
function greet(name, options) {
if (typeof name === "undefined"Compare with undefined directly instead of using typeof.) {
name = "stranger";
}
return typeof options.loud !== "undefined"Compare with undefined directly instead of using typeof.;
}unicorn/no-unnecessary-array-flat-depthhas suggestions
Disallow passing 1 as the depth to Array#flat()
const tags = posts.map(post => post.tags).flatPassing 1 as the depth argument is unnecessary.(1);unicorn/no-unnecessary-array-splice-countfixable
Disallow .length or Infinity as the count of splice() and toSpliced()
queue.splice(3, queue.lengthPassing queue.length as the deleteCount argument is unnecessary.);
queue.splice(3, InfinityPassing Infinity as the deleteCount argument is unnecessary.);
const firstThree = queue.toSpliced(3, Number.POSITIVE_INFINITYPassing Number.POSITIVE_INFINITY as the deleteCount argument is unnecessary.);unicorn/no-unnecessary-awaitfixable
Disallow await on a value that cannot be a promise
async function load(ids) {
const users = awaitUnexpected await on a non-Promise value [fetchUser(ids[0]), fetchUser(ids[1])];
const retries = awaitUnexpected await on a non-Promise value 3;
const total = awaitUnexpected await on a non-Promise value (users.length + retries);
return total;
}unicorn/no-unnecessary-slice-endfixable
Disallow .length or Infinity as the end of slice()
const rest = items.slice(1, items.lengthPassing items.length as the end argument is unnecessary.);
const extension = filename.slice(dot, InfinityPassing Infinity as the end argument is unnecessary.);
const tail = lines.slice(-5, Number.POSITIVE_INFINITYPassing Number.POSITIVE_INFINITY as the end argument is unnecessary.);unicorn/no-unreadable-array-destructuring
Disallow array destructuring that skips two or more values in a row
const [, , , year]Array destructuring may not contain consecutive ignored values. = match;
function area([, , width, height]Array destructuring may not contain consecutive ignored values.) {
return width * height;
}unicorn/no-unreadable-iifehas suggestions
Disallow calling an arrow function in place when its body is in parentheses
const label = (() => (count === 1 ? "item" : "items")IIFE with parenthesized arrow function body is considered unreadable.)();unicorn/no-useless-collection-argumenthas suggestions
Disallow empty arguments and fallbacks in new Set(), Map, WeakSet, and WeakMap
const seen = new Set([]The empty array is useless);
const cache = new Map(undefinedThe undefined is useless);
function unique(items) {
return new Set(items ?? []The empty array is useless);
}unicorn/no-useless-error-capture-stack-tracehas suggestions
Disallow Error.captureStackTrace() in the constructor of an Error subclass
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
Error.captureStackTrace(this, ValidationError)Do not use Error.captureStackTrace(…) in the constructor of an Error subclass;
}
}unicorn/no-useless-fallback-in-spreadfixable
Disallow an empty object as the fallback when spreading into an object literal
function request(url, options, extraHeaders) {
const headers = { accept: "application/json", ...(extraHeaders || {})Empty fallbacks in spreads are unnecessary };
return fetch(url, { ...(options ?? {})Empty fallbacks in spreads are unnecessary, headers });
}unicorn/no-useless-iterator-to-arrayfixable
Disallow .toArray() on an iterator where the iterator itself works
for (const name of users.keys().toArrayfor…of can iterate over an iterable, .toArray() is unnecessary.()) {
console.log(name);
}
const ids = new Set(users.values().map(user => user.id).toArraynew Set(…) accepts an iterable, .toArray() is unnecessary.());
const list = [...users.values().toArraySpread works on iterables, .toArray() is unnecessary.()];
const hasAdmin = users.values().toArrayIterator has a .some() method, .toArray() is unnecessary.().some(user => user.admin);unicorn/no-useless-length-check
Disallow checking an array's length next to .some() or .every() where it changes nothing
if (errors.length > 0Found a useless array length check && errors.some(error => error.fatal)) {
stop();
}
const isDone = tasks.length === 0Found a useless array length check || tasks.every(task => task.done);
const hasDraft = posts.some(post => post.draft) && posts.length !== 0Found a useless array length check;unicorn/no-useless-promise-resolve-rejectfixable
Disallow returning Promise.resolve() or Promise.reject() from an async function or promise callback
async function findUser(id) {
if (!id) {
return Promise.reject(new Error("missing id"))Prefer throw error over return Promise.reject(error).;
}
return Promise.resolve(cache.get(id))Prefer return value over return Promise.resolve(value).;
}
fetchUser().then(user => Promise.resolve(user.name)Prefer return value over return Promise.resolve(value).);unicorn/no-useless-spreadfixable
Disallow spreads that only build an array or object to take it apart or copy it again
const colors = [...Using a spread operator here creates a new array unnecessarily.["red", "green"], extra];
const config = { ...Using a spread operator here creates a new object unnecessarily.{ debug: false }, ...overrides };
log(...Using a spread operator here creates a new array unnecessarily.["saved", file]);unicorn/no-useless-switch-casehas suggestions
Disallow empty case clauses that fall through to the last default
switch (status) {
case "active":
show(user);
break;
case "banned":Useless case in switch statement.
default:
hide(user);
}unicorn/no-useless-undefinedfixable
Disallow undefined where leaving it out means the same
let selected = undefinedDo not use useless undefined.;
function find(id, fallback = undefinedDo not use useless undefined.) {
if (!id) {
return undefinedDo not use useless undefined.;
}
return load(id, undefinedDo not use useless undefined.);
}
const noop = () => undefinedDo not use useless undefined.;unicorn/no-zero-fractionsfixable
Disallow numbers with a zero fraction or a dangling dot
const scale = 1.0Don't use a zero fraction in the number.;
const opacity = 0.50Don't use a zero fraction in the number.;
const total = 10.Don't use a dangling dot in the number.;
const limit = 2.0e3Don't use a zero fraction in the number.;unicorn/number-literal-casefixable
Require lowercase prefixes and exponents, and uppercase hexadecimal digits, in numbers
const mask = 0XUnexpected number literal prefix in uppercase.FF;
const color = 0xff8800Unexpected hexadecimal digits in lowercase.;
const flags = 0BUnexpected number literal prefix in uppercase.1010;
const mode = 0OUnexpected number literal prefix in uppercase.755;
const big = 1.5EUnexpected exponential notation in uppercase.9;unicorn/numeric-separators-stylefixable
Enforce how digits are grouped with _ in numbers
const population = 1000000Invalid group length in numeric value.;
const price = 12_34_567Invalid group length in numeric value.;
const color = 0xFF8800Invalid group length in numeric value.;
const flags = 0b10101010Invalid group length in numeric value.;unicorn/prefer-add-event-listener
Require addEventListener() instead of assigning to on properties such as onclick
button.onclickPrefer addEventListener() over their on-function counterparts. = () => save(form);
window.onloadPrefer addEventListener() over their on-function counterparts. = null;unicorn/prefer-array-find
Require .find() or .findLast() instead of taking one element from .filter()
const admin = users.filterPrefer find over filtering and accessing the first result.(user => user.isAdmin)[0];
const first = users.filterPrefer find over filtering and accessing the first result.(user => user.isActive).shift();
const [owner] = users.filterPrefer find over filtering and accessing the first result.(user => user.isOwner);
const newest = users.filterPrefer find over filtering and accessing the first result.(user => user.isActive).at(-1);
const lastGuest = users.filterPrefer find over filtering and accessing the first result.(user => user.isGuest).pop();unicorn/prefer-array-flatfixable
Require .flat() instead of flattening an array with concat, reduce or flatMap
const a = pages.flatMap(items => items)Prefer Array#flat() over legacy techniques to flatten arrays.;
const b = pages.reduce((all, items) => all.concat(items), [])Prefer Array#flat() over legacy techniques to flatten arrays.;
const c = pages.reduce((all, items) => [...all, ...items], [])Prefer Array#flat() over legacy techniques to flatten arrays.;
const d = [].concat(...pages)Prefer Array#flat() over legacy techniques to flatten arrays.;
const e = [].concat.apply([], pages)Prefer Array#flat() over legacy techniques to flatten arrays.;unicorn/prefer-array-flat-mapfixable
Require one .flatMap() instead of .map().flat() or .filter().flatMap()
const tags = posts.map(post => post.tags).flat()Array.flatMap performs Array.map and Array.flat in one step.;
const words = lines.map(line => line.split(" ")).flat(1)Array.flatMap performs Array.map and Array.flat in one step.;unicorn/prefer-array-index-of
Require .indexOf() instead of .findIndex() when looking for a value
const index = names.findIndexPrefer indexOf over findIndex for simple equality checks(name => name === "admin");
const last = names.findLastIndexPrefer indexOf over findIndex for simple equality checks(name => name === current);unicorn/prefer-array-somehas suggestions
Require .some() to test whether an array has a matching element
if (users.findPrefer .some(…) over .find(…) or .findLast(…).(user => user.isAdmin)) {
showAdminPanel();
}
const hasAdmin = users.findPrefer .some(…) over .find(…) or .findLast(…).(user => user.isAdmin) !== undefined;
const hasGuest = users.findIndexPrefer .some(…) over .findIndex(…) or .findLastIndex(…).(user => user.isGuest) !== -1;
const hasActive = users.filterPrefer .some(…) over non-zero length check from .filter(…).(user => user.isActive).length > 0;unicorn/prefer-atfixable
Require .at() to read an element counted from the end of an array or string
const last = items[items.length - 1]Prefer .at() over [index].;
const secondToLast = items.slice(-2)[0]Prefer .at() over slice()[0].;
const tail = items.slice(-1).pop()Prefer .at() over slice().pop/shift.;
const newest = _.last(items)Prefer .at() over _.last().;
const end = name.charAt(name.length - 1)Prefer .at() over charAt().;
const letter = name.substring(index, index + 1)Prefer String#at() over String#substring() when getting one character.;unicorn/prefer-bigint-literalsfixable
Require BigInt literals such as 123n instead of BigInt(123)
const zero = BigInt(0Prefer bigint literals over BigInt(...).);
const limit = BigInt("9007199254740993"Prefer bigint literals over BigInt(...).);
const mask = BigInt(0xFFPrefer bigint literals over BigInt(...).);unicorn/prefer-blob-reading-methods
Require blob.text() and blob.arrayBuffer() instead of a FileReader
function readText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener("load", () => resolve(reader.result));
reader.addEventListener("error", () => reject(reader.error));
reader.readAsTextPrefer Blob#text() over FileReader#readAsText(blob).(file);
});
}unicorn/prefer-class-fieldsfixable
Require class fields instead of this assignments of fixed values in the constructor
class Counter {
constructor() {
this.count = 0Prefer class field declaration over this assignment in constructor for static values.;
}
}unicorn/prefer-classlist-togglefixable
Require classList.toggle() instead of choosing between add() and remove()
if (isOpen) {Prefer classList.toggle() over classList.add() and classList.remove()
menu.classList.add("open");
} else {
menu.classList.remove("open");
}
isOpen ? menu.classList.add("open") : menu.classList.remove("open")Prefer classList.toggle() over classList.add() and classList.remove();
menu.classList[isOpen ? "add" : "remove"]("open")Prefer classList.toggle() over classList.add() and classList.remove();unicorn/prefer-code-pointfixable
Require codePointAt() and String.fromCodePoint() over charCodeAt() and String.fromCharCode()
const code = text.charCodeAtPrefer codePointAt over charCodeAt(0);
const letter = String.fromCharCodePrefer fromCodePoint over fromCharCode(code);unicorn/prefer-date-nowfixable
Require Date.now() to get the current time in milliseconds
const start = new Date().getTime()Prefer Date.now() over new Date().getTime();
const stamp = +new Date()Prefer Date.now() over new Date();
const elapsed = new Date()Prefer Date.now() over new Date() - start;unicorn/prefer-default-parametershas suggestions
Require default parameters instead of reassigning a parameter in the function body
function greet(name) {
name = name || "stranger"Prefer default parameters over reassignment for name.;
return `Hello, ${name}`;
}unicorn/prefer-dom-node-appendfixable
Require .append() instead of .appendChild() on DOM nodes
const item = document.createElement("li");
list.appendChildPrefer Node#append() over Node#appendChild() for DOM nodes.(item);unicorn/prefer-dom-node-datasetfixable
Require .dataset instead of the attribute methods for data-* attributes
element.setAttributePrefer using dataset over setAttribute.("data-user-id", user.id);
const id = element.getAttributePrefer using dataset over getAttribute.("data-user-id");
if (element.hasAttributePrefer using dataset over hasAttribute.("data-user-id")) {
element.removeAttribute("data-user-id"Prefer using dataset over removeAttribute.);
}unicorn/prefer-dom-node-remove
Require child.remove() instead of parent.removeChild(child)
item.parentNode.removeChildPrefer childNode.remove() over parentNode.removeChild(childNode).(item);
list.removeChildPrefer childNode.remove() over parentNode.removeChild(childNode).(list.firstChild);unicorn/prefer-dom-node-text-contentfixable
Require .textContent instead of .innerText
const title = heading.innerTextPrefer .textContent over .innerText.;
status.innerTextPrefer .textContent over .innerText. = "Saved";
const { innerTextPrefer .textContent over .innerText. } = heading;unicorn/prefer-event-target
Require EventTarget instead of EventEmitter
import { EventEmitter } from "node:events";
class Download extends EventEmitterPrefer EventTarget over EventEmitter. {}
const bus = new EventEmitterPrefer EventTarget over EventEmitter.();unicorn/prefer-export-fromhas suggestions
Require export ... from to re-export instead of an import and a separate export
import { parse } from "./parser.js";Prefer re-exporting directly from the source module.
export { parse };unicorn/prefer-global-thishas suggestions
Require globalThis instead of window, self and global
globalPrefer globalThis over environment-specific global aliases like window, self, and global..cache = new Map();
windowPrefer globalThis over environment-specific global aliases like window, self, and global..setTimeout(refresh, 1000);
if (typeof windowPrefer globalThis over environment-specific global aliases like window, self, and global. !== "undefined") {
start();
}unicorn/prefer-import-meta-propertiesfixable
Require import.meta.dirname and import.meta.filename instead of building them from the URL
import path from "node:path";
import { fileURLToPath } from "node:url";
const filename = fileURLToPath(import.meta.url)Do not construct filename using fileURLToPath().;
const dirname = path.dirname(filename)Do not construct dirname.;unicorn/prefer-includeshas suggestions
Require .includes() instead of comparing .indexOf() with -1 or 0
if (tags.indexOfPrefer includes() over indexOf() when checking for existence or non-existence.("draft") !== -1) {
hide(post);
}
if (email.indexOfPrefer includes() over indexOf() when checking for existence or non-existence.("@") < 0) {
reject(email);
}unicorn/prefer-keyboard-event-keyfixable
Require event.key instead of keyCode, charCode and which
input.addEventListener("keydown", event => {
if (event.keyCodeUse .key instead of .keyCode === 13) {
submit();
}
});unicorn/prefer-logical-operator-over-ternaryhas suggestions
Require || or ?? instead of a ternary that repeats its condition
const name = user.name ? user.name : "Anonymous"Prefer using a logical operator over a ternary.;
const port = !config.port ? 3000 : config.portPrefer using a logical operator over a ternary.;unicorn/prefer-math-min-maxhas suggestions
Require Math.min() and Math.max() instead of a ternary that picks the smaller or larger value
const width = size > 100 ? 100 : sizePrefer Math.min() and Math.max() over ternaries for simple comparisons.;
const volume = level < 0 ? 0 : levelPrefer Math.min() and Math.max() over ternaries for simple comparisons.;unicorn/prefer-math-trunchas suggestions
Require Math.trunc() instead of bitwise tricks to drop the fraction of a number
const seconds = (elapsed / 1000) | 0Prefer Math.trunc() over instead of | 0.;
const page = ~~(index / pageSize)Prefer Math.trunc() over instead of ~ 0.;
total >>= 0Prefer Math.trunc() over instead of >>= 0.;unicorn/prefer-modern-dom-apishas suggestions
Require .before(), .after(), .append(), .prepend() and .replaceWith() over older DOM methods
list.insertBeforePrefer using before over insertBefore.(item, marker);
list.replaceChildPrefer using replaceWith over replaceChild.(item, placeholder);
heading.insertAdjacentTextPrefer using append over insertAdjacentText.("beforeend", "!");
heading.insertAdjacentElementPrefer using after over insertAdjacentElement.("afterend", note);unicorn/prefer-modern-math-apis
Require Math.log10(), Math.log2(), Math.hypot() and Math.abs() over hand-written formulas
const digits = Math.log(value) * Math.LOG10EPrefer Math.log10(x) over Math.log(value) * Math.LOG10E;
const bits = Math.log(value) / Math.LN2Prefer Math.log2(x) over Math.log(value) / Math.LN2;
const distance = Math.sqrt(dx * dx + dy ** 2)Prefer Math.hypot(…) over alternatives;
const size = Math.sqrt(delta * delta)Prefer Math.abs(x) over alternatives;unicorn/prefer-module
Require ES modules instead of CommonJS
"use strict";Do not use "use strict" directive.
const path = requireDo not use require.("path");
moduleDo not use module..exports = path.join(__dirnameDo not use __dirname., "config.json");unicorn/prefer-native-coercion-functions
Require passing String, Number, BigInt, Boolean and Symbol directly instead of wrapping them
const ids = rows.map(id => Number(id)The function is equivalent to Number. Call Number directly.);
const names = users.map(user => user.name).filter(name => nameThe arrow function in the callback of the array is equivalent to Boolean. Replace the callback with Boolean.);
function toText(value) {The function is equivalent to String. Call String directly.
return String(value);
}unicorn/prefer-negative-indexfixable
Require a negative index instead of .length - index
const lastTwo = items.slice(items.length - 2)Prefer negative index over .length - index when possible.;
const last = items.at(items.length - 1)Prefer negative index over .length - index when possible.;
queue.splice(queue.length - 1, 1)Prefer negative index over .length - index when possible.;
const rest = Array.prototype.slice.call(list, list.length - 2)Prefer negative index over .length - index when possible.;unicorn/prefer-node-protocolfixable
Require the node: prefix when importing Node.js built-in modules
import fs from "fs"Prefer using the node: protocol when importing Node.js built-in modules.;
import { readFile } from "fs/promises"Prefer using the node: protocol when importing Node.js built-in modules.;
export { EventEmitter } from "events"Prefer using the node: protocol when importing Node.js built-in modules.;
const os = await import("os"Prefer using the node: protocol when importing Node.js built-in modules.);unicorn/prefer-number-coercionhas suggestions
Require Number() instead of parseFloat() and base-10 parseInt()
const price = parseFloat(input.value)Prefer Number(input.value).;
const quantity = Number.parseInt(form.quantity, 10)Prefer Math.trunc(Number(form.quantity)).;unicorn/prefer-number-propertiesfixable
Require Number.isNaN, Number.parseInt and the like instead of the globals
const width = parseIntUse Number.parseInt instead of the global parseInt(style.width, 10);
if (isNaNUse Number.isNaN instead of the global isNaN(width)) {
reset(NaNUse Number.NaN instead of the global NaN);
}
const isValid = window.isFiniteUse Number.isFinite instead of the global isFinite(width);unicorn/prefer-object-from-entries
Require Object.fromEntries() to turn key-value pairs into an object
const byId = users.reducePrefer Object.fromEntries over manual object construction from entries.((result, user) => ({ ...result, [user.id]: user }), {});
const ages = users.reduce((result, user) => Object.assignPrefer Object.fromEntries over manual object construction from entries.(result, { [user.name]: user.age }), {});
const query = _.fromPairsPrefer Object.fromEntries over manual object construction from entries.(pairs);unicorn/prefer-optional-catch-bindingfixable
Require catch {} without a parameter when the error is not used
function parse(text) {
try {
return JSON.parse(text);
} catch (errorPrefer omitting the catch binding parameter if it is unused) {
return undefined;
}
}
try {
await upload(file);
} catch ({ message }Prefer omitting the catch binding parameter if it is unused) {
retry(file);
}unicorn/prefer-prototype-methodsfixable
Require borrowing a method from the prototype instead of from an empty [] or {}
const links = [].slicePrefer using Array.prototype.slice..call(document.links);
const isOwn = {}.hasOwnPropertyPrefer using Object.prototype.hasOwnProperty..call(user, "name");
const first = Reflect.apply([].atPrefer using Array.prototype.at., items, [0]);unicorn/prefer-query-selectorfixable
Require querySelector() and querySelectorAll() instead of the getElement... methods
const menu = document.getElementByIdPrefer .querySelector() over .getElementById().("menu");
const items = menu.getElementsByClassNamePrefer .querySelectorAll() over .getElementsByClassName().("item active");
const links = menu.getElementsByTagNamePrefer .querySelectorAll() over .getElementsByTagName().("a");
const emails = document.getElementsByNamePrefer .querySelectorAll() over .getElementsByName().("email");unicorn/prefer-reflect-applyhas suggestions
Require Reflect.apply() instead of Function.prototype.apply()
function logged() {
console.log("called");
return handler.apply(this, arguments)Prefer Reflect.apply() over Function#apply().;
}
Function.prototype.apply.call(handler, null, [event])Prefer Reflect.apply() over Function#apply().;unicorn/prefer-regexp-testfixable
Require RegExp#test() instead of String#match() and RegExp#exec() for a yes or no answer
if (email.matchPrefer RegExp#test() over String#match() and RegExp#exec().(/@/)) {
send(email);
}
const isHex = Boolean(/^[0-9a-f]+$/i.execPrefer RegExp#test() over String#match() and RegExp#exec().(color));
const label = !name.matchPrefer RegExp#test() over String#match() and RegExp#exec().(/\S/) ? "(empty)" : name;unicorn/prefer-response-static-jsonhas suggestions
Require Response.json() instead of new Response(JSON.stringify())
Bun.serve({
routes: {
"/user": () => new Response(JSON.stringifyPrefer using Response.json(…) over JSON.stringify().({ name: "Ada" })),
"/missing": () => new Response(JSON.stringifyPrefer using Response.json(…) over JSON.stringify().({ error: "not found" }), { status: 404 }),
},
});unicorn/prefer-set-hasfixable
Require Set#has() instead of Array#includes() for an array that is only searched
const blocked = ["admin", "root", "system"]should be a Set, and use .has() to check existence or non-existence.;
function isAllowed(name) {
return !blocked.includes(name);
}unicorn/prefer-set-sizefixable
Require Set#size instead of converting the Set to an array to read length
const uniqueCount = [...new Set(tags)].lengthUse Set#size instead of converting a Set to an array and using its length property.;
const visitors = new Set(ids);
console.log(Array.from(visitors).lengthUse Set#size instead of converting a Set to an array and using its length property.);unicorn/prefer-single-callfixable
Require one call instead of several in a row to push(), unshift(), classList.add() and the like
errors.push("name is required");
errors.pushDo not call Array#push() multiple times.("email is not valid");
queue.unshift(second);
queue.unshiftDo not call Array#unshift() multiple times.(first);
button.classList.add("active");
button.classList.addDo not call Element#classList.add() multiple times.("large");
importScripts("parser.js");
importScriptsDo not call importScripts() multiple times.("worker-utils.js");unicorn/prefer-spreadfixable
Require the spread operator instead of Array.from(), concat(), slice() and toSpliced()
const links = Array.from(document.querySelectorAll("a"))Prefer the spread operator (...) over Array.from();
const copy = items.slice()Prefer the spread operator (...) over array.slice();
const sorted = scores.toSpliced()Prefer the spread operator (...) over array.toSpliced().sort();
const all = items.concat(more, last)Prefer the spread operator (...) over array.concat();unicorn/prefer-string-rawfixable
Require String.raw for strings that escape backslashes
const logs = "C:\\Users\\ada\\logs"String.raw should be used to avoid escaping \.;
const version = new RegExp("\\d+\\.\\d+"String.raw should be used to avoid escaping \.);unicorn/prefer-string-replace-allfixable
Require replaceAll() instead of replace() with a global regex
const slug = title.replacePrefer String#replaceAll() over String#replace() when using a regex with the global flag.(/\s+/g, "-");
const path = name.replaceAll(/\./gThis pattern can be replaced with .., "/");unicorn/prefer-string-slicefixable
Require slice() instead of substr() and substring()
const year = date.substringPrefer String#slice() over String#substring()(0, 4);
const month = date.substrPrefer String#slice() over String#substr()(5, 2); // 2 is a lengthunicorn/prefer-string-starts-ends-withfixable
Require startsWith() and endsWith() instead of a regex with ^ or $
const secure = /^https:/.testPrefer String#startsWith over a regex with a caret.(url);
const isScript = /\.js$/.testPrefer String#endsWith over a regex with a dollar sign.(file);unicorn/prefer-string-trim-start-endfixable
Require trimStart() and trimEnd() instead of trimLeft() and trimRight()
const command = input.trimLeftPrefer trimStart over trimLeft();
const line = text.trimRightPrefer trimEnd over trimRight();unicorn/prefer-structured-clonehas suggestions
Require structuredClone() for deep clones
const draft = JSON.parse(JSON.stringify(settings))Use structuredClone(…) to create a deep clone.;
const backup = _.cloneDeep(settings)Use structuredClone(…) to create a deep clone.;unicorn/prefer-ternary
Require a ternary instead of an if/else where both branches do the same kind of thing
function label(user) {
if (user.isAdmin) {Prefer ternary expressions over simple if-else statements.
return "admin";
} else {
return "member";
}
}
let timeout;
if (retrying) {Prefer ternary expressions over simple if-else statements.
timeout = 5000;
} else {
timeout = 1000;
}unicorn/prefer-top-level-await
Require top-level await instead of promise chains and async wrappers at the top of a module
(async () => {Prefer top-level await over using an async IIFE.
await connect();
})();
loadConfig().then(config => start(config))Prefer top-level await over using a promise chain.;
async function main() {
await migrate();
}
main()Prefer top-level await over an async function call.;unicorn/prefer-type-errorfixable
Require TypeError when the throw follows a type check
function repeat(text, times) {
if (typeof text !== "string") {
throw new ErrorPrefer throwing a TypeError over a generic Error after a type checking if-statement("text must be a string");
}
if (!Number.isInteger(times)) {
throw new ErrorPrefer throwing a TypeError over a generic Error after a type checking if-statement("times must be an integer");
}
return text.repeat(times);
}unicorn/relative-url-stylefixable
Enforce one style for relative URLs in new URL(): with or without ./
const logo = new URL("./logo.png"Remove the ./ prefix from the relative URL., import.meta.url);
const icon = new URL(`./${name}.svg`Remove the ./ prefix from the relative URL., import.meta.url);unicorn/require-array-join-separatorfixable
Require the separator argument of Array#join()
const header = columns.join()Enforce using the separator argument with Array#join();
const names = Array.prototype.join.call(arguments)Enforce using the separator argument with Array#join();unicorn/require-module-attributeshas suggestions
Disallow an empty attribute list in import, export and import()
import settings from "./settings.json" with {}import statement with empty attribute list is not allowed.;
export { version } from "./package.json" with {}export statement with empty attribute list is not allowed.;
const en = await import("./en.json", {}import expression with empty attribute list is not allowed.);
const de = await import("./de.json", { with: {}import expression with empty attribute list is not allowed. });unicorn/require-module-specifiersfixable
Disallow empty braces in import and export statements
import {}Empty import specifier is not allowed from "./setup.js";
import app, {}Empty import specifier is not allowed from "./app.js";
export {}Empty export specifier is not allowed;unicorn/require-number-to-fixed-digits-argumentfixable
Require the digits argument of Number#toFixed()
const total = price.toFixed()Number method .toFixed() should have an argument;unicorn/require-post-message-target-originhas suggestions
Require the targetOrigin argument of window.postMessage()
window.parent.postMessage({ type: "ready" })Missing the targetOrigin argument.;
frame.contentWindow.postMessage(token)Missing the targetOrigin argument.;unicorn/switch-case-bracesfixable
Enforce braces, or no braces, around the body of each case
switch (event.type) {
case "open":Missing braces in case clause.
show(event.target);
break;
default:Missing braces in case clause.
ignore(event);
}unicorn/switch-case-break-position
Require break, return, continue and throw inside the braces of a case
switch (event.type) {
case "open": {
show(event.target);
}
break;Move break inside the block statement.
}unicorn/text-encoding-identifier-casefixable
Enforce one spelling for the encoding names utf8 and ascii
const text = await fs.readFile(file, "UTF-8"Prefer utf8 over UTF-8.);
const bytes = Buffer.from(name, "ASCII"Prefer ascii over ASCII.);
const decoder = new TextDecoder("utf8"Prefer utf-8 over utf8.);unicorn/throw-new-errorfixable
Require new when creating an error
throw Error("Not found")Require new when throwing an error.;
throw errors.ValidationError("Name is required")Require new when throwing an error.;
const timeout = TypeError("Expected a number")Require new when throwing an error.;promise 16 rules
promise/always-return
Require every then() callback to return a value or throw
fetchUser(id)
.then(user => {Each then() should return a value or throw
user.lastSeen = Date.now();
})
.then(user => render(user)); // user is undefinedpromise/avoid-new
Disallow creating promises with new Promise()
import fs from "node:fs";
function readConfig(path) {
return new Promise((resolve, reject) => {Avoid creating new promises
fs.readFile(path, "utf8", (err, text) => {
if (err) reject(err);
else resolve(text);
});
});
}promise/catch-or-return
Require a promise chain to end with catch() or to be returned
function refresh() {
loadItems().then(render)Expected catch or return.;
}promise/no-callback-in-promise
Disallow calling a callback inside then() or catch()
function getUser(id, callback) {
fetchUser(id)
.then(user => callback(null, user)Avoid calling back inside of a promise)
.catch(callbackAvoid calling back inside of a promise);
}promise/no-multiple-resolved
Disallow paths that call resolve or reject more than once in a new Promise() executor
function readConfig(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, "utf8", (err, text) => {
if (err) {
reject(err);
}
resolve(text)Promise should not be resolved multiple times. Promise is potentially resolved on line 5.; // also runs after reject(err)
});
});
}promise/no-nesting
Disallow then() and catch() nested inside another then() or catch()
getUser(id).then(user => {
return getOrders(user).thenAvoid nesting promises.(orders => render(orders));
});promise/no-new-staticsfixable
Disallow new on static Promise methods such as Promise.all
async function loadPage() {
return await newDo not use new on Promise.all Promise.all([loadUser(), loadOrders()]);
}promise/no-promise-in-callback
Disallow promises inside error-first callbacks
fs.readFile(path, "utf8", (err, text) => {
if (err) throw err;
upload(text).thenAvoid using promises inside of callbacks.(notify);
});promise/no-return-in-finally
Disallow return in a finally() callback
loadItems()
.finally(() => {
hideSpinner();
return "done";Don't return in a finally callback
})
.then(result => console.log(result)); // the items, not "done"promise/no-return-wrap
Disallow returning Promise.resolve() or Promise.reject() from a promise callback
fetchUser(id).then(user => {
if (!user.active) {
return Promise.reject(new Error("inactive user"))Expected throw instead of Promise.reject;
}
return Promise.resolve(user.profile)Avoid wrapping return values in Promise.resolve;
});promise/param-names
Require the parameters of a new Promise() executor to be named resolve and reject
new Promise((rejectPromise constructor parameters must be named to match ^_?resolve$, resolvePromise constructor parameters must be named to match ^_?reject$) => {
setTimeout(resolve, 1000); // rejects the promise
});promise/prefer-await-to-callbacks
Require async/await instead of error-first callbacks
function readConfig(path, callbackPrefer async/await to the callback pattern) {
fs.readFile(path, "utf8", (err, text) => {Prefer async/await to the callback pattern
if (err) return callback(err)Prefer async/await to the callback pattern;
callback(null, JSON.parse(text))Prefer async/await to the callback pattern;
});
}promise/prefer-await-to-then
Require await instead of then(), catch() and finally()
function showUser(id) {
fetchUser(id)
.thenPrefer await to then()/catch()/finally()(user => render(user))
.catchPrefer await to then()/catch()/finally()(err => showError(err));
}promise/prefer-catch
Require catch() instead of a second argument to then()
loadItems().then(render, showError)Prefer catch to then(a, b) or then(null, b); // an error in render is not handledpromise/spec-only
Disallow Promise static methods that are not part of the language
const pages = await Promise.mapAvoid using non-standard Promise.map method.(urls, url => fetch(url));promise/valid-params
Require the right number of arguments for Promise methods
const results = Promise.all(loadUser(), loadOrders())Promise.all() requires 1 argument, but received 2.;Node.js (n) 15 rules
n/callback-return
Require a return with every call to a callback
function loadUser(id, callback) {
if (!id) {
callback(new Error("missing id"))Expected return with your callback function.;
}
db.find(id, callback);
}n/exports-style
Enforce either module.exports or exports in CommonJS files
exportsUnexpected access to exports..parse = function parse(text) {
return JSON.parse(text);
};n/global-require
Require require() calls to be at the top level of the file
function readConfig(file) {
const fs = require("fs")Unexpected require().;
return fs.readFileSync(file, "utf8");
}
if (process.env.DEBUG) {
require("./debug")Unexpected require().;
}n/handle-callback-err
Require callbacks to use their error parameter
fs.readFile("config.json", (errExpected error to be handled., data) => {
start(JSON.parse(data));
});n/no-exports-assignfixable
Disallow assigning to exports
exportsUnexpected assignment to exports. = {
parse,
stringify,
};n/no-mixed-requires
Disallow require() calls mixed with other variables in one declaration
const fs = require("fs"),Do not mix require and other declarations.
retries = 3;n/no-new-require
Disallow new require()
const app = new require("./app")Unexpected use of new operator with require;n/no-path-concat
Disallow joining __dirname and __filename to a path with + or a template
const config = __dirname + "/config.json"Use path.join() or path.resolve() instead of string concatenation;
const cache = `${__dirname}/cache/${name}`Use path.join() or path.resolve() instead of string concatenation;
const logs = __dirname + path.sepUse path.join() or path.resolve() instead of string concatenation + "logs";n/no-process-env
Disallow process.env
if (process.envDisallowed usage of process.env..NODE_ENV === "development") {
enableLogging();
}n/no-process-exit
Disallow process.exit()
if (!config.valid) {
console.error("invalid config");
process.exit(1)Don't use process.exit(); throw an error instead.;
}n/no-sync
Disallow calls to functions with names that end in Sync
function handle(request) {
const page = fs.readFileSync("index.html", "utf8")Unexpected sync method: readFileSync.;
return new Response(page);
}n/no-top-level-await
Disallow await at the top level of a module
import { connect } from "./db.js";
export const db = await connect()Top-level await prevents this module from being loaded with require(esm).;
for await (const row of db.rows()) {Top-level await prevents this module from being loaded with require(esm).
console.log(row);
}n/no-unsupported-features/es-builtins
Disallow JavaScript built-ins that the Node.js versions you support do not have
if (Object.hasOwnThe Object.hasOwn is still an experimental feature and is not supported until Node.js 16.9.0. The configured version range is >=16.0.0.(user, "email")) {
notify(user);
}n/no-unsupported-features/es-syntax
Disallow JavaScript syntax that the Node.js versions you support do not have
class Cache {
static instance;
static {class-static-block is not supported until Node.js >=16.11.0. The configured version range is >=16.0.0.
Cache.instance = new Cache();
}
}
const word = /[\p{L}--[a-z]]/vregexp-v-flag is not supported until Node.js >=20.0.0. The configured version range is >=16.0.0.;n/no-unsupported-features/node-builtins
Disallow Node.js APIs that the Node.js versions you support do not have
import { parseArgsThe util.parseArgs is still an experimental feature and is not supported until Node.js 20.0.0. The configured version range is >=16.0.0. } from "node:util";
const { values } = parseArgs({ options: { url: { type: "string" } } });
const response = await fetchThe fetch is still an experimental feature and is not supported until Node.js 21.0.0. The configured version range is >=16.0.0.(values.url);
console.log(import.meta.dirnameThe import.meta.dirname is still an experimental feature and is not supported until Node.js 24.0.0 (backported: ^22.16.0). The configured version range is >=16.0.0.);oxc 27 rules
oxc/approx-constanthas suggestions
Disallow numbers that approximate a constant of the Math object
function circleArea(radius) {
return 3.14159Approximate value of PI found. * radius * radius;
}
const diagonal = side * 1.4142Approximate value of SQRT2 found.;oxc/bad-array-method-on-arguments
Disallow calling array methods on the arguments object
function total() {
return arguments.reduceBad array method on arguments.((sum, price) => sum + price, 0);
}oxc/bad-bitwise-operatorhas suggestions
Disallow & and | where && and || were probably meant
const city = user & user.cityBad bitwise operator;
const label = title | "Untitled"Bad bitwise operator;
options |= {}Bad bitwise operator;oxc/bad-char-at-comparison
Disallow comparing a single character with a longer string
if (path.charAt(0)Invalid character comparison === "./") {
resolveRelative(path);
}
const prefix = "https://example.com";
if (prefix[0]Invalid character comparison === "ht") {
useTls();
}oxc/bad-comparison-sequence
Disallow chained comparisons such as a < b < c
if (min < valueBad comparison sequence < max) {
accept(value);
}
const allEqual = width === heightBad comparison sequence === depth;oxc/bad-match-all-arg
Require the g flag on a regular expression passed to matchAll()
const words = [...text.matchAllGlobal flag (g) is missing in the regular expression supplied to the matchAll method.(/\w+/)];
const tag = new RegExp("#\\w+", "i");
for (const match of text.matchAllGlobal flag (g) is missing in the regular expression supplied to the matchAll method.(tag)) {
tags.push(match[0]);
}oxc/bad-min-max-func
Disallow Math.min and Math.max combinations that always give the same result
const percent = Math.min(Math.max(input, 100), 0)Math.min and Math.max combination leads to constant result;
const channel = Math.max(255, Math.min(0, red))Math.min and Math.max combination leads to constant result;oxc/bad-object-literal-comparison
Disallow comparing a value with {} or []
if (items === []Unexpected array literal comparison.) {
showPlaceholder();
}
if (filters !== {}Unexpected object literal comparison.) {
applyFilters(filters);
}oxc/bad-replace-all-arg
Require the g flag on a regular expression passed to replaceAll()
const slug = title.replaceAllGlobal flag (g) is missing in the regular expression supplied to the replaceAll method.(/\s+/, "-");
const digits = new RegExp("\\d");
const masked = cardNumber.replaceAllGlobal flag (g) is missing in the regular expression supplied to the replaceAll method.(digits, "*");oxc/branches-sharing-codehas suggestions
Disallow the same statements at the start or end of every branch of an if
ifAll if blocks contain the same code at the end (user.isAdmin) {
showAdminMenu();
render();
} else {
showMenu();
render();
}oxc/const-comparisons
Disallow comparisons that are always true, always false, or repeated
if (age > 65Unexpected constant comparison && age < 18) {
addDiscount();
}
if (status >= 400 && status >= 500Left-hand side of && operator has no effect.) {
retry();
}
while (index < indexThis comparison will always evaluate to false) {
index++;
}oxc/double-comparisonshas suggestions
Disallow two comparisons of the same values that one operator can express
if (stock === minimum || stock < minimumUnexpected double comparisons.) {
reorder();
}
const changed = before < after || before > afterUnexpected double comparisons.;oxc/erasing-opfixable
Disallow operations with 0 that erase the other operand
const tax = price * 0Unexpected erasing operation. This expression will always evaluate to zero.;
const masked = flags & 0Unexpected erasing operation. This expression will always evaluate to zero.;
const ratio = 0 / totalUnexpected erasing operation. This expression will always evaluate to zero.;oxc/misrefactored-assign-ophas suggestions
Disallow a += a + b, where the target is repeated on the right
total += total + priceMisrefactored assign op. Variable appears on both sides of an assignment operation;
scale *= 2 * scaleMisrefactored assign op. Variable appears on both sides of an assignment operation;
cart.remaining -= cart.remaining - usedMisrefactored assign op. Variable appears on both sides of an assignment operation;oxc/missing-throwhas suggestions
Require throw before a new Error() that is not used
function setAge(age) {
if (age < 0) {
new Error("age must not be negative")Missing throw;
}
user.age = age;
}oxc/no-accumulating-spread
Disallow spreading the accumulator in reduce() and in loops
const names = users.reduce((result, user) => [...resultDo not spread accumulators in Array.prototype.reduce(), user.name], []);
const byId = users.reduce((result, user) => ({ ...resultDo not spread accumulators in Array.prototype.reduce(), [user.id]: user }), {});oxc/no-async-await
Disallow async functions
asyncasync is not allowed function loadUser(id) {
const response = await fetch(`/users/${id}`);
return response.json();
}
const refresh = asyncasync is not allowed () => {
await loadUser(current.id);
};
class Store {
asyncasync is not allowed save() {}
}oxc/no-async-endpoint-handlers
Disallow async functions as Express route handlers and middleware
app.get("/users/:id", asyncExpress endpoint handler for /users/:id should not be async. (req, res) => {
const user = await db.findUser(req.params.id);
res.json(user);
});oxc/no-barrel-file
Disallow files that load too many modules through export *
export * from "./users.js"Barrel file detected, 4 modules are loaded which exceeds the threshold of 3.;
export * from "./orders.js";oxc/no-const-enumfixable
Disallow TypeScript const enum
export constUnexpected const enum enum Status {
Active,
Archived,
}oxc/no-map-spreadhas suggestions
Disallow object and array spreads in the return value of map and flatMap callbacks
function loadTasks() {
const tasks = readTasks();
return tasks.mapSpreading to modify object properties in map calls is inefficient(task => ({ ...task, done: false }));
}oxc/no-optional-chaining
Disallow optional chaining (?.)
const city = user?.address?.cityOptional chaining is not allowed.;oxc/no-rest-spread-properties
Disallow ... in object literals and object destructuring
const updated = { ...userobject spread property are not allowed., name: "Ada" };oxc/no-this-in-exported-function
Disallow this in exported functions
export function save(item) {
thisthis should not be used in exported functions.items.push(item);
}oxc/number-arg-out-of-range
Disallow out-of-range arguments to toString, toFixed, toExponential, and toPrecision
const binary = count.toString(1)Radix or precision arguments of number-related functions should not exceed the limit;oxc/only-used-in-recursionfixable
Disallow parameters that are only passed on to the function's own recursive calls
function countNodes(node, depthParameter depth is only used in recursive calls) {
if (!node) return 0;
return 1 + countNodes(node.left, depth) + countNodes(node.right, depth);
}oxc/uninvoked-array-callback
Disallow array methods with a callback directly on new Array(n)
const scores = new Array(5).mapUninvoked array callback(() => 0);Bun 29 rules
bun/consistent-directive-prefixfixable
Require comments that turn rules off to start with the same word, eslint or oxlint
// oxlintUse eslint instead of oxlint.-disable-next-line no-console
console.log(version);bun/formatfixable
Require code to be formatted the way bun fmt prints it
const user = {name:'Ada'}Replace name:'Ada'} with ·name:·"Ada"·};bun/jsx-no-forbidden-nesting
Disallow a JSX element inside another element that cannot contain it
const card = (
<a href={post.url}>
<h2>{post.title}</h2>
<button<button> cannot be inside <a>. onClick={save}>Save</button>
</a>
);bun/no-array-just-to-countfixable
Disallow .split(separator).length with a one-character separator
const lineCount = source.split("\n").lengthsplit() builds an array only to read its length. Count the separators instead.;bun/no-cast-to-brand
Disallow casts to a branded type outside the module that declares it
import type { UserId } from "./ids";
export function userOf(params: { id: string }) {
return load(params.id as UserIdThis cast creates a UserId from an unchecked value. Only src/ids should create one.);
}bun/no-console-window-flash
Require windowsHide for a child process that does not share a stream with its parent
import { execFile } from "node:child_process";
execFileOn Windows, this process briefly opens a console window. Set windowsHide: true.("git", ["status", "--short"], (error, stdout) => {
show(stdout);
});bun/no-cwd-dependent-path
Disallow relative paths that depend on the directory the process was started in
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("config.json"config.json is relative to the working directory of the process. Resolve it from import.meta.dirname., "utf8"));bun/no-eager-dynamic-import
Disallow import() that runs as soon as the module is loaded
const { marked } = await import("marked")This import() runs as soon as the module is imported. Call it where it is needed, or use an import statement.;
export function render(text) {
return marked.parse(text);
}bun/no-eager-native-addon
Disallow loading a .node native addon as soon as the module is loaded
const native = require("./build/Release/resize.node")The native addon ./build/Release/resize.node loads as soon as the module is imported. Load it inside the function that needs it.;
export function resize(image, width) {
return native.resize(image, width);
}bun/no-env-at-module-scope
Disallow reading environment variables as soon as the module is loaded
const apiUrl = process.env.API_URLprocess.env.API_URL is read as soon as the module is imported. Read it inside the function that needs it.;
export function fetchUser(id) {
return fetch(`${apiUrl}/users/${id}`);
}bun/no-literal-temp-dir
Disallow hard-coded temporary directories such as /tmp in favor of os.tmpdir()
import { writeFileSync } from "node:fs";
writeFileSync("/tmp/report.json"/tmp is not the temporary directory on every system. Use os.tmpdir()., JSON.stringify(report));
writeFileSync(`/var/tmp/${name}.log`/var/tmp is not the temporary directory on every system. Use os.tmpdir()., lines.join("\n"));bun/no-negative-repeat-counthas suggestions
Disallow a repeat() count that comes from a subtraction and can be negative
function pad(label, width) {
return label + " ".repeat(width - label.lengthrepeat() throws a RangeError if this count is negative. Give it a lower bound.);
}bun/no-redundant-catch-typefixable
Disallow : unknown on the variable of a catch clause
try {
await save(draft);
} catch (error: unknownWith strict or useUnknownInCatchVariables, a catch variable is already unknown. Remove the annotation.) {
report(error);
}bun/no-restricted-text
Disallow text that matches a pattern in strings, templates, JSX text, comments, and regular expressions
const api = "http://Use https.api.example.com";
const avatar = `http://Use https.img.example.com/${user.id}.png`;bun/no-side-effects-on-import
Disallow top-level statements that call something or assign to a property
import { createServer } from "./server.js";
createServer().listen(3000)This runs as soon as the module is imported. Move it into an exported function.;bun/no-unbounded-requests
Require an axios request to have a timeout or a signal
import axios from "axios";
const user = await axios.getThis axios request has no time limit. Set timeout or pass a signal.(`/users/${id}`);
await axios.postThis axios request has no time limit. Set timeout or pass a signal.("/users", user, { headers });
const api = axios.create({ baseURL: "https://api.example.com", timeout: 0timeout: 0 means no time limit. });bun/no-unhandled-emitter-errors
Require an error listener on event emitters that Node.js functions return
import { spawn } from "node:child_process";
const child = spawnspawn() emits error when it fails, which crashes the process if nothing listens. Add an error listener.("git", ["status", "--short"]);
child.stdout.on("data", show);
child.on("exit", done);bun/no-unportable-commands
Disallow starting a process with a command that does not work the same on every system
import { exec, spawn } from "node:child_process";
spawn("npm"On Windows, npm is the script npm.cmd, which cannot run without a shell., ["install"]);
spawn("rm"rm does not exist on Windows., ["-rf", "dist"]);
exec("bun run build && bun run deploy"&& is shell syntax, and the shell differs between systems.);bun/no-unseeded-random-in-property-test
Disallow Math.random() and fc.sample() inside a fast-check property
import fc from "fast-check";
import { pad } from "./text";
fc.assert(
fc.property(fc.string(), text => {
const width = Math.floor(Math.random()Math.random() ignores the seed of the run, so a failure cannot be replayed or shrunk. Make the value a parameter of the property. * 80);
return pad(text, width).length >= width;
}),
);bun/no-useless-spread-fallbackhas suggestions
Disallow an empty object as the fallback in ...(condition ? value : {})
const headers = {
accept: "application/json",
...(apiKey ? { "x-api-key": apiKey } : {}This empty object is not needed. Spreading false, null or undefined into an object does nothing.),
};bun/prefer-builtin-sleephas suggestions
Prefer Bun.sleep() to a promise that only waits for setTimeout
await new Promise(resolve => setTimeout(resolve, 500))This promise only waits. Use Bun.sleep(ms) or setTimeout(ms) from node:timers/promises.;bun/prefer-lazy-schema
Disallow building a validation schema while the module loads
import { z } from "zod";
export const User = z.object({This schema is built as soon as the module is imported. Build it when it is first used.
name: z.string().min(1),
email: z.string().email(),
});bun/prefer-local-modulefixable
Require importing a package through the module in your project that wraps it
import axios from "axios"Import from @/http instead of axios.;
const users = await axios.get("/users");bun/prefer-timeout-signal
Prefer AbortSignal.timeout() to an AbortController that only a timer aborts
const controller = new AbortController()Only a timer aborts this controller. Use AbortSignal.timeout(ms) instead.;
setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, { signal: controller.signal });bun/prefer-url-for-sibling-files
Prefer new URL("./file", import.meta.url) to a path joined onto the module's directory
import { join } from "node:path";
const schema = await Bun.file(join(import.meta.dirname, "schema.sql")Bundlers cannot follow this path. Use new URL("./file", import.meta.url).).text();bun/react-no-latest-value-ref
Disallow a ref that is set to the latest value on every render
import { useRef } from "react";
function Chat({ onMessage }) {
const onMessageRef = useRef(onMessage);
onMessageRef.current = onMessageonMessageRef.current is set during render. React may render more than once, or throw the result away.;
// ...
}bun/react-no-local-render-helpers
Disallow renderItem() functions that are defined inside a component and used in its JSX
function UserList({ users }) {
const renderUserrenderUser is created again on every render, and React does not see it as a component. Move it out and make it a component. = user => <li key={user.id}>{user.name}</li>;
return <ul>{users.map(renderUser)}</ul>;
}bun/react-no-namespace-hooksfixable
Require React hooks to be imported by name: useState(), not React.useState()
import React from "react";
function Counter() {
const [count, setCount] = React.useStateImport useState by name from react.(0);
React.useEffectImport useEffect by name from react.(() => {
document.title = `${count} clicks`;
}, [count]);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}bun/react-prefer-updater-function
Prefer setItems(previous => ..) to a new array or object made from the state of the last render
import { useState } from "react";
function Cart() {
const [items, setItems] = useState([]);
async function add(id) {
const item = await loadItem(id);
setItems([...itemsitems may be out of date when setItems is called. Pass a function: setItems(previous => ..)., item]);
}
// ...
}bun:test 65 rules
bun:test/consistent-test-filename
Require test files to follow one naming pattern
iThe file user.spec.ts is a test file, but its name does not match the expected pattern.mport { expect, test } from "bun:test";
test("has a name", () => {
expect(loadUser().name).toBe("Ada");
});bun:test/consistent-test-itfixable
Enforce one of test or it for defining tests
import { describe, expect, it, test } from "bun:test";
itEnforce test and it usage conventions("adds", () => {
expect(1 + 1).toBe(2);
});
describe("subtract", () => {
testEnforce test and it usage conventions("goes below zero", () => {
expect(1 - 2).toBe(-1);
});
});bun:test/expect-expect
Require every test to make at least one assertion
import { test } from "bun:test";
testTest has no assertions("parses the port", () => {
parseConfig("port = 3000");
});bun:test/max-expects
Enforce a maximum number of expect() calls in a test
import { expect, test } from "bun:test";
test("creates a user", () => {
const user = createUser({ name: "Ada", email: "ada@example.com" });
expect(user.id).toBeGreaterThan(0);
expect(user.name).toBe("Ada");
expect(user.email).toBe("ada@example.com");
expect(user.role).toBe("member");
expect(user.active).toBe(true);
expectEnforces a maximum number assertion calls in a test body.(user.createdAt).toBeInstanceOf(Date);
});bun:test/max-nested-describe
Enforce a maximum depth of nested describe() calls
import { describe, expect, test } from "bun:test";
describe("cart", () => {
describe("checkout", () => {
describe("with a coupon", () => {Enforces a maximum depth to nested describe calls.
test("lowers the total", () => {
expect(checkout({ coupon: "SAVE10" }).total).toBe(90);
});
});
});
});bun:test/no-alias-methodsfixable
Disallow matcher aliases such as toBeCalled in favor of toHaveBeenCalled
import { expect, mock, test } from "bun:test";
test("saves the draft", () => {
const save = mock();
save("draft");
expect(save).toBeCalledWithUnexpected alias toBeCalledWith("draft");
});bun:test/no-commented-out-tests
Disallow tests that are inside a comment
import { expect, test } from "bun:test";
// test("rounds down", () => {Some tests appear to be inside comments.
// expect(Math.floor(1.9)).toBe(1);
// });bun:test/no-conditional-expect
Disallow expect inside if, catch and other code that only runs sometimes
import { expect, test } from "bun:test";
test("rejects bad JSON", () => {
try {
JSON.parse("{");
} catch (error) {
expectUnexpected conditional expect(error).toBeInstanceOf(SyntaxError);
}
});bun:test/no-conditional-in-test
Disallow if, switch, ? :, &&, || and ?? inside a test
import { expect, test } from "bun:test";
import { label, users } from "./users";
test("labels every user", () => {
for (const user of users) {
const expected = user.admin ? "Admin" : "Member"Avoid having conditionals in tests.;
expect(label(user)).toBe(expected);
}
});bun:test/no-conditional-tests
Disallow test and describe inside an if statement
import { expect, test } from "bun:test";
if (process.platform === "linux") {Avoid having conditionals in tests
test("reads the kernel version", async () => {
expect(await Bun.file("/proc/version").text()).toContain("Linux");
});
}bun:test/no-disabled-testson by default
Disallow skipped tests such as test.skip, xtest and xdescribe
import { describe, expect, test, xtest } from "bun:test";
describe.skipDisabled test suite("checkout", () => {
test("adds the tax", () => {
expect(total(100)).toBe(108);
});
});
xtestDisabled test("applies the coupon", () => {
expect(total(100, "SAVE10")).toBe(97.2);
});bun:test/no-done-callback
Disallow the done callback in tests and hooks in favor of promises
import { expect, test } from "bun:test";
test("reads the config", doneFunction parameter(s) use the done argument => {
readConfig((error, config) => {
expect(config.port).toBe(3000);
done();
});
});bun:test/no-duplicate-hooks
Disallow using the same hook twice in one describe block
import { beforeEach, describe, test } from "bun:test";
describe("cart", () => {
beforeEach(() => {
cart.clear();
});
test("starts empty", () => {});
beforeEach(() => {Duplicate beforeEach in describe block.
cart.add("apple");
});
});bun:test/no-exporton by default
Disallow exports from a file that has tests
import { expect, test } from "bun:test";
export function makeCartDo not export from a test file.() {
return { items: [] };
}
test("starts empty", () => {
expect(makeCart().items).toEqual([]);
});bun:test/no-focused-testson by defaulthas suggestions
Disallow test.only and describe.only
import { expect, test } from "bun:test";
test.onlyUnexpected focused test.("adds", () => {
expect(1 + 1).toBe(2);
});bun:test/no-hooks
Disallow beforeAll, beforeEach, afterEach and afterAll
import { beforeEach, expect, test } from "bun:test";
let cart;
beforeEachDo not use setup or teardown hooks.(() => {
cart = createCart();
});
test("starts empty", () => {
expect(cart.items).toEqual([]);
});bun:test/no-identical-title
Disallow two tests or two describe blocks with the same title in one block
import { describe, expect, test } from "bun:test";
describe("parsePrice", () => {
test("reads a number", () => {
expect(parsePrice("12")).toBe(12);
});
test("reads a number"Test title is used multiple times in the same describe block., () => {
expect(parsePrice("12.50")).toBe(12.5);
});
});bun:test/no-interpolation-in-snapshots
Disallow ${} in inline snapshots
import { expect, test } from "bun:test";
test("creates a user", () => {
const user = createUser("Ada");
expect(user).toMatchInlineSnapshot(`Do not use string interpolation inside of snapshots
{
"id": ${user.id},
"name": "Ada",
}
`);
});bun:test/no-large-snapshots
Disallow snapshots that are longer than a set number of lines
import { expect, test } from "bun:test";
test("loads the user", () => {
expect(loadUser(1)).toMatchInlineSnapshotSnapshot is too long.(`
{
"email": "ada@example.com",
"id": 1,
"name": "Ada",
}
`);
});bun:test/no-restricted-jest-methods
Disallow the jest and vi methods that you list
import { expect, jest, test } from "bun:test";
test("retries after a second", () => {
const send = jest.spyOnPass the dependency in instead of spying on it(mailer, "send");
retry(send);
jest.advanceTimersByTimeUse of advanceTimersByTime is not allowed(1000);
expect(send).toHaveBeenCalledTimes(2);
});bun:test/no-restricted-matchers
Disallow the matchers and modifiers you list
import { expect, test } from "bun:test";
test("a new user is active", () => {
const user = { active: true };
expect(user.active).toBeTruthyUse of toBeTruthy is disallowed();
});bun:test/no-standalone-expect
Disallow expect outside of a test or it block
import { beforeEach, describe, expect } from "bun:test";
describe("cart", () => {
const cart = createCart();
expectexpect must be inside of a test block.(cart.items).toHaveLength(0);
beforeEach(() => {
expectexpect must be inside of a test block.(cart.total).toBe(0);
});
});bun:test/no-test-prefixesfixable
Require .skip instead of the x prefix
import { expect, xdescribe, xtest } from "bun:test";
xtestUse test.skip instead.("subtracts", () => {
expect(3 - 1).toBe(2);
});
xdescribeUse describe.skip instead.("division", () => {});bun:test/no-test-return-statement
Disallow return expect(..) in a test
import { expect, test } from "bun:test";
test("adds the prices", () => {
const total = sum([1, 2]);
returnTests should not return a value expect(total).toBe(3);
});bun:test/no-unneeded-async-expect-functionfixable
Disallow an async function around a single awaited call in expect
import { expect, test } from "bun:test";
test("rejects an empty name", async () => {
await expect(async () => {Unnecessary async function wrapper
await saveUser({ name: "" });
}).rejects.toThrow("name is required");
await expect(async () => await saveUser({ name: "" })Unnecessary async function wrapper).rejects.toThrow();
});bun:test/padding-around-after-all-blocksfixable
Require a blank line before each afterAll call
import { afterAll, describe } from "bun:test";
describe("database", () => {
const db = connect();
aMissing padding before afterAll blockfterAll(() => db.close());
});bun:test/padding-around-test-blocksfixable
Require a blank line before each test and it call
import { describe, expect, test } from "bun:test";
describe("cart", () => {
const cart = createCart();
tMissing padding before test blockest("starts empty", () => {
expect(cart.items).toEqual([]);
});
tMissing padding before test blockest.skip("adds an item", () => {
expect(cart.add("apple").items).toEqual(["apple"]);
});
});bun:test/prefer-called-oncefixable
Require toHaveBeenCalledOnce() instead of toHaveBeenCalledTimes(1)
import { expect, mock, test } from "bun:test";
test("notifies the listener", () => {
const listener = mock();
publish("ready", listener);
expect(listener).toHaveBeenCalledTimes(1)The use of toBeCalledTimes(1) and toHaveBeenCalledTimes(1) is discouraged.;
});bun:test/prefer-called-timesfixable
Require toHaveBeenCalledTimes(1) instead of toHaveBeenCalledOnce()
import { expect, mock, test } from "bun:test";
test("does not retry after a success", () => {
const request = mock();
retryFetch(request);
expect(request).toHaveBeenCalledOnce()Use toBeCalledTimes(1) or toHaveBeenCalledTimes(1) instead of toBeCalledOnce() or toHaveBeenCalledOnce();
});bun:test/prefer-called-withhas suggestions
Require toHaveBeenCalledWith() instead of toHaveBeenCalled()
import { expect, mock, test } from "bun:test";
test("sends a welcome email", () => {
const send = mock();
register("ada@example.com", send);
expect(send).toHaveBeenCalledSuggest using toBeCalledWith() or toHaveBeenCalledWith().();
});bun:test/prefer-comparison-matcherhas suggestions
Require matchers such as toBeGreaterThan() instead of comparing inside expect()
import { expect, test } from "bun:test";
test("applies the discount", () => {
const price = discounted(100);
expect(price < 100).toBeSuggest using the built-in comparison matchers(true);
expect(price < 0).toBeSuggest using the built-in comparison matchers(false);
});bun:test/prefer-describe-function-titlehas suggestions
Require describe(fn, ...) instead of a string with the name of an imported function
import { describe, expect, test } from "bun:test";
import { slugify } from "./slugify";
describe("slugify"Title description can not have the same content as a imported function name., () => {
test("replaces spaces", () => {
expect(slugify("a b")).toBe("a-b");
});
});bun:test/prefer-each
Require test.each and describe.each instead of a loop around tests
import { expect, test } from "bun:test";
const cases = [
[1, 2],
[2, 4],
];
for (const [input, expected] of cases) Enforce using each rather than manual loops{
test(`doubles ${input}`, () => {
expect(input * 2).toBe(expected);
});
}bun:test/prefer-ending-with-an-expect
Require the last statement of a test to be an assertion
import { expect, test } from "bun:test";
testTest must end with an assertion("renames the user", async () => {
const user = await createUser("Ada");
expect(user.name).toBe("Ada");
await user.rename("Grace");
});bun:test/prefer-equality-matcherhas suggestions
Require toBe or toEqual instead of comparing with === inside expect
import { expect, test } from "bun:test";
test("the session belongs to the user", () => {
expect(session.userId === user.id).toBeSuggest using the built-in equality matchers.(true);
expect(user.role !== "admin").toBeSuggest using the built-in equality matchers.(true);
});bun:test/prefer-expect-assertionshas suggestions
Require expect.assertions() or expect.hasAssertions() at the start of every test
import { expect, test } from "bun:test";
test("rejects a wrong password", async () => {Every test should have either expect.assertions(<number of assertions>) or expect.hasAssertions() as its first expression.
try {
await login("ada", "wrong");
} catch (error) {
expect(error).toBeInstanceOf(AuthError);
}
});bun:test/prefer-expect-resolveshas suggestions
Require await expect(promise).resolves instead of expect(await promise)
import { expect, test } from "bun:test";
test("loads the user", async () => {
expect(await loadUser(1)Prefer await expect(...).resolves over expect(await ...) syntax.).toEqual({ id: 1, name: "Ada" });
});bun:test/prefer-expect-type-offixable
Require toBeTypeOf instead of expect(typeof value).toBe(...)
import { expect, test } from "bun:test";
test("the user has a name", () => {
expect(typeof user.name).toBe("string")Type assertions should be done using toBeTypeOf().;
expect(typeof user.save).not.toEqual("undefined")Type assertions should be done using toBeTypeOf().;
});bun:test/prefer-hooks-in-order
Require hooks to be written in the order they run
import { afterEach, beforeEach } from "bun:test";
afterEach(() => {
db.rollback();
});
beforeEach(() => {Test hooks are not in a consistent order.
db.begin();
});bun:test/prefer-hooks-on-top
Require hooks to come before the tests in their block
import { beforeEach, expect, test } from "bun:test";
test("starts empty", () => {
expect(cart.items).toHaveLength(0);
});
beforeEach(() => {Suggest having hooks before any test cases.
cart.clear();
});bun:test/prefer-lowercase-titlefixable
Require the titles of tests and describe blocks to start with a lowercase letter
import { expect, test } from "bun:test";
test("Adds an item"Enforce lowercase test names, () => {
expect(cart.add(item)).toBe(1);
});bun:test/prefer-mock-promise-shorthandfixable
Require mockResolvedValue() and mockRejectedValue() for mocks that return a promise
import { mock } from "bun:test";
const fetchUser = mock().mockReturnValuePrefer mock resolved/rejected shorthands for promises.(Promise.resolve({ name: "Ada" }));
const saveUser = mock().mockImplementationOncePrefer mock resolved/rejected shorthands for promises.(() => Promise.reject(new Error("offline")));bun:test/prefer-mock-return-shorthandfixable
Require mockReturnValue() for a mock that only returns a value
import { mock } from "bun:test";
const getPort = mock().mockImplementationMock functions that return simple values should use mockReturnValue/mockReturnValueOnce.(() => 3000);
const getUser = mock().mockImplementationOnceMock functions that return simple values should use mockReturnValue/mockReturnValueOnce.(() => {
return { name: "Ada" };
});bun:test/prefer-snapshot-hint
Require a hint on toMatchSnapshot() and toThrowErrorMatchingSnapshot()
import { expect, test } from "bun:test";
test("renders the menu", () => {
expect(renderMenu({ open: false })).toMatchSnapshot()Snapshot is missing a hint.;
expect(renderMenu({ open: true })).toMatchSnapshot()Snapshot is missing a hint.;
});bun:test/prefer-spy-onhas suggestions
Require spyOn() instead of assigning jest.fn() to a property
import { beforeEach, jest, mock } from "bun:test";
beforeEach(() => {
Date.now = mockSuggest using spyOn().(() => 1700000000000);
console.warn = jest.fnSuggest using jest.spyOn() or vi.spyOn().();
});bun:test/prefer-strict-boolean-matchershas suggestions
Require toBe(true) and toBe(false) instead of toBeTruthy() and toBeFalsy()
import { expect, test } from "bun:test";
test("checks the password", () => {
expect(isValid("correct horse")).toBeTruthy()Use toBe(true) instead of toBeTruthy.;
expect(isValid("")).toBeFalsy()Use toBe(false) instead of toBeFalsy.;
});bun:test/prefer-strict-equalhas suggestions
Require toStrictEqual() instead of toEqual()
import { expect, test } from "bun:test";
test("creates a user", () => {
expect(createUser("Ada")).toEqualSuggest using toStrictEqual().({ name: "Ada" });
});bun:test/prefer-to-befixable
Require toBe() for primitive literals, and toBeNull(), toBeUndefined(), toBeNaN()
import { expect, test } from "bun:test";
test("parses the header", () => {
expect(header.version).toEqualUse toBe when expecting primitive literals.(2);
expect(header.name).toStrictEqualUse toBe when expecting primitive literals.("config");
expect(header.parent).toBe(null)Use toBeNull instead.;
expect(header.comment).not.toBeDefined()Use toBeUndefined instead.;
expect(header.size).not.toBe(undefined)Use toBeDefined instead.;
expect(header.date).not.toBeUndefined()Use toBeDefined instead.;
expect(header.ratio).toEqual(NaN)Use toBeNaN instead.;
});bun:test/prefer-to-be-falsyhas suggestions
Require toBeFalsy() instead of toBe(false)
import { expect, test } from "bun:test";
test("a new cart has no discount", () => {
const cart = createCart();
expect(cart.hasDiscount).toBe(false)Use toBeFalsy instead.;
expect(cart.isOpen).not.toEqual(false)Use toBeFalsy instead.;
});bun:test/prefer-to-be-truthyhas suggestions
Require toBeTruthy() instead of toBe(true)
import { expect, test } from "bun:test";
test("an admin can delete posts", () => {
const user = createUser({ role: "admin" });
expect(user.canDelete).toBe(true)Use toBeTruthy instead.;
expect(user.isBanned).not.toEqual(true)Use toBeTruthy instead.;
});bun:test/prefer-to-containhas suggestions
Require toContain() instead of comparing the result of includes() with a boolean
import { expect, test } from "bun:test";
test("a draft is tagged", () => {
const tags = getTags(draft);
expect(tags.includes("draft")).toBeSuggest using toContain().(true);
expect(tags.includes("published")).toEqualSuggest using toContain().(false);
});bun:test/prefer-to-have-been-calledfixable
Require not.toHaveBeenCalled() instead of toHaveBeenCalledTimes(0)
import { expect, mock, test } from "bun:test";
test("only notifies on failure", () => {
const onError = mock();
const onDone = mock();
run(task, { onError, onDone });
expect(onError).toHaveBeenCalledTimes(0)Prefer toHaveBeenCalled() over toHaveBeenCalledTimes(0);
expect(onDone).not.toHaveBeenCalledTimes(0)Prefer toHaveBeenCalled() over toHaveBeenCalledTimes(0);
});bun:test/prefer-to-have-been-called-timesfixable
Require toHaveBeenCalledTimes() instead of checking the length of mock.calls
import { expect, mock, test } from "bun:test";
test("saves each item once", () => {
const save = mock();
saveAll(["a", "b"], save);
expect(save.mock.calls).toHaveLength(2)Prefer toHaveBeenCalledTimes() over toHaveLength() when asserting mock call counts;
});bun:test/prefer-to-have-lengthfixable
Require toHaveLength() instead of comparing the length property
import { expect, test } from "bun:test";
test("finds the active users", () => {
const users = findUsers({ active: true });
expect(users.length).toBeSuggest using toHaveLength().(2);
expect(users[0].roles.length).not.toEqualSuggest using toHaveLength().(0);
});bun:test/prefer-todofixable
Require test.todo for tests that have no body yet
import { test } from "bun:test";
testSuggest using test.todo.("rejects an expired token", () => {});
testSuggest using test.todo.("refreshes the token");
test.skipSuggest using test.todo.("logs out", function () {});bun:test/require-hook
Require setup and teardown code to be inside a hook
import { describe, expect, test } from "bun:test";
let db = connect();Require setup and teardown code to be within a hook.
db.seed(["ada", "grace"])Require setup and teardown code to be within a hook.;
describe("users", () => {
clearCache()Require setup and teardown code to be within a hook.;
test("finds a user", () => {
expect(db.find("ada")).toBeDefined();
});
});bun:test/require-mock-type-parameters
Require a type parameter on jest.fn() and vi.fn()
import { jest, vi } from "bun:test";
const findUser = jest.fnMissing type parameters on mock function call();
const log = vi.fnMissing type parameters on mock function call((message: string) => {});bun:test/require-test-timeout
Require every test to set a timeout
import { expect, test } from "bun:test";
test("loads the report", async () => {Test is missing a timeout.
expect(await loadReport()).toBeDefined();
});bun:test/require-to-throw-message
Require toThrow() to say which error it expects
import { expect, test } from "bun:test";
test("rejects a negative amount", async () => {
expect(() => withdraw(-5)).toThrowRequire a message for toThrow.();
expect(() => withdraw(-5)).toThrowErrorRequire a message for toThrowError.();
await expect(transfer(-5)).rejects.toThrowRequire a message for toThrow.();
});bun:test/require-top-level-describe
Require tests and hooks to be inside a describe block
import { beforeEach, expect, test } from "bun:test";
beforeEach(() => {Require test cases and hooks to be inside a describe block
cart.clear();
});
test("starts empty", () => {Require test cases and hooks to be inside a describe block
expect(cart.size).toBe(0);
});bun:test/valid-describe-callbackon by default
Require describe to get a callback that is a function with no parameters
import { describe, test } from "bun:test";
describe("cart"Describe requires name and callback arguments); // no callback
describe("cart", "adds an item"Second argument must be a function); // not a function
describe("cart", (done) => {Unexpected argument(s) in describe callback
test("adds an item", () => {});
done();
});bun:test/valid-expecton by defaulthas suggestions
Require expect to get a value, end in a called matcher, and be awaited when it is async
import { expect, test } from "bun:test";
test("adds the tax", () => {
expect(total(100))Expect must have a corresponding matcher call.; // no matcher
expect(total(100)).toBeMatchers must be called to assert.; // the matcher is not called
expect()Expect requires at least 1 argument.toBe(108); // no value
expect(total(100)).never.toBe(0)Expect has an unknown modifier.; // `never` is not a modifier
});bun:test/valid-expect-in-promiseon by default
Require a promise chain that contains expect to be awaited or returned from the test
import { expect, test } from "bun:test";
test("loads the user", () => {
loadUser(1).then((user) => {Expect in a promise chain must be awaited or returned
expect(user.name).toBe("Ada");
});
});bun:test/valid-titlefixable
Require test and describe titles to be non-empty, with no stray spaces or repeated prefix
import { describe, test } from "bun:test";
describe("describe cart"Should not have duplicate prefix, () => {
test(""Should not have an empty title, () => {});
test(" adds an item"Should not have leading or trailing spaces, () => {});
test("test removes an item"Should not have duplicate prefix, () => {});
test(404Title must be a string, () => {});
});bun:test/warn-todo
Disallow .todo on test, it and describe
import { describe, test } from "bun:test";
test.todoThe use of .todo is not recommended.("refunds the order");
describe.todoThe use of .todo is not recommended.("checkout");Jest 60 rules
jest/consistent-test-itfixable
Enforce one of test or it for defining tests
import { describe, expect, it, test } from "@jest/globals";
itEnforce test and it usage conventions("adds", () => {
expect(1 + 1).toBe(2);
});
describe("subtract", () => {
testEnforce test and it usage conventions("goes below zero", () => {
expect(1 - 2).toBe(-1);
});
});jest/expect-expect
Require every test to make at least one assertion
import { test } from "@jest/globals";
testTest has no assertions("parses the port", () => {
parseConfig("port = 3000");
});
test.skipTest has no assertions("parses the host", () => {
parseConfig("host = localhost");
});jest/max-expects
Enforce a maximum number of expect() calls in a test
import { expect, test } from "@jest/globals";
test("creates a user", () => {
const user = createUser({ name: "Ada", email: "ada@example.com" });
expect(user.id).toBeGreaterThan(0);
expect(user.name).toBe("Ada");
expect(user.email).toBe("ada@example.com");
expect(user.role).toBe("member");
expect(user.active).toBe(true);
expectEnforces a maximum number assertion calls in a test body.(user.createdAt).toBeInstanceOf(Date);
});jest/max-nested-describe
Enforce a maximum depth of nested describe() calls
import { describe, expect, test } from "@jest/globals";
describe("cart", () => {
describe("checkout", () => {
describe("with a coupon", () => {Enforces a maximum depth to nested describe calls.
test("lowers the total", () => {
expect(checkout({ coupon: "SAVE10" }).total).toBe(90);
});
});
});
});jest/no-alias-methodsfixable
Disallow matcher aliases such as toBeCalled in favor of toHaveBeenCalled
import { expect, jest, test } from "@jest/globals";
test("saves the draft", () => {
const save = jest.fn();
save("draft");
expect(save).toBeCalledWithUnexpected alias toBeCalledWith("draft");
});jest/no-commented-out-tests
Disallow tests that are inside a comment
import { expect, test } from "@jest/globals";
// test("rounds down", () => {Some tests appear to be inside comments.
// expect(Math.floor(1.9)).toBe(1);
// });jest/no-conditional-expect
Disallow expect inside if, catch and other code that only runs sometimes
import { expect, test } from "@jest/globals";
test("rejects bad JSON", () => {
try {
JSON.parse("{");
} catch (error) {
expectUnexpected conditional expect(error).toBeInstanceOf(SyntaxError);
}
});jest/no-conditional-in-test
Disallow if, switch, ? :, &&, || and ?? inside a test
import { expect, test } from "@jest/globals";
import { label, users } from "./users";
test("labels every user", () => {
for (const user of users) {
const expected = user.admin ? "Admin" : "Member"Avoid having conditionals in tests.;
expect(label(user)).toBe(expected);
}
});jest/no-confusing-set-timeout
Disallow jest.setTimeout inside a block, after a test, or more than once in a file
import { describe, expect, jest, test } from "@jest/globals";
import { upload } from "./upload";
describe("upload", () => {
jest.setTimeoutjest.setTimeout should be placed before any other jest methods.jest.setTimeout should only be called in a global scope(10_000);
test("sends the file", async () => {
expect(await upload("report.pdf")).toBe(true);
});
});jest/no-deprecated-functionsfixable
Disallow Jest functions that have been renamed or replaced
import { jest } from "@jest/globals";
jest.useFakeTimers();
jest.runTimersToTimejest.runTimersToTime has been deprecated in favor of jest.advanceTimersByTime(1000);
const api = jest.genMockFromModulejest.genMockFromModule has been deprecated in favor of jest.createMockFromModule("./api");jest/no-disabled-tests
Disallow skipped tests such as test.skip, xtest and xdescribe
import { describe, expect, test, xtest } from "@jest/globals";
describe.skipDisabled test suite("checkout", () => {
test("adds the tax", () => {
expect(total(100)).toBe(108);
});
});
xtestDisabled test("applies the coupon", () => {
expect(total(100, "SAVE10")).toBe(97.2);
});jest/no-done-callback
Disallow the done callback in tests and hooks in favor of promises
import { expect, test } from "@jest/globals";
test("reads the config", doneFunction parameter(s) use the done argument => {
readConfig((error, config) => {
expect(config.port).toBe(3000);
done();
});
});jest/no-duplicate-hooks
Disallow using the same hook twice in one describe block
import { beforeEach, describe, test } from "@jest/globals";
describe("cart", () => {
beforeEach(() => {
cart.clear();
});
test("starts empty", () => {});
beforeEach(() => {Duplicate beforeEach in describe block.
cart.add("apple");
});
});jest/no-export
Disallow exports from a file that has tests
import { expect, test } from "@jest/globals";
export function makeCartDo not export from a test file.() {
return { items: [] };
}
test("starts empty", () => {
expect(makeCart().items).toEqual([]);
});jest/no-focused-testshas suggestions
Disallow test.only, describe.only, fit and fdescribe
import { expect, fit, test } from "@jest/globals";
test.onlyUnexpected focused test.("adds", () => {
expect(1 + 1).toBe(2);
});
fitUnexpected focused test.("subtracts", () => {
expect(2 - 1).toBe(1);
});jest/no-hooks
Disallow beforeAll, beforeEach, afterEach and afterAll
import { beforeEach, expect, test } from "@jest/globals";
let cart;
beforeEachDo not use setup or teardown hooks.(() => {
cart = createCart();
});
test("starts empty", () => {
expect(cart.items).toEqual([]);
});jest/no-identical-title
Disallow two tests or two describe blocks with the same title in one block
import { describe, expect, test } from "@jest/globals";
describe("parsePrice", () => {
test("reads a number", () => {
expect(parsePrice("12")).toBe(12);
});
test("reads a number"Test title is used multiple times in the same describe block., () => {
expect(parsePrice("12.50")).toBe(12.5);
});
});jest/no-interpolation-in-snapshots
Disallow ${} in inline snapshots
import { expect, test } from "@jest/globals";
test("creates a user", () => {
const user = createUser("Ada");
expect(user).toMatchInlineSnapshot(`Do not use string interpolation inside of snapshots
{
"id": ${user.id},
"name": "Ada",
}
`);
});jest/no-jasmine-globalsfixable
Disallow Jasmine globals such as jasmine.any, spyOn, fail and pending
jasmine.DEFAULT_TIMEOUT_INTERVAL"Illegal usage of jasmine global" = 5000;
test("saves the user", () => {
spyOn"Illegal usage of global spyOn"(db, "insert");
save({ name: "Ada" });
expect(db.insert).toHaveBeenCalledWith(jasmine.anyIllegal usage of `any`(Object));
});
test("exports to PDF", () => {
pendingIllegal usage of `pending`,();
});jest/no-large-snapshots
Disallow snapshots that are longer than a set number of lines
import { expect, test } from "@jest/globals";
test("loads the user", () => {
expect(loadUser(1)).toMatchInlineSnapshotSnapshot is too long.(`
{
"email": "ada@example.com",
"id": 1,
"name": "Ada",
}
`);
});jest/no-mocks-import
Disallow importing from a __mocks__ directory
import { expect, test } from "@jest/globals";
import { sendEmail } from "./__mocks__/mailer"Mocks should not be manually imported from a __mocks__ directory.;
import { register } from "./register";
test("sends a welcome email", () => {
register("ada@example.com");
expect(sendEmail).toHaveBeenCalled();
});jest/no-restricted-jest-methods
Disallow the jest methods that you list
import { expect, jest, test } from "@jest/globals";
test("retries after a second", () => {
const send = jest.spyOnPass the dependency in instead of spying on it(mailer, "send");
retry(send);
jest.advanceTimersByTimeUse of advanceTimersByTime is not allowed(1000);
expect(send).toHaveBeenCalledTimes(2);
});jest/no-restricted-matchers
Disallow the matchers and modifiers you list
import { expect, test } from "@jest/globals";
test("a new user is active", () => {
const user = { active: true };
expect(user.active).toBeTruthyUse of toBeTruthy is disallowed();
});jest/no-standalone-expect
Disallow expect outside of a test or it block
import { beforeEach, describe, expect } from "@jest/globals";
describe("cart", () => {
const cart = createCart();
expectexpect must be inside of a test block.(cart.items).toHaveLength(0);
beforeEach(() => {
expectexpect must be inside of a test block.(cart.total).toBe(0);
});
});jest/no-test-prefixesfixable
Require .only and .skip instead of the f and x prefixes
import { expect, fit, xdescribe, xtest } from "@jest/globals";
fitUse it.only instead.("adds", () => {
expect(1 + 1).toBe(2);
});
xtestUse test.skip instead.("subtracts", () => {
expect(3 - 1).toBe(2);
});
xdescribeUse describe.skip instead.("division", () => {});jest/no-test-return-statement
Disallow return expect(..) in a test
import { expect, test } from "@jest/globals";
test("adds the prices", () => {
const total = sum([1, 2]);
returnJest tests should not return a value expect(total).toBe(3);
});jest/no-unneeded-async-expect-functionfixable
Disallow an async function around a single awaited call in expect
import { expect, test } from "@jest/globals";
test("rejects an empty name", async () => {
await expect(async () => {Unnecessary async function wrapper
await saveUser({ name: "" });
}).rejects.toThrow("name is required");
await expect(async () => await saveUser({ name: "" })Unnecessary async function wrapper).rejects.toThrow();
});jest/no-untyped-mock-factoryfixable
Require a type parameter on jest.mock() and jest.doMock() with a factory
import { jest } from "@jest/globals";
jest.mockjest.mock() factories should not be used without an explicit type parameter.("./mailer", () => ({
send: jest.fn(),
}));jest/padding-around-after-all-blocksfixable
Require a blank line before each afterAll call
import { afterAll, describe } from "@jest/globals";
describe("database", () => {
const db = connect();
aMissing padding before afterAll blockfterAll(() => db.close());
});jest/padding-around-test-blocksfixable
Require a blank line before each test and it call
import { describe, expect, test } from "@jest/globals";
describe("cart", () => {
const cart = createCart();
tMissing padding before test blockest("starts empty", () => {
expect(cart.items).toEqual([]);
});
tMissing padding before test blockest.skip("adds an item", () => {
expect(cart.add("apple").items).toEqual(["apple"]);
});
});jest/prefer-called-withfixable
Require toHaveBeenCalledWith() instead of toHaveBeenCalled()
import { expect, jest, test } from "@jest/globals";
test("sends a welcome email", () => {
const send = jest.fn();
register("ada@example.com", send);
expect(send).toHaveBeenCalledSuggest using toBeCalledWith() or toHaveBeenCalledWith().();
});jest/prefer-comparison-matcherfixable
Require matchers such as toBeGreaterThan() instead of comparing inside expect()
import { expect, test } from "@jest/globals";
test("applies the discount", () => {
const price = discounted(100);
expect(price < 100).toBeSuggest using the built-in comparison matchers(true);
expect(price < 0).toBeSuggest using the built-in comparison matchers(false);
});jest/prefer-each
Require test.each and describe.each instead of a loop around tests
import { expect, test } from "@jest/globals";
const cases = [
[1, 2],
[2, 4],
];
for (const [input, expected] of cases) Enforce using each rather than manual loops{
test(`doubles ${input}`, () => {
expect(input * 2).toBe(expected);
});
}jest/prefer-ending-with-an-expect
Require the last statement of a test to be an assertion
import { expect, test } from "@jest/globals";
testTest must end with an assertion("renames the user", async () => {
const user = await createUser("Ada");
expect(user.name).toBe("Ada");
await user.rename("Grace");
});jest/prefer-equality-matcherhas suggestions
Require toBe or toEqual instead of comparing with === inside expect
import { expect, test } from "@jest/globals";
test("the session belongs to the user", () => {
expect(session.userId === user.id).toBeSuggest using the built-in equality matchers.(true);
expect(user.role !== "admin").toBeSuggest using the built-in equality matchers.(true);
});jest/prefer-expect-assertionshas suggestions
Require expect.assertions() or expect.hasAssertions() at the start of every test
import { expect, test } from "@jest/globals";
test("rejects a wrong password", async () => {Every test should have either expect.assertions(<number of assertions>) or expect.hasAssertions() as its first expression.
try {
await login("ada", "wrong");
} catch (error) {
expect(error).toBeInstanceOf(AuthError);
}
});jest/prefer-expect-resolvesfixable
Require await expect(promise).resolves instead of expect(await promise)
import { expect, test } from "@jest/globals";
test("loads the user", async () => {
expect(await loadUser(1)Prefer await expect(...).resolves over expect(await ...) syntax.).toEqual({ id: 1, name: "Ada" });
});jest/prefer-hooks-in-order
Require hooks to be written in the order they run
import { afterEach, beforeEach } from "@jest/globals";
afterEach(() => {
db.rollback();
});
beforeEach(() => {Test hooks are not in a consistent order.
db.begin();
});jest/prefer-hooks-on-top
Require hooks to come before the tests in their block
import { beforeEach, expect, test } from "@jest/globals";
test("starts empty", () => {
expect(cart.items).toHaveLength(0);
});
beforeEach(() => {Suggest having hooks before any test cases.
cart.clear();
});jest/prefer-importing-jest-globalsfixable
Require describe, test, expect and jest to be imported from @jest/globals
describeImport the following Jest functions from @jest/globals: describe, expect, test("cart", () => {
test("starts empty", () => {
expect(cart.items).toHaveLength(0);
});
});jest/prefer-jest-mockedfixable
Require jest.mocked(fn) instead of fn as jest.Mock
import { jest } from "@jest/globals";
import { fetchUser, saveUser } from "./api";
jest.mock("./api");
(fetchUser as jest.MockPrefer jest.mocked() over fn as jest.Mock.).mockReturnValue({ name: "Ada" });
const mockSaveUser = saveUser as jest.MockedFunction<typeof saveUser>Prefer jest.mocked() over fn as jest.Mock.;jest/prefer-lowercase-titlefixable
Require the titles of tests and describe blocks to start with a lowercase letter
import { expect, test } from "@jest/globals";
test("Adds an item"Enforce lowercase test names, () => {
expect(cart.add(item)).toBe(1);
});jest/prefer-mock-promise-shorthandfixable
Require mockResolvedValue() and mockRejectedValue() for mocks that return a promise
import { jest } from "@jest/globals";
const fetchUser = jest.fn().mockReturnValuePrefer mock resolved/rejected shorthands for promises.(Promise.resolve({ name: "Ada" }));
const saveUser = jest.fn().mockImplementationOncePrefer mock resolved/rejected shorthands for promises.(() => Promise.reject(new Error("offline")));jest/prefer-mock-return-shorthandfixable
Require mockReturnValue() for a mock that only returns a value
import { jest } from "@jest/globals";
const getPort = jest.fn().mockImplementationMock functions that return simple values should use mockReturnValue/mockReturnValueOnce.(() => 3000);
const getUser = jest.fn().mockImplementationOnceMock functions that return simple values should use mockReturnValue/mockReturnValueOnce.(() => {
return { name: "Ada" };
});jest/prefer-snapshot-hint
Require a hint on toMatchSnapshot() and toThrowErrorMatchingSnapshot()
import { expect, test } from "@jest/globals";
test("renders the menu", () => {
expect(renderMenu({ open: false })).toMatchSnapshot()Snapshot is missing a hint.;
expect(renderMenu({ open: true })).toMatchSnapshot()Snapshot is missing a hint.;
});jest/prefer-spy-onhas suggestions
Require jest.spyOn() instead of assigning jest.fn() to a property
import { beforeEach, jest } from "@jest/globals";
beforeEach(() => {
Date.now = jest.fnSuggest using jest.spyOn() or vi.spyOn().(() => 1700000000000);
console.warn = jest.fnSuggest using jest.spyOn() or vi.spyOn().();
});jest/prefer-strict-equalhas suggestions
Require toStrictEqual() instead of toEqual()
import { expect, test } from "@jest/globals";
test("creates a user", () => {
expect(createUser("Ada")).toEqualSuggest using toStrictEqual().({ name: "Ada" });
});jest/prefer-to-befixable
Require toBe() for primitive literals, and toBeNull(), toBeUndefined(), toBeNaN()
import { expect, test } from "@jest/globals";
test("parses the header", () => {
expect(header.version).toEqualUse toBe when expecting primitive literals.(2);
expect(header.name).toStrictEqualUse toBe when expecting primitive literals.("config");
expect(header.parent).toBe(null)Use toBeNull instead.;
expect(header.comment).not.toBeDefined()Use toBeUndefined instead.;
expect(header.size).not.toBe(undefined)Use toBeDefined instead.;
expect(header.date).not.toBeUndefined()Use toBeDefined instead.;
expect(header.ratio).toEqual(NaN)Use toBeNaN instead.;
});jest/prefer-to-containfixable
Require toContain() instead of comparing the result of includes() with a boolean
import { expect, test } from "@jest/globals";
test("a draft is tagged", () => {
const tags = getTags(draft);
expect(tags.includes("draft")).toBeSuggest using toContain().(true);
expect(tags.includes("published")).toEqualSuggest using toContain().(false);
});jest/prefer-to-have-been-calledfixable
Require not.toHaveBeenCalled() instead of toHaveBeenCalledTimes(0)
import { expect, jest, test } from "@jest/globals";
test("only notifies on failure", () => {
const onError = jest.fn();
const onDone = jest.fn();
run(task, { onError, onDone });
expect(onError).toHaveBeenCalledTimes(0)Prefer toHaveBeenCalled() over toHaveBeenCalledTimes(0);
expect(onDone).not.toHaveBeenCalledTimes(0)Prefer toHaveBeenCalled() over toHaveBeenCalledTimes(0);
});jest/prefer-to-have-been-called-timesfixable
Require toHaveBeenCalledTimes() instead of checking the length of mock.calls
import { expect, jest, test } from "@jest/globals";
test("saves each item once", () => {
const save = jest.fn();
saveAll(["a", "b"], save);
expect(save.mock.calls).toHaveLength(2)Prefer toHaveBeenCalledTimes() over toHaveLength() when asserting mock call counts;
});jest/prefer-to-have-lengthfixable
Require toHaveLength() instead of comparing the length property
import { expect, test } from "@jest/globals";
test("finds the active users", () => {
const users = findUsers({ active: true });
expect(users.length).toBeSuggest using toHaveLength().(2);
expect(users[0].roles.length).not.toEqualSuggest using toHaveLength().(0);
});jest/prefer-todofixable
Require test.todo for tests that have no body yet
import { test } from "@jest/globals";
testSuggest using test.todo.("rejects an expired token", () => {});
testSuggest using test.todo.("refreshes the token");
test.skipSuggest using test.todo.("logs out", function () {});jest/require-hook
Require setup and teardown code to be inside a hook
import { describe, expect, test } from "@jest/globals";
let db = connect();Require setup and teardown code to be within a hook.
db.seed(["ada", "grace"])Require setup and teardown code to be within a hook.;
describe("users", () => {
clearCache()Require setup and teardown code to be within a hook.;
test("finds a user", () => {
expect(db.find("ada")).toBeDefined();
});
});jest/require-to-throw-message
Require toThrow() to say which error it expects
import { expect, test } from "@jest/globals";
test("rejects a negative amount", async () => {
expect(() => withdraw(-5)).toThrowRequire a message for toThrow.();
expect(() => withdraw(-5)).toThrowErrorRequire a message for toThrowError.();
await expect(transfer(-5)).rejects.toThrowRequire a message for toThrow.();
});jest/require-top-level-describe
Require tests and hooks to be inside a describe block
import { beforeEach, expect, test } from "@jest/globals";
beforeEach(() => {Require test cases and hooks to be inside a describe block
cart.clear();
});
test("starts empty", () => {Require test cases and hooks to be inside a describe block
expect(cart.size).toBe(0);
});jest/valid-describe-callback
Require describe to get a title and a plain synchronous callback
import { describe, expect, test } from "@jest/globals";
describe("cart", async () => {No async describe callback
const items = await loadItems();
test("has items", () => {
expect(items.length).toBeGreaterThan(0);
});
});jest/valid-expecthas suggestions
Require expect to get a value, end in a called matcher, and be awaited when it is async
import { expect, test } from "@jest/globals";
test("adds the tax", () => {
expect(total(100))Expect must have a corresponding matcher call.; // no matcher
expect(total(100)).toBeMatchers must be called to assert.; // the matcher is not called
expect()Expect requires at least 1 argument.toBe(108); // no value
expect(total(100), "after tax")Expect takes at most 1 argument.toBe(108); // more than one argument
expect(total(100)).never.toBe(0)Expect has an unknown modifier.; // `never` is not a modifier
});jest/valid-expect-in-promise
Require a promise chain that contains expect to be awaited or returned from the test
import { expect, test } from "@jest/globals";
test("loads the user", () => {
loadUser(1).then((user) => {Expect in a promise chain must be awaited or returned
expect(user.name).toBe("Ada");
});
});jest/valid-titlefixable
Require test and describe titles to be non-empty strings with no stray spaces or repeated prefix
import { describe, test } from "@jest/globals";
describe("describe cart"Should not have duplicate prefix, () => {
test(""Should not have an empty title, () => {});
test(" adds an item"Should not have leading or trailing spaces, () => {});
test("test removes an item"Should not have duplicate prefix, () => {});
test(404Title must be a string, () => {});
});Vitest 73 rules
vitest/consistent-each-for
Enforce either .each or .for for parameterized tests
import { expect, test } from "vitest";
test.eachtest can not be used with .each to create parameterized test.([
[1, 2, 3],
[2, 3, 5],
])("adds %i and %i", (a, b, sum) => {
expect(a + b).toBe(sum);
});vitest/consistent-test-filename
Require test files to follow one naming pattern
iThe file user.spec.ts is a test file, but its name does not match the expected pattern.mport { expect, test } from "vitest";
test("has a name", () => {
expect(loadUser().name).toBe("Ada");
});vitest/consistent-test-itfixable
Enforce one of test or it for defining tests
import { describe, expect, it, test } from "vitest";
itEnforce test and it usage conventions("adds", () => {
expect(1 + 1).toBe(2);
});
describe("subtract", () => {
testEnforce test and it usage conventions("goes below zero", () => {
expect(1 - 2).toBe(-1);
});
});vitest/consistent-vitest-vifixable
Enforce one of vi or vitest for Vitest's helper object
import { expect, test, vitestThe vitest function accessor used is not allowed } from "vitest";
test("calls the listener", () => {
const listener = vitestThe vitest function accessor used is not allowed.fn();
emit("save", listener);
expect(listener).toHaveBeenCalled();
});vitest/expect-expect
Require every test to make at least one assertion
import { test } from "vitest";
testTest has no assertions("parses the port", () => {
parseConfig("port = 3000");
});vitest/hoisted-apis-on-tophas suggestions
Require vi.mock, vi.unmock and vi.hoisted at the top level of the file
import { beforeEach, vi } from "vitest";
beforeEach(() => {
vi.mock("./database")Hoisted API cannot be used in a runtime location in this file.;
});vitest/max-expects
Enforce a maximum number of expect() calls in a test
import { expect, test } from "vitest";
test("creates a user", () => {
const user = createUser({ name: "Ada", email: "ada@example.com" });
expect(user.id).toBeGreaterThan(0);
expect(user.name).toBe("Ada");
expect(user.email).toBe("ada@example.com");
expect(user.role).toBe("member");
expect(user.active).toBe(true);
expectEnforces a maximum number assertion calls in a test body.(user.createdAt).toBeInstanceOf(Date);
});vitest/max-nested-describe
Enforce a maximum depth of nested describe() calls
import { describe, expect, test } from "vitest";
describe("cart", () => {
describe("checkout", () => {
describe("with a coupon", () => {Enforces a maximum depth to nested describe calls.
test("lowers the total", () => {
expect(checkout({ coupon: "SAVE10" }).total).toBe(90);
});
});
});
});vitest/no-alias-methodsfixable
Disallow matcher aliases such as toBeCalled in favor of toHaveBeenCalled
import { expect, test, vi } from "vitest";
test("saves the draft", () => {
const save = vi.fn();
save("draft");
expect(save).toBeCalledWithUnexpected alias toBeCalledWith("draft");
});vitest/no-commented-out-tests
Disallow tests that are inside a comment
import { expect, test } from "vitest";
// test("rounds down", () => {Some tests appear to be inside comments.
// expect(Math.floor(1.9)).toBe(1);
// });vitest/no-conditional-expect
Disallow expect inside if, catch and other code that only runs sometimes
import { expect, test } from "vitest";
test("rejects bad JSON", () => {
try {
JSON.parse("{");
} catch (error) {
expectUnexpected conditional expect(error).toBeInstanceOf(SyntaxError);
}
});vitest/no-conditional-in-test
Disallow if, switch, ? :, &&, || and ?? inside a test
import { expect, test } from "vitest";
import { label, users } from "./users";
test("labels every user", () => {
for (const user of users) {
const expected = user.admin ? "Admin" : "Member"Avoid having conditionals in tests.;
expect(label(user)).toBe(expected);
}
});vitest/no-conditional-tests
Disallow test and describe inside an if statement
import { expect, test } from "vitest";
import { readFile } from "node:fs/promises";
if (process.platform === "linux") {Avoid having conditionals in tests
test("reads the kernel version", async () => {
expect(await readFile("/proc/version", "utf8")).toContain("Linux");
});
}vitest/no-disabled-tests
Disallow skipped tests such as test.skip and describe.skip
import { describe, expect, test } from "vitest";
describe.skipDisabled test suite("checkout", () => {
test("adds the tax", () => {
expect(total(100)).toBe(108);
});
});
test.skipDisabled test("applies the coupon", () => {
expect(total(100, "SAVE10")).toBe(97.2);
});vitest/no-duplicate-hooks
Disallow using the same hook twice in one describe block
import { beforeEach, describe, test } from "vitest";
describe("cart", () => {
beforeEach(() => {
cart.clear();
});
test("starts empty", () => {});
beforeEach(() => {Duplicate beforeEach in describe block.
cart.add("apple");
});
});vitest/no-focused-testshas suggestions
Disallow test.only and describe.only
import { describe, expect, test } from "vitest";
test.onlyUnexpected focused test.("adds", () => {
expect(1 + 1).toBe(2);
});
describe.onlyUnexpected focused test.("subtraction", () => {
test("subtracts", () => {
expect(2 - 1).toBe(1);
});
});vitest/no-hooks
Disallow beforeAll, beforeEach, afterEach and afterAll
import { beforeEach, expect, test } from "vitest";
let cart;
beforeEachDo not use setup or teardown hooks.(() => {
cart = createCart();
});
test("starts empty", () => {
expect(cart.items).toEqual([]);
});vitest/no-identical-title
Disallow two tests or two describe blocks with the same title in one block
import { describe, expect, test } from "vitest";
describe("parsePrice", () => {
test("reads a number", () => {
expect(parsePrice("12")).toBe(12);
});
test("reads a number"Test title is used multiple times in the same describe block., () => {
expect(parsePrice("12.50")).toBe(12.5);
});
});vitest/no-import-node-testhas suggestions
Disallow importing from node:test in a project that uses Vitest
import { describe, it } from "node:test"Do not import from node:test;
describe("slugify", () => {
it("lowercases the title", () => {});
});vitest/no-importing-vitest-globalsfixable
Disallow importing test, expect, vi and the other Vitest globals
import { describeDo not import/require global functions from vitest., expect, it } from "vitest";
describe("slugify", () => {
it("lowercases the title", () => {
expect(slugify("Hello")).toBe("hello");
});
});vitest/no-interpolation-in-snapshots
Disallow ${} in inline snapshots
import { expect, test } from "vitest";
test("creates a user", () => {
const user = createUser("Ada");
expect(user).toMatchInlineSnapshot(`Do not use string interpolation inside of snapshots
{
"id": ${user.id},
"name": "Ada",
}
`);
});vitest/no-large-snapshots
Disallow snapshots that are longer than a set number of lines
import { expect, test } from "vitest";
test("loads the user", () => {
expect(loadUser(1)).toMatchInlineSnapshotSnapshot is too long.(`
{
"email": "ada@example.com",
"id": 1,
"name": "Ada",
}
`);
});vitest/no-mocks-import
Disallow importing from a __mocks__ directory
import { expect, test } from "vitest";
import { sendEmail } from "./__mocks__/mailer"Mocks should not be manually imported from a __mocks__ directory.;
import { register } from "./register";
test("sends a welcome email", () => {
register("ada@example.com");
expect(sendEmail).toHaveBeenCalled();
});vitest/no-restricted-matchers
Disallow the matchers and modifiers you list
import { expect, test } from "vitest";
test("a new user is active", () => {
const user = { active: true };
expect(user.active).toBeTruthyUse of toBeTruthy is disallowed();
});vitest/no-restricted-vi-methods
Disallow the vi methods you list
import { vi } from "vitest";
vi.mockPass a fake as an argument instead("./mailer");vitest/no-standalone-expect
Disallow expect outside of a test or it block
import { beforeEach, describe, expect } from "vitest";
describe("cart", () => {
const cart = createCart();
expectexpect must be inside of a test block.(cart.items).toHaveLength(0);
beforeEach(() => {
expectexpect must be inside of a test block.(cart.total).toBe(0);
});
});vitest/no-test-prefixesfixable
Require .only and .skip instead of the f and x prefixes
import { expect } from "vitest";
fitUse it.only instead.("adds", () => {
expect(1 + 1).toBe(2);
});
xtestUse test.skip instead.("subtracts", () => {
expect(3 - 1).toBe(2);
});
xdescribeUse describe.skip instead.("division", () => {});vitest/no-test-return-statement
Disallow return expect(..) in a test
import { expect, test } from "vitest";
test("adds the prices", () => {
const total = sum([1, 2]);
returnJest tests should not return a value expect(total).toBe(3);
});vitest/no-unneeded-async-expect-functionfixable
Disallow an async function around a single awaited call in expect
import { expect, test } from "vitest";
test("rejects an empty name", async () => {
await expect(async () => {Unnecessary async function wrapper
await saveUser({ name: "" });
}).rejects.toThrow("name is required");
await expect(async () => await saveUser({ name: "" })Unnecessary async function wrapper).rejects.toThrow();
});vitest/padding-around-after-all-blocksfixable
Require a blank line before each afterAll call
import { afterAll, describe } from "vitest";
describe("database", () => {
const db = connect();
aMissing padding before afterAll blockfterAll(() => db.close());
});vitest/padding-around-test-blocksfixable
Require a blank line before each test and it call
import { describe, expect, test } from "vitest";
describe("cart", () => {
const cart = createCart();
tMissing padding before test blockest("starts empty", () => {
expect(cart.items).toEqual([]);
});
tMissing padding before test blockest.skip("adds an item", () => {
expect(cart.add("apple").items).toEqual(["apple"]);
});
});vitest/prefer-called-exactly-once-withfixable
Require toHaveBeenCalledExactlyOnceWith() over separate call count and argument checks
import { expect, test, vi } from "vitest";
test("saves the draft", () => {
const save = vi.fn();
autosave("draft", save);
expect(save).toHaveBeenCalledOnce()Prefer toHaveBeenCalledExactlyOnceWith over toHaveBeenCalledOnce and toHaveBeenCalledWith on the same target.;
expect(save).toHaveBeenCalledWith("draft");
});vitest/prefer-called-oncefixable
Require toHaveBeenCalledOnce() instead of toHaveBeenCalledTimes(1)
import { expect, test, vi } from "vitest";
test("notifies the listener", () => {
const listener = vi.fn();
publish("ready", listener);
expect(listener).toHaveBeenCalledTimes(1)The use of toBeCalledTimes(1) and toHaveBeenCalledTimes(1) is discouraged.;
});vitest/prefer-called-timesfixable
Require toHaveBeenCalledTimes(1) instead of toHaveBeenCalledOnce()
import { expect, test, vi } from "vitest";
test("does not retry after a success", () => {
const request = vi.fn();
retryFetch(request);
expect(request).toHaveBeenCalledOnce()Use toBeCalledTimes(1) or toHaveBeenCalledTimes(1) instead of toBeCalledOnce() or toHaveBeenCalledOnce();
});vitest/prefer-called-withfixable
Require toHaveBeenCalledWith() instead of toHaveBeenCalled()
import { expect, test, vi } from "vitest";
test("sends a welcome email", () => {
const send = vi.fn();
register("ada@example.com", send);
expect(send).toHaveBeenCalledSuggest using toBeCalledWith() or toHaveBeenCalledWith().();
});vitest/prefer-comparison-matcherfixable
Require matchers such as toBeGreaterThan() instead of comparing inside expect()
import { expect, test } from "vitest";
test("applies the discount", () => {
const price = discounted(100);
expect(price < 100).toBeSuggest using the built-in comparison matchers(true);
expect(price < 0).toBeSuggest using the built-in comparison matchers(false);
});vitest/prefer-describe-function-titlefixable
Require describe(fn, ...) instead of a string with the name of an imported function
import { describe, expect, test } from "vitest";
import { slugify } from "./slugify";
describe("slugify"Title description can not have the same content as a imported function name., () => {
test("replaces spaces", () => {
expect(slugify("a b")).toBe("a-b");
});
});vitest/prefer-each
Require test.each and describe.each instead of a loop around tests
import { expect, test } from "vitest";
const cases = [
[1, 2],
[2, 4],
];
for (const [input, expected] of cases) Enforce using each rather than manual loops{
test(`doubles ${input}`, () => {
expect(input * 2).toBe(expected);
});
}vitest/prefer-equality-matcherhas suggestions
Require toBe or toEqual instead of comparing with === inside expect
import { expect, test } from "vitest";
test("the session belongs to the user", () => {
expect(session.userId === user.id).toBeSuggest using the built-in equality matchers.(true);
expect(user.role !== "admin").toBeSuggest using the built-in equality matchers.(true);
});vitest/prefer-expect-assertionshas suggestions
Require expect.assertions() or expect.hasAssertions() at the start of every test
import { expect, test } from "vitest";
test("rejects a wrong password", async () => {This test should have either expect.assertions(<number of assertions>) or expect.hasAssertions() as its first expression.
try {
await login("ada", "wrong");
} catch (error) {
expect(error).toBeInstanceOf(AuthError);
}
});vitest/prefer-expect-resolvesfixable
Require await expect(promise).resolves instead of expect(await promise)
import { expect, test } from "vitest";
test("loads the user", async () => {
expect(await loadUser(1)Prefer await expect(...).resolves over expect(await ...) syntax.).toEqual({ id: 1, name: "Ada" });
});vitest/prefer-expect-type-offixable
Require toBeTypeOf instead of expect(typeof value).toBe(...)
import { expect, test } from "vitest";
test("the user has a name", () => {
expect(typeof user.name).toBe("string")Type assertions should be done using toBeTypeOf.;
expect(typeof user.save).not.toEqual("undefined")Type assertions should be done using toBeTypeOf.;
});vitest/prefer-hooks-in-order
Require hooks to be written in the order they run
import { afterEach, beforeEach } from "vitest";
afterEach(() => {
db.rollback();
});
beforeEach(() => {Test hooks are not in a consistent order.
db.begin();
});vitest/prefer-hooks-on-top
Require hooks to come before the tests in their block
import { beforeEach, expect, test } from "vitest";
test("starts empty", () => {
expect(cart.items).toHaveLength(0);
});
beforeEach(() => {Suggest having hooks before any test cases.
cart.clear();
});vitest/prefer-import-in-mockfixable
Require import() instead of a string path in vi.mock() and vi.doMock()
import { vi } from "vitest";
vi.mock("./api", async importOriginal => {Mocked modules must be dynamic imported.
const api = await importOriginal();
return { ...api, fetchUser: vi.fn() };
});vitest/prefer-importing-vitest-globalsfixable
Require describe, test, expect and vi to be imported from vitest
describeDo not use Vitest global functions("cart", () => {
test("starts empty", () => {
expect(cart.items).toHaveLength(0);
});
});vitest/prefer-lowercase-titlefixable
Require the titles of tests and describe blocks to start with a lowercase letter
import { expect, test } from "vitest";
test("Adds an item"Enforce lowercase test names, () => {
expect(cart.add(item)).toBe(1);
});vitest/prefer-mock-promise-shorthandfixable
Require mockResolvedValue() and mockRejectedValue() for mocks that return a promise
import { vi } from "vitest";
const fetchUser = vi.fn().mockReturnValuePrefer mock resolved/rejected shorthands for promises.(Promise.resolve({ name: "Ada" }));
const saveUser = vi.fn().mockImplementationOncePrefer mock resolved/rejected shorthands for promises.(() => Promise.reject(new Error("offline")));vitest/prefer-mock-return-shorthandfixable
Require mockReturnValue() for a mock that only returns a value
import { vi } from "vitest";
const getPort = vi.fn().mockImplementationMock functions that return simple values should use mockReturnValue/mockReturnValueOnce.(() => 3000);
const getUser = vi.fn().mockImplementationOnceMock functions that return simple values should use mockReturnValue/mockReturnValueOnce.(() => {
return { name: "Ada" };
});vitest/prefer-snapshot-hint
Require a hint on toMatchSnapshot() and toThrowErrorMatchingSnapshot()
import { expect, test } from "vitest";
test("renders the menu", () => {
expect(renderMenu({ open: false })).toMatchSnapshot()Snapshot is missing a hint.;
expect(renderMenu({ open: true })).toMatchSnapshot()Snapshot is missing a hint.;
});vitest/prefer-spy-onhas suggestions
Require vi.spyOn() instead of assigning vi.fn() to a property
import { beforeEach, vi } from "vitest";
beforeEach(() => {
Date.now = vi.fnSuggest using jest.spyOn() or vi.spyOn().(() => 1700000000000);
console.warn = vi.fnSuggest using jest.spyOn() or vi.spyOn().();
});vitest/prefer-strict-boolean-matchersfixable
Require toBe(true) and toBe(false) instead of toBeTruthy() and toBeFalsy()
import { expect, test } from "vitest";
test("checks the password", () => {
expect(isValid("correct horse")).toBeTruthy()Use toBe(true) instead of toBeTruthy.;
expect(isValid("")).toBeFalsy()Use toBe(false) instead of toBeFalsy.;
});vitest/prefer-strict-equalhas suggestions
Require toStrictEqual() instead of toEqual()
import { expect, test } from "vitest";
test("creates a user", () => {
expect(createUser("Ada")).toEqualSuggest using toStrictEqual().({ name: "Ada" });
});vitest/prefer-to-befixable
Require toBe() for primitive literals, and toBeNull(), toBeUndefined(), toBeNaN()
import { expect, test } from "vitest";
test("parses the header", () => {
expect(header.version).toEqualUse toBe when expecting primitive literals.(2);
expect(header.name).toStrictEqualUse toBe when expecting primitive literals.("config");
expect(header.parent).toBe(null)Use toBeNull instead.;
expect(header.comment).not.toBeDefined()Use toBeUndefined instead.;
expect(header.size).not.toBe(undefined)Use toBeDefined instead.;
expect(header.date).not.toBeUndefined()Use toBeDefined instead.;
expect(header.ratio).toEqual(NaN)Use toBeNaN instead.;
});vitest/prefer-to-be-falsyhas suggestions
Require toBeFalsy() instead of toBe(false)
import { expect, test } from "vitest";
test("a new cart has no discount", () => {
const cart = createCart();
expect(cart.hasDiscount).toBe(false)Use toBeFalsy instead.;
expect(cart.isOpen).not.toEqual(false)Use toBeFalsy instead.;
});vitest/prefer-to-be-objectfixable
Require toBeObject() when expectTypeOf() checks for an object
import { expectTypeOf, test } from "vitest";
test("the config is an object", () => {
const config = loadConfig();
expectTypeOf(config).toBeInstanceOfPrefer toBeObject() for object assertions(Object);
expectTypeOf(config instanceof Object).toBeTruthyPrefer toBeObject() for object assertions();
expectTypeOf(config.name instanceof Object).toBeFalsyPrefer toBeObject() for object assertions();
});vitest/prefer-to-be-truthyhas suggestions
Require toBeTruthy() instead of toBe(true)
import { expect, test } from "vitest";
test("an admin can delete posts", () => {
const user = createUser({ role: "admin" });
expect(user.canDelete).toBe(true)Use toBeTruthy instead.;
expect(user.isBanned).not.toEqual(true)Use toBeTruthy instead.;
});vitest/prefer-to-containfixable
Require toContain() instead of comparing the result of includes() with a boolean
import { expect, test } from "vitest";
test("a draft is tagged", () => {
const tags = getTags(draft);
expect(tags.includes("draft")).toBeSuggest using toContain().(true);
expect(tags.includes("published")).toEqualSuggest using toContain().(false);
});vitest/prefer-to-have-been-called-timesfixable
Require toHaveBeenCalledTimes() instead of checking the length of mock.calls
import { expect, test, vi } from "vitest";
test("saves each item once", () => {
const save = vi.fn();
saveAll(["a", "b"], save);
expect(save.mock.calls).toHaveLength(2)Prefer toHaveBeenCalledTimes() over toHaveLength() when asserting mock call counts;
});vitest/prefer-to-have-lengthfixable
Require toHaveLength() instead of comparing the length property
import { expect, test } from "vitest";
test("finds the active users", () => {
const users = findUsers({ active: true });
expect(users.length).toBeSuggest using toHaveLength().(2);
expect(users[0].roles.length).not.toEqualSuggest using toHaveLength().(0);
});vitest/prefer-todofixable
Require test.todo for tests that have no body yet
import { test } from "vitest";
testSuggest using test.todo.("rejects an expired token", () => {});
testSuggest using test.todo.("refreshes the token");
test.skipSuggest using test.todo.("logs out", function () {});vitest/require-awaited-expect-poll
Require expect.poll and expect.element assertions to be awaited or returned
import { expect, test } from "vitest";
test("shows the saved message", () => {
expect.poll(() => status.text)expect.poll must be awaited or returned.toBe("Saved")expect.poll must be awaited or returned;
expect.element(banner)expect.element must be awaited or returned.toBeVisible()expect.element must be awaited or returned;
});vitest/require-hook
Require setup and teardown code to be inside a hook
import { describe, expect, test } from "vitest";
let db = connect();Require setup and teardown code to be within a hook.
db.seed(["ada", "grace"])Require setup and teardown code to be within a hook.;
describe("users", () => {
clearCache()Require setup and teardown code to be within a hook.;
test("finds a user", () => {
expect(db.find("ada")).toBeDefined();
});
});vitest/require-local-test-context-for-concurrent-snapshots
Require the expect of the test context for snapshots in concurrent tests
import { describe, expect, test } from "vitest";
test.concurrent("renders the header", () => {
expect(render("header")).toMatchSnapshot()Require local Test Context for concurrent snapshot tests;
});
describe.concurrent("footer", () => {
test("renders the links", () => {
expect(render("links")).toMatchSnapshot()Require local Test Context for concurrent snapshot tests;
});
});vitest/require-mock-type-parameters
Require a type parameter on vi.fn()
import { vi } from "vitest";
const findUser = vi.fnMissing type parameters on mock function call();
const log = vi.fnMissing type parameters on mock function call((message: string) => {});vitest/require-test-timeout
Require every test to set a timeout
import { expect, test } from "vitest";
test("loads the report", async () => {Test is missing a timeout.
expect(await loadReport()).toBeDefined();
});vitest/require-to-throw-message
Require toThrow() to say which error it expects
import { expect, test } from "vitest";
test("rejects a negative amount", async () => {
expect(() => withdraw(-5)).toThrowRequire a message for toThrow.();
expect(() => withdraw(-5)).toThrowErrorRequire a message for toThrowError.();
await expect(transfer(-5)).rejects.toThrowRequire a message for toThrow.();
});vitest/require-top-level-describe
Require tests and hooks to be inside a describe block
import { beforeEach, expect, test } from "vitest";
beforeEach(() => {Require test cases and hooks to be inside a describe block
cart.clear();
});
test("starts empty", () => {Require test cases and hooks to be inside a describe block
expect(cart.size).toBe(0);
});vitest/valid-describe-callback
Require describe to get a title and a callback with no parameters and no return
import { describe, test } from "vitest";
describe("cart"Describe requires name and callback arguments); // no callback
describe("cart", "adds an item"Second argument must be a function); // not a function
describe("cart", (done) => {Unexpected argument(s) in describe callback
test("adds an item", () => {});
done();
});
describe("cart", () => {
return test("adds an item", () => {});Unexpected return statement in describe callback
});
describe("cart", () => test("adds an item", () => {})Unexpected return statement in describe callback); // returns the result of `test()`vitest/valid-expecthas suggestions
Require expect to get a value, end in a called matcher, and be awaited when it is async
import { expect, test } from "vitest";
test("adds the tax", () => {
expect(total(100))Expect must have a corresponding matcher call.; // no matcher
expect(total(100)).toBeMatchers must be called to assert.; // the matcher is not called
expect()Expect requires at least 1 argument.toBe(108); // no value
expect(total(100)).never.toBe(0)Expect has an unknown modifier.; // `never` is not a modifier
});vitest/valid-expect-in-promise
Require a promise chain that contains expect to be awaited or returned from the test
import { expect, test } from "vitest";
test("loads the user", () => {
loadUser(1).then((user) => {Expect in a promise chain must be awaited or returned
expect(user.name).toBe("Ada");
});
});vitest/valid-titlefixable
Require test and describe titles to be non-empty strings with no stray spaces or repeated prefix
import { describe, test } from "vitest";
describe("describe cart"Should not have duplicate prefix, () => {
test(""Should not have an empty title, () => {});
test(" adds an item"Should not have leading or trailing spaces, () => {});
test("test removes an item"Should not have duplicate prefix, () => {});
test(404Title must be a string, () => {});
});vitest/warn-todo
Disallow .todo on test, it and describe
import { describe, test } from "vitest";
test.todoThe use of .todo is not recommended.("refunds the order");
describe.todoThe use of .todo is not recommended.("checkout");Prettier 1 rule
prettier/prettierfixable
Require code to be formatted the way Prettier prints it
const user = {name:'Ada'}Replace name:'Ada'} with ·name:·"Ada"·};