Base

Principles

Syntax

  1. Using Prettier for better formatting.
  2. Syntactic homogeneity. Semantically identical constructions must use the same syntax. To achieve that we fix spacings, paddings, quotes, shorthand techniques, etc. E.g., curly and dot-notation.
  3. Minimization of git diffs when code is being changed or refactored. Changing or extending code must change minimal count of symbols which aren't connected with the change in logic. By the most part this is achieved by using Prettier.
  4. Avoid disabling linting rules. We believe that linting rules shouldn't interfere with writing code. Disabling rules is allowed as an exception and a temporary measure. Because of this, we don't use frequently disabled rules like no-mixed-operators, prefer-arrow-callback, no-useless-return, etc.
  5. No warnings. All kinds of undesirable constructions must be fixed. Warning add noise to the result of project building and quickly become being ignored. As an exception warnings are allowed in a situation of a long refactoring but only as a temporary measure. [~]

Semantics

  1. Explicit is better than implicit. JS has many implicit defaults so to avoid confusion we prefer being explicit and not using ambiguous constructions. E.g., no-implicit-coercion.
  2. Minimization of errors. JavaScript is an interpreted language so all the incorrect or meaningless constructions can be detected only during execution. We try to minimize the amount of constructions that can lead to runtime errors. E.g., guard-for-in and eqeqeq.
  3. Make code readable without limiting developers too much. We don't want for linting to be in the way of writing logic so we don't use rules like complexity or max-depth. But we still try to restrict semantic constructions which make reading code difficult and use rules like max-params or no-nested-ternary.
  4. Make sure code is ready for production environment. Code shouldn't contain temporary or debug construction. E.g., no-eval, no-console, no-debugger, etc. [~]

Rules overview

[~]

Prettier

eslint-plugin-prettier is used for integration of Prettier and ESLint.

eslint-config-prettier turns off all the rules from ESLint and its plugins that might conflict with Prettier. [~]

ESLint rules

We are using eslint:recommended as starting point. You can see list of included rules here marked with a check mark. [~]

camelcase

// Fail
const my_var = { prop_name: 1 }

// Pass
const myVar = { propName: 1 }

Names starting with UNSAFE_ are allowed to support React's legacy lifecycle methods:

class MyComponent extends React.Component {
  UNSAFE_componentWillUpdate() {}
}

[~]

curly

// Fail
if (true) return null

// Pass
if (true) { return null }

[~]

default-param-last

// Fail
function f(foo = 0, bar) {}

// Pass
function f(bar, foo = 0) {}

[~]

dot-notation

// Fail
obj['param']

// Pass
obj.param

[~]

eqeqeq

Prefer strict equals to avoid implicit checks.

// Fail
if (x == null) {}

// Pass
const isNil = (v) => v === null || v === undefined

if (isNil(x)) {}

[~]

guard-for-in

For iterating over object keys use Object.keys instead of for ... in as it returns object's own property names and ignores inherited ones.

// Fail
for (key in obj) { }

// Pass
Object.keys(obj).forEach((key) => {})

[~]

max-params

Functions may have maximum 3 parameters:

// Fail
const myFn = (a, b, c, d) => {}

// Pass
const myFn = ({ a, b, c, d }) => {}

If you're using reselect and errors are reported for createSelector with too many params, switch to createStructuredSelector. [~]

no-bitwise

Quite often bitwise operator is just a typo in logical operator:

// Fail
const x = y | z

// Pass
const x = y || z

If you need to use bitwise operators, disable this rule for the lines containing them. [~]

no-console

Remove console.log from production code:

// Fail
console.log('Debug info')

// Pass
console.info('Info')
console.warn('Warning')
console.error('Error')

[~]

no-eval

Forbid usages of eval as potentially dangerous:

// Fail
eval("var a = 0")

[~]

no-implicit-coercion

All implicit coercions except !! are disallowed:

// Fail
+foo
1 * foo
'' + foo
`${foo}`
~foo.indexOf(bar)

// Pass
!!foo

[~]

no-nested-ternary

// Fail
return a ? b ? c : d : e

// Pass
if (a) {
  return b ? c : d
} else {
  return e
}

[~]

no-new-wrappers

// Fail
const n = new Number(1)

// Pass
const n = 1

[~]

no-param-reassign

// Fail
function f(foo) {
  foo++
}

// Pass
function f(foo) {
  const bar = foo + 1
}

[~]

no-shadow

// Fail
rows.forEach((item) => {
  item.columns.forEach((item) => {
    // `item` is shadowed
  })
})

