There’s more to programming than just writing code. While unlikely, it is possible to write imperfect code the first time through, or have requirements change over time. In these cases, you have to refactor the code you’ve already written to fix mistakes and change functionality.
You can go about refactoring your code in several ways; a small change can be done manually, but a larger change requires a tool to automate the task. Many programmers use regular expressions to modify their code - but are they the best choice? Programs such as sed
that use regular expressions view your code as simply a blob of text - it’s 2014, we can do better than that!
Grasp is a command line utility that allows you to search and replace your JavaScript code, with one important distinctive feature: it searches the structure behind your code (the abstract syntax tree), rather than simply the text you’ve written. That means Grasp is smart! It knows the difference between an if
statement and an identifier, and it knows what parts make up those nodes (eg. an if
statement has a test
, a consequent
, and optionally an alternative
- the else
). It combines this with an easy to understand syntax for searching and replacing, making it an incredibly powerful tool for refactoring your JavaScript code.
Install Grasp with npm install -g grasp
(more info)
In this guide we’ll go over how to do some JavaScript refactoring tasks using Grasp:
- Renaming an identifier
- Multiple arguments to a single options object
- Reducing the scope of your changes
- Refactoring a function call
- To Yoda conditions, we change
- Changing how you do default arguments
First up, the simplest example:
Renaming an identifier
This task is quite simple using Grasp! You have the following code:
and you’ve decided that remote
is not a good name for variable you are using - you want to refactor your code and change remote
to remoteConnection
.
First you try using regular expressions, and do sed 's/remote/remoteConnection/g' file.js
. At first that looks good, but then you look at the second line of your updated code:
Uh oh! The first argument to createConnection
was changed from 'remote'
to 'remoteConnection'
, and your code is now broken! Because tools such as sed
that use regular expressions see your code as simply text, they don’t know the difference between changing remote
, the identifier, and remote
, the part of a string. You could create a more complex regular expression to do what you want, or you could use a program that is smart and knows all about the structure behind your JavaScript code: Grasp.
We can do this two ways in Grasp, first we can use the squery query mode (which is the default) which uses a CSS style selector interface. To find all identifiers named remote
we can use ident[name=remote]
, or its shorthand: #remote
.
First we can test to make sure we are finding what we want:
$ grasp '#remote' file.js 1:var remote = createConnection('remote', 'user', false); 2:remote.send(msg); 3:remote.on('receive', function (data) { 5: remote.send(process(data)); 7: remote.end();
Looks good! Now we can set a replacement using the --replace
or -R
options, and we get:
$ grasp '#remote' -R remoteConnection file.js
Windows users, you may need to use double quotes rather than single quotes to wrap your arguments, eg. grasp "#remote" ...
Let’s look at our updated code:
Looks good! The identifiers are changed, but the string 'remote'
is left unchanged.
By default, doing a replacement on some code simply prints out the result. You can change your files in place using the --in-place
or -i
option, or if you want to do something more complex, you can look at the to option.
We can do the same replacement with the equery query mode, which uses JavaScript code examples. We enabled it with the --equery
or -e
flag. Because we are looking for the identifier remote
, all we need to do is type out an example of what we want: remote
.
$ grasp -e remote -R remoteConnection file.js
That was simple, but what if we want to do something a bit more complex?
Multiple arguments to a single options object
You have the function calc
in your code, but its signature is becoming a bit unwieldy as you’ve added functionality:
It’s becoming hard to remember the exact order of the parameters when you call the function, so you decide to refactor your code and make the function take in one argument, an object, with all the options specified by name:
But how can you systematically change every single call to calc
in your entire codebase to the new argument format?
Can we use regular expressions? No, because each call to calc
we want to find and modify could have an arbitrarily complex expression for each argument. What we can do is use Grasp, it parses the JavaScript, queries its underlying structure, and lets us find and modify what we want.
Grasp has two query modes, squery with a CSS like syntax, and equery which uses JavaScript code patterns. The later is particularly adept for this task so we will enable it using the --equery
or -e
flag.
What will our pattern be? We type out an example of what we want, and use named wildcards (which start with a dollar sign $
) to match any expression and save it to that name. Our pattern: calc($user, $target, $action, $amount, $clear)
.
We can do a test search to make sure we’re matching what we want with:
$ grasp -e 'calc($user, $target, $action, $amount, $clear)' file.js
If we want to recursively search a directory, we can use the --recursive
or -r
flag:
$ grasp -r -e 'calc($user, $target, $action, $amount, $clear)' .
Now we can specify a replacement, which is done with the --replace
or -R
flag. Each of our matched nodes will be replaced with the text we provide. We can accessed the named portions of each match (which we specified using $name
) with the {{name}}
syntax.
For our replacement we have: calc({ user: {{user}}, target: {{target}}, action: {{action}}, amount: {{amount}}, clear: {{clear}} })
For the sake of brevity, we can use shorter names for our named wildcards, and then we can put it all together:
$ grasp -r -e 'calc($u, $t, $a, $n, $c)' -R 'calc({ user: {{u}}, target: {{t}}, action: {{a}}, amount: {{n}}, clear: {{c}} })' .
And with that, all calls to calc
- with whatever expressions you can think of as their arguments - are modified.
We changed
to
automatically! Not only can we make very complex modifications, but we can can target them precisely:
Reducing the scope of your changes
What if you want to change an identifier in a file, but don’t want to change every single instance of that identifier in the entire file? How do you reduce the scope of the changes you want to make?
Squery is modelled after CSS selectors. In CSS, if you want to only style certain elements that are children of another element, you use a space between the two selectors, eg.
You can do the same in squery. For instance, say you want to change the identifier name
to fullName
, but only in the function getUserInfo
, you don’t want to change the use of name
outside of that function because it means something else.
First, we query for functions named getUserInfo
, we can do that with func[id=#getUserInfo]
- the id
field of a function is an identifier, and remember that we can easily query identifiers using the #identifierNname
syntax. Then, to select children of that function that are the identifier name
, we can do func[id=#getUserInfo] #name
.
Putting it all together, you can do:
$ grasp 'func[id=#getUserInfo] #name' -R fullName file.js
and we get as our updated code:
Note that the identifier name
in the function getInput
remains unchanged, while name
has been changed to fullName
in the getUserInfo
function!
Refactoring a function call
You have the function setValue
. Originally it had just two arguments, key
and value
, but the requirements were changed at the last minute (which never usually happens!) and you had to add a boolean option silent
.
You’re unhappy with it, and want to refactor the silent version into a new function.
But how can you replace every single call to setValue
to the appropriate new function? You can easily do it with Grasp! You will do two passes, one for the silent version, and one for the non-silent version. You can use equery (-e
, --equery
) to do what you want - setValue($k, $v, true)
will match calls when the silent argument is true
, and setValue($k, $v, false)
will match calls when the silent argument is false
, and we can access k
and v
when creating our replacement.
Remember that you can update your files in-place with the --in-place
or -i
option
First,
$ grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js
and then,
$ grasp -e 'setValue($k, $v, false)' -R 'setValue({{k}}, {{v}})' file.js
Your calls to setValue
have been changed!
We’ve done a couple of reasonably complicated examples using equery, but what about with squery?
To Yoda conditions, we change
You just heard about Yoda conditions and you think they’re pretty great because you’re a big fan of green large-eared fictional beings. You want to refactor your current conditions to all be in Yoda style!
You want to find all ==
operators where the left hand side is an identifier, and switch it so the identifier is on the right hand side. For example, change:
to
First, we find all ==
binary ops with biop[op="=="]
, then we limit that to where the left hand side is an identifier: biop[op="=="][left=ident]
. We can test out the search:
$ grasp 'biop[op="=="][left=ident]' file.js
Now that we have that done, we can create the replacement. With squery, we can access parts of our match with the {{ selector }}
syntax. Thus, we create our replacement, switching left and right: {{.right}} {{.op}} {{.left}}
.
Putting it all together:
$ grasp 'biop[op="=="][left=ident]' -R '{{.right}} {{.op}} {{.left}}' file.js
and we get the proper final result
You could select ==
and ===
operators with biop[op~=/===?/]
Grasp the JavaScript, you must!
Changing how you do default arguments
So far in your JavaScript coding career you’ve been using a popular pattern when you need default arguments in one of your functions:
You’ve realized however that this isn’t the best way to do things, because the default will be used if you use a valid input that happens to be falsey, such as 0
.
You can do defaults the proper way with this pattern: arg == null && (arg = default)
- this will only set the default if the arg is undefined
or null
. How can you systematically change all the instances in your code to the new pattern? We can use equery for this task (--equery
or -e
).
One cool trick we can use is that if we specify a named wildcard (eg. $name
) more than once in a pattern, then the two instances must be the same. Thus we can find the old default pattern with $arg = $arg || $default
. Then, we can add the replacement and put it all together:
$ grasp -e '$arg = $arg || $default' -R '{{arg}} == null && ({{arg}} = {{default}})' file.js
Complicated changes are easy with Grasp!
Conclusion
I hope you have learned a few of things and will consider using Grasp the next time you need to search, replace, or refactor your JavaScript code. Check out the documentation, demo, and home page for more information! For another example of refactoring JavaScript with Grasp, check out this blog post.