Codemods with jscodeshift

Refactor like a boss, but why should I care?

All code continuously needs to be refactored and maintained. This is the truth especially within the javascript world where new things happen every week. If its not a new design pattern its a new version of a tool, a third-party library or even the language itself.

isCodeClean ? 😊 💚 🍔 🍺 : 💩 🔥 😱

Code that is not maintained leads to malignant creatures and will begin to cause fires in different places, and eventually either people up or panic. Code taken care of leads to love, joy and time for hamburger and beer.

So, do you have a large code base with both old and new code? Perfect! Maybe also a mix of different code styles and standards spread a little here and there? Even better! Are you trying to keep up with the pace of the javascript community and always write code according to the latest and most hottest patterns, but don’t have the energy to always migrate the old code?

Then codemods and jscodeshift could be something for you!

Because, for the most part, the problem is not that the refactoring is particularly difficult, but instead the fact that it is everywhere and looks a little bit different in all places. Search/replace or regex does not work well with different code styles.

Ok, but how does it work?

Codemod and jscodeshift are tools created by Facebook whose purpose is to change code, your production code. Programmatic search/replace one might say.

Jscodeshift is a tool that has knowledge of javascript language and can read code, change its content and structure and then recreate the code again.

The actual code change is done by letting the tool convert the source code into an Abstract Syntax Tree (AST), an advanced tree structure of the code. The tool then allows you to make changes to the AST tree with a transform function, which you or someone else writes. As a final step the tool can recreate code, based on the modified AST tree, with either retained or enhanced format.

Crystal clear? Let’s look at an example.

A typical transform function

const transformer = (file, api) => {
   /* get the jscodeshift helper */
   const j = api.jscodeshift;

   /* create a js object representation of your source code */
   const root = j(file.source);
   root
      .find(/* grab the code you want to update */)
      .forEach(/* do something to it */)
      .toSource(/* return the newly printed code */);
};

A concrete example

Here is transform function that adds a trailing comma to all arrays that does not already have one. The array must not be empty and the codemod will leave the format of each array (multiline or not) unchanged.

const transformer = (file, api) => {
   const j = api.jscodeshift;
   const root = j(file.source);

   const multiLineArrays = path => {
      const { loc } = path.node;
      return loc.start.line < loc.end.line;
   }

   return root
      .find(j.ArrayExpression)
      .filter(multiLineArrays)
      .forEach(path => path.node.original = null)
      .toSource({ trailingComma: 1, wrapColumn: 1});
}

Using the fantastic online tool AST explorer you can see the code look both before and after we have run our transform function.

I love it, give me more

While giving a speech at a conference at my consultant firm Aptitud I created this repo that shows some example code, React code, before and after applying a bunch of codemods I found pre made in various repos on Github. The codemods where:

1_createClassToComponent

var CarList = React.createClass({
   getInitialState: function() { }
   setSelectedCar: function(selectedCar) { }
   ...

becomes

class CarList extends React.Component {
   state = { }
   setSelectedCar = selectedCar => { }
   ...

2_trailingComma

cars: [
   'Volvo',
   'BMW',
   'Tesla',
   'Lada'
]

becomes

cars: [
   'Volvo',
   'BMW',
   'Tesla',
   'Lada',
],

 3_objectShortHand

var state = { selectedCar: selectedCar }

becomes

var state = { selectedCar }

 4_objectAssignToMerge

var state = Object.assign({}, state.history, selectedCar.id)

becomes

var state = { ...state.history, ...selectedCar.id }

 5_stringConcatToTemplateString

var message = 'The best car is ' + randomCar

becomes

var message = `The best car is ${randomCar}`

 6_functionExpressionBindThisToArrowFunction

onClick={function(e) {
   this.setSelectedCar(e.target.value)
}.bind(this)}

becomes

onClick={e => this.setSelectedCar(e.target.value)}

 7_varToLetAndConst

var cars = this.cars.map(car => ...
var message = 'No cars'
   if (cars.length) {
      var randomCar = null
      message = `The best car is ${randomCar}`
      ...

becomes

const cars = this.cars.map(car => ...
let message = 'No cars';
   if (cars.length) {
      const randomCar = null;
      message = `The best car is ${randomCar}`
      ...

You can clone the code and run the transforms yourself, just follow the README, or check out this PR for an overview of the changes before and after the codemods where applied.

I’m sold! But give me some pros and cons anyway

Pros

  • Codemods are like any other code, it can be:
    • source controlled
    • shared so you don’t have to write everything yourself (es5 -> esnext, react, jest)
    • tested, even test driven developed
  • It’s not just for javascript, codemods can be used for html, css and more
  • A code mods are secure, non-trivial, changes to large code bases. Imagine automating these changes not only to one file, but to thousands, and having it done within a few seconds.

Cons

  • Advanced at first to grasp both how an AST works and how to use the jscodeshift api

Hope you found this introduction useful and that you will try it on your code base, so also you will get a lot of love, joy and time for hamburger and beer. 😊

Annonser

En reaktion på ”Codemods with jscodeshift

Kommentera

Fyll i dina uppgifter nedan eller klicka på en ikon för att logga in:

WordPress.com Logo

Du kommenterar med ditt WordPress.com-konto. Logga ut / Ändra )

Twitter-bild

Du kommenterar med ditt Twitter-konto. Logga ut / Ändra )

Facebook-foto

Du kommenterar med ditt Facebook-konto. Logga ut / Ändra )

Google+ photo

Du kommenterar med ditt Google+-konto. Logga ut / Ändra )

Ansluter till %s