« Scott Nonnenberg


ESLint Part 1: Exploration

2016 Jun 14

eslint logo

I recently spent some time to review my ESLint setup. It’s got lots of rules, and a healthy ecosystem of plugins. It’s intimidating! Maybe I can help you make some sense of it…

Why ESLint?

For a long time, JSLint, then JSHint and JSCS were the primary Javascript linting tools. They were important, because Javascript is such an easy language to make mistakes in. To wit, JSLint originally came from the author of Javascript: The Good Parts, which famously covers a small subset of the language. Of course, the rest is suggested to be ‘the bad parts’ - JSLint was designed to help you avoid them!

There are three major factors that caused ESLint to become the gold standard for linting:

1. Support for modern Javascript

React was released in early 2013, and with it came a return to compiling something other than pure Javascript into its final browser-ready form. CoffeeScript was popular for a while, but seemed more like unnecessary syntactic sugar. React, however, was substantially easier to use with JSX. To sweeten the pot, tools to translate JSX also included support for ES2015. And none of the leading linters could handle JSX or modern Javascript syntax.

2. Plugin architecture

While the previous linting tools did have a large set of options to configure, adding something like JSX support required a fork of the project. In ESLint, however, support for something totally new could be added via plugin. All sorts of plugins are available today, vastly expanding upon ESLint’s core value proposition.

3. No rules on by default

Of course, ESLint does have a substantial and very useful set of built-in rules. When ESLint started out in June 2013 it was just like its predecessors in that it was quite opinionated, preloaded with a default set of enabled rules. But all rules are now off by default, as of the version 1.0.0, released in mid-2015. ESLint is no longer an opinionated tool, but a blank slate, ready to be molded into something useful.

Jumping in

I was a late adopter of ESLint. I got away with it because I didn’t really like JSX when I was first using React, so JSHint and JSCS were good enough for me. Thus, ESLint was a big project for indeterminate gain. Eventually I gave in, took the plunge, and upgraded my standard approach: Webpack, Babel, and ESLint instead of RequireJS and JSHint.

I figured that the easiest way to jump in was a preset. The AirBnB ESLint configuration had a pretty good ethos: prefer the ES2015 way of doing things. What better way to jump into the new syntax with both feet?

This served me well for a while, but I slowly realized that I was being kept from detail I cared about. Yes, because of ESLint’s excellent support for building upon others’ configurations, I could make any customization I wanted on top of the config I was extending. But it encouraged me to take what was given to me. And I was sure I was missing something.

Making it my own

Recently, instead of upgrading to the latest AirBnB config, I embarked on my own odyssey of ESLint discovery. My first step was copying all of AirBnB’s configuration into a single .eslintrc.js file. It felt really good having it all in front of me like that - I could clearly see how many rules were disabled!

And then I went, rule by rule, attempting to turn everything on. But I didn’t do it in a vacuum; I used an existing project with a couple thousand lines of code to test each change. In some cases I absolutely changed a lot of code ('comma-dangle': ['error', 'always-multiline']) and in others I wanted to turn it on but the current codebase didn’t really allow it ('max-statements': 'off').

When I was done, I wanted to be sure. I went looking for something that would guarantee that I had made a decision for every rule, and found the find-eslint-rules node module. Once installed, I had a new npm script to tell me whether I had configured, one way or another, every available rule available.

Expanding horizons with plugins

I felt good, but I wasn’t done. A large amount of the value in the world of ESLint comes from its ecosystem. I needed to explore the world of ESLint plugins. I had encountered some via the AirBnB config, some via newsletters and social media, but I wanted to do a sweep myself.

And so, I went deep into the NPM registry with a generic ‘eslint-plugin’ search. Page after page, collecting plugins that looked interesting. Here are some I found notable…

General

eslint-plugin-import is an amazing plugin, alerting you to missing or malformed require() or import statements. Before, you had to run your code to catch incorrect references! It made me realize the kinds of high-value things ESLint could do.