// Pass
rows.forEach((row) => {
  item.columns.forEach((column) => {
  })
})

[~]

no-template-curly-in-string

// Fail
"Hello, ${name}"

// Pass
`Hello, ${name}`

[~]

no-throw-literal

// Fail
throw 'Error'

// Pass
throw new Error('Error')

[~]

no-undef-init

// Fail
let foo = undefined

// Pass
let foo

[~]

no-underscore-dangle

// Fail
const _private = 1

// Pass
items.map((_item, idx) => idx)

[~]

no-unneeded-ternary

// Fail
const isYes = answer === 1 ? true : false

// Pass
const isYes = answer === 1

[~]

no-unused-expressions

// Fail
a || b
(function f() {})

// Pass
a && b()
a ? b() : c()

[~]

no-unused-vars

// Fail
const { foo, bar } = props
console.log(foo)

// Pass
const { foo } = props
console.log(foo)

[~]

Hanging unused parameters in functions are not allowed:

// Fail
function f(a, b, c, d) {
  console.log(b)
}

// Pass
function f(a, b) {
  console.log(b)
}

[~]

Omitted params in object destructuring can be left unused:

// Pass
const { foo, ...rest } = props
console.log(rest)

[~]

no-var

// Fail
var foo

// Pass
let foo

[~]

object-shorthand

// Fail
const a = { x : x }

// Pass
const a = { x }

[~]

one-var

// Fail
let foo, bar

// Pass
let foo
let bar

[~]

operator-assignment

// Fail
x = x + y

// Pass
x += y

[~]

padding-line-between-statements

We only configure paddings between exports and other statements to emphasize exported values. You may customize this rule in your project depending on your needs.

// Fail
const a = 1
export const b = 2

// Pass
const a = 1

export const b =2

[~]

prefer-const

// Fail
let a = 3

// Pass
const a = 3

[~]

radix

ES5 no longer treats strings with leading 0 as octal literal, so there's no need to pass the radix 10:

// Fail
const n = parseInt('071', 10)

// Pass
const n = parseInt('071')

[~]

Disabled rules

[~]

no-irregular-whitespace

We disable this rule as it can't properly allow using non-breaking whitespaces inside JSX tags. It forces to use   instead:

<>Push the&nbsp;button</>

Which leads to a problem when converting JSX text node to a string:

<>{'Push the&nbsp;button'}</>

If developer don't pay attention or text is too big to look through it properly, &nbsp; moves into string without any warning and shows up in UI as a group of symbols, not a whitespace. [~]

no-use-before-define

We don't enable this rule as it is often more practical to place usages before definitions. E.g., in React component it is better to place component code first and helper functions below. That way the most important thing in the file comes first. [~]

Unicorn

Additional rules from eslint-config-unicorn. [~]

unicorn/explicit-length-check

Checking for .length of an empty array can lead to rendering 0 instead of not rendering anything. This rule forces to always explicitly check for array length by comparing it:

// Fail
return items.length && <Items />

// Pass
return items.length > 0 && <Items />

[~]

unicorn/filename-case

// Fail
my-component.js
my_component.js

// Pass
myComponent.js
MyComponent.js
en-US.js

[~]

unicorn/no-abusive-eslint-disable

// Fail
// eslint-disable-next-line
console.log(message);

// Pass
// eslint-disable-next-line no-console
console.log(message);

[~]

unicorn/throw-new-error

By design Error(msg) and new Error(msg) can be used interchangeably but for the sake of homogeneity it is better to always use only one form.

// Fail
throw Error()

// Pass
throw new Error()

We prefer new Error for consistency with custom subclassed errors which cannot be created without new:

class MyError extends Error {}
throw MyError() // Uncaught TypeError
throw new MyError() // OK

[~]

Rules for asynchronous code

Using eslint-config-promise. [~]

prefer-promise-reject-errors

// Fail
Promise.reject('Error')

// Pass
Promise.reject(new Error('Error'))

[~]

require-await

// Fail
async function f() {
  doSomethingSynchronously()
}

// Pass
async function f() {
  await doSomethingAsynchronously()
}

[~]

promise/catch-or-return

// Fail
myPromise.then(doSomething)

// Pass
myPromise.then(doSomething).catch(handleError)

[~]

promise/no-return-in-finally

// Fail
myPromise.finally(() => {
  return 'done'
})

// Pass
myPromise.finally(() => {
  alert('done')
})

[~]

promise/no-return-wrap

// Fail
myPromise.then((val) => Promise.resolve(val * 2))
myPromise.then(() => Promise.reject(new Error()))

