Today I faced a very interesting exercise. I had to make a function that took a string as its argument and returned the same string but written in spinal tap case (which apparently is the same as or very similar to kebab case, just-like-this). Up until here, there's nothing to fear, I knew I could just .split().join().toLowerCase()
it away. The challenge for me came when I had to turn a string such as "ThisIsAString"
into spinal tap. My first thought was how on earth do I divide a string by its capitals and keep the capitals at the same time?
The answer to my problem is lookaheads. I had learned about them a while ago, but hadn't really understood them. So this exercise was the perfect opportunity to actually learn how to use them.
The first bit of the exercise was quite simple. So I'm just going to put it here and not say much about it. This creates an array separating where there is a space or an underscore. Then joins it againt into a string separating each element with a hyphen and then turns it all to lower case. Easy peasy.
const toSpinal = str => str.split(/\s|_/).join('-').toLowerCase()
As you'll have noticed, when you turn a string into an array using split()
, it also removes the character(s) that mark the boundaries between elements. So in our example, the string "I Like_Potatoes"
would become "i-like-potatoes"
.
So, What about These Lookaheads?
After this lengthy introduction, let's return to the problem at hand. My function so far works very well for anything that isn't "ILikePotatoes"
. But to adecuately process such string, I know that I can't simply use a regular expression like [A-Z]
, because that would also remove the capital letters. So I'd get something like "--ike-otatoes"
.
This is where lookaheads come in handy. Lookaheads allow us to check whether an expression A is followed by another expression B. It will only return A if B follows (or doesn't follow) it, but it will leave B unmatched and untouched. We write a lookahead using (?=)
(or (?!)
if we want the expression not to be followed by the lookahead).
const regEx = /[A-Z](?=\W)/g
const str = "A. Potato, B- Onion, C: All of the above"
str.match(regEx) // returns A, B, C
The lookahead (?=\W)
tells the program to look for any non-word character after the previous expression. So all capital letters that are not followed by a non-word character, will not return a match. But notice that the match()
only returns the three capital letters A, B and C, not the character that follows them.
If we wanted the string to return a match for capital letters that are not followed by a non-word character, we could simply use a negative lookahead ((?!)
):
const regEx = /[A-Z](?!\W)/g
const str = "A. Potato, B- Onion, C: All of the above"
str.match(regEx) // returns P, O, A
If we write a regular expression with a lookahead but with no preceeding expression, it will match all that matches the lookahead but return empty. We can use this to our advantage when we want to turn the string "ILikePotatoes"
into spinal tap case:
const toSpinal = str => str.split(/\s|_|(?=[A-Z])/).join('-').toLowerCase() //Note that the lookahead comes after a | so it isn't attached to the previous expression
console.log(toSpinal("I like potatoes")) //i-like-potatoes
console.log(toSpinal("ILikeCarrots")) //i-like-carrots
console.log(toSpinal("ThisIsA_really We1rdString")) //this-is-a-really-we1rd-string
Conclusion
Lookaheads allow us to define whether an expression will be matched or not based on what follows it (or doesn't). So it will only match an expression if both the expression itself and the lookahead match. But it will only return the expression, without the lookahead.
Top comments (8)
Hey! Thanks to you I made something!! A regex to detect uncommented console.log()!! What do you think?
And the output:
Your next goal is to get it to work with these :)
That's pretty cool! I imagine something like this would be useful to comment
console.log
s once you're done debugging.Nice write-up, I didn't even know about lookaheads!
I think it's marginally better known as kebab-case, although I've also heard it as lisp-case.
Thanks!
Thanks also for the link. I'll update the post and add it.
By the way, you can also do lookbehinds in Javascript, which work the same way, except that they return a match if the expression is preceeded by the lookbehind.
thanks Perl for regexes :)
Great article. Itβs always really interesting to see the power of regular expressions and what one can do with them.
Indeed, the more I learn about them, the more I like them.