Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RegExp.test() returns different result for same str depending on how (where ?) it is called

I've just notice a strange JS behavior that lead to an annoying bug..

Basically, I test a str with a RegExp object (.test() method) in a if statement. For the same string tested, if in my code I have only a if, the regexp.test() return true and it goes fine into the if.

The problem is if I had a else (and I need it), for some reason, for the same str tested, the regexp.test() returns false and it goes to the else...

What is this behavior ?

I have run many tests...

TL/DR : For the same string tested on the same RegExp, if there is only a IF statement the regexp.test() returns true, but if I had a else it returns false.

some code

I forgot to say that the bug doesn't occurs with all words..

http://jsfiddle.net/zrwkU/13/

Write the word "armoire" in the text field and press enter. This jsfiddle has the "else return false" and nothing happens.

Remove the "else return false" in the searchDeeper function ( if (regexp.test(tested)){ ) and do the test again. Now it goes into the if and a msgbox popup.

like image 534
Marc Dupuis Avatar asked Nov 27 '12 14:11

Marc Dupuis


2 Answers

/regex/g.lastIndex = 0

With patterns having the global 'g' flag set, the RegExp methods; test() and exec() keep track of the last place they matched and store this integer number in the RegExp object's: lastIndex property. This index is automatically advanced as each match is applied to any string. Note that both test() and exec() behave this way. Once any match fails, this last index property is automatically reset to zero. You can manually reset it to zero yourself, too, and it sure looks like that's what you need to do here. Add a line to reset RegExp.lastIndex like so:

if (regexp != null){
    regexp.lastIndex = 0;
    if (regexp.test(tested)){
        alert('ok');
        regexp.lastIndex = 0;
        var res = regexp.exec(tested);
        tested = tested.replace(res, '<span class="underlined">' + res + '</span>');
    }else{
        return false;
    }
}

Here is another solution which avoids this last-index-issue altogether. It uses the String.match() method instead:

A String.match() alternative:

if (regexp != null){
    if (tested.match(regexp)){
        alert('ok');
        regexp.lastIndex = 0;
        var res = tested.match(regexp);
        tested = tested.replace(res, '<span class="underlined">' + res + '</span>');
    }else{
        return false;
    }
}
like image 57
ridgerunner Avatar answered Oct 04 '22 19:10

ridgerunner


I think the only issue is that where you have

}else{
    return false;
}

What you actually want is

}else{
   continue;
}

in order to continue the outer loop for (var k in cats){.

As per this update: http://jsfiddle.net/zrwkU/14/

By way of explanation - with your code, as soon as a call to test fails, thats it - out of the method you jump using return false. By changing to a continue you continue to "search deeper" in other categories too.

like image 25
Jamiec Avatar answered Oct 04 '22 21:10

Jamiec