TypeScript
Types
array-type
- Severity: error
- Configuration:
- Require
T[]
syntax (default: "array"
)
- Require
We require T[]
syntax for arrays. This is a matter of consistency. Even for very, very complex types, we still require putting []
at the end, instead of using Array<>
. If the rule provides more customization, such as allowing (T | U)[]
, we may reconsider.
ban-types
- Severity: error
- Configuration:
- Extend defaults (
extendDefaults: true
) - Unban
{}
(types: { "{}": false }
)
- Extend defaults (
The types listed in the default options are either dangerous or misleading. The only exception is {}
, which in our eyes is safe to use. It may not behave as one expects, but it doesn't hinder type safety. It is also used by TypeScript in utility types.
consistent-indexed-object-style
- Severity: error
- Configuration:
- Require
{ [key: string]: T }
syntax ("index-signature"
)
- Require
Always use the index signature syntax. It's not much longer:
ts
type A = { [x: string]: T };type B = Record<string, T>;
ts
type A = { [x: string]: T };type B = Record<string, T>;
But it allows annotating the semantics of the key. Always use a descriptive name, such as { [url: string]: JSX.Element }
.
method-signature-style
- Severity: error
- Configuration:
- Require
foo: () => T
syntax ("property"
)
- Require
The property syntax is strictly more type-safe. See the rule docs for more information. One exception allowed is when adding types for library methods, such as when polyfilling. This is because the lib declarations use methods anyway.
no-explicit-any
- Severity: warning
Never use any
. If you trace the assignability of your types carefully, you should always be able to find a suitable type. Only able to use any
is a sign of overcomplicated API design. It is only set to warning because we acknowledge the necessity of any
, such as when indexing an unknown object.
no-invalid-void-type
- Severity: warning
- Configuration:
- Do not allow
void
asthis
(allowAsThisParameter: false
) - Allow
void
in generic type arguments (allowInGenericTypeArguments: true
)
- Do not allow
Only use void
as the return type of functions. In other places, void
behaves like unknown
and is not type safe. We allow void
in generic type arguments because it is useful for Promise<void>
. However, do not write this: void
—if you don't care about the this
value, just omit the this
parameter.
no-misused-new
- Severity: error
Do not declare a method called new
in a class. Do not declare a method called constructor
in an interface. This is probably a mistake and you meant to declare a constructor in both cases.
prefer-function-type
- Severity: error
Use type Foo = () => T
instead of interface Foo { (): T }
. The former is more readable while the latter may be simply due to missing a property name. If you want to make an augmentable callable type, use a disable comment.
ts
interface Plugin {(ast: AST): AST;}// User codeinterface Plugin {extension1: MyExtension;}
ts
interface Plugin {(ast: AST): AST;}// User codeinterface Plugin {extension1: MyExtension;}
Type/value declarations
consistent-type-definitions
- Severity: off
We don't mind whether interface
or type
is used. Generally:
- Use
interface
for public APIs that are extendable so that users may be able to declaration-merge. - Use
interface
for large object types. - Use
type
for convenience types that are small.
init-declarations
- Severity: error
- Configuration:
- Require variables to be initialized (
"always"
)
- Require variables to be initialized (
- Related:
See the base rule for more information. This extension rule prevents false positives for TypeScript-specific syntax so it is safe to use in JavaScript files too.
no-empty-interface
- Severity: error
- Configuration:
- Allow extended interfaces to be empty (
allowSingleExtends: true
)
- Allow extended interfaces to be empty (
Empty interfaces are a form of {}
, which match any non-nullish type. However, we allow interface X extends Y {}
as a way to alias interfaces, because then we can declaration-merge X
.
no-redeclare
- Severity: error
- Configuration:
- Allow declaration merging (
ignoreDeclarationMerge: true
)
- Allow declaration merging (
- Related:
This reduces the errors reported by the base rule. It is safe to use in JavaScript files because it does not change behaviors outside of TypeScript.
We allow "declaration merging" between variables and types:
ts
const User = z.object({});type User = z.infer<typeof User>;
ts
const User = z.object({});type User = z.infer<typeof User>;
If the rule reports for this case and it really makes sense, you can disable the rule for that line. (typescript-eslint/typescript-eslint#6441)
no-shadow
- Severity: warning
- Configuration:
- Ignore type declarations and value declarations shadowing each other (
ignoreTypeValueShadowing: true
) - Do not allow function type parameter name to shadow another variable (
ignoreFunctionTypeParameterNameValueShadow: false
)
- Ignore type declarations and value declarations shadowing each other (
We allow type declarations and value declarations to shadow each other. This is because they are in different namespaces and you always access each one with a different syntax. However, a function parameter name always lives in value space even when it belongs in a type, so we don't allow it to shadow another variable.
ts
const test = 1;type Func = (test: string) => typeof test; // What is `test` here?
ts
const test = 1;type Func = (test: string) => typeof test; // What is `test` here?
This rule is safe to use in JavaScript files because it does not change behaviors outside of TypeScript.
no-unused-vars
- Severity: error
- Related:
See the base rule for more information. This extension rule adds support for TypeScript-specific syntax so it is safe to use in JavaScript files too.
no-use-before-define
- Severity: warning
- Configuration:
- Check use-before-define enums (
enums: true
) - Allow referencing anything in type space (
ignoreTypeReferences: true
) - Allow all type declarations to be hoisted (
typedefs: false
)
- Check use-before-define enums (
See the base rule for more information. Because types behave like functions, they are safe to be referenced everywhere. However, when enums are used as values, they are not hoisted. This is also checked by TypeScript.
Functions
adjacent-overload-signatures
- Severity: error
We require overload signatures to be adjacent to each other. This makes the signatures of functions easier to read. Note that the rule has a known bug: typescript-eslint/typescript-eslint#4576.
default-param-last
- Severity: error
- Disabled in JavaScript
- Related:
We disable the base rule but enable the TypeScript version. In TypeScript files, we require default parameters to be at the end of the parameter list. This is because required parameters must be passed values, so optional parameters before them have no effect and should just have | undefined
in their types. In JavaScript, we don't require this because it is not possible to indicate optionality except through default values.
no-invalid-this
- Severity: off
- Related:
Even in TypeScript files, we don't need this rule, because this rule falls into a dilemma:
- In TypeScript files, its utility clashes exactly with the TypeScript compiler if one adds the
this
parameter anyway; - In JavaScript files, it does not prevent any additional false positives.
unified-signatures
- Severity: warning
- Configuration:
- Do not allow differently named parameters as distinct overloads (
ignoreDifferentlyNamedParameters: false
)
- Do not allow differently named parameters as distinct overloads (
You should only use overloads to associate input with output, or associate multiple inputs. Combine signatures as much as possible, because doing so allows passing a union.
ts
functiondoSomething (id : number): void;functiondoSomething (ids : number[]): void;functiondoSomething (ids : number | number[]): void {// ...}declare constmaybeIds : number | number[];No overload matches this call. Overload 1 of 2, '(id: number): void', gave the following error. Argument of type 'number | number[]' is not assignable to parameter of type 'number'. Type 'number[]' is not assignable to type 'number'. Overload 2 of 2, '(ids: number[]): void', gave the following error. Argument of type 'number | number[]' is not assignable to parameter of type 'number[]'. Type 'number' is not assignable to type 'number[]'.2769No overload matches this call. Overload 1 of 2, '(id: number): void', gave the following error. Argument of type 'number | number[]' is not assignable to parameter of type 'number'. Type 'number[]' is not assignable to type 'number'. Overload 2 of 2, '(ids: number[]): void', gave the following error. Argument of type 'number | number[]' is not assignable to parameter of type 'number[]'. Type 'number' is not assignable to type 'number[]'.doSomething (); // Error maybeIds
ts
functiondoSomething (id : number): void;functiondoSomething (ids : number[]): void;functiondoSomething (ids : number | number[]): void {// ...}declare constmaybeIds : number | number[];No overload matches this call. Overload 1 of 2, '(id: number): void', gave the following error. Argument of type 'number | number[]' is not assignable to parameter of type 'number'. Type 'number[]' is not assignable to type 'number'. Overload 2 of 2, '(ids: number[]): void', gave the following error. Argument of type 'number | number[]' is not assignable to parameter of type 'number[]'. Type 'number' is not assignable to type 'number[]'.2769No overload matches this call. Overload 1 of 2, '(id: number): void', gave the following error. Argument of type 'number | number[]' is not assignable to parameter of type 'number'. Type 'number[]' is not assignable to type 'number'. Overload 2 of 2, '(ids: number[]): void', gave the following error. Argument of type 'number | number[]' is not assignable to parameter of type 'number[]'. Type 'number' is not assignable to type 'number[]'.doSomething (); // Error maybeIds
ts
functiondoSomething (ids : number | number[]): void {// ...}declare constmaybeIds : number | number[];doSomething (maybeIds ); // Error
ts
functiondoSomething (ids : number | number[]): void {// ...}declare constmaybeIds : number | number[];doSomething (maybeIds ); // Error
In case the two overloads have very distinct semantics, name the parameter as lengthOrElement
instead of creating two overloads with different names.
Generics
consistent-generic-constructors
- Severity: error
- Configuration:
- Require
new Foo<T>()
syntax ("constructor"
)
- Require
Use const foo = new Foo<T>()
instead of const foo: Foo<T> = new Foo()
. It is shorter and less prone to refactoring.
no-array-constructor
- Severity: off
- Related:
We never allow the Array
constructor. Even in TypeScript, use [] as T[]
instead of Array<T>()
.
no-unnecessary-type-constraint
- Severity: error
Do not write T extends unknown
or T extends any
. They are completely redundant as of the latest TS version (T extends unknown
may not be in older versions when generic types were non-nullable by default).
no-unused-expressions
- Severity: error
- Related:
See the base rule for more information. This extension rule is useful for TypeScript because it adds support for instantiation expressions. It is safe to use in JavaScript files because it does not change behaviors outside of TypeScript.
ts
foo<T>;
ts
foo<T>;
Classes
class-literal-property-style
- Severity: warning
- Configuration:
- Require using getters for readonly fields (
"getters"
)
- Require using getters for readonly fields (
We require using getters for readonly fields.
readonly
only provides safety in TypeScript but does not interface well with JavaScript.readonly
cannot be declared in JavaScript, so enforcing"fields"
may lead to unfixable errors in JavaScript.- Getters can be overridden in subclasses while fields may accidentally override subclass getters.
Note that our accessor-pairs
rule requires every getter to have an accompanying setter. You may want to disable this in TypeScript to achieve compile-time readonliness.
explicit-member-accessibility
- Severity: off
Never annotate member accessibility. If you want private members, use #
instead of private
. Public fields are allowed and not discouraged or unfavored over accessors.
no-dupe-class-members
- Severity: error
- Related:
This rule overrides the base rule to allow overloads. It is safe to use in JavaScript files because it does not change behaviors outside of TypeScript.
no-extraneous-class
- Severity: warning
- Configuration:
- Do not allow classes with only constructors (
allowConstructorOnly: true
) - Do not allow empty classes (
allowEmpty true
) - Do not allow classes with only static members (
allowStaticOnly: true
) - Do not allow classes with decorators (
allowWithDecorator: true
)
- Do not allow classes with only constructors (
You should not use classes as namespaces. All classes should have at least one method, because if it only contains fields, a factory function is probably better. Also consider whether the style of utility functions + data is better than a class.
Note that in JavaScript you may have false positives:
js
class A {constructor() {this.a = 1;}}
js
class A {constructor() {this.a = 1;}}
You should not do this in JavaScript. Add a field declaration to make the class statically analyzable—this helps with performance by preventing unnecessary hidden class transitions.
js
class A {a;constructor() {this.a = 1;}}
js
class A {a;constructor() {this.a = 1;}}
parameter-properties
- Severity: warning
- Configuration:
- Always use class properties (
prefer: "class-property"
) - Allow no exceptions (
allow: []
)
- Always use class properties (
Don't use parameter properties because they don't save many characters but are non-standard. Different TypeScript versions may change the instantiation order and subtly break your code.
ts
class A {b = this.a; // Will this work?constructor(public a: number) {}}class B {a;b;constructor(a: number) {this.a = a;this.b = a; // This will work}}
ts
class A {b = this.a; // Will this work?constructor(public a: number) {}}class B {a;b;constructor(a: number) {this.a = a;this.b = a; // This will work}}
Enums
prefer-enum-initializers
- Severity: error
Always use initializers for enum members. Without initializers, you may subtly break consumers' code if they rely on the exact value of each member.
diff
enum Rights {+ NONE,USER,ADMIN,}if (user.right === 1) {// Before: user rights// Now: no rights}
diff
enum Rights {+ NONE,USER,ADMIN,}if (user.right === 1) {// Before: user rights// Now: no rights}
prefer-literal-enum-member
- Severity: warning
- Configuration:
- Allow bitwise enum members (
allowBitwiseExpressions: true
)
- Allow bitwise enum members (
Using non-literal members is confusing because people may not expect enums to create naming scopes. If you want to declare enums depending on other variables, consider a namespace instead. We allow bitwise enum members because they are useful for flags.
ts
enum Flags {A = 1 << 0,B = 1 << 1,C = 1 << 2,}
ts
enum Flags {A = 1 << 0,B = 1 << 1,C = 1 << 2,}
Namespaces
no-namespace
- Severity: off
We don't forbid namespaces. We think they enable interesting use cases such as aggregating types and values. However, we do not recommend using them as a way to organize code. Use ES modules instead. You should only use them if the namespace's name itself is part of the API.
However, if you use namespaces, don't use the module
keyword. This is enforced by the prefer-namespace-keyword
rule.
prefer-namespace-keyword
- Severity: error
Always use namespace
instead of module
. module
declarations are soon to be a JavaScript feature and will cause compatibility issues.
Imports
consistent-type-imports
- Severity: error
- Configuration:
- Allow
: import(...)
style (disallowTypeAnnotations: false
) - Fix to inline type imports (
fixStyle: "inline-type-imports"
) - Require
import type
syntax (prefer: "type-imports"
)
- Allow
Use import type
to clearly signal that the import is only used for type information. This is useful for import elision. We fix to inline type imports to avoid separate statements, and we always use non-legacy TypeScript versions. If all imports are fixed to inline type imports, no-import-type-side-effects
will fix this to import type
automatically.
We allow : import(...)
style type annotations. This is useful in an ambient context where import statements are not allowed.
no-import-type-side-effects
- Severity: error
Do not write import { type X } from "..."
. Instead, always use import type
if only types are imported from the module. Note that this may spontaneously arise from auto-fixing consistent-type-imports
due to our use of fixStyle: "inline-type-imports"
.
no-require-imports
- Severity: error
- Disabled in JavaScript
Do not use require
in TypeScript because if you use const x = require(...)
then there will be no types. import x = require(...)
is fully obscure with esModuleInterop
. However, you may use require
in JavaScript if you are authoring CommonJS.
no-var-requires
- Severity: error
- Disabled in JavaScript
Same as above, you should not use require
in TypeScript.
Exports
no-useless-empty-export
- Severity: error
Do not export an empty list unless it helps to make the file a module. If the file is a .ts
file (not a declaration) and it contains other imports/exports, then this export {};
statement is not necessary.
Type annotations
explicit-function-return-type
- Severity: off
We don't require explicit return types for functions. Let TypeScript infer as much as possible to reduce verboseness. We do require explicit module boundary types, though.
explicit-module-boundary-types
- Severity: warning
- Disabled in JavaScript
We require explicit module boundary types. This makes sure that any module boundary is type safe. Although 90% of the time this "boundary" is actually internal, it still helps us to prevent any accidental public API changes.
typedef
- Severity: off
This rule is too strict and does not allow type inference. Let TypeScript infer types where it can, and only add types for consistency, clarity, or narrower types. Always compile with noImplicitAny
.
Type assertions
consistent-type-assertions
- Severity: error
- Configuration:
- Require
as T
syntax (assertionStyle: "as"
) - Allow object literal values to have asserted type anywhere (
objectLiteralTypeAssertions: "allow"
)
- Require
Use as T
instead of <T>
. The latter is soft-deprecated because it does not work in JSX. The former is also easier to read and easier to search for.
We allow object literals to be asserted everywhere. We don't think there's any difference between const x = {} as T
and const x = y as T
, so if the latter is allowed, so should the former.
However, remember use as T
only when you are sure that the type is correct. It is not type safe and can lead to runtime errors.
no-confusing-non-null-assertion
- Severity: error
You should never use !
in a comparison, because it does not change the comparison outcome or type checking.
no-extra-non-null-assertion
- Severity: error
Never use !!
because it is redundant. We don't forbid !
so there's no point in screaming "this is really non-null!!"
no-non-null-asserted-nullish-coalescing
- Severity: error
Do not use non-null assertions on a nullish coalescing LHS. This defeats the purpose.
no-non-null-asserted-optional-chain
- Severity: error
Do not use non-null assertions at the end of an optional chain. Sometimes this is necessary, for example, when passing a value to a function that actually handles undefined
but does not declare it in the type, but in most cases, it is not necessary and doing so masks potential bugs.
no-non-null-assertion
- Severity: off
There are a significant number of places where non-null assertions are inevitable:
Weak built-in types
Some TypeScript lib types are not accurate. One common example is regular expressions. The discussion is in microsoft/TypeScript#32098.
ts
constObject is possibly 'undefined'.2532Object is possibly 'undefined'.author =commit .match (/(?<name>.+),(?<time>\d+)/)?.groups .name ;// But I know that `name` always exists in `groups`!
ts
constObject is possibly 'undefined'.2532Object is possibly 'undefined'.author =commit .match (/(?<name>.+),(?<time>\d+)/)?.groups .name ;// But I know that `name` always exists in `groups`!
Control-flow analysis failure
Control-flow analysis is not preserved across callback boundaries, because TypeScript cannot predict when a callback will be called. See microsoft/TypeScript#9998
ts
import {useRef } from "react";functionuseDemo () {constref =useRef <(a : number) => void>(null);if (ref .current ) {// Do other stuff...[1, 2, 3].Cannot invoke an object which is possibly 'null'.2721Cannot invoke an object which is possibly 'null'.forEach ((a ) =>ref .current (a ));// But I just checked `ref.current`!}}
ts
import {useRef } from "react";functionuseDemo () {constref =useRef <(a : number) => void>(null);if (ref .current ) {// Do other stuff...[1, 2, 3].Cannot invoke an object which is possibly 'null'.2721Cannot invoke an object which is possibly 'null'.forEach ((a ) =>ref .current (a ));// But I just checked `ref.current`!}}
Clearer developer intention
Sometimes, you are really sure that a value exists, but TypeScript doesn't believe you.
ts
constmanagers = [{name : "npm",lockFile : "package-lock.json" },{name : "yarn",lockFile : "yarn.lock" },{name : "pnpm",lockFile : "pnpm-lock.yaml" },] asconst ;constObject is possibly 'undefined'.2532Object is possibly 'undefined'.lockFile =managers .find ((obj ) =>obj .name === "npm").lockFile ;// It absolutely exists, but making it strongly typed is unnecessary trouble
ts
constmanagers = [{name : "npm",lockFile : "package-lock.json" },{name : "yarn",lockFile : "yarn.lock" },{name : "pnpm",lockFile : "pnpm-lock.yaml" },] asconst ;constObject is possibly 'undefined'.2532Object is possibly 'undefined'.lockFile =managers .find ((obj ) =>obj .name === "npm").lockFile ;// It absolutely exists, but making it strongly typed is unnecessary trouble
This is also related to the previous two points in many cases, but in other cases where TypeScript cannot ensure existence with control-flow analysis, we need to convey developer intent through allowing undefined
or not.
prefer-as-const
- Severity: error
Use x as const
instead of x as x
when x
is a literal. This is because as const
is more type safe and more readable.
TS comments
ban-ts-comment
- Severity: error
- Configuration:
- Allow
@ts-check
; require@ts-expect-error
to have a description; disallow@ts-ignore
and@ts-nocheck
- Require descriptions to be at least 3 characters long (
minimumDescriptionLength: 3
)
- Allow
You should never use @ts-ignore
or @ts-nocheck
. They completely turn off the checker and have no sense of granularity. @ts-expect-error
is better because it requires an error to be present, but it is still dangerous an should only be applied when there cannot be any other type errors.
prefer-ts-expect-error
- Severity: error
Use @ts-expect-error
instead of @ts-ignore
or @ts-nocheck
. This rule can auto-fix @ts-ignore
to @ts-expect-error
.
triple-slash-reference
- Severity: error
- Never allow
/// <reference lib="..." />
(lib: "never"
) - Never allow
/// <reference path="..." />
(path: "never"
) - Allow
/// <reference types="..." />
when the file has no import (types: "prefer-import"
)
- Never allow
You should generally use modules instead of triple-slash references. /// <reference path="..." />
can be substituted with a side-effect import. /// <reference lib="..." />
can be substituted with the lib
compiler option. /// <reference types="..." />
can be substituted with an import, except when you want to load ambient declarations, such as declare module
and declare global
. In this case, the triple slash reference should always live in an ambient declaration itself.
Plain JavaScript
These rules probably shouldn't be in typescript-eslint in the first place.
no-dynamic-delete
- Severity: off
delete
is necessary for transforming objects:
ts
for (const key of Object.keys(obj)) {delete obj[key];}
ts
for (const key of Object.keys(obj)) {delete obj[key];}
no-this-alias
- Severity: warn
- Configuration:
- Allow destructuring values from
this
(allowDestructuring: true
) - Do not allow any particular alias (
allowedNames: []
)
- Allow destructuring values from
- Related:
You should generally avoid this
aliasing because it is rare to rely on nested this
values. In case when this is actually necessary, you should use a semantic name instead of self
or that
.
prefer-for-of
- Severity: error
Use a for-of
loop instead of for
when the pattern is clearly convertible. Note that this rule is not 100% safe as not every indexed collection is iterable.