Metaprogramming in JS: Write your first codemod!
By: Kacper Kula
Tags: Web Development, Metaprogramming, Jscodeshift, Tutorial, JavaScript

Removing console.logs

After finishing a feature we usually spend some time cleaning up our code. This might involve renaming variables, removing unused code or removing all debugging statements — like console.log . The last can be automated with a fairly simple codemod!

We want to remove all instances of console.log

All the three console.log invocations above are correct. To remove them using regular expressions would be complex. However with codemods we can do it in just few lines of code:

A codemod must be a function that is the default export from the file. It accepts two arguments: the file descriptor and a libraries object it can use. By convention jscodeshift is usually destructured and assigned to j .

By calling jscodeshift on our source we compute an AST. We can then query it in the same way we query DOM elements in JavaScript using querySelectorAll .

The find method accepts two arguments: the first is the type of the node we are looking for, the second one is a query that allows us to narrow down the search to the specific instances. In our case we want to find CallExpression , which is how function calls are represented in our tree. We want to find only the calls that are performed on a property of the console object.

Our code will match not only console.log expressions but also console.warn and any other function calls on property of the console object. If you want to restrict it further, try analysing the tree from the previous article and modifying the query object we passed to find .

After we have successfully found all the matches, we can remove them from the tree using remove . This method modifies the original tree. Finally we call toSource because jscodeshift expects us to return a string containing the new code. A cool feature is that the original formatting is preserved for all the nodes we haven’t touched.

Automatically moving hard-coded colors to a design system

In modern apps we often use a design system as a central place for all styles rather than hard-coding values for colors, spacings and fonts. However during development it’s usually much easier style components directly, then migrate to design system tokens just before we are ready to raise a pull request. This is not only an error-prone task, but it’s also time-consuming. It could easily be automated!

In this example I will use a React Native StyleSheet object, but the solution can be adopted to work with any library that defines styles in JavaScript like Emotion or JSS (CSS in JS).

Let’s consider the following structure of design system tokens:

Example of Design System tokens object.

Using this object we can refer to the colors from our design system. Wherever we want to use a red color in our project we can refer to DESIGN_SYSTEM.tokenA .

Now let’s consider the following code in React Native:

Only the color red in the first call should be replaced. The other calls, even though they look similar, do not create a StyleSheet.

First let’s write a function that compares colors:

areTheSame function that compares two colors.

Because colors come in different formats, we can use a color library to convert any type to RGB. Then by comparing each color component (red, green, blue and alpha) we can determine if the colors are the same or not. In a real-life solution we could compare the colors using the CIELAB color space and match all colors that are indistinguishable to the naked eye.

Next we need to look for matches in our design system:

Finding matches in our Design System.

Finally we can write our codemod which will use these functions to automatically convert matching colors into design system equivalents:

You can find a full solution in this GitHub Gist.

First we find all the call expressions on StyleSheet.create . Then we can take all the arguments and extract all their properties. For each of argument we filter out all the properties that do not match "color" . For the matching properties we try to find a token in our design system and if there is a match we modify the tree and change the value of the property. By calling j.memberExpression we create a new expression like DESIGN_SYSTEM.tokenA . Finally we convert our modified tree to source and return it as the result of the function.

Detecting hard-coded secrets in the code