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.
Here is a view of the ESLint directory structure:
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:
That can be represented by the following syntax tree:
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:
One of the most important pieces of information found here is the node type. In this example there are three types of nodes: VariableDeclaration, VariableDeclarator 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.
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:
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.
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.