// Pass
myPromise.then((val) => val * 2)
myPromise.then(() => {
  throw new Error()
})

[~]

promise/param-names

// Fail
new Promise((reject, resolve) => {})

// Pass
new Promise((resolve, reject) => {})

[~]

Import rules

Using eslint-config-import and eslint-config-unicorn. [~]

Extending plugin:import/errors. [~]

import/no-duplicates

// Fail
import { App } from './App'
import { SOME_CONST } from './App'

// Pass
import { App, SOME_CONST } from './App'

[~]

import/first

// Fail
init()
import { App } from './App'

// Pass
import { App } from './App'
init()

[~]

import/newline-after-import

// Fail
import { App } from './App'
render(App)

// Pass
import { App } from './App'

render(App)

[~]

import/no-cycle

// Fail

 // a.js
import { b } from './b'
export const a = 1

// b.js
import { a } from './a'
export const b = 2

[~]

import/no-default-export

// Fail
export default function foo() {}

// Pass
export function foo() {}

[~]

import/order

Group imports by module type and sort them alphabetically inside a group.

Order of import groups: [~]

  1. builtin
import * from 'path'

[~]

  1. external
import * from 'lodash'

[~]

  1. internal
import * from 'src/utils'
import * from '@/utils'
import * from '~/utils'

[~]

  1. parent
import * from '../'

[~]

  1. sibling
import * from './sibling'

[~]

  1. index
import * from '.'

[~]

sort-imports

Sort import members:

// Fail
import { c, a, b } from 'module'

// Pass
import { a, b, c } from 'module'

[~]

unicorn/import-index

// Fail
import * from './index'

// Pass
import * from '.'

[~]

Rules for regular expressions

[~]

prefer-named-capture-group

We use named capture groups as they are more explicit while matching or replacing:

// Fail
const regexp = /(ba[rz])/
const groupResult = regexp.exec('bar')[1] // 'bar'
'foobar'.replace(regexp, '($1)') // 'foo(bar)'

// Pass
const regexp = /(?<named>ba[rz])/
const groupResult = regexp.exec('bar').groups.named // 'bar'
'foobar'.replace(regexp, '($<named>)') // 'foo(bar)'

If you don't need the result of capturing, mark group as a non-capturing:

// Pass
const regexp = /(?:ba[rz])/

[~]

prefer-regex-literals

// Fail
new RegExp("abc")

// Pass
/abc/
new RegExp(someVariable)
new RegExp(`${prefix}abc`)

[~]

unicorn/better-regex

// Fail
/[0-9]/

// Pass
/\d/

[~]

React

Syntactic rules

Principles

Syntax

  1. As with the base config we use Prettier for familiar formatting defaults.
  2. Making JSX more compact. JSX is similar to HTML and tend to extend by width. We use rules which help make our markup narrower. E.g., react/jsx-boolean-value.
  3. Minimizing the count of symbols and their kinds. JSX (as HTML too) has very noisy syntax. We try to omit unnecessary and optional symbols and prefer to use more compact syntax. When we need to use some decorative symbols, we prefer more consistent syntax. E.g., react/jsx-no-useless-fragment, react/jsx-tag-spacing.
  4. We try not to affect the look of JSX code too much so we don't force the use of specific form of syntax. E.g., we don't use react/jsx-sort-props. [~]

Semantics

  1. Explicit declaration React semantics rules. React and JSX have many specific semantic rules, the violation of which leads to error. For example react hooks, lifecycle, component declarations have many implicit rules which has no meaning in Vanilla JS context. But if this rules is breaked, react will works incorrectly. Because JS interpreter(and base linting rules for JS) doesn't know anything about state, props, lifecycle, hooks and so on. We declare this rules in linting and help base lint rules(as "no-unused-vars") works with React syntax/semantics. E.g., react-hooks/rules-of-hooks, react/no-typos, react/jsx-no-constructed-context-values, react/no-access-state-in-setstate, etc.
  2. Prevent usage of constructions which break a11y or web security. We use config jsx-a11y/recommended and rule react/no-danger.
  3. Prevent usage of deprecated or unconventional API. Most of the rules covering this issue are included in recommended config, additionally we enable react/no-unsafe.
  4. Optimize React for performance. React's performance relies on change detection of props by reference. To make it work, we should avoid several constructions which lead to the generation of new references. E.g., react/prefer-stateless-function, react/no-array-index-key
  5. Enforce usage of prop-types in non-TS projects. We prefer to use props-types because it is a standardized way to declare component API. That's why we enable rules helping to write better prop types: e.g., react/prop-types, react/forbid-prop-types, react/no-unused-prop-types etc.
  6. Don't specify naming for React props. Though React code has typical props templates (handlers, boolean flags, etc), we don't enforce naming convention for them. Naming convention must be project-specific so we don't use rules like react/boolean-prop-naming or react/jsx-handler-names. We still use react/jsx-pascal-case to encourage consistent component naming between projects.
  7. Don't enforce conventions about structure of your project and components. All these conventions are be project-specific and must be defined by project developers. We don't use rules like react/static-property-placement, react/no-multi-comp or react/jsx-max-depth. [~]

