CodeMod Tutorial - Part 2 - How?

Royston Shufflebotham is a Front-end developer and architect at i2, in Cambridge, UK.
He got his first taste of coding in 1981, and really hasn't stopped since then, working his way through backend and frontend jobs in text matching, web & PKI infrastructure and crypto, finance, web server software, laboratory automation and investigative analysis, journeying through Quick Basic, Perl, TCL, C++ (several times), Delphi, C#, Java (several times) and JavaScript.
He appears to have finally settled down as a Front-end developer, preferring to work in React and TypeScript.
He also has a very unhealthy fondness for fiddling round with, or writing, developer tools and DevOps instead of writing real production code.
NOTE: This is Royston's personal blog.
If you fancy some legal nonsense, here is some:
Whilst the author does take some care to ensure the information presented here is accurate and useful, he makes no claims or representations as to accuracy, completeness, correctness, suitability or validity of any information on this site, and refuses to be liable for any errors, omissions, or delays in this information or any losses, injuries or damages arising from its display or use. All information is provided on an as-is basis with no warranties, and confers no rights. There is no warranty that the site is free of viruses or other harmful components. It is the reader's responsibility to verify their own facts and to secure their own systems.
The views, thoughts and opinions expressed in this blog are those of the author only and do not necessarily reflect the official policy or position of any other agency, organization, employer or company, including, but not limited to, my employer.
These views are also subject to change, revision and rethinking at any time; please do not hold us to them in perpetuity.
Oh, and in case you're wondering, it's pronounced ˈrɔɪstən ˈʃʌfl̩boθəm (or ˈrɔɪstən ˈʃʌfl̩bəʊθəm if it's easier for you)(guide). Yeah, I'm definitely helping there...
In part 1 of this series we did a lot of talking about what Codemods are and why you should (or shouldn't) write them. This time we're going to get our hands dirty and write one.
There's quite a bit to get through in this part: we're going to examine an AST and construct a complete - albeit simple - codemod, little-by-little.
Our Codemod
To keep this particular article at a reasonable length,
we'll develop a very simple codemod. It'll simply rename
a method in all JavaScript classes it sees. Specifically, it'll
rename componentWillUpdate to UNSAFE_componentWillUpdate.
(This change forms part of a Codemod for React but you don't need to know
anything about React to follow this tutorial.)
Writing a Codemod
I've found this set of steps handy when developing codemods. If you don't do these things you can easily develop a codemod that changes the wrong code or changes the right code in the wrong way. The TL;DR is "think before you code, and have some tests".
- Figure out what we want to change
- Ensure you understand precisely what structures you want the codemod to change, and precisely what you want it to change those things into.
- Figure out what we don't want to change
- Do you always want to make the change, or only when you're extending a particular class?
- Do you always want to make the change, or only when there aren't any parameters?
- Find structures similar to what you think you want to target and decide if you want those to change too
- Construct a 'test bed'
- Develop one or more 'test' files containing examples of positive (things you do want to change) and negative (things you don't want to change) sample code
- Build a target profile
- Identify the AST structures you want to change and construct
- Fire tracer bullets
- Write codemod code that targets the structure you're hoping to reach, and makes a tiny change there. Don't attempt to make the full final change straight away. Focus initially on targeting.
- You may need to evolve your testbed as you do this
- Apply changes
- Now that you're targeting the correct nodes, write codemod code that makes the desired changes
- Use the codemod
- And be sure to check the output for unexpected changes!
- Evaluate
- Was it easier/quicker/better than making the changes by hand?
Let's work through those steps for our codemod:
1. Figure out what we want to change
That's easy. We want to find all class methods called componentWillUpdate and rename them
to UNSAFE_componentWillUpdate. We don't need to change structure; we're just changing the
name of an identifier.
2. Figure out what we don't want to change
Let's think about things similar to what we want to change, to identify related things we don't want to change.
A convenient way to do this is to take the thing we want to target and create variants of it, changing one thing at a time:
- Our start point is that we want to change methods called
componentWillUpdate. - We don't want to change methods that aren't called
componentWillUpdate. - We don't want to change all functions that are called
componentWillUpdate; only class methods. - We don't want to change
staticclass functions that are calledcomponentWillUpdate; only instance methods. - We only want to change the name of the
componentWillUpdatemethod. None of its contents should change. - Do we always want to change methods called
componentWillUpdate? If we were sharing this codemod publicly and therefore wanted to be super-careful, we should probably only change that method if it's on a class extending React'sComponentclass, but let's just assume for the sake of this tutorial that we don't need to worry about that because we happen to know there are no uses ofcomponentWillUpdateoutside of our React components.
3. Construct a testbed
We now write some sample code to illustrate all the above positive and negative matches:
class Class1 {
// This should be renamed
componentWillUpdate() {
// This code should not be lost
return false;
}
// This should not be renamed
notComponentWillUpdate() {
}
}
// This should not be renamed
function componentWillUpdate() {
}
class Class2 {
// This should not be renamed
static componentWillUpdate() {
}
}
As we develop our codemod we'll run it against the above code to gain confidence that it's changing the correct pieces of code.
4. Build a target profile
We need to identify the AST pattern we want to target. A particularly easy way to do this is to use the excellent AST Explorer web site to explore the AST.
JSCodeShift uses the recast library, so make sure that's the selected option.
Here is a link to AST Explorer with the above code already entered, and the correct options selected: https://astexplorer.net/#/gist/4ba8eac986f73baf39c5c9683af03b93/10917053e0e1b9b41cc3dd9b806f0cec69d9ac00
Go take a look at that. I'll wait. Make sure you click around the sample code (on the left) and examine the appropriate parts of the syntax tree (on the right). Look to see what sorts of AST nodes we're going to be interested in.
After a little clicking around we can see that we're interested in MethodDefinition nodes whose key is an Identifier with a name of componentWillUpdate.
Aside
One area of difficulty when working with these ASTs is that different parsers produce slightly different AST structures. The esprima parser generates MethodDefinition nodes whereas the babylon7 parser calls them ClassMethods.
For this reason, it's vital to ensure you're using the exact same parser configuration in AST Explorer that you'll be using when running your codemod.
Note in particular that recast is merely a layer on top of a real parser, so when we use recast in AST Explorer, it's important to choose the appropriate parser from the recast settings.
5. Fire tracer bullets
This is where things get interesting.
At this point, we're actually going to create and run a real codemod. The neat thing is that we can do it directly inside AST Explorer.
Click the 'Transform' button in AST Explorer and select 'JSCodeShift'. The two-window display will change into a four-window display. The two new panes at the bottom are the codemod source (bottom-left) and the resulting output (bottom-right).
AST Explorer gives us a sample codemod that reverses all of the identifiers in the source. That sample is a little more complex than we need, so replace the codemod code with this simpler skeleton:
1. export default function transformer(file, api) {
2. const j = api.jscodeshift;
3.
4. return j(file.source)
5. .find(j.Identifier)
6. .forEach(path => path.node.name = 'Foo')
7. .toSource();
8. }
It's only 8 lines long but it is a complete codemod! Let's walk through it, line-by-line:
- 1: The codemod consists of a single exported function called with the current file and the JSCodeShift API.
- 2: We extract the
jscodeshifthelper from the API and call itjas we will be using it a lot. - 4: Here we begin a multi-line chained operation passing objects from one function to another: we start by parsing the current file. JSCodeShift will return a wrapper around the file that allows us to perform sophisticated search and replace operations.
- 5: We search for anything of type
Identifier. Note the uppercaseIinj.Identifier: this references the type Identifier. - 6: We tweak each (Identifier) node, changing its
nametoFoo. We'll cover thepathbusiness in detail in the next article, so for now, just focus on the fact that we can writepath.nodeto get the node in question. - 7: We regenerate the output code from our modified tree by calling
.toSource().
If you look at the output pane you'll see that our code has been heavily transformed to:
class Foo {
// This should be renamed
Foo() {
// This code should not be lost
return false;
}
// This should not be renamed
Foo() {
}
}
// This should not be renamed
function Foo() {
}
class Foo {
// This should not be renamed
static Foo() {
}
}
Every Identifier - every mention of x, somefunc, Array and NotArray - has been changed into Foo, as we said it should in the code.
That's not (of course) what we want from our final codemod, but it serves to show how powerful codemods are, and it provides a good basis for developing our codemod. It'll require remarkably few changes to turn that into our final codemod!
Target the correct thing
Firstly, we don't want to target all Identifiers.
From our AST investigations earlier, we know we want to start by finding all the MethodDefinitions.
Our tracer bullet will simply be to set the name of the method to Foo. That's achieved by setting the .name property of
the method's .key property.
i.e.
1. export default function transformer(file, api) {
2. const j = api.jscodeshift;
3.
4. return j(file.source)
5. .find(j.MethodDefinition)
6. .forEach(path => path.node.key.name = 'Foo')
7. .toSource();
8. }
The output:
class Class1 {
// This should be renamed
Foo() {
// This code should not be lost
return false;
}
// This should not be renamed
Foo() {
}
}
// This should not be renamed
function componentWillUpdate() {
}
class Class2 {
// This should not be renamed
static Foo() {
}
}
We're now renaming all methods on classes, and we're not renaming the classes or functions.
But we're still hitting methods we don't want: we're renaming methods whose name is not componentWillUpdate and we're renaming static methods.
Happily, the .find() method takes a second parameter that supports property-based filtering.
We only want to target:
- methods named
componentWillUpdate. We're looking for methods whosekeyproperty is an object with anameproperty equal tocomponentWillUpdate. - methods which are not
static. We're looking for methods whosestaticproperty is false.
We adjust our .find() call as follows:
1. export default function transformer(file, api) {
2. const j = api.jscodeshift;
3.
4. return j(file.source)
5. .find(j.MethodDefinition, {
6. key: { name: 'componentWillUpdate' },
7. static: false
8. })
9. .forEach(path => (path.node.key.name = "Foo"))
10. .toSource();
11. }
which produces the transformed output:
class Class1 {
// This should be renamed
Foo() {
// This code should not be lost
return false;
}
// This should not be renamed
notComponentWillUpdate() {
}
}
// This should not be renamed
function componentWillUpdate() {
}
class Class2 {
// This should not be renamed
static componentWillUpdate() {
}
}
That's perfect. We're now targeting exactly the one method we want to, and leaving everything else alone.
6. Apply changes
For this codemod, this bit's easy. We don't need any information
from the existing node. We just want to set its name to UNSAFE_componentWillUpdate, which merely requires us to change
from setting Foo to UNSAFE_componentWillUpdate:
1. export default function transformer(file, api) {
2. const j = api.jscodeshift;
3.
4. return j(file.source)
5. .find(j.MethodDefinition, {
6. key: { name: 'componentWillUpdate' },
7. static: false
8. })
9. .forEach(path => (path.node.key.name = "UNSAFE_componentWillUpdate"))
10. .toSource();
11. }
So the output is now:
class Class1 {
// This should be renamed
UNSAFE_componentWillUpdate() {
// This code should not be lost
return false;
}
// This should not be renamed
notComponentWillUpdate() {
}
}
// This should not be renamed
function componentWillUpdate() {
}
class Class2 {
// This should not be renamed
static componentWillUpdate() {
}
}
That looks perfect!
7. Use the codemod
At this point, we'd run it across our codebase and carefully check the output. We've done some due diligence so we're hoping it'll work well, but there are often more edge cases that we've missed.
8. Evaluate
This isn't a fabulous codemod. It only replaces one method name, and it doesn't rename existing references to that method. And to be honest, with what it actually does, we could probably have achieved the same thing with a regular-expression-based search and replace.
If this was a codemod we'd be sharing with others, it would be worth turning our sample code into a repeatable unit test, but we won't cover that here.
Of course, our main objective here was to illustrate how to create a simple codemod, and it hopefully served that purpose. Let me know?
Summary
We've covered how to use JSCodeShift to create a simple codemod from scratch, but, as importantly, we've covered a process for developing more reliable codemods.
Have fun developing some codemods, and let me know how you get on!
In part 3, we'll cover a more advanced codemod which involves moving existing code around.

