TypeScript
We are using TypeScript ESLint and its recommended configs as a starting point. [~]
Rules overview
Upgrading warnings from recommended config to errors
@typescript-eslint/explicit-module-boundary-types
Exported functions should have return type declared explicitly to avoid unexpected changes in return type.
In case you want all functions to have return type specified, use rule @typescript-eslint/explicit-function-return-type
.
// Fail
export const getString = () => 'string'
// Pass
export const getString = (): string => 'string'
const getString = () => 'string'
@typescript-eslint/no-explicit-any
// Fail
let a: any
// Pass
let a: unknown
@typescript-eslint/no-non-null-assertion
// Fail
return foo!.bar
// Pass
return foo?.bar
Clarifying recommended rules
@typescript-eslint/no-floating-promises
If function returns promise but we want to ignore this promise it should be explicitly shown that this is an async function call. Add void
before such function calls to distinguish them from synchronous ones.
const asyncSideEffect = async () => { await doSomething() }
// Fail
asyncSideEffect()
// Pass
await asyncSideEffect() // wait for result
void asyncSideEffect() // ignore and don't wait
@typescript-eslint/restrict-template-expressions
Avoid null
or undefined
being included in a string:
// Fail
const print = (a?: string | null) => `a: ${a}`
// Pass
const print = (a?: string | null) => `a: ${a ?? 'empty'}`
Disabling recommended rules
@typescript-eslint/no-unsafe-assignment
@typescript-eslint/no-unsafe-call
@typescript-eslint/no-unsafe-member-access
@typescript-eslint/no-unsafe-return
Disabled this group of rules as they flag usages of external libraries which typings contain any.
[~]
@typescript-eslint/unbound-method
Disabled as it flags usages of unbound methods from external libraries (e.g., _.noop
from Lodash)
[~]
Enabling new rules
@typescript-eslint/array-type.md
Use T[]
for primitives and type references and Array<T>
for other cases:
// Fail
type A = Array<string>
type B = { param: string }[]
type C = (string | number)[]
// Pass
type A = string[]
type B = Array<{ param: string }>
type C = Array<string | number>
@typescript-eslint/consistent-indexed-object-style
// Fail
type A = { [key: Key]: Value }
// Pass
type A = Record<Key, Value>
@typescript-eslint/consistent-type-assertions
To prevent conflicts with JSX:
// Fail
const a = <T>b
// Pass
const a = b as T
@typescript-eslint/consistent-type-definitions
Prefer type
to interface
:
type
can be used for any type variations (objects, arrays, unions, primitives and so on) whileinterface
is only for object shapes. So for the sake of consistency it is better to usetype
everywhere and not mix it withinterface
.type
can only be defined once, whileinterface
can be defined multiple times and these definitions will merge.
// Fail
interface A {
foo: string
}
// Pass
type A = {
foo: string
}
@typescript-eslint/consistent-type-imports
Prefer type imports: it helps to better distinguish types from similarly named variables and avoid import/no-cycle
errors when importing types.
import type { T } from 'lib'
// Fail
import { T } from 'lib'
// Pass
import type { T } from 'lib'
There are some cases when you might need to disable this rule (e.g. with NestJS). [~]
@typescript-eslint/method-signature-style
// Fail
type A = {
method(): void
}
// Pass
type A = {
method: () => void
}
@typescript-eslint/naming-convention
The only common naming convention we enforce is PascalCase for types. You may add more naming conventions based on your project needs.
// Fail
type myType = {}
// Pass
type MyType = {}
@typescript-eslint/prefer-nullish-coalescing
Prefer ??
to !!
as ??
will not fallback if value is falsy (0
, ''
, false, etc.):
const a: number | null | undefined = 0
// Fail
const b = a || 999 // result: 999
// Pass
const b = a ?? 999 // result: 0
@typescript-eslint/prefer-optional-chain
const a: {
b? : Array<{
c?: () => void
}>
} | undefined = {}
// Fail
a && a.b && a.b[0] && a.b[0].c && a.b[0].c()
// Pass
a?.b?.[0]?.c?.()
@typescript-eslint/prefer-reduce-type-parameter
Provide result type for Array#reduce
calls when it can't be properly inferred from initial value.
// Fail
[1, 2, 3].reduce((arr, num) => arr.concat(num * 2), [] as number[])
// Pass
[1, 2, 3].reduce<number[]>((arr, num) => arr.concat(num * 2), [])
@typescript-eslint/prefer-ts-expect-error
// Fail
// @ts-ignore
const str: string = 1
// Pass
// @ts-expect-error: I know what I'm doing
const str: string = 1
@typescript-eslint/require-array-sort-compare
// Fail
[1, 5, 10].sort() // [1, 10, 5]
// Pass
[1, 5, 10].sort((a, b) => a - b) // [1, 5, 10]
['c', 'a', 'b'].sort()
TS alternatives to JS rules
Some ESLint rules have better alternatives in @typescript-eslint
so we enable them instead of ESLint rules.
[~]
@typescript-eslint/default-param-last
@typescript-eslint/dot-notation
@typescript-eslint/init-declarations
In TS instead of no-undef-init
we enable opposite rule init-declarations
to avoid runtime errors with let
variables:
// Fail
let str: string // Actually it's `undefined`, but TS sees it as a `string`
// Pass
let str: string | undefined = undefined