ASTs: How Frichti use them for large & easy refactoring
In this post, we are introducing how to use Abstract syntax trees (AST) to create some eslint custom rules in order to make refactoring much easier and code more suited to your guidelines.
Refactoring
Refactoring is one of the most important parts of the frontend engineer’s job. It is very broad and can go from simple tasks like changing the variable’s name, a function or a component to a complete architecture restructuration, without changing functionalities or introducing regressions.
And as you know… sometimes… refactoring large projects with an historical background and some technical debts could be really painful especially when tasks are just boring like renaming hundreds of variables and sources of imports across hundreds of files.
The problem
Last year, our awesome design’s team released “La Recette”, the first version of the Frichti’s design system. It was a real enhancement for the frontend devs team, as it helps us to improve our development flow and the communication between the product team and developers .
However, the implementation of a new design system in the code base wasn’t as easy as we thought because of many reasons:
- Colors naming conventions have changed to be more consistent and some colors have totally evolved as you can see the matching table below:
- We also seized the day of those changes to create a separate
npm
package in order to reuse our design system through our different frontend apps. That way, the source of import of colors has changed drastically, and we had to convert the whole code base this way:
/**
** Old legacy code
**/
import { DEPRECATED_COLORS, PRODUCT_CARD_WIDTH } from '@contants/index'const StyledProductCard = styled.div`
background-color: ${DEPRECATED_COLORS.black};
width: ${PRODUCT_CARD_WIDTH};
`/**
** New code
**/
import COLORS from '@frichti/la-recette/colors'
import { PRODUCT_CARD_WIDTH } from '@contants/index'const StyledProductCard = styled.div`
background-color: ${COLORS.dark[900]};
width: ${PRODUCT_CARD_WIDTH};
`
The problem with those challenges was that it was too risky to do a “search and replace” in the whole code base as we could face some cases where the colors were not consistent. Also, developers have to know the matching array and we should inform them for each deprecated color the equivalent one in the new design system. So, we asked ourselves : how can we handle such a tricky subject ?
ASTs to rescue…
“An Abstract Syntax Tree, or AST, is a tree representation of the source code of a computer program that conveys the structure of the source code.” https://deepsource.io/glossary/ast/
To illustrate the AST representation of add
function in JavaScript, for example, would look like the following:
Here we have our Program which is the first node of our tree and then we have a node that defines each action or statement occurring in our code below:
function add(a, b) {
return a + b;
}
Of course, this tree could also be represented as a JSON (JavaScript Object Notation). Concerning our initial problem, for instance:
import { DEPRECATED_COLORS, PRODUCT_CARD_WIDTH } from '@contants/index'
will be represented as the JSON below using the babel-eslint parser:
Our solution
The solution that we found out for this problem (and many others that I’ll list at the end of this post) was to create a custom eslint rule using ASTs. The idea was to warn the developer who works on a file that uses a deprecated color that he must replace it by the new color and suggest him (or better, auto fix the issue) the equivalent color.
Now, let’s see how we can create our first custom rule!
First step: create the plugin
As we knew that we would create other rules in the future, we decided to create a global eslint plugin to contain all our frontend rules. I’ll not break down here how to create an npm
package, there are tons of articles about that, but what you should know is that an eslint plugin should respect a naming convention. The package name should be prefixed by eslint-plugin-*
.
In our case we named our package as:
@frichti/eslint-plugin-frontend-rules
Second step: create our rule
Here we will firstly create a rule that will disallow usage of a specific named variable (DEPRECATED_COLORS in our example) and suggest a matching variable (COLORS in this case).
And here we are!! 🎉🎉
By removing the comments on the fixer, the code will automatically be resolved into this:
Conclusion
Our rule is more configurable and offers some other options like matching object properties with new ones, as we could replace the DEPRECATED_COLORS.black
by the new COLORS.dark[900]
for instance.
Finally, our package is available on npm and the whole code can be found on Github. Feel free to install the npm package or contribute with new pull request.
Of course, this example is kept simple in order to understand some of ASTs pros and what it offers. A lot of other things can be done using them like babel plugins, codemods….
At Frichti, we internally created a lot of other eslint rules to remain consistent in our code base, reinforce some guidelines rules or make some long refactoring tasks easier to develop. For example, we have:
- A rule that disallows calling analytics methods more than once in another method.
- A rule that disallows direct xState context assignment without passing by
assign
(the @xstate/immer one or native one) method. - And a lot of other specific cases rules
Thanks for reading, feel free to comment or reach out for questions !
Cheers 🍻