DEV Community

Andrew Vassilyev
Andrew Vassilyev

Posted on

ESLint Plugin. What was missed in the doc?

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 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 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 to an ESLint traverser(LOL. line 1358)

It is a bad idea because the plugin tries to traverse the AST tree by itself. Let's try to use the ESLint traverse instead of creating our own.

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 to traverse the AST tree. Why is it important?

Let's see a plugin structure:

create(context) {
  return {'Selector': callback}
}
Enter fullscreen mode Exit fullscreen mode

No. It is a bad view of the plugin structure. Let's add the meaningful markup to the code.

create(context) {
  // Memory
  return {'CSS-like selector': callback}
}
Enter fullscreen mode Exit fullscreen mode

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

The source code was simple

async function foo() {
  await 1;
}
Enter fullscreen mode Exit fullscreen mode

Yes, Babel transforms our async code to the state-machine. And... structure is similar to our plugin structure. We have cases(CSS-Selectors) and a memory part.

Pseudo-async version of plugins

In a better world, the plugin would look like this:

async create(context) {
  // do smth
  await waitForSelector('Selector');
  // do smth
  await waitForSelector('Selector 2');
}
Enter fullscreen mode Exit fullscreen mode

And we can transform this code to babel-like output:

create(context) {
  let flag1 = false;

  return {
    'Selector': () => {flag1 = true},
    'Selector:exit': () => {flag1 = false},
    'Selector2': () => {if (!flag1) {return} /* do logic */}
  }
}
Enter fullscreen mode Exit fullscreen mode

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

Traverse
Image description

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:

create(context) {
  return {
    `CallExpression`: traverseAndAnalyse,
  };
}
Enter fullscreen mode Exit fullscreen mode

But you can remember about ESLint traverse logic and memory place:

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,
  };
}
Enter fullscreen mode Exit fullscreen mode

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 - a helpful tool to see the AST version of your code
ESLint Selectors - the list of selectors
typescript parser playground. astexplorer analogue for typescript. It has an essential feature: ESQuery filter. It allows you to debug your selectors in real-time.

Top comments (0)