Skip to main content

Command Palette

Search for a command to run...

Make ESLint do the Work — Adding Custom Rules

Find out how to leverage ESLint to encourage your own best practices, by creating your own linting rules.

Updated
4 min read

ESLint is a great tool for encouraging best practices within a JS/TS codebase. It takes what sometimes exists just in a document on confluence somewhere into something that can be automatically checked for in every change.

Automating these checks means reviewers or collaborators can focus on higher-level questions, instead of comments focussed on pointing at the same issue that’s been repeated many times over or missed in previous commits.

ESLint comes with a great set of built-in rules and some great community libraries (we’ll cover more on setting this up for a project in a future post), but they have to draw the line somewhere. Some codebases or companies might have more specific needs or preferences. For example, to support migrating away from a particular library method call or avoiding an approach that has known issues.

ESLint can be extended in 2 ways — using “no-restricted” rules or creating a custom plugin.

Configuring “no-restricted” Rules

There are 3 built-in rules that we can use to configure custom restrictions:

These rules make it particularly easy to extend ESLint, without the need to implement an actual plugin.

Example

To demonstrate that, let’s run through an example.

While reviewing code changes, we’ve noticed some instances of the following approach:

async function someFunction() {
  const result = await asyncFunction()
    .then(x => { /* do something */});
  return result;
}

We’ve had a conversation in the team around why it’s not ideal to mix promise chains with async/await syntax in a single call like this, so we’d rather that one or the other was used in a particular code block. Ideally, though, we’d automate this check so that await and then aren’t used together in future.

We can do this with the no-restricted-syntax rule. First, though, we need to understand what syntax is to be restricted.

Restricted syntax can take an Abstract Syntax Tree (AST) selector. ASTs are a representation of the structure of code and are used by a variety of static analysis tools and code generators. https://www.twilio.com/blog/abstract-syntax-trees is a decent intro to ASTs if you want to know more. The ESLint docs also have some examples of using AST selectors to restrict syntax.

How do you know what AST selector to use for your use case? Well, the first step I’d suggest is to take the snippet of code that has the syntax to be restricted and copy it into https://astexplorer.net/. This tool parses and displays the AST for the code that you’ve provided. If you click on a particular method call or parameter, you’ll see that particular node in the tree become highlighted, as shown below.

A screenshot from astexplorer.net, showing the part of the tree that represents the call to the

By selecting the await keyword and seeing what’s focussed in the AST, we can see that AwaitExpression is the name for the node type we’re looking for. But we don’t want to restrict the use of all await calls, so we need to figure out what properties of that node to use to make our selector more specific.

Similar to CSS attribute selectors, we can specifically look for AwaitExpression nodes that have a particular nested property value. If we expand the properties of that node, we eventually find what we’re looking for — the “then” property name. If we use the keys to create a path to the “then” keyword, we can pull together the below AST attribute selector:

AwaitExpression[argument.callee.property.name="then"]

To configure the no-restricted-syntax rule to generate an error using our selector with a more helpful error message, we can add the following to the rules section of our .eslintrc (or wherever else ESLint config is stored):

"no-restricted-syntax": [
  "error",
  {
    "message": "promise.then is unnecessary when using async/await",
    "selector": "AwaitExpression[argument.callee.property.name=\" then\"]"
  }
]

(Note the quotes around “then” are escaped).

This takes some practice, but this approach can be very powerful when looking to automate checks for best practices and conventions in a codebase. The examples in the ESLint docs on restricting syntax are a good starting point, but ultimately the best way to get started and learn is to give it a shot!

Custom Plugin

In some cases, those rules won’t be enough. In those cases, you can take a look at implementing a custom ESLint rule. This still deals with an AST but gives you much more control to create custom logic to implement the desired check.

Check out https://eslint.org/docs/developer-guide/working-with-plugins#create-a-plugin for info on how to get started, and also https://dev.to/spukas/how-to-write-your-first-eslint-plugin-145, which walks through creating a simple plugin.

And that’s it! Next time you see something that you think would be good to automatically check for in a project you’re working on, think about how this could be done via an ESLint rule.

Further Reading

https://www.twilio.com/blog/abstract-syntax-trees https://eslint.org/docs/developer-guide/selectors https://dev.to/spukas/how-to-write-your-first-eslint-plugin-145 https://astexplorer.net/