Playing around with ESLint

Matthijs groenPosted by Matthijs Groen on 28-1-2021

In order to learn myself Functional Programming (FP) using Javascript, I wanted to restrict myself from cheating and forcing myself to a particular syntax.

To do this, I used ESLint

eslint is a linting tool for EcmaScript, where you can define your own rules of what you consider to be “good” code.

ESLint has no fixed set of rules. There are recommended configurations for different environments, but you are free to add or modify your own rules.

I Also gave a presentation @ Kabisa about this subject. So you can also checkout the video below.


Powerful rule

There is one rule that is really, really powerful: no-restricted-syntax (https://eslint.org/docs/rules/no-restricted-syntax)

It works like this: The EcmaScript/JavaScript you write is a piece of text that goes through a parser. This parser in turn will create a data model of the code that has been typed. This is called an Abstract Syntax Tree (AST).

You can see this in action using https://astexplorer.net/ (You can explore the AST of many languages there). You can enter code in the left input, and it will show the AST (Abstract Syntax Tree) on the right side.

You can compare this tree a bit like HTML also is a tree of nodes (and attributes). Following this same concept, you can target specific elements in this tree using a selector (just like in CSS!)

https://eslint.org/docs/developer-guide/selectors

To show you some examples how neat this is:

1
2
3
4
5
6
7
8
9
10
11
{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "CatchClause > BlockStatement[body.length=0]",
        "message": "No empty catch blocks!"
      }
    ]
  }
}

This line will now add an error if you have empty catch blocks.

or this one:

1
2
3
4
5
6
7
8
9
10
11
{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "CatchClause > :not(CallExpression[callee.name=\"logError\"])",
        "message": "Missing error reporting in catch block"
      }
    ]
  }
}

Having the option for a custom message can also give hints of how you want it to be done:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "FunctionExpression",
        "message": "Please use the lambda () => notation"
      },
      {
        "selector": "VariableDeclarator[id.name=/get.*/] > ArrowFunctionExpression > AssignmentExpression",
        "message": "No assignments in getters"
      },
      {
        "selector": "VariableDeclarator[id.name=/select.*/] > ArrowFunctionExpression[params.0.name!=\"state\"]",
        "message": "First argument of selector should be called state"
      }
    ]
  }
}

No-restricted-syntax

ESLint has a lot of great rules out of the box, but the no-restricted-syntax takes the linting to a whole new level.

There are some things you cannot do however. I wanted to create a rule to restrict self-referencing recursion. But since I needed the identifier to match, you cannot use placeholders to match it.

I had to create a custom ESLint plugin for this. Fortunately, creating one is really easy, using yeoman and the generator created here.

“Follow the instructions”

Its mainly a “follow the instructions” exercise. There were 2 pitfalls I ran into:

One was specifying a parser for my rules, because the default parser is ES3, and not a modern JS parser.

So my generated test looked like this:

1
2
3
4
5
6
7
ruleTester.run("fp-challenge", rule, {
  invalid: [
    {
      code: "const fib = n => n <= 1 ? 1 : fib(n - 1) + fib(n - 2);",
    },
  ],
});

And I needed to add parser options to that test:

1
2
3
4
5
6
7
8
ruleTester.run("fp-challenge", rule, {
  invalid: [
    {
      code: "const fib = n => n <= 1 ? 1 : fib(n - 1) + fib(n - 2);",
      parserOptions: { ecmaVersion: 6 },
    },
  ],
});

Before I got the proper error message.

The other pitfall is if you want the user to allow specifying options for your rule, you need to provide a JSON schema to allow valdiation for these options. But the problem is, you need to restart the tests before the provided JSON schema is reparsed. So if you run the tests using yarn test --watch you keep wondering why tests keep failing or keep passing, and not pick up the changes you made to the schema.

I found the Understanding JSON schema helpful in the defintion of a schema. The main website is confusing to find the right info.

Having a proper ESlint configuration helps in enforcing best practices of your team, and even leave helpful hints to other members just joining your team. It can help capture the spirit of development within the project itself.

Happy linting!

To checkout the plugin I wrote for my FP Challenge, check: https://github.com/matthijsgroen/eslint-plugin-fp-challenge

Matthijs groen

Matthijs Groen

Full Stack Web Developer • Hobby Indie Game Developer Github: matthijsgroen • Twitter: @matthijsgroen

Bij Kabisa staat privacy hoog in het vaandel. Wij vinden het belangrijk dat er zorgvuldig wordt omgegaan met de data die onze bezoekers achterlaten. Zo zult u op onze website geen tracking-cookies vinden van third-parties zoals Facebook, Hotjar of Hubspot. Er worden alleen cookies geplaatst van Google en Vimeo. Deze worden gebruikt voor analyses, om zo de gebruikerservaring van onze websitebezoekers te kunnen verbeteren. Tevens zorgen deze cookies ervoor dat er relevante advertenties worden getoond. Lees meer over het gebruik van cookies in ons privacy statement.