ESLint Plugin. What was missed in the doc?
Tags: eslint, tutorial, ast
Published: 2024-05-09 01:22:31

---
title: ESLint Plugin. What was missed in the doc?
published: true
tags: eslint, tutorial, AST
---

ESLint is a potent tool, but you cannot create a good plugin because the documentation doesn't explain how it works. For example, you want to do something with non-closured variables inside a function. It is not a trivial task. The first idea that came to my head was to find similar solutions in open source. The most popular is the `react-hooks/exhaustive-deps` rule. But if you try to [see the sources](https://github.com/facebook/react/blob/04b058868c9fc61c78124b12efb168734d79d09e/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js) you meet a total horror. Let's try to understand how to avoid this complexity inside ESLint plugins.

## Prelude

Let's [open the official tutorial](https://eslint.org/docs/latest/extend/custom-rule-tutorial) on how to create a plugin. It looks nice, and I suppose the authors of the React plugin saw this tutorial. Why am I thinking like that? Because they use a [single attach](https://github.com/facebook/react/blob/04b058868c9fc61c78124b12efb168734d79d09e/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js#L1358) to an ESLint traverser(LOL. line 1358)
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mni0udr3upkjblwm8uhh.png)
It is a bad idea because the plugin tries to traverse the AST tree by itself. Let's [try to use](https://github.com/XaveScor/eslint-plugin-wrap-variables/blob/5b97ba1530500e90ebdc8b702dd15e1924e4f1f5/src/rule.ts#L263-L279) the ESLint traverse instead of creating our own.
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wrfyum57gur6k4xea718.png)
This move allows you to simplify our code significantly. Why can we do what?

## How does ESLint work?

The central meaning of the article is **ESLint use [DFS](https://en.wikipedia.org/wiki/Depth-first_search) to traverse the AST tree**. Why is it important?

Let's see a plugin structure:
```javascript
create(context) {
  return {'Selector': callback}
}
```
No. It is a bad view of the plugin structure. Let's add the meaningful markup to the code.
```javascript
create(context) {
  // Memory
  return {'CSS-like selector': callback}
}
```
Let's remember this and look up to another place.

## Babel and async code

Do you know how Babel transforms your async code for Internet Explorer? [Link to babel playgroud](https://babeljs.io/repl#?browsers=IE%2011&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=IYZwngdgxgBAZgV2gFwJYHsL3egFAShgG8AoGGKTEZGYGAXhgCYzaB3YVGgRgG4SAvkA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=env%2Creact%2Cstage-2&prettier=false&targets=&version=7.24.5&externalPlugins=&assumptions=%7B%7D)
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/grq0j6hpzadaqmoktv1e.png)
The source code was simple
```javascript
async function foo() {
  await 1;
}
```
Yes, Babel transforms our async code to the [state-machine](https://developer.mozilla.org/en-US/docs/Glossary/State_machine). And... structure is similar to our plugin structure. We have cases(CSS-Selectors) and a memory part.
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2r4v4jnh6zyigxxq14su.png)
## Pseudo-async version of plugins

In a better world, the plugin would look like this:
```javascript
async create(context) {
  // do smth
  await waitForSelector('Selector');
  // do smth
  await waitForSelector('Selector 2');
}
```
And we can transform this code to babel-like output:
```javascript
create(context) {
  let flag1 = false;

  return {
    'Selector': () => {flag1 = true},
    'Selector:exit': () => {flag1 = false},
    'Selector2': () => {if (!flag1) {return} /* do logic */}
  }
}
```
You can use memory to save and toggle flags inside state-machine logic. Do you remember about BFS? Yes. We can use selectors to set `am I inside block` flags to determine whether we should execute logic.

And I use it inside my plugin:
[Memory](https://github.com/XaveScor/eslint-plugin-wrap-variables/blob/5b97ba1530500e90ebdc8b702dd15e1924e4f1f5/src/rule.ts#L123-L129)
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/75801f04su63aqnz5877.png)
[Traverse](https://github.com/XaveScor/eslint-plugin-wrap-variables/blob/5b97ba1530500e90ebdc8b702dd15e1924e4f1f5/src/rule.ts#L263-L279)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y0i03vhjn0c6b2j6pr6d.png)

## How to use this info?
Let's try to implement the logic from `react-hooks/exhaustive-deps`: we need to run the analysis inside the defined before functions. For instance, inside `useCallback`.
You can write like the React team:
```javascript
create(context) {
  return {
    `CallExpression`: traverseAndAnalyse,
  };
}
```
But you can remember about ESLint traverse logic and memory place:
```javascript
const functionNameFromConfig = 'useCallback';

create(context) {
  let insideF = false;
  function enterF() {insideF = true;}
  function exitF() {insideF = false;}
  function runAnalysis() {
    if (!isnideF) {
      return;
    }
    // analyse
  }

  return {
    `CallExpression[callee.name="${functionNameFromConfig}"]`: enterF,
    `CallExpression[callee.name="${functionNameFromConfig}"]:exit`: exitF,
    `AnyExpression inside function`: runAnalysis,
  };
}
```
Think async, but write code like Babel. Do not traverse AST by yourself. It is a significant simplification of the code.

## Useful links
[https://astexplorer.net](https://astexplorer.net) - a helpful tool to see the AST version of your code
[ESLint Selectors](https://eslint.org/docs/latest/extend/selectors#what-syntax-can-selectors-have) - the list of selectors
[typescript parser playground](https://typescript-eslint.io/play#ts=5.4.3&showAST=es&fileType=.tsx&code=FBA&eslintrc=N4KABGBEBOCuA2BTAzpAXGYBfEWg&tsconfig=N4KABGBEDGD2C2AHAlgGwKYCcDyiAuysAdgM6QBcYoEEkJemy0eAcgK6qoDCAFutAGsylBm3TgwAXxCSgA&tokens=false). astexplorer analogue for typescript. It has an essential feature: ESQuery filter. It allows you to debug your selectors in real-time.