🗨️ You can discuss this article on Reddit.
⚠️ Are you getting this error?
Error: Error while loading rule '@typescript-eslint/no-implied-eval': You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.
See this update article.
This post describes how I setup ESLint in different scenarios. We’ll start with a simple plain JavaScript project and then we’ll deal with TypeScript, and also React. The aim is to do the things right and avoid installing random packages or copy/pasting snippets of configuration until things work.
It is a complete guide that is made to be read from top to bottom. If you are already confident with ESLint, you could probably jump to the end of each section and grab the configuration right away though.
This guide heavily relies on Airbnb configuration packages as they are extremely popular and will most likely satisfy you if you don’t already have a preference. However, you can totally use any other style guide packages if you prefer.
If you’ve never heard of Airbnb style guide, I highly recommend that you take a look at the following pieces of documentation:
- Airbnb JavaScript style guide
- Airbnb React style guide
- Airbnb CSS in JavaScript style guide
- Airbnb Sass style guide
There are explanations for every settings. It is also a great starting point if you wish to design your own configuration.
Summary
[TOC]
Complete ESLint configurations are available at the end of each section, when appropriate.
Why ESLint?
There is no competing project to my knowledge. ESLint is highly configurable and well maintained. Most people or company who design JavaScript style guides implement it for ESLint.
A few alternatives I had the chance to try are:
-
standardJS is a JavaScript linter that enforces the standard style guide. It is therefore not configurable and is not really made for TypeScript. It is a decent solution if you are dealing with a plain JavaScript project and do not want to spend time configuring your linter. Otherwise, we can definitely do better with ESLint. Moreover, standardJS also is available as a standalone ESLint configuration!
-
gts is a TypeScript linter that implements Google’s style guide. It is a nice solution, especially if you are looking for something that works with 0 configuration. The thing is that
gtsactually uses ESLint under the hood and we can totally extract the ESLint configuration it is using. -
TSLint is a TypeScript linter that used to be very popular. But is has been deprecated and is being merged with ESLint. The exact situation is a bit vague but in reality, TypeScript linting is totally possible inside ESLint, with the appropriate tooling.
It is also worth mentioning that most text editors have ESLint plugins. You will have no trouble integrating live linting into VSCode, Vim, or Emacs. Some IDEs such as WebStorm even integrate it out of the box.
In short, ESLint is the best platform to build your linting configuration on.
ESLint installation
Just like any package, ESLint can be installed at two levels:
- The global level, thanks to
npm -g - The project level
It would make sense to install ESLint at the global level so that it could be invoked from anywhere. However, I prefer to install it at the project level for a few reasons:
- Allows to have different versions of ESLint for different projects
- Does not hide the ESLint dependency. There is no reason not to make it explicit.
- Coworkers and automation tools (such as a CI) will install ESLint just like they install the other development dependencies of the project. No extra setup or documentation required.
The two mechanisms that allow us to properly install and use ESLint at the project level are:
- The devDependency
entry in the
package.jsonfile - Local packages binary invocation thanks to npx
Now that we know that, installing ESLint is as simple as running (inside the project’s directory):
npm i eslint --save-dev
In order to run ESLint inside the project:
npx eslint
It may be a good idea to rely on npm’s task running mechanism to hide the
command line arguments we are using and get a cleaner interface. In the
package.json file we can add:
"scripts": {
"lint": "eslint ."
}
The . parameter allows to run ESLint in the current directory. It can now be
invoked via the lint task:
npm run lint
ESLint is recursive by default so it will correctly lint any .js file.
ESLint configuration file
ESLint reads configuration files at various locations. Most of the time, a single configuration file at the root of the project is enough.
ESLint allows for multiple configuration formats:
- JavaScript
- JSON
- YAML
I find the YAML format to be the most concise and enjoyable. This is the format
I am going to use for this guide. Therefore, our configuration file will be
named .eslintrc.yaml. Let’s create it at the root of our project:
touch .eslintrc.yaml
Note: the JavaScript format may be mandatory if you need to add logic to your configuration.
We are going to tell ESLint about the language features (the ECMA version) we want to enable, but also the environment our code will run on. Without these pieces of information, ESLint will report false positives. Here is what we can put in our configuration file for a Node.js ES6 project:
parserOptions:
ecmaVersion: 6
env:
node: true
It is now time to configure some rules!
Getting Started / Plain JavaScript
Let’s start with the plain JavaScript scenario. We have a project with a bunch
of .js files that we would like to lint.
We could create a configuration from scratch, tweaking the rules that ESLint exposes to us. But it is not a very good idea for multiple reasons:
- It is time consuming
- It is hard to maintain
- It reflects your own understanding of JavaScript
A lot of companies such as Google, Airbnb or Facebook spend a lot of time maintaining configuration that are already widely adopted, have sane defaults and are kept up to date.
My favorite one is Airbnb’s and this is the one we are going to use. Let’s install it:
npx install-peerdeps --dev eslint-config-airbnb-base
Most online tutorials will recommend that you install the eslint-config-airbnb
package, which also includes configurations for React, React Hooks, etc. This is
not necessary in our case, as we are dealing with a plain JavaScript project.
Note that we are not using npm to install the package but
npx install-peerdeps.This is because the configuration package has peer
dependencies. This is actually the case for most ESLint configuration packages
as they usually depend on ESLint plugins, or even other configuration packages.
Once the package is installed, let’s use it in our configuration:
extends:
- airbnb-base
We are inheriting Airbnb’s configuration and ESLint now reports errors. Here is what a report looks like:
> eslint .
/Users/geographer/Documents/eslint/plain-js/src/index.js
4:13 error Expected '===' and instead saw '==' eqeqeq
5:5 warning Unexpected console statement no-console
5:42 error Missing semicolon semi
9:3 warning Unexpected console statement no-console
10:3 warning Unexpected console statement no-console
11:3 error Unnecessary return statement no-useless-return
14:2 error Missing semicolon semi
✖ 7 problems (4 errors, 3 warnings)
3 errors and 0 warnings potentially fixable with the `--fix` option.
For each file, we get a list of errors.
- The first column tells us the line number and the column of the error
- The second column is the severity of the error
- The third column describes the error itself
- The fourth column is the internal name of the error
Knowing the internal name of an error allows us to search for it in the
documentation to know more, or quickly tweak
our configuration. We are in a Node project so we might consider that a
console statement is not a problem. Here is how we can disable the rule
no-console:
rules:
no-console: off
Rules can also have parameters, which are passed as a list. Let’s say that we
only want to allow console.error and console.warn:
rules:
no-console:
- error
- allow:
- warn
- error
Our settings override the configuration packages we are inheriting.
The report also mentions the --fix option. It works really well when it comes
to automatically fix simple problems such as indentation or missing semicolons.
There is also --fix-dry-run which gives an overview of the fixes, without
actually writing the filesystem.
For a plain JavaScript project, this is enough configuration for me. I try not to override Airbnb’s rules as this configuration is super popular as it is and it might disturb my coworkers.
Let’s recap! The dependencies are:
"devDependencies": {
"eslint": "^6.1.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.20.0" (peer dependency)
}
And the final configuration is:
parserOptions:
ecmaVersion: 6
env:
node: true
extends:
- eslint:recommended
- airbnb-base
rules:
no-console:
- error
- allow:
- warn
- error
TypeScript configuration
Let’s move on and configure ESLint to work with a TypeScript project.
The main problem with TypeScript is that ESLint is not able to parse it (its AST, to be exact) out of the box. Therefore we need to use a custom parser: @typescript-eslint/parser.
Let’s install it:
npm install @typescript-eslint/parser --save-dev
Note: the parser is responsible for reading input files and generating abstract representations that can be understood by ESLint.
We can now tell ESLint to use this parser instead of the default one:
parser: "@typescript-eslint/parser"
Let’s also update our package.json and tell ESLint that it must not
lint .js (the default) files but .ts files instead:
"scripts": {
"lint": "eslint . --ext .ts"
},
ESLint can now correctly parse our TypeScript files.
Note: the typescript package has to be installed. I assume that you are in a
TypeScript project already.
Just like we did with the JavaScript project, we are now going to install Airbnb base configuration:
npx install-peerdeps --dev eslint-config-airbnb-typescript
npm i eslint-plugin-import --save-dev
eslint-plugin-import has to be installed manually. I don’t really
know why it is not a peer dependency as the configuration does not run
without it.
Let’s update our ESLint configuration in order to inherit from Airbnb’s:
extends:
- airbnb-typescript/base
You could also extend from airbnb-typescript, which enables support
for React, React Hooks, TSX… But this is not necessary in our case.
The linting now works for TypeScript, just like it used to for JavaScript. The
airbnb-typescript plugin actually wraps eslint-config-airbnb so that it
works with TypeScript. You do not need to explicitly inherit from airbnb-base
for this to work, as all the wrapping is done under the hood.
Still, we don’t have any TypeScript-specific rules for now. We reproduced the JavaScript setup we got before in TypeScript, but nothing more. In order to get TypeScript-specific rules, we are going to inherit from another configuration:
extends:
- airbnb-typescript/base
- plugin:@typescript-eslint/recommended
You might think that we never installed @typescript-eslint, but it is actually
a peer dependency of eslint-config-airbnb-typescript, so we are fine.
Inheriting from this configuration allows us to get TypeScript-specific
recommended rules, such as Missing return type on function.
A lot of tutorials recommend that you also inherit from
plugin:@typescript-eslint/eslint-recommended. This configuration actually is
the TypeScript wrapper for eslint:recommended. (Unlike Airbnb, ESLint does not
perform its TypeScript wrapping under the hood so you need to explicitly inherit
from both configurations.) I don’t think these configurations are necessary in
our case as Airbnb’s ones are already doing all the work.
This setup is totally reasonable, although the rules might require a bit more tweaking than in plain JavaScript. The rules documentation is available here.
Another important thing to understand is that TypeScript on its own is a very powerful linting tool. Let’s consider this code:
const compute = (expr: string): number => eval(expr);
compute(3 + 3);
It is obviously incorrect as 3 + 3 is not a string. However, ESLint will not
complain about that. Why? Simply because it is not a linting issue, but an
actual compilation error:
2:9 - error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
2 compute(3 + 3);
~~~~~
Found 1 error.
When using an IDE, both the ESLint, and the TypeScript compiler outputs are often combined, which is even more powerful.
Finally, you will see a lot of articles recommending that you include this in you ESLint configuration:
plugins:
- "@typescript-eslint"
If you are not using ESLint’s recommended settings, this is not necessary. The reason is that Airbnb already enables this plugin for the scope of its rules. Other configurations that are designed differently might require it though. Actually, the same goes for the parser settings that we put in our configuration at the beginning of this section. But I usually keep it as it immediately indicates that this configuration is for TypeScript.
Let’s do a little recap! The dependencies are:
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.17.0", (peer dependency)
"@typescript-eslint/parser": "^2.17.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-typescript": "^6.3.1",
"eslint-plugin-import": "^2.20.0"
}
And the final configuration is:
# This line can actually be removed
parser: "@typescript-eslint/parser"
parserOptions:
ecmaVersion: 6
env:
node: true
extends:
- airbnb-typescript/base
- plugin:@typescript-eslint/recommended
# Add your own rules here, as needed
React Configuration
Adding React support to ESLint is very easy, especially with Airbnb’s configuration.
One common mistake is to think that the following setting enables React support:
parserOptions:
ecmaFeatures:
jsx: true
React indeed uses JSX, but in such a way that ESLint can not deal with it. In
order to add React support to ESLint, we should use a plugin called
eslint-plugin-react.
JavaScript Project
When dealing with a JavaScript project, replace eslint-config-airbnb-base by
eslint-config-airbnb, which has React support.
npx install-peerdeps --dev eslint-config-airbnb
Then, in the ESLint configuration, inherit from it:
extends:
- airbnb
It might also be a good idea to update the env setting, since we are not in a
Node.js project anymore:
env:
browser: true
Update package.json and tell ESLint to also parse .jsx files:
"scripts": {
"lint": "eslint . --ext .js,.jsx"
}
That’s it! We can now lint our React project. If you are dealing with
Next.js, or any framework that automatically inject
react in JSX files, you might be interested in this setting:
rules:
react/react-in-jsx-scope: off
If your code takes advantage of the React Hooks,
I recommend that you also inherit from the related configuration (it is not
automatically enabled via airbnb):
extends:
- airbnb
- airbnb/hooks
Airbnb’s React support enables quite a few rules related to accessibility. They can be quite noisy, especially if accessibility is not a requirement. There is no mechanism to disable them but here is a fairly clean workaround:
extends:
- airbnb-base
- airbnb/rules/react
- airbnb/hooks
We manually enable all the sub-configurations, instead of the meta one,
excluding rules/react-a11y. The downside to this approach is that if another
sub-configuration is included in the Airbnb package at some point, we will need
to manually import it too. I recommend to leave the accessibility rules enabled
anyway, as they are not hard to deal with at all.
Let’s recap! The dependencies are as follows:
"devDependencies": {
"eslint": "^6.1.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-plugin-import": "^2.20.0", (peer dependency)
"eslint-plugin-jsx-a11y": "^6.2.3", (peer dependency)
"eslint-plugin-react": "^7.18.0", (peer dependency)
"eslint-plugin-react-hooks": "^1.7.0" (peer dependency)
}
And here is our configuration:
parserOptions:
ecmaVersion: 6
env:
browser: true
extends:
- airbnb
- airbnb/hooks
rules:
react/react-in-jsx-scope: off
You should take the time to take a look at the list of ESLint React rules.
TypeScript project
When dealing with a TypeScript project, the package to install is
eslint-config-airbnb-typescript. It does not have explicit peer dependencies,
which is weird, because a few other packages are required.
npm install eslint-config-airbnb-typescript \
eslint-plugin-import \
eslint-plugin-jsx-a11y \
eslint-plugin-react \
eslint-plugin-react-hooks \
@typescript-eslint/eslint-plugin \
--save-dev
Let’s update our ESLint configuration:
env:
browser: true
extends:
- airbnb-typescript
- airbnb/hooks
- plugin:@typescript-eslint/recommended
And our package.json so that we can deal with .tsx files:
"scripts": {
"lint": "eslint . --ext .ts,.tsx"
}
That’s all! Here are the dependencies:
"devDependencies": {
"eslint": "^6.8.0",
"@typescript-eslint/eslint-plugin": "^2.17.0",
"eslint-config-airbnb-typescript": "^6.3.1",
"eslint-plugin-import": "^2.20.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.18.0",
"eslint-plugin-react-hooks": "^2.3.0"
},
And the complete configuration:
parserOptions:
ecmaVersion: 6
env:
browser: true
extends:
- airbnb-typescript
- airbnb/hooks
- plugin:@typescript-eslint/recommended
rules:
react/react-in-jsx-scope: off
There are no specific settings for the React + TypeScript combo to my knowledge. Components are just functions (or classes, if you are old-school :p) so the TypeScript linting just deals with them as such. Therefore I can not recommend more documentation to you!
VSCode configuration
VSCode has great support of ESLint through this plugin. Here are my recommended settings:
"eslint.run": "onSave",
Running ESLint on save feels less laggy.
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
The setting ensure that VSCode will lint JS, JSX, TS, and TSX files.
"javascript.preferences.quoteStyle": "single",
"typescript.preferences.quoteStyle": "single",
VSCode has its own setting when it comes to quote style. Just set it so that it follows your ESLint configuration.
"javascript.updateImportsOnFileMove.enabled": "always",
"typescript.updateImportsOnFileMove.enabled": "always",
This setting allows to update import declarations when moving files around.
Not related to ESLint but very cool so here it is!
Final words
I see a lot of incomplete tutorials and overkill configurations online. I hope that his guide helped you better understand ESLint, and the Airbnb configuration packages.
You can now read rules documentation, experiment with different style guides, and design your perfect setup!
A little exercise for the reader: integrate Prettier to our current ESLint setup.