eslint-plugin-security is similarly creative. The Node.js Security Project reviews node modules and their nsp tool checks for vulnerable node modules in your dependencies. This plugin helps them do that job, and it can help you with your project as well. Note: it will fire false positives!

eslint-plugin-better draws from more Douglas Crockford wisdom, attempting to limit you to the ‘good parts’ of Javascript. If you have some features of Javascript you want to avoid in your project, this plugin might be for you!

eslint-plugin-filenames makes filenames consistent across your project. It’s about more than just consistency - incorrect casing can cause problems on different OSes, since some are case-sensitive, some aren’t. You definitely don’t want something that works locally to break only the server, right?

React

eslint-plugin-react is a must-have if you’re using React with JSX. It will communicate that variables used inside of JSX are used globally, so ESLint’s no-unused-vars rule stops firing. It can also push you towards simple Stateless Components when appropriate, and definitely helps with standardizing JSX formatting across your project.

eslint-plugin-jsx-a11y helps you remember to include accessibility attributes in your JSX. It’s so easy to forget about this stuff. I very much recommend using this plugin.

Functional design

eslint-plugin-immutable is a simple little plugin that keeps me honest when I’m trying to use functional design in my projects. No let keyword, no mutation of objects, and no this. Sadly, it doesn’t work very well with CommonJS.

eslint-plugin-no-loops takes it even further, pushing you towards map() and reduce() instead of iteration. It’s a great exercise to implement something without loops when you’d usually reach for a loop. Quite frequently I find that the logic gets simpler and easier to reason about.

Testing

eslint-plugin-chai-expect is a tiny little plugin. It checks for common assertion mistakes. My favorite is terminating-properties which reminds you whether it should be expect().to.exist or expect().to.exist(). It’s the former. :0)

eslint-plugin-bdd, another plugin, protects against something I’ve accidentally included in pull requests a couple times. Without an easy way to --grep your tests runs, it’s common to use .skip() or .only() to exclude or run a subset of your tests. Use it, but don’t commit it!

Funny

eslint-plugin-json is not a plugin I’d recommend. I like JSON format checking as much as the next guy, but this one pulls in all of JSHint just to check for well-formed JSON!

eslint-plugin-no-js is very opinionated. It recommends that you dispense with Javascript entirely. As it says, “your code sucks.” :0)

Close, but not quite

eslint-plugin-graphql was so close! I really like GraphQL and I use it. I just don’t use it in a way that would make using this plugin easy since I’m not using Relay. I’ll probably get there, especially if I submit a PR!

eslint-plugin-promise is needed. I see incorrect, error-swallowing use of promises all the time. But this plugin has some key bugs. Again, I’ll probably come back to this one later.

So many!

I tried others that didn’t make the cut, and there are certainly plugins I missed entirely. Let me know if there’s one you find useful that’s not in this list!

Announcing: My Configuration!

After all my explorations, including tests in a number of projects, I decided to bring all that learning together into one core project: @scottnonnenberg/eslint-config-thehelp, available via npm now! It greatly shrank my .eslintrc.js file in each project, and makes it far easy to start up a new project.

One install

I wanted to make it a single install, so my configuration includes ESLint and all needed plugins. And this means, given ESLint plugin resolution rules, that you need npm version 3 and its flat node_modules directory structure. It seems like a reasonable tradeoff. peerDependencies are messy.

Principles

As I was coming up with my ruleset, I kept a couple key principles in mind. First:

Rules specify the 98% case. eslint-disable will cover the rest. I can periodically search for those exceptions and re-verify their need.

Additional more-specific principles emerged as I refined my approach:

  • Always use ‘error’ or ‘off’ instead of 0 and 2. Numbers are for real config values.
  • No warnings. Either disallow it completely, or don’t worry about it at all.
  • All ‘off’ rules must have a reason mentioned in comments.
  • Don’t include any configuration if that configuration is the same as the defaults.
  • Rules are in alphabetical order: first core ESLint, then plugins sorted by name
  • All rules from core and plugins must be included

