Why does a RegExp with global flag give wrong results?

JavascriptRegex

Javascript Problem Overview


What is the problem with this regular expression when I use the global flag and the case insensitive flag? Query is a user generated input. The result should be [true, true].

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
result.push(re.test('Foo Bar'));
// result will be [true, false]

var reg = /^a$/g;
for(i = 0; i++ < 10;)

console.log(reg.test("a"));

Javascript Solutions


Solution 1 - Javascript

A RegExp object with the g flag keeps track of the lastIndex where a match occurred, so on subsequent matches it will start from the last used index, instead of 0. Take a look:

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));

alert(re.lastIndex);

result.push(re.test('Foo Bar'));

If you don't want to manually reset lastIndex to 0 after every test, just remove the g flag.

Here's the algorithm that the specs dictate (section 15.10.6.2):

> RegExp.prototype.exec(string) > > Performs > a regular expression match of string > against the regular expression and > returns an Array object containing the > results of the match, or null if the > string did not match The string > ToString(string) is searched for an > occurrence of the regular expression > pattern as follows:

> 1. Let R be this RexExp object. > 2. Let S be the value of ToString(string). > 3. Let length be the length of S. > 4. Let lastIndex be the value of the lastIndex property on R. > 5. Let i be the value of ToInteger(lastIndex). > 6. If the global property is false, let i = 0. > 7. If i < 0 or i > length then set the lastIndex property of R to 0 and return null. > 8. Call [[Match]], giving it the arguments S and i. If [[Match]] > returned failure, go to step 9; > otherwise let r be its State result > and go to step 10. > 9. Let i = i+1. > 10. Go to step 7. > 11. Let e be r's endIndex value. > 12. If the global property is true, set the lastIndex property of R to e. > 13. Let n be the length of r's captures array. (This is the same > value as 15.10.2.1's > NCapturingParens.) > 14. Return a new array with the following properties: > - The index > property is set to the position of the > matched substring within the complete > string S. > - The input property is set > to S. > - The length property is set to > n + 1. > - The 0 property is set to the > matched substring (i.e. the portion of > S between offset i inclusive and > offset e exclusive). > - For each > integer i such that i > 0 and i ≤ n, > set the property named ToString(i) to > the ith element of r's captures array.

Solution 2 - Javascript

You are using a single RegExp object and executing it multiple times. On each successive execution it continues on from the last match index.

You need to "reset" the regex to start from the beginning before each execution:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
// result is now [true, true]

Having said that it may be more readable to create a new RegExp object each time (overhead is minimal as the RegExp is cached anyway):

result.push((/Foo B/gi).test(stringA));
result.push((/Foo B/gi).test(stringB));

Solution 3 - Javascript

RegExp.prototype.test updates the regular expressions' lastIndex property so that each test will start where the last one stopped. I'd suggest using String.prototype.match since it doesn't update the lastIndex property:

!!'Foo Bar'.match(re); // -> true
!!'Foo Bar'.match(re); // -> true

Note: !! converts it to a boolean and then inverts the boolean so it reflects the result.

Alternatively, you could just reset the lastIndex property:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));

Solution 4 - Javascript

Removing global g flag will fix your problem.

var re = new RegExp(query, 'gi');

Should be

var re = new RegExp(query, 'i');

Solution 5 - Javascript

You need to set re.lastIndex = 0 because with g flag regex keep track of last match occured, so test will not go to test the same string, for that you need to do re.lastIndex = 0

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
re.lastIndex=0;
result.push(re.test('Foo Bar'));

console.log(result)

Solution 6 - Javascript

Using the /g flag tells it to continue searching after a hit.

If the match succeeds, the exec() method returns an array and updates properties of the regular expression object.

Before your first search:

myRegex.lastIndex
//is 0

After the first search

myRegex.lastIndex
//is 8

Remove the g and it exits the search after each call to exec().

Solution 7 - Javascript

I had the function:

function parseDevName(name) {
  var re = /^([^-]+)-([^-]+)-([^-]+)$/g;
  var match = re.exec(name);
  return match.slice(1,4);
}

var rv = parseDevName("BR-H-01");
rv = parseDevName("BR-H-01");

The first call works. The second call doesn't. The slice operation complains about a null value. I assume this is because of the re.lastIndex. This is strange because I would expect a new RegExp to be allocated each time the function is called and not shared across multiple invocations of my function.

When I changed it to:

var re = new RegExp('^([^-]+)-([^-]+)-([^-]+)$', 'g');

Then I don't get the lastIndex holdover effect. It works as I would expect it to.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionaboutView Question on Stackoverflow
Solution 1 - JavascriptIonuț G. StanView Answer on Stackoverflow
Solution 2 - JavascriptRoatin MarthView Answer on Stackoverflow
Solution 3 - JavascriptJamesView Answer on Stackoverflow
Solution 4 - Javascriptuser2572074View Answer on Stackoverflow
Solution 5 - JavascriptAshishView Answer on Stackoverflow
Solution 6 - JavascriptScott SchlechtleitnerView Answer on Stackoverflow
Solution 7 - JavascriptChelmiteView Answer on Stackoverflow