Variables & names
This page discusses rules around variable declaration, use of properties, and naming.
Variable declarations
block-scoped-var
- Severity: error
Because var
s are forbidden altogether, this rule is mostly moot. In the rare case where you need to use var
(such as to declare globals), such vars should not be deceptively inside a block.
init-declarations
- Severity: error
- Disabled by the typescript config
- Configuration:
- Require variables to be initialized (
"always"
)
- Require variables to be initialized (
- Related:
@typescript-eslint/init-declarations
ts(2454): Variable 'a' is used before being assigned.
We require variables to be initialized. Otherwise, it's possible to circumvent TypeScript:
ts
leta : number;functionuseA () {console .log (a ); // -> undefined}useA ();a = 1;
ts
leta : number;functionuseA () {console .log (a ); // -> undefined}useA ();a = 1;
You should almost always initialize variables upfront (and use const
where possible). Use ternaries instead of if...else
. If you need to lazy initialize a variable, initialize it to undefined
, so that you remember to explicitly check for undefined
before using it.
no-const-assign
- Severity: error
- Related:
ts(2588): Cannot assign to 'a' because it is a constant.
Re-assigning const variables causes a runtime error.
ts
consta = 1;Cannot assign to 'a' because it is a constant.2588Cannot assign to 'a' because it is a constant.= 2; // -> TypeError: Assignment to constant variable. a
ts
consta = 1;Cannot assign to 'a' because it is a constant.2588Cannot assign to 'a' because it is a constant.= 2; // -> TypeError: Assignment to constant variable. a
no-implicit-globals
- Severity: error
- Configuration:
- Disallow global lexical declarations too (
lexicalBindings: true
)
- Disallow global lexical declarations too (
We have forbidden using var
. let
and const
at the top level also behave weirdly due to TDZ. You should probably be modularizing your code anyway.
no-redeclare
- Severity: error
- Configuration:
- Check redeclaration of globals (
builtinGlobals: false
)
- Check redeclaration of globals (
- Related:
Do not redeclare var
/function
. This is probably a mistake. Note that let
/const
cannot be redeclared and doing so is a syntax error in the first place.
no-shadow
- Severity: warning
- Configuration:
- Ignore shadowing of globals (
builtinGlobals: true
) - Check shadowing of all variables declared in the outer scope (
hoist: "all"
) - Allow shadowing of uninitialized variables (
ignoreOnInitialization: true
)
- Ignore shadowing of globals (
We avoid shadowing because doing so is a refactoring hazard.
ts
function foo(x) {doSomething((x) => {console.log(x); // What is this x meant to be?// If I change the parameter name, should I change this too?});}
ts
function foo(x) {doSomething((x) => {console.log(x); // What is this x meant to be?// If I change the parameter name, should I change this too?});}
The issue isn't better because the variable is only declared afterwards.
ts
function foo() {doSomething((x) => {console.log(x); // What is this x meant to be?// If I change the parameter name, should I change this too?});// If I move this declaration before doSomething(), there// shouldn't be a differenceconst x = 1;}
ts
function foo() {doSomething((x) => {console.log(x); // What is this x meant to be?// If I change the parameter name, should I change this too?});// If I move this declaration before doSomething(), there// shouldn't be a differenceconst x = 1;}
However, shadowing is allowed when the variable is initialized later. The following pattern is encouraged:
ts
const x = (() => {let x = 0;// ...return x;})();
ts
const x = (() => {let x = 0;// ...return x;})();
We allow shadowing globals—this is for a pragmatic concern. There are some extremely generically named globals like name
and Plugin
which we don't want to prevented from being used as local variables. However, you should probably avoid using names like fetch
.
no-var
We disallow var
statements. var
is fully predated by let
/const
and its hoisting behavior makes code harder to debug. There's not a single reason to use var
today. If you need to share one variable between two blocks, declare it in the upper scope. If you need to declare a global variable (which you probably shouldn't anyway), directly modify globalThis
(which also works in modules).
ts
declare varglobalVar : number;globalThis .globalVar = 1;
ts
declare varglobalVar : number;globalThis .globalVar = 1;
no-undef-init
- Severity: off
We require variables to be initialized (through init-declarations
). In case there's no reasonable default value, you should use explicit undefined
.
no-unused-vars
- Severity: error
- Configuration:
- Check unused trailing function parameters (
args: "after-used"
) - Check unused caught errors (
caughtErrors: "all"
) - Ignore unused variables with rest element (
ignoreRestSiblings: true
) - Check unused variables in the top-level scope (
vars: "all"
)
- Check unused trailing function parameters (
- Related:
Unused variables are a sign of refactoring artifact and should be removed as early as possible. However, there are the following exceptions:
tsx
// Satisfying a type signatureconst plugin: Plugin = (ast, options) => {// Only use options, but ast has to be declared};// Removing properties from objectsfunction Component(props: Props) {const { someProp, ...rest } = props;return <div {...rest} />;}
tsx
// Satisfying a type signatureconst plugin: Plugin = (ast, options) => {// Only use options, but ast has to be declared};// Removing properties from objectsfunction Component(props: Props) {const { someProp, ...rest } = props;return <div {...rest} />;}
If you have an unused error variable, omit the catch binding.
ts
try {// ...} catch {console.error("Failed");}
ts
try {// ...} catch {console.error("Failed");}
no-use-before-define
- Severity: warning
- Configuration:
- Allow export declarations before declarations (
allowNamedExports: false
) - Check class declarations (
classes: true
) - Allow function declarations to be hoisted (
functions: true
) - Check variable declarations (
variables: true
)
- Allow export declarations before declarations (
You should generally avoid using variables before they are declared, as doing so leads to an error. For functions, you are free to let them get hoisted. In fact, we recommend the following style:
ts
function doSomething() {doA();doB();doC();function doA() {}function doB() {}function doC() {}}
ts
function doSomething() {doA();doB();doC();function doA() {}function doB() {}function doC() {}}
This rule has known false negatives:
ts
function foo() {console.log(x);}foo(); // Should not workconst x = 1;
ts
function foo() {console.log(x);}foo(); // Should not workconst x = 1;
no-useless-rename
- Severity: error
Don't rename a variable to the same name in import, export, and destructuring.
one-var
- Severity: off
Generally, you should put each variable declaration on its own line. However, when it makes sense (for example, multiple variables used for very similar purposes: let start = 0, end = 0;
), you are free to declare multiple variables consecutively.
prefer-const
- Severity: error
- Configuration:
- Require
const
as long as any of the destructured variables should beconst
(destructuring: "any"
) - Do not ignore variables that are only assigned once and read before assignment (
ignoreReadBeforeAssign: false
)
- Require
Only use let
when the variable is actually reassigned. Otherwise, use const
, which makes TypeScript infer narrower types, and makes the type of each variable easier to trace.
In destructuring, we require using const
when any of the variables should be const
. Otherwise, this may lead to spillover writability. If you want to make some of the variables let
, you should destructure them separately.
ts
const result = doSomething();const { a, b } = result;let { c, d } = result;
ts
const result = doSomething();const { a, b } = result;let { c, d } = result;
prefer-destructuring
- Severity: error
- Configuration:
- Require destructuring for arrays (
array: true
) - Require destructuring for objects (
object: true
) - Do not require destructuring when the variable is renamed (
enforceForRenamedProperties: false
)
- Require destructuring for arrays (
Destructuring is generally preferred over accessing properties directly. It makes the code more concise and easier to read. There are some catches:
- When you are accessing a high array index (for example,
const char = str[5]
), you may not want to use destructuring likeconst [, , , , , char] = str
. Disable the rule in this case. - In performance-critical cases, array destructuring is slower than property access.
const { 0: x } = a
may be faster thanconst [x] = a
. This does not matter in general. - Not all index accesses can be safely refactored to array destructuring, unless the object is also iterable. You should use your own discretion when fixing the error.
vars-on-top
- Severity: error
We don't usually allow var
s. When you do use them, put them at the top level of functions/scripts to minimize its quirks.
Naming conventions
camelcase
- Severity: error
- Configuration:
- Require destructured variables to be camelCase (
ignoreDestructuring: false
) - Require global variables to be camelCase (
ignoreGlobals: false
) - Require imported variables to be camelCase (
ignoreImports: false
) - Ignore property names in object literals (
properties: "never"
)
- Require destructured variables to be camelCase (
Until we fully use @typescript-eslint/naming-convention
, we will still use this rule to enforce camelCase where possible. We don't check object properties because the object may be passed to a third-party library:
ts
checkESLint({config: {camel_case: true,},});
ts
checkESLint({config: {camel_case: true,},});
id-denylist
- Severity: off
You may want to configure this yourself if you want to ban certain identifiers.
id-length
- Severity: off
We don't think length is a good metric for name quality.
id-match
- Severity: off
This rule is fully covered by @typescript-eslint/naming-convention
.
no-shadow-restricted-names
- Severity: error
Don't declare a binding called undefined
, NaN
, Infinity
, eval
, or arguments
. You know exactly what values they represent.
no-underscore-dangle
- Severity: off
You should generally avoid underscored names and prefer proper encapsulation instead (such as through closures and private names). Do not use underscores to represent throwaway names; just leave it unused (such cases include object destructuring to throw the property away, or function parameters). However, there are cases where you have to use underscores, such as when the name is expected by an external API.
Globals
no-alert
- Severity: error
There is no good reason to use alert
/confirm
/prompt
in production. They are blocking and look too much like system dialogs.
no-console
- Severity: can be enabled
console.log
is commonly left as debugging artifacts and can occasionally disrupt the console log formatting. For example, Webpack has the unified logger interface for emitting messages. Projects are encouraged to encapsulate their own logger instance as well for unified message formatting and semantics.
However, in more casual projects without a wrapped logger, using console.log
may be intentional. This rule can be overridden in user-land.
no-eval
- Severity: error
- Configuration:
- Do not allow indirect
eval
(allowIndirect: false
)
- Do not allow indirect
There is not much reason you should use eval
—many safe alternatives exist. Indirect eval
in strict mode tends to be safe but is still frowned upon. In case you really need to dynamically evaluate code, use new Function
instead, which also allows injecting variables via parameters.
no-global-assign
- Severity: off
We have to turn this rule off, because we cannot make ESLint aware of every global, so the reports are too inconsistent and unhelpful. However, you should know from your heart to only reassign variables in your scope. If you want to modify globals, use globalThis.x
instead to make your intention explicit.
no-implied-eval
- Severity: error
There is no good reason to use setTimeout
/setInterval
with a string argument. Use a function instead.
no-iterator
- Severity: error
Don't use the __iterator__
property. No one implements it.
no-new-func
- Severity: error
You should generally avoid using the Function
constructor, because it is just another form of dynamic evaluation. However, compared to eval
, it is easier to be used safely, and in case when you need to dynamically evaluate code, you should prefer to use this instead of eval
.
no-new-native-nonconstructor
- Severity: error
- Related:
ts(7009): 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.
Don't construct Symbol
and BigInt
because they are not meant for construction.
no-new-wrappers
- Severity: error
Don't construct String
, Number
, and Boolean
objects because they are much harder to use and do not have any benefits over primitives.
no-obj-calls
- Severity: error
- Related:
ts(2349): This expression is not callable. Type 'Math' has no call signatures.
Don't call Math
, JSON
, and other namespaces.
no-object-constructor
- Severity: error
Don't use new Object()
because it's just a longer way of writing {}
. Always use Object
with an argument.
no-proto
- Severity: error
Don't access the __proto__
property. Use Object.getPrototypeOf
and Object.setPrototypeOf
instead. Note that the __proto__
syntax in object literals is still allowed and should be preferred over Object.create
.
ts
// Write this:const obj = {__proto__: null,};// Instead of this:const obj = Object.create(null);
ts
// Write this:const obj = {__proto__: null,};// Instead of this:const obj = Object.create(null);
TypeScript does not support the __proto__
syntax in object literals yet. However, Object.create
will be always typed as any
, so the former should still be preferred. Cast with null as never
when necessary.
no-prototype-builtins
- Severity: error
Don't use Object.prototype
methods because they are not safe against null
and undefined
. Generally, you don't need to jump hoops to get similar behavior.
ts
// Instead of:foo.hasOwnProperty("bar");foo.propertyIsEnumerable("bar");foo.isPrototypeOf(bar);// Write:Object.hasOwn(foo, "bar");Object.getOwnPropertyDescriptor(foo, "bar")?.enumerable;Object.prototype.isPrototypeOf.call(foo, bar); // Do you really need this?
ts
// Instead of:foo.hasOwnProperty("bar");foo.propertyIsEnumerable("bar");foo.isPrototypeOf(bar);// Write:Object.hasOwn(foo, "bar");Object.getOwnPropertyDescriptor(foo, "bar")?.enumerable;Object.prototype.isPrototypeOf.call(foo, bar); // Do you really need this?
no-restricted-properties
- Severity: off
You may want to configure this yourself if you want to ban certain identifiers or certain properties.
no-undef
- Severity: error
- Configuration:
- Disallow using
typeof
on undefined variables (typeof: true
)
- Disallow using
- Related:
ts(2304): Cannot find name 'a'.
Don't use undefined variables. If you want to check if a variable is defined, use if ("x" in globalThis)
or if (typeof globalThis.x !== "undefined")
.
Note that this rule is only useful in a plain-JS project. In a TypeScript project, you should use TypeScript checks instead. You may find cases where ESLint is unaware of a global variable. In this case, either change your env
setting, or use globalThis.x
.
no-undefined
- Severity: off
There's virtually no risk to use undefined
nowadays, especially with rules like no-shadow-restricted-names
. Furthermore, because undefined
is so pervasive as the implicit "value of absence", it's hard to avoid it. You should generally use undefined
instead of null
as the default value, unless the latter has a semantic difference from undefined
.
prefer-object-has-own
- Severity: error
Use Object.hasOwn
instead of Object.prototype.hasOwnProperty.call
. It's shorter and more readable. If you need compatibility, install a polyfill. (You should never use x.hasOwnProperty
, by the way; see no-prototype-builtins
.)