Base
Principles
Syntax
- Using Prettier for better formatting.
- Syntactic homogeneity. Semantically identical constructions must use the same syntax. To achieve that we fix spacings, paddings, quotes, shorthand techniques, etc. E.g.,
curly
anddot-notation
. - 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.
- 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. - 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
- 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
. - 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
andeqeqeq
. - 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
ormax-depth
. But we still try to restrict semantic constructions which make reading code difficult and use rules likemax-params
orno-nested-ternary
. - 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 button</>
Which leads to a problem when converting JSX text node to a string:
<>{'Push the button'}</>
If developer don't pay attention or text is too big to look through it properly,
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: [~]
builtin
import * from 'path'
external
import * from 'lodash'
internal
import * from 'src/utils'
import * from '@/utils'
import * from '~/utils'
parent
import * from '../'
sibling
import * from './sibling'
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
- As with the base config we use Prettier for familiar formatting defaults.
- 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
. - 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
. - 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
- 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. - Prevent usage of constructions which break a11y or web security. We use config
jsx-a11y/recommended
and rulereact/no-danger
. - 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
. - 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
- Enforce usage of
prop-types
in non-TS projects. We prefer to useprops-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. - 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
orreact/jsx-handler-names
. We still usereact/jsx-pascal-case
to encourage consistent component naming between projects. - 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
orreact/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 />
Disabling recommended rules
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.
[~]
Disabling recommended rules
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.
[~]
Clarifying recommended rules
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.
Recommended rules for manual configuration
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
todo-plz/ticket-ref
— require TODOs to contain an associated issue ticket:// Fail // TODO fix that later // Pass // TODO will be fixed in PROJ-123
import/dynamic-import-chunkname
— if you are using dynamic imports in webpack.import/no-extraneous-dependencies
— forbid importing dependencies which are not listed as dependencies inpackage.json
.eslint-plugin-no-unsanitized
— if you are inserting HTML directly in JS. You'll need to choose a sanitizer library and configure this plugin accordingly.
TypeScript
@typescript-eslint/no-unnecessary-condition
— requires TS 4.1 or higher withnoUncheckedIndexedAccess
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 useUnreachableCaseError
fromts-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. whenuseEffect
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 viaoverrides
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
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