daniel reed

ESLint no-restricted-syntax

Keeping codebases clean is hard. And so is writing ESLint plugins.

When migrating between libraries, enforcing coding styles, or anytime you need to analyse the code being written to a codebase,

ESLint comes with a useful rule built in called no-restricted-syntax. This allows you to write custom ESLint rules without the hassle of scaffolding the code, writing advanced transformers, or even testing.

Instead, you give an AST selector, and the message you want to show. It's made for small, simple rules. If you want anything advanced, or even the option to pass a --fix command to help solve the issue, you're going to need to write a full rule.

But writing a rule for the no-restricted-syntax plugin is pretty easy, and all you're going to need is a selector and a message. The selector is an AST selector which ESLint will use to check as it traverses your code.

Example

This is a very contrived example, but hopefully shows what you can do with no-restricted-syntax. Here we're going to make a rule that warns developers about using alert() in their React components.

const Counter = () => {

 const handleClick = () => {
   alert('You clicked ' + ref.current + ' times!');
 }

 return <button onClick={handleClick}>Click me!</button>;
};

AST link: https://astexplorer.net/#/gist/073b94ad46a79cce3d53267ca5fe64a2/latest

In the AST link, you can see how the @typescript-eslint/parser has analysed the code into an AST (abstract syntax tree), breaking down the code into something more understandable for a compiler.

If you click on the alert function, you will see everything the explorer has found about that piece of code in the context of the whole block. We can see it has an Identifier and a name as well as some arguments, and a range of where the code is in the block. A little further up we can see that this is a CallExpression, a function that can be called and executed.

Using all of these hints, we can create a rule for ESLint to find this same code and return a warning or error.

Adding to ESLint

I'm going to assume you have an existing ESLint config for the rest of this guide. The no-restricted-syntax takes an array of rules, where the selector takes a string with a matching query, and the message is the warning or error message you want to show.

First up, you will need to add no-restricted-syntax as a rule to your ESLint config:

 "rules": {
   "no-restricted-syntax": [
     "warn",
     {},
   ],
 }

Now we are ready to add our restricted syntax. In our example above, we can use what we learnt from the AST explorer and use the CallExpression and name values to create a selector:

{
 "selector": "CallExpression[callee.name='alert']",
 "message": "Don't use alert"
}

Now we can add this to our existing config:

{
 "rules": {
   "no-restricted-syntax": [
     "warn",
     {
       "selector": "CallExpression[callee.name='alert']",
       "message": "Don't use alert"
     }
   ]
 }
}

Now if you restart your ESLint server, you should see the warning in the console (or when you hover on the code in your editor) when you try to use alert, along with the warning message we added.

Getting advanced

This is just the beginning of what you can do with custom ESLint rules. You can start to get more advanced once you get comfortable with the AST explorer and finding target selectors. For example, if you want to prevent your team importing from a specific library, you can use something like this:

{
 "selector": "ImportDeclaration[source.value='lodash']",
 "message": "Don't use lodash. Use lodash-es instead or a native alternative."
}

Maybe you want to prevent the use a specific import from a library.

import { compact } from "lodash-es";
    // ☝️ use a native alternative
export const myFunction = (data: unknown[]) => {
 return compact(data);
}
{
 "selector": "ImportDeclaration[source.value='lodash-es'] > ImportSpecifier[imported.name='compact']",
 "message": "Don't use lodash. Use JavaScript instead: https://youmightnotneed.com/lodash#compact"
}

Quick note: Using the > is a way for you to tell ESLint to look through an array of results and find the first match. ImportSpecifier returns an array of items so we want to loop through them to find the matching name.

That's it. Play around and see how using no-restricted-syntax can help you enforce coding styles and best practices in your codebase.

Here are some links to the docs, the AST explorer, and some blogs that helped me understand how to write rules:

#eslint