It was the summer of 2013 and I was working on a project for my employer, Box. I had just finished wiring up JSDoc1 as a nightly build using a plugin to detect T32 patterns in our code and document them automatically. It occurred to me that these patterns might be easy to get wrong, and I started looking for a way to automatically detect incorrect patterns. I immediately turned to JSHint3 because we were already using it and I thought it could support plugins. Unfortunately, it could not.
Legacy Problems Link
I had made a couple small contributions to JSHint over the years, and had also co-created CSS Lint8, so I had a decent amount of experience both writing and modifying linters. There were some things about JSHint that bothered me, and we tried to address them in CSS Lint. Even so, I felt that CSS Lint wasn’t anywhere near where I’d want a modern linter to be. Across JSHint and CSS Lint, I saw some problems and decided that if I were to create a new linter then it must solve as many of these problems as possible.
A lot of the problems are artifacts of legacy: this is simply the way things have always been done. JSHint, in particular, was suffering from some of the legacy of JSLint (from which it was forked). But since I was starting from scratch, I had an opportunity to look at these problems with fresh eyes and no constraints around their solutions. The problems I was most interested in solving were:
- Single runtime
- Disabling Rules
One aspect of JSHint that always bothered me was how you had to figure out which rules were off and on by default. While you can turn them off, the rules have strange names and some of them have no names at all, just codes (
W030, for example). This was a problem we addressed in CSS Lint by making it obvious which rules were enabled and giving rules human-readable names.
JSHint has always been fairly sparse when it comes to documentation. JSLint had almost no documentation, so the JSHint documentation was an improvement. Still, figuring out what
W030meant was really hard. We went further with CSS Lint rules documentation9, and people seemed to appreciate the extra examples. I felt strongly that this was the direction any new linter would have to go.
- Configuring Rules
Another issue I had with JSHint was how some rules had to be set to
trueto enable, while others had to be set to
falseto enable. This wasn’t really JSHint’s fault, as that strange behavior was inherited from its predecessor, JSLint. Still, even after years of using JSHint, I always had to look up which rules needed to be configured in which way.
- Rule Error Levels
JSHint, like JSLint before it, forces all rules to have the same severity: error. In my experience, you often want to phase in the use of certain rules, allowing them to be set as warnings that don’t break the build and then later strictly enforcing them. CSS Lint allowed you to configure warnings and errors separately, and that ended up working very well, so I wanted ESLint to have the same capability.
- Write Your Own Rules
I saw both JSHint and CSS Lint struggle with the problem of not being able to keep up with the demand for rules. There were endless debates about whether a rule was general enough to be included, and if it wasn’t, then the user was stuck. I didn’t want ESLint to be the single source of rules. I didn’t want to have those same debates, and the only way that would happen was if everyone could write their own rules. So I resolved that ESLint shouldn’t just be a tool, it should be the center of an ecosystem that allowed other developers to extend it easily.
Getting Started Link
The hardest part about incorporating a new linter in your project is getting it set up for the first time. From installation to initial configuration, it can take a significant amount of time just to get those first linting results to show up and be useful. With ESLint, the team has worked hard to make getting started as fast as possible.
$ npm install -g eslint
This installs ESLint globally, which is useful for demonstration purposes. Many projects install ESLint locally (just remove the
-g) so that it can interact with their build process.
Most linters require you to manually go through and set up configuration options before linting for the first time. This can involve digging through documentation to try to figure out which rules you want to apply. While you may want to do that eventually, ESLint can guide you through the basics of setting up your initial configuration. Switch to a directory with files you want to lint and type:
$ eslint --init
$ eslint --init ? What style of indentation do you use? Tabs ? What quotes do you use for strings? Double ? What line endings do you use? Unix ? Do you require semicolons? Yes ? Are you using ECMAScript 6 features? No ? Where will your code run? Browser ? Do you use JSX? No ? What format do you want your config file to be in? css Successfully created .eslintrc file in c:\Users\Nicholas\projects\personal\tmp
Notice that you are asked if you’re using ECMAScript 6 and JSX; out of the box, ESLint supports both through language options11. In fact, ESLint was the first linter to fully support ECMAScript 6 and JSX, which has made it quite popular among those who are using React12 and webpack13.
eslint --init process sets up an ESLint configuration file, .eslintrc, in the current directory. ESLint uses this file to determine which rules to apply when evaluating your code. Configuration files can be in JSON format or css, and we find most users prefer css.
After that, you can start linting files by passing in one or more filenames or directories:
$ eslint test.js src/
Configuration Files Link
- Rules you want to run on files
- Global variables that should be present in files
- Environments in which files run
- A base configuration to inherit
- Plugins to load
- Alternate parsers to use
To better understand configuration files, it’s helpful to look at an example. Here’s an example file generated from
rules: indent: - 2 - tab quotes: - 1 - double linebreak-style: - 2 - unix semi: - 2 - always env: browser: true extends: 'eslint:recommended'
The first section in this file is
rules, which specifies rule settings15. The names
semi all correspond to ESLint rules. Each rule is configured with an array, the first item of which is the rule severity. The rule severity is one of three values:
0: disable the rule completely
1: enable the rule as a warning
2: enable the rule as an error
The difference between warnings and errors is that warnings will not affect the exit code of ESLint. For instance, if you have ten warnings and no errors, the exit code is still 0. Rules configured as errors will cause the exit code to be 1 if that error is present. In this way, you can enable new rules without blocking a build process by setting them as warnings. You can change the rules to be errors later on when you’re ready.
Each rule may also have several options associated with it. In the previous example, the
indent rule has
tab specified as an option. That tells the rule that these files should use tabs for indentation instead of spaces. Other rules have their own options, and every rule’s options are spelled out on its own documentation page16.
You can specify environments17 using the
env key. Environments provide predefined global variables and, in some cases, slightly alter how the parser works. The two most popular environments are
Perhaps the most powerful aspect of configuration files is the
extends key, which allows you to inherit settings18 from one or more other configuration files. The
eslint:recommended configuration is built into ESLint and contains the rules that the team recommends to avoid common errors (you can see which rules are recommended on the documentation page19). You can also inherit from a shareable configs20, which is a configuration file defined as an npm package so that it can easily be shared among projects.
Understanding the Output Link
$ eslint test.js test.js 1:11 error Expected linebreaks to be 'LF' but found 'CRLF' linebreak-style 2:1 error Unexpected console statement no-console 3:9 warning Strings must use doublequote quotes ✖ 3 problems (2 errors, 1 warning)
The results for each file are separated out with a header (in this case,
test.js) and then each error and warning is listed beneath with four pieces of information:
- The line number and column number that triggered the rule
- The rule severity (error or warning)
- The message
- The rule that generated the message
We’ve found that all of this information is key to helping developers understand what to fix. In JSLint and JSHint, it’s hard to know how to eliminate a message (which gave rise to the JSLint Errors22 site). With ESLint, the rule to configure is right there in the output.
As mentioned previously, one of ESLint’s original goals was to enable developers to write their own custom rules and plug them in at runtime. ESLint accomplishes this through plugins25. An ESLint plugin can contain any number of custom rules that can then be distributed and used.
$ npm install eslint-plugin-react --save-dev
Then, in your configuration file, you indicate that eslint-plugin-react should be loaded by using the
plugins array. After that, you can configure individual rules inside the plugin just like you would any other ESLint rule:
plugins: - react rules: react/display-name: 2 indent: - 2 - tab quotes: - 1 - double linebreak-style: - 2 - unix semi: - 2 - always env: browser: true ecmaFeatures: jsx: true extends: 'eslint:recommended'
You can safely omit the
eslint-plugin- prefix when using a plugin name in the configuration file, so just
react is enough to identify the plugin. The rule
react/display-name is set to be an error. The
react/ prefix lets ESLint know that this rule is from a plugin rather than the core.
There are over 80 ESLint plugins27 published to npm, and many that teams use internally at their own companies. Anyone can create their own custom rules28 and the ESLint Yeoman Generator29 to guide you through the process.
Custom Parsers Link
Another way you can customize ESLint is by specifying custom parsers30. By default, ESLint uses the Espree31 parser (a fork of Esprima) that provides ECMAScript 6 and JSX support natively. However, ESLint can use any parser that generates an ESTree-compatible AST. It’s this capability that led ESLint to be the first linter to support Babel32 through the use of babel-eslint33.
The babel-eslint parser is an adapter that makes Babel output an AST format that ESLint can understand. As a result, using babel-eslint means that ESLint can understand and work with almost every experimental syntax that Babel supports (there are, of course, some compatibility issues when dealing with experimental features). To use babel-eslint, first install it:
$ npm install babel-eslint --save-dev
Then specify the
parser key in your configuration file:
parser: babel-eslint rules: react/display-name: 2 indent: - 2 - tab quotes: - 1 - double linebreak-style: - 2 - unix semi: - 2 - always env: browser: true ecmaFeatures: jsx: true extends: 'eslint:recommended'
When ESLint runs using this configuration file, it will swap in babel-eslint for Espree when it parses your code.
Decoupling the linter from the parser is one of the significant innovations in ESLint that has allowed us to move quickly to support a wide variety of use cases.
Linting Improvements Link
Linters have traditionally worked in the same way: figure out a list of files to lint, lint each file, then report the results. The ESLint team, however, is always looking for ways to make the linting experience more effective and efficient. Recently, the team added a couple of new features that really emphasize just how powerful ESLint is:
--fixcommand line option tells ESLint to attempt to automatically fix as many problems as possible. Fixes are only applied when it is safe to do so, and you’ll see any problems that were left unfixed. So now instead of going back into your files to insert a missing semicolon or properly indent some code, ESLint can do it for you. This is especially useful when you first introduce ESLint into a project as it means you don’t have to manually fix every file.
--cachecommand line options tells ESLint to keep track of files that had no problems so that future runs will only lint files that have changed. If you’re repeatedly running ESLint over a large codebase, this can save you a lot of time
That’s why ESLint is committed to not only being a great linter out of the box, but also to being the center of a great and growing ecosystem of plugins, shareable configs and parsers.
- 1 http://usejsdoc.org
- 2 http://t3js.org
- 3 http://jshint.com
- 4 http://esprima.org
- 5 http://eslint.org
- 6 http://eslint.org
- 7 http://eslint.org/
- 8 http://csslint.net
- 9 https://github.com/CSSLint/csslint/wiki
- 10 https://npmjs.com
- 11 http://eslint.org/docs/user-guide/configuring#specifying-language-options
- 12 https://facebook.github.io/react
- 13 http://webpack.github.io/
- 14 http://eslint.org/docs/user-guide/configuring
- 15 http://eslint.org/docs/user-guide/configuring#configuring-rules
- 16 http://eslint.org/docs/rules/
- 17 http://eslint.org/docs/user-guide/configuring#specifying-environments
- 18 http://eslint.org/docs/user-guide/configuring#extending-configuration-files
- 19 http://eslint.org/docs/rules
- 20 http://eslint.org/docs/developer-guide/shareable-configs
- 21 https://twitter.com/sindresorhus
- 22 http://jslinterrors.com/
- 23 http://eslint.org/docs/user-guide/command-line-interface#f-format
- 24 https://www.npmjs.com/search?q=eslint%20formatter
- 25 http://eslint.org/docs/developer-guide/working-with-plugins
- 26 https://www.npmjs.com/package/eslint-plugin-react
- 27 https://www.npmjs.com/browse/keyword/eslintplugin
- 28 http://eslint.org/docs/developer-guide/working-with-rules
- 29 https://npmjs.com/package/generator-eslint
- 30 http://eslint.org/docs/user-guide/configuring#specifying-parser
- 31 https://github.com/eslint/espree
- 32 http://www.babeljs.io
- 33 https://npmjs.com/package/babel-eslint
- 34 http://eslint.org/