JavaScript Regexp Exec Fails Every Other Time
TL;DR
In JavaScript, if your regular expression has the g
flag and you call exec
or test
repeatedly on the same regexp. exec
and test
will set lastIndex
to 0 only when they fail to find a match.
If things are not working as expected, you might need to set lastIndex
to 0...
Say you have the following snippet:
var textCommand = /triggers?\s+a?\s?build(?:.+tag.+:(\w+))?/ig;
if(textCommand && textCommand.test(payload.data.head_commit.message)){
var match = textCommand.exec(payload.data.head_commit.message);
console.log(match, match && match[1]);
}
And you test it against this string:
This triggers a build and will tag it :latest.
You would expect match to return something, but you would be wrong. I was having this issue and trying to debug I ended up tracing exec to console, like this:
var textCommand = /triggers?\s+a?\s?build(?:.+tag.+:(\w+))?/ig;
if(textCommand && textCommand.test(payload.data.head_commit.message)){
var match = textCommand.exec(payload.data.head_commit.message);
console.log(match, match && match[1]);
console.log(textCommand.exec(payload.data.head_commit.message));
}
And that second statement actually worked.
0: 'triggers a build and will tag it :latest'
1: 'latest'
It took me a bit to realize the culprit was the g
flag. I actually RTFM, and this was the clue:
If your regular expression uses the "g" flag, you can use the exec() method multiple times to find successive matches in the same string. When you do so, the search starts at the substring of str specified by the regular expression's lastIndex property (test() will also advance the lastIndex property).
What's happening is that since we are using the g
flag, on a successful test
the lastIndex
property will be different than 0 and then when we use exec
the regexp will try to match the pattern starting at lastIndex
.
You can solve this by reseting lastIndex
:
var textCommand = /triggers?\s+a?\s?build(?:.+tag.+:(\w+))?/ig;
if(textCommand && textCommand.test(payload.data.head_commit.message)){
textCommand.lastIndex = 0;
var match = textCommand.exec(payload.data.head_commit.message);
console.log(match, match && match[1]);
}