Maintaining JavaScript Code Quality with ESLint

By

As a lead UI engineer on the consumer web team at PayPal I’ve often seen patterns of mistakes that repeated themselves over and over again. In order to put an end to the most egregious errors we started using JSHint early on in the project. Despite its usefulness in catching major syntax errors, it did not know anything about our code, our patterns or our projects. In trying to improve code quality across the whole consumer web team we needed to find a linting tool that let us teach it how we wanted to code.

Enter ESLint

ESLint was started in 2013 by Nicholas Zakas with the goal of building a customizable linting tool for JavaScript. With that goal in mind Nicholas decided to make each rule standalone and provided a mechanism to easily add additional rules to the system.

Here is a view of the ESLint directory structure:

Screen Shot 2014-12-11 at 9.51.01 AM

ESLint uses the Esprima parser to convert the source code it is linting into an abstract syntax tree. That tree is passed into each of the rules for further analysis. When a violation is found it is reported back up to ESLint and then displayed.

Understanding Abstract Syntax Trees

An abstract syntax tree (AST) is a data-structure that represents the meaning of your code.

Let’s use this simple statement as an example:

Screen Shot 2014-12-11 at 6.52.00 PM

That can be represented by the following syntax tree:

Screen Shot 2014-12-11 at 6.53.34 PM

When Esprima generates an AST from a piece code it returns an object. That object includes information not found in the original source, such as the node type, which proves useful later in the linting process.

Here is the Esprima generated AST for our earlier example:

Screen Shot 2014-12-11 at 9.54.54 AM

One of the most important pieces of information found here is the node type. In this example there are three types of nodes: VariableDeclarationVariableDeclarator and BinaryExpression. These types and other meta data generated by the parser help programs understand what is happening in the code. We’ll take advantage of that information as we learn to write custom rules for ESLint.

Building A Custom Rule

Here’s a simple example of a rule we use internally to prevent overriding a property we rely on in our express routes. Overriding this property led to several bugs in our code, so it was a great candidate for a custom rule.

Screen Shot 2014-12-11 at 9.58.56 AM

As you can see our rule is returning an object which takes as its key the name of the AST node we want to inspect. In our case we’re looking for nodes of type AssignmentExpression. We want to know when a variable is being assigned. As ESLint traverses the AST if it finds an AssignmentExpression it will pass that node into our function for further inspection.

Within our function we’re checking to see if that expression is happening as part of a MemberExpression. A MemberExpression occurs when we’re assigning a value to a property on an object. If that’s the case we explicitly check for the name of the object and the property and then call context.report() to notify ESLint when there has been a violation.

This is a simple example of the power and ease in which custom rules can be built using ESLint. More information about building custom rules can be found on the Working with Rules section of the ESLint home page.

Packaging Custom Lint Rules

ESLint allows you to reference custom rules in a local directory using the –rulesdir flag. You simply tell ESLint where to look for the rule and enable it in your configuration file (using the filename as the key). This works well if the rules are relevant to a single project, however at PayPal we have many teams and many projects which can benefit from our custom rules. Our preferred method is to bundle rules together as an ESLint plugin an install them with NPM.

To create an ESLint plugin you need to create a module which exports two properties: rules and rulesConfig. In our case we’re using the requireindex module to create the rules object based on the contents of our rules/ folder. In either case the key should match the name of the rule and the value should be the rule itself. The rulesConfig property, on the other hand, allows you to define the default severity for each of those rules (1 being a warning, 2 for an error). Any module defined in the node_modules folder with the name eslint-plugin-*  will be made accessible to ESLint.

Here is what our internal plugin looks like:

Screen Shot 2014-12-11 at 10.24.04 AM

If you search for “eslint-plugin” on the npm website there are many plugins provided by the community at large. These shared modules help us identify best practices and catch potential sources of bugs and can be brought in to any any of our projects.

Conclusion

We love ESLint at PayPal. We’ve built it into our continuous integration pipeline and have it integrated with our IDEs. We’re glad to see a vibrant community growing up around ESLint and hope to give back more in the future. As our own Douglas Crockford is fond of saying, “Don’t make bugs!” That’s pretty hard to do, but as least linting helps us catch many of them before they can do much harm.

Jamund Ferguson

Jamund is UI Engineer at PayPal who loves writing JavaScript. He has 2 kids and enjoys spending as much time with them as possible.