React rules

As a base we use recommended configs from eslint-plugin-react and eslint-plugin-react-hooks. [~]

react/button-has-type

// Fail
return <button>Hello</button>

// Pass
return <button type="button">Hello</button>

[~]

react/default-props-match-prop-types

// Fail
MyComponent.propTypes = {
  foo: React.PropTypes.string.isRequired,
}
MyComponent.defaultProps = {
  foo: "foo"
}

// Pass
MyComponent.propTypes = {
  foo: React.PropTypes.string.isRequired,
}

// Pass
MyComponent.propTypes = {
  foo: React.PropTypes.string,
}
MyComponent.defaultProps = {
  foo: "foo"
}

[~]

react/forbid-prop-types

// Fail
Component.propTypes = {
  a: PropTypes.any,
  b: PropTypes.array,
  c: PropTypes.object
}

// Pass
Component.propTypes = {
  a: PropTypes.objectOf(PropTypes.number),
  b: PropTypes.arrayOf(PropTypes.string),
  c: PropTypes.shape({
    c1: PropTypes.number,
    c2: PropTypes.string,
  })
}

[~]

react/function-component-definition

We prefer components as arrow function for the sake of homogeneity. Benefit of arrow function over function expression is inability to use this.

// Fail
export function MyComponent() {}

// Pass
export const MyComponent = () => {}

[~]

react/jsx-boolean-value

// Fail
return <Component disabled={true} />

// Pass
return <Component disabled />

[~]

react/jsx-fragments

// Fail
return <React.Fragment>...</React.Fragment>

// Pass
return <>...</>
return <React.Fragment key="key">...</React.Fragment>

[~]

react/jsx-no-constructed-context-values

// Fail
return (
  <MyContext.Provider value={{ foo }}>
    ...
  </MyContext.Provider>
)

// Pass
const value = useMemo(() => ({ foo }), [foo])
return (
  <MyContext.Provider value={value}>
    ...
  </MyContext.Provider>
)

[~]

react/jsx-pascal-case

// Fail
return <My_component />
return <MY_COMPONENT />

// Pass
return <MyComponent />

[~]

react/no-access-state-in-setstate

// Fail
this.setState({ value: this.state.value + 1 })

// Pass
this.setState(state => ({ value: state.value + 1 }))

[~]

react/no-array-index-key

In a few cases when only index is available as a key, rule can be disabled for a specific line.

// Fail
return items.map((item, index) => (
  <Item key={index} />
))

// Pass
return items.map((item, index) => (
  <Item key={item.id} />
))

[~]

react/no-danger

// Fail
return <div dangerouslySetInnerHTML={{ __html: 'Hello World' }}></div>

[~]

react/no-typos

// Fail
class MyComponent extends React.Component {
  static PropTypes = {
    a: PropTypes.bol
  }
  static defaultprops = {}
  getDerivedStateFromProps() {}
  ComponentWillMount() {}
  componentdidupdate() {}
}

// Pass
class MyComponent extends React.Component {
  static propTypes = {
    a: PropTypes.bool
  }
  static defaultProps = {}
  static getDerivedStateFromProps() {}
  componentWillMount() {}
  componentDidUpdate() {}
}

[~]

react/no-unsafe

// Fail
class MyComponent extends React.Component {
  UNSAFE_componentWillMount() {}
  UNSAFE_componentWillReceiveProps() {}
  UNSAFE_componentWillUpdate() {}
}

[~]

react/no-unused-prop-types

// Fail
const MyComponent = ({ firstName }) => <span>{firstName}</span>
MyComponent.propTypes = {
  firstName: PropTypes.string,
  lastName: PropTypes.string,
}

// Pass
const MyComponent = ({ firstName, lastName }) => <span>{firstName} {lastName}</span>
MyComponent.propTypes = {
  firstName: PropTypes.string,
  lastName: PropTypes.string,
}

[~]

react/prefer-stateless-function

Though we prefer to use functional components with hooks over class components, we don't enforce it for all the components, only for class components with no state or lifecycle methods.

