TypeScript

We are using TypeScript ESLint and its recommended configs as a starting point. [~]

Rules overview

[~]

[~]

@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

[~]

[~]

@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'}`

[~]

[~]

@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) while interface is only for object shapes. So for the sake of consistency it is better to use type everywhere and not mix it with interface.
  • type can only be defined once, while interface 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

[~]

@typescript-eslint/no-duplicate-imports

[~]

@typescript-eslint/no-shadow

[~]

@typescript-eslint/no-throw-literal

[~]

@typescript-eslint/no-unused-expressions

[~]

@typescript-eslint/no-useless-constructor

[~]

@typescript-eslint/no-unused-vars

[~]