Multiple configurations

In my explorations I discovered a couple different types of configurations I’d need, so I included this set in the package:

With ESLint it’s really easy to choose one or more of these to merge into your final configuration:

{
  extends: [
    '@scottnonnenberg/thehelp/core',
    '@scottnonnenberg/thehelp/es2015',
    '@scottnonnenberg/thehelp/react',
  ],
}

Testing

It’s worth talking about my test strategy for the package. Sadly, I have had to release new versions because ESLint plugin resolution rules don’t work with npm link. Thus it’s a bit hard to test.

But I can do these things, and I’d recommend them for you too:

Optimizations

While digging pretty deep into ESLint, I discovered a couple things I didn’t know before. Maybe you’ll find them useful?

Hierarchical configuration

You probably have one .eslintrc.js or eslintrc.yml in your root project folder. But did you know that you can put one of these files in any folder, and eslint will pick it up? What’s more, ESLint will merge configuration in parent directories with that for the current directory. That’s why I added the test and scripts configurations to @scottnonnenberg/eslint-config-thehelp.

For example, in test/.eslintrc.js I can include this:

{
  extends: [
    '@scottnonnenberg/thehelp/test',
  ],
}

All of my global project configurations will apply, and the changes in the test configuration will be merged in. Fancy!

Editor Integration

I resisted adding a linter into my project for a long time, preferring to wait to do a test/lint run later. But the additional value ESLint provides over classic linting is useful real-time. And with SublimeText 3, SublimeLinter, and its ESLint plugin it’s all very fluid, no configuration fuss or undue delays.

I love getting immediate feedback about incorrect paths in require() or import. The red everywhere when I start a new file can be annoying, but I think it’s worth it.

Gotchas with babel-eslint

babel-eslint is an alternate parser which can be useful if you’re using bleeding edge Babel language constructs. In my case, I like to use static properties in my React components for propTypes.

It doesn’t always work smoothly, however. Sometimes bugs are fixed for the default parser but not for babel-eslint.

I ran into some performance issues as well. If ESLint is slow for you with babel-eslint, first ensure you installed everything with npm 3.x, which automatically de-dupes modules. Then, double-check the versions of all your top-level Babel-related dependencies. I saw a big improvement when I made a couple key versions consistent:

Before (17.8 seconds)

{
  "babel-cli": "6.7.7",
  "babel-eslint": "6.0.4",
  "babel-preset-es2015": "6.6.0",
}

After (4.9 seconds)

{
  "babel-cli": "6.9.0",
  "babel-eslint": "6.0.4",
  "babel-preset-es2015": "6.9.0",
}

Use it!

ESLint is an amazingly useful tool. If you’re not using it today, I recommend that you start. If you already are, I hope this article helps you leverage it fully!

And you can use @scottnonnenberg/eslint-config-thehelp directly, submit pull requests to improve it, or fork it and make it your own!

But first check out the next post in the series: ESLint Part 2: Contribution, about contributing back to the ESLint ecosystem. Or skip to the final post, ESLint Part 3: Analysis!

I won't share your email with anyone. See previous emails.

NEXT:

ESLint Part 2: Contribution 2016 Jun 16

I recently wrote about my ESLint exploration and configuration node module. But I went further than that - I contributed back to the ESLint community! I submitted several pull requests and released... Read more »

PREVIOUS:

Customizing Agile 2016 Jun 07

Ever since the term Agile started to take hold in larger organizations, there’s been a growing backlash among developers. Sadly, this is to be expected. As I pointed out in my last post on Agile... Read more »


Hi, I'm Scott. I've written both server and client code in many languages for many employers and clients. I've also got a bit of an unusual perspective, since I've spent time in roles outside the pure 'software developer.' You can find me on Mastodon.