// Fail
class MyComponent extends React.Component {
  render() {
    return <span>{this.props.name}</span>
  }
}

// Pass
const MyComponent = ({ name }) => <span>{name}</span>

[~]

react/self-closing-comp

// Fail
return <MyComponent></MyComponent>

// Pass
return <MyComponent />

[~]

[~]

react/display-name

Disabled this rule as it gives false positives for render props. Also we prefer named exports which guarantees that exported component functions have proper names. [~]

react/react-in-jsx-scope

Disabled this rule as it is common to have React as a global variable (e.g., out-of-the-box in Next.js or via webpack's ProvidePlugin). [~]

react-hooks/exhaustive-deps

This rule may trigger a lot of unwanted errors when useEffect is used as watcher. It is recommended to manually enable this rule inside files which heavily rely on memoization. [~]

JSX A11y rules

Using plugin:jsx-a11y/recommended config as a starting point. [~]

[~]

jsx-a11y/accessible-emoji

Deprecated [~]

jsx-a11y/no-onchange

Deprecated [~]

jsx-a11y/label-has-associated-control

Disabled until this issue is fixed. [~]

jsx-a11y/no-autofocus

It may be considered OK to use autofocus on pages which consist only of form (e.g., login page). In other cases usage of autofocus may lead to a confusion of a screen reader user. We leave the decision to use autofocus to developers on a case-by-case basis. [~]

[~]

jsx-a11y/anchor-is-valid

// Fail
return <a onClick={foo}>Perform action</a>

// Pass
return <a href="/some/valid/uri">Navigate to page</a>

This rule may lead to a lot of errors when linting Next.js projects so consider either turning it off or following these recommendations. [~]

jsx-a11y/mouse-events-have-key-events

If an element has some logic tied to hover it should be duplicated via focus/blur keyboard events. It is important for both purely visual changes (e.g., highlighting) and actual page changes (e.g., showing hint).

// Fail
return <div onMouseOver={() => {}} />

// Pass
return (
  <div
    tabindex={0}
    onMouseOver={() => {}}
    onFocus={() => {}}
  />
)

[~]

jsx-a11y/anchor-has-content

This rule is always linting <a> tags. Additionally we configured this rule for <Link> component. If you have other custom link components configure this rule inside your project specifying the name of your custom link component.

// Fail
return <a />
return <Link />

// Pass
return <a>Link content</a>
return <Link>Link content</Link>

[~]

Recommendations

Here we present some advices regarding further ESLint configuration in your project.

There are some helpful ESLint rules and plugins which are too strict to enable for all projects. We recommend to look through them and enable those that fit your project's needs.

JavaScript

TypeScript

  • @typescript-eslint/no-unnecessary-condition — requires TS 4.1 or higher with noUncheckedIndexedAccess option enabled. Combination of this TS option and linting rule will provide better type safety but you'll need to write more type guards.
    const items: Item[] = []
    
    // Fail
    // noUncheckedIndexedAccess: false
    const item = items[0] // type: Item
    if (item) {} // no-unnecessary-condition will report error here
    
    // Pass
    // noUncheckedIndexedAccess: true
    const item = items[0] // type: Item | undefined
    if (item) {}
    
  • @typescript-eslint/switch-exhaustiveness-check — it may be overkill to enable this rule globally so you can enable it in specific files and/or use UnreachableCaseError from ts-essentials

React

  • react-hooks/exhaustive-deps — we recommend enable this rule only the files which are heavily rely on memoization. In some situations following this rule may lead to breaking logic (e.g. when useEffect is used as a watcher). To enable this rule in specific file add this comment at the file start:
    /* eslint react-hooks/exhaustive-deps: "error" */
    
  • eslint-plugin-react-perf — this plugin has several rules requiring memoization of objects, arrays and functions and JSX passed as props. These rules may be helpful in performance-critical places, but it may be too restrictive to enable them globally. We recommend enabling these rules either via overrides ESLing config field or by adding this comment in specific files:
    /* eslint
        react-perf/jsx-no-new-object-as-prop: "error",
        react-perf/jsx-no-new-array-as-prop: "error",
        react-perf/jsx-no-new-function-as-prop: "error",
        react-perf/jsx-no-jsx-as-prop: "error"
    */
    
  • eslint-plugin-react-redux — for projects using Redux.
  • plugin:functional/no-mutations — if you want to enforce usage of readonly data structures and avoid mutations.

Linting performance

If you feel that linting your project takes too long you can run ESLint with TIMING=1 environment variable and it'll show you which rules are taking the most time.

[~]

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

[~]