AST selectors rule
Tags: webdev, javascript, eslint, abstractsyntaxtrees
Published: 2019-04-30 21:30:07

---
title: "AST selectors rule"
description: use a basic knowledge of abstract syntax trees to enforce code standards by adding simple ESlint rules implemented only using AST selectors, requiring no javascript!
tags: ['web development','javascript','eslint', 'abstract syntax trees']
published: true
---

My [previous article on abstract syntax trees](https://www.webpusher.ie/2019/02/07/ast-fun-and-profit/) ran through a quick, but relatively broad, overview of syntax trees and how to manipulate them.

This second article will show you how to use a basic knowledge of abstract syntax trees to enforce code standards by adding simple ESlint rules implemented only using AST selectors, requiring _no javascript!_

## Rule - 'no-restricted-syntax'

Eslint provides a `no-restricted-syntax` rule that allows you add simple rules using AST selectors - which are very similar to CSS selectors.

I'll run through a couple of examples in this article
- [enforce const over var](#constnotvar)
- [ensure `FormattedMessage` never renders a `span`](#formattedmessage)

Examples provided here can be found in the `AST Selectors` folder in the accompanying github repo https://github.com/mlennox/abstractsyntaxforfunandprofit

AST selectors are [implemented using esquery](https://github.com/estools/esquery). Also, the [eslint documentation on selectors](https://eslint.org/docs/developer-guide/selectors) is indispensable as a reference.

## <a name="constnotvar"></a> Const not var

I'll use an example from a previous article - enforce the use of `const` instead of `var`. There is already an [excellent 'no-var' rule](https://eslint.org/docs/rules/no-var#require-let-or-const-instead-of-var-no-var) built-in to eslint. This is implemented as an eslint plugin, which requires some effort to write!

However, we can reproduce most of the functionality of the `no-var` plugin using only AST selectors. As I've already mentioned, AST selectors are based on CSS selectors and won't be a challenge if you have worked with CSS before. I'll explain the construction of the rule in a way that is accessible to those with no knowledge of CSS selectors.

Using the very simple variable declaration below to test against, we'll write an AST selector that will enforce the 'no var' rule in our IDE.

```javascript
var willIt = true;
```

To start, we'll need to remind ourselves of the structure of the AST for a simple `var` variable declaration.

![abstract syntax tree of variable declaration showing attribute kind is var](https://thepracticaldev.s3.amazonaws.com/i/j9fbjahqwcfg73c7rd5n.png)

Firstly, lets try and state the problem in english

> highlight, as an error, any `VariableDeclaration` of kind `var`

Simple enough.

### Creating the selector

Firstly, we need to know how to select our variable declaration. Remember, the node type for our variable declaration is simply `VariableDeclaration`. The AST selector we use is a node type selector - which is simply the type of the node, like so

```javascript
VariableDeclaration
```

Next, as we are selecting against _all_ the nodes in the abstract syntax tree for every file in your codebase, we need to refine our selection to only those of kind `var`. 

The `kind` we refer to is an attribute of the `VariableDeclaration` node.

We can select all nodes that have a `kind` attribute using the following selector

```javascript
[kind]
```

And to select any `kind` attribute that has the value `var` we expand the selector like so

```javascript
[kind='var']
```

Now we have a selector that will select _all_ `kind` attributes with the value `var`, but we only want to select `VariableDeclaration` nodes that have that attribute and value, so:

```javascript
VariableDeclaration[kind='var']
```

This is our final selector, but how do we add that to our list of eslint rules?

### Adding the rule

To apply the rule to our codebase we add the example `no-restricted-syntax` rule to the `rules` section of the `.eslintrc.js` config file

```javascript
"rules": {
    "no-restricted-syntax": [
      "error", "VariableDeclaration[kind='var']"
    ],
}
```

This produces the following error in VS Code

![](https://thepracticaldev.s3.amazonaws.com/i/om6gj1zt7o4unfhfavjd.png)

I think you'll agree that `Using 'VariableDeclaration[kind='var'] is not allowed` is a _**really**_ bad error message. 

### Custom error message

Eslint supports a custom message for rule violations, so let's add that

```javascript
"rules": {
    "no-restricted-syntax": [
      "error",  {
        "selector": "VariableDeclaration[kind='var']",
        "message": "All variables must be declared as 'const', do not use 'var'"
      }
    ],
}
```

This looks a lot better _and_ the added structure to the configuration has the bonus of easier maintenance of your custom eslint rules.

![](https://thepracticaldev.s3.amazonaws.com/i/8zbfspimejvf1wclakt5.png)

What about a more complex example?

## <a name="formattedmessage"></a> React JSX internationalisation - FormattedMessage

If you use `react-intl` you will be familiar with the [FormattedMessage](https://github.com/yahoo/react-intl/wiki/Components#formattedmessage) component that facilitates localised messages in your app.

The `FormattedMessage` component wraps the message in a `span` by default.

```javascript
<FormattedMessage id={`someMessageId`} />
// results in : <span>some message text</span>
```

You can avoid the `span` by using this construction instead

```javascript
<FormattedMessage id={`someMessageId`}>{text => text}</FormattedMessage>
// results in : some message text
```

I don't like it when spurious HTML is added to my layout, so let's write an eslint rule to ensure it doesn't happen. As before we'll state our problem goal in plain english

> highlight, as an error, any FormattedMessage that does not contain child elements

We make a very reasonable assumption here that _any_ children will use the general approach that we require, for example

```javascript
    :
    :
<FormattedMessage id={`someMessageId`}>
  {labelText => (
    <MyComponent
      label={labelText}
      props={this.props}
      />
  )}
</FormattedMessage>
<FormattedMessage id={`anotherMessageId`}>
  {messageText => this.renderSomeStuff(messageText)}
</FormattedMessage>
    :
    :
```

This saves us from having to consider the types and format of the child components.

### AST explorer + JSX = problem

The ever useful [AST explorer](https://astexplorer.net/) does not handle `JSX` so we'll need to use a different approach to visualise the abstract syntax tree.

#### Babel parser with jsx plugin

The helper file `showTree.js` is included in the [github repo](https://github.com/mlennox/abstractsyntaxforfunandprofit/tree/master/ASTselectors/FormattedMessage) but you cannot run this helper function from the repo root:

```bash
cd ASTselectors/FormattedMessage
node showTree.js
```

This will turn the stateless react component in the file `basicReact.js` into a JSON abstract syntax tree. We can use this to try and visualise how we might build a selector that selects only the `FormattedMessage` nodes that have no `{text => text}` child function.

### Visualising the tree structure

The simplified abstract syntax tree for the second `FormattedMessage` in the file `basicReact.js` is shown below. 

Note that the structure is relatively complex - a generic `JSXElement` as a parent container with the attributes `openingElement` and `closingElement` containing instances of the `FormattedMessage` tags themselves and the children of the `JSXElement` are a `JSXEXpressionContainer` containing the anonymous arrow function AST for `{text => text}`

```javascript
{
  "type": "JSXElement",
  "openingElement": {
    "type": "JSXOpeningElement",
    "name": {
      "type": "JSXIdentifier",
      "name": "FormattedMessage"
    },
    "attributes": [ /* not important to us */ ],
    "selfClosing": false
  },
  "closingElement": {
    "type": "JSXClosingElement",
    "name": {
      "type": "JSXIdentifier",
      "name": "FormattedMessage"
    }
  },
  "children": [{
    "type": "JSXExpressionContainer",
    "expression": {
      "type": "ArrowFunctionExpression",
      "params": [{
        "type": "Identifier",
        "name": "text"
      }],
      "body": {
        "type": "Identifier",
        "name": "text"
      }
    }
  }]
}
```

As usual a graphic representation of the simplified abstract syntax tree shows the hierarchy much more clearly.

![](https://thepracticaldev.s3.amazonaws.com/i/9ixzbmw8wnjdc8wis59n.png)


We won't be using the correctly structured `FormattedMessage` AST as a reference when building our selector, I supply this to as a reference to ensure we don't construct a selector that will _also_ select a properly constructed `FormattedMessage`.

Now Lets compare that to the self-closing `FormattedMessage`. A simplified version of the JSON AST is shown below

```javascript
{
  "type": "JSXElement",
  "openingElement": {
    "type": "JSXOpeningElement",
    "name": {
      "type": "JSXIdentifier",
      "name": "FormattedMessage"
    },
    "attributes": [ /* not important to us... */ ],
    "selfClosing": true
  },
  "closingElement": null,
  "children": []
}
```

#### Constructing the selector - approach 1 : JSXElement has no child elements

Referring to the JSON AST, we can see the parent `JSXElement` has no child elements we can select on that basis

```javascript
{
  "type": "JSXElement",
  "children": []
}
```

The selector is simple enough, we want to select the `JSXElement` where the `children` attribute is empty.

```javascript
JSXElement[children='']
```

Its important to note here that the `children` attribute is slightly confusing as the children it refers to are the children of the `openingElement` / `closingElement`. In regard to the AST selectors, the `openingElement` and `closingElement` themselves are the direct descendants (yes, children - hence the confusion) of the parent `JSXElement`. So armed with this information we know we can use descendant selectors to select the `JSXOpeningElement`

```javascript
JSXElement[children=''] JSXOpeningElement
```

This is still too specific. We are still selecting many elements, we only want to select `FormattedMessage` elements inside a `JSXElement` that has an empty `children` attribute.

Once again, some explanation is required. As far as AST selectors are concerned, the direct descendants of the `JSXOpeningElement` in the abstract syntax tree are not the components referred to in the `children` attribute of the parent `JSXElement` but the `JSXIdentifier` referred to in the `name` attribute of the `JSXOpeningElement`.

Because the `name` attribute of the `JSXOpeningElement` is not a simple string it is not possible to use the attribute selector, as they only allow simple matching rules. For instance, the example below, or similar variations, would not work

```javascript
// bad! does not work!
JSXOpeningElement[name='JSXIdentifier.name=FormattedMessage']
```


As far as the AST selectors are concerned, the name attribute element is a descendant element and can be selected using a descendant selector paired with an attribute selector that matches the all important string `FormattedMessage`.

```javascript
JSXElement[children=''] JSXOpeningElement JSXIdentifier[name='FormattedMessage']
```

This will select the self-closing `FormattedString` components in the codebase and will ignore those that wrap components. Success!

But wait, there's more - this can be simpler.

The selector does not gain any specificity from using the `JSXOpeningElement`. We already know that the parent `JSXElement` indicates there are no child components, so we don't need to worry that our selector is going to select the `JSXClosingElement` as it is not there. We can simplify the selector by removing the reference to `JSXOpeningElement`.

```javascript
JSXElement[children=''] JSXIdentifier[name='FormattedMessage']
```

And our final rule, in place in the eslint config

```javascript
"error", {
  "selector": "JSXElement[children=''] JSXIdentifier[name='FormattedMessage']",
  "message": "Please use {text => text} function as child of FormattedMessage to avoid spurious span"
}
```

#### Constructing the selector - approach 2 : JSXOpeningElement is self-closing

There is a different approach we can take that only selects against the opening element itself without requiring reference to the parent `JSXElement` with an empty `children` attribute. Look at the JSON AST of the `JSXOpeningElement`.

```javascript
{
  "type": "JSXOpeningElement",
  "name": {
    "type": "JSXIdentifier",
    "name": "FormattedMessage"
  },
  "attributes": [ /* not important to us */ ],
  "selfClosing": true
},
```

The important property here is `selfClosing` if it is `true`, as it is here, it means there is no closing tag and therefore no child components.

Instead of selecting the parent `JSXElement` we can now directly select the `JSXOpeningElement` that is self-closing.

```javascript
JSXOpeningElement[selfClosing=true]
```

And we already know how to filter our selected components to a `FormattedMessage` by using a descendant selector combined with an attribute selector.

```javascript
JSXOpeningElement[selfClosing=true] JSXIdentifier[name='FormattedMessage']
```

The final eslint config would be

```javascript
"error", {
  "selector": "JSXOpeningElement[selfClosing=true] JSXIdentifier[name='FormattedMessage']",
  "message": "Please use {text => text} function as child of FormattedMessage to avoid spurious span"
}
```

## Conclusion

AST selectors can be very useful in providing a simple way of adding a new ESlint rule, and they also leverage any existing CSS selector knowledge you may have. However, they suffer the same limitations as CSS selectors and quickly become cumbersome for what should be relatively simple selections. The selection of a node based on the contents of the attributes of the children of a sibling node is common, but not simple to achieve using AST selectors; while there is an adjacent and descendant selector there is no _previous_ selector.

The next post in this series will look at writing _"proper"_ ESlint plugins that are much more flexible, and useful.