Easier TypeScript tooling with TSQuery
Tags: typescript, tooling, ast, javascript
Published: 2019-10-27 17:44:21

## TLDR; 🔥🔥🔥

Need to use the TypeScript APIs for traversing AST nodes? TSQuery might make that a little bit easier. You can use CSS-ish selectors to query the Abstract Syntax Tree, just like you would query the DOM tree:

{% stackblitz tsquery-example-1 %}

> ### If you’re not sure what an AST is, or just want to a bit of a reminder, [check out this talk I gave at JS Conf AU 201](https://www.youtube.com/watch?v=UKCoRKzRqN4)8 for a refresher, or just read on further!

❤️ Love it already? [Check out the code on Github](https://github.com/phenomnomnominal/tsquery).

🦄 Try it out! [Have a play in the playground](https://tsquery-playground.firebaseapp.com/) made by [Uri Shaked](https://dev.to/urishaked)

⁉️ Want to know more? Read on…

___

## What is TSQuery?

[**TSQuery**](https://github.com/phenomnomnominal/tsquery) is a rewrite of [**ESQuery**](https://github.com/estools/esquery) for TypeScript. ESQuery is a neat little library that allows you to use CSS-like selectors to query a JavaScript AST. That turns out to be a really powerful and flexible way to get information about a piece of JavaScript code! Over the past few months I’ve been writing more TS tooling than JS, and I’ve really missed the power of ESQuery — so I decided to port it over.

A whole range of selectors are supported:

  * AST node types: `ClassDeclaration`, `Identifier`, `VariableDeclaration`, etc.

  * Attributes: `[name]`, `[left.value=4]`, `[name.name=/^I.*/]`, `[statements.length<3]`

  * `:first-child`, `:last-child`, `nth-child(6)`

  * `node descendant`, `node > child`, `node ~ sibling`, `node + adjacent`

  * and [more](https://github.com/phenomnomnominal/tsquery#selectors)!

If we look back at our example of a selector from above, we can break it down and explain what’s going on:

    tsquery(ast, 'ClassDeclaration[name.name="MyClass"] > Constructor');

Here we start with a query for a specific `Node` type, a `ClassDeclaration`. We want to find one where the `name` property (which is an `Identifier` node) has a `name` with the value `"MyClass"`. We then want to find a *direct descendent* of that which is a `Node` of type `Constructor`. We can run this query over the code from above and get an array full on all the matching nodes. In this case, there would only be one result: the node for the constructor!

🌈 Neat!

___

## So, why do we need TSQuery?

I’ve been working with TypeScript a lot lately, and I really love it ❤️! If you went back and told that to the 2014 version of me, I probably wouldn’t believe you, but it’s true!

In my ~5 years of using TypeScript in a fairly large AngularJS/Angular project, I’ve found that adding types to JavaScript makes it easier to *build stuff good*™, and to *maintain* that stuff as a codebase grows and evolves. Having types makes it easier to reason about my code, makes refactoring safer, and generally gives me more confidence in the software I create and ship 🚢.

Those reasons alone would be enough to make me really love TypeScript. But the real killer feature is the incredible ecosystem of tooling that has been developed by the TypeScript team and community, including:

  * The integrations for various IDEs (such as [Webstorm](https://www.jetbrains.com/help/webstorm/typescript-support.html), or [VS Code](https://code.visualstudio.com/docs/languages/typescript)) that make refactoring super easy

  * Specialised tools like [TSLint](https://github.com/palantir/tslint) for automated code-style checking

  * The [whole suite of tools that power the Angular CLI](https://github.com/angular/devkit) for code generation and automatic updating

  * All these things are built on top of the TypeScript language, and they combine to make a very powerful ecosystem!

__

## But it’s not all roses 🌹🌹🌹…

These tools are all great to *use*, but writing them can be quite a painful experience. There are lots of barriers to getting started with creating your own tools. To me, the biggest obstacle is getting your head around the idea of an **Abstract Syntax Tree (AST)**, and how you can interrogate and manipulate one.

An AST is a *data structure* that represents the *structure of code* in a programming language, *without any actual syntax*. It describes the ideas that make up a piece of code, without talking about the specific keywords, or specific syntactical tokens.

> ### You can read more about ASTs in [this excellent article](https://blog.buildo.io/a-tour-of-abstract-syntax-trees-906c0574a067) by [Gabriele Petronella](https://medium.com/u/cc96b9707c6c)

![A tree (but not abstract, nor related to syntax).](https://thepracticaldev.s3.amazonaws.com/i/de1tn0i7asovvoywerlg.jpeg)

An example of an AST could look something like this:

![An example of an AST for of some TypeScript code.](https://thepracticaldev.s3.amazonaws.com/i/8hb24k0v0mzxrp99ee1b.png)

The “abstract” nature of the data structure is important, as it means that an AST doesn’t necessarily correlate to any particular programming language. It also means that you no longer need to use regular expressions or string manipulation to decipher or modify your source code! Instead, you can refer to parts of the code using the underlying concepts, whether it be the idea of a class, or the fact that a class has a name, or a constructor.

The above AST is a description of the following piece of TypeScript code:

    export class MyClass {
        constructor () {
    
        }
    }

Let’s say we want to know something about the constructor of `MyClass`: Does it take any arguments? Does it actually do anything? We could look at the code directly and find out the answers, but we could also find out by looking at the AST.

TypeScript gives us an easy way to create the **AST** of a block of code, with the `createSourceFile()` function. It can be used like this:

{% stackblitz tsquery-example-2 %}

Et voilà, we now have a `SourceFile` object. If we print it out, we can see the same tree structure of the **AST** as before:

![The AST structure of the code from above, as generated by TypeScript. Note that the kind is now a number — TypeScript uses the SyntaxKind enum to represent the node type.](https://thepracticaldev.s3.amazonaws.com/i/nk1cxp4wawjb8hzlcx5q.png)

Looking at this we can start to see the *parent* — *child* relationships between the nodes. We have a `SourceFile` (the root node of the AST), which has a series of `statements`. The first statement is a `ClassDeclaration` which has a number of `members`. The first member is the `Constructor` which has a `body`, which has its own set of `statements` 😅 … phew!

Thankfully, we don’t have to memorise all the different names of all the different types of children! TypeScript also gives us an easy way to iterate over all the *child* nodes of an AST node, with the `forEachChild()` function.

We can use `forEachChild()` to loop over the AST, and manually filter out the nodes until we get what we’re after:

{% stackblitz tsquery-example-3 %}

That works well, and code [just](https://github.com/palantir/tslint/blob/master/src/rules/classNameRule.ts#L51) [like](https://github.com/palantir/tslint/blob/master/src/rules/interfaceNameRule.ts#L58) [this](https://github.com/palantir/tslint/blob/master/src/rules/noObjectLiteralTypeAssertionRule.ts#L52) powers much of the [TSLint project](https://github.com/palantir/tslint/tree/master/src/rules). But it isn’t particularly easy to read, or write, or maintain. And to even get started you have to know about the finer details of TypeScripts’ `SourceFile` APIs. We can do better!

We have a tree structure that we run *queries* against to *select* tree nodes. This is directly analogous to using CSS selectors to query the DOM and select elements!

Let’s look at the TSQuery code for doing the same thing:

{% stackblitz tsquery-example-1 %}

That’s a bit better, isn’t it? No more `createSourceFile()`, and no more `forEachChild()`!

TSQuery replaces all the manual iterating and filtering of the earlier example with familiar CSS-like selectors. My hope is that, by using a familiar mental model, we can break down some barriers and enable more developers to build really useful tools for the TypeScript ecosystem.

TSQuery also makes it possible to compose, share, and manipulate AST selectors in ways that wouldn’t have really been possible before!

I hope you like it, and I can’t wait to see what people make with it!

___

## What next?

Hopefully I’ve explained why this is a *Good Thing*™️, and your mind is bursting with great ways to use TSQuery!

I’m going to follow this post up with some examples of how TSQuery can be used, including:

  * Creating custom TSLint rules
  * Creating custom Angular Schematics
  * Finding out interesting stuff about your code base
  * AND MORE!? ⚡️️️️️️ ⚡️️️️️️ ⚡️️️️️️

Until then, please reach out with any questions, ideas, anything! ❤️