regex.test() only works every other time
Asked Answered
A

3

46

Regex test() is giving me issues in Firefox and Chrome, yet it works flawlessly in Opera and Safari.

troubled code:

var pattern = /(\s+(?!\$\w+)|(^(?!\$\w+)))/g;
if(pattern.test(String(id).replace(/\s+OR|AND\s+/g, ''))) {
 searchError("You suck.");
 return 1;
}

When you pass in white space, it blocks it every time. When you pass in something like '$a b' then it will work every other time in Firefox/Chrome. WEIRD.

Ancalin answered 8/10, 2010 at 14:39 Comment(2)
Replace id with something static and see if that works.Anhydrous
omg :-) seems like a fun bug ...Hedvige
F
97

It's a bug in the RegEx engine, a similar question with the same issue came up here.

From my answer to that question: It's a bug with the way regexes are implemented in ECMAScript 3, there's a great post on the details here.

The basics are a /regex/ with the g modifier doesn't reset correctly, so multiple .test() calls alternate between true and false if everyone should be true, every other calls successfully resets it.

Feverous answered 8/10, 2010 at 14:43 Comment(12)
Oh snap. you guys are awesome. Thanks for the quick replies and helpful links (this was a tough one to search solutions for).Ancalin
@Jackson, if this answers your question (it should), then click the little check-mark, to the left -- to mark it as the accepted answer.Postaxial
@Brock Adams This must have skipped my attention. Thanks for the reminder.Ancalin
Which absolutely sucks if you're checking something with a statement like this, looking for a match in either of two item fields if (regex.test( item.name) || regex.test( item.description )) where half the expressions match half the time, depending on the search term order, resulting in extremely illogical search results. Shocked this stil hasn't been fixed (May 2014).Publisher
The problem is not that it doesn't reset correctly (as that is exepected), but that the literal is "static" and yields the same RegExp object every time it is evaluated - not having a fresh .lastIndex property either. Of course, that's only ES3.Pretor
Just ran into this on Chrome 42.. news to me.Jacqulynjactation
what the flipping heck... i was going insane why my console.log was returning true yet my if condition straight after was falseZwickau
I'm just here to say that this issue is still occurring 10 years later in Chrome 81Rubalcava
And in node v13.11.0, as of May 4th 2020. I was going absolutely insane.Communism
For me wrapping the regex, e.g. pattern to ` (new RegExp(pattern))` seems to workSenter
I don't know the history of this issue, but it is described under Using test() on a regex with the "global" flag section in MDN test method - so, I wouldn't call it a bug, but a rather weird feature...Claim
This is a feature to test for multiple matches. If you match a new string, it is good practice to first set r.lastIndex = 0 before using test. You may also use it in one line if-statement like so: if(r.lastIndex = 0 || r.test(str)){ ... }.Kame
C
6

Update: 9/27/23 - I am STILL running into this issue.

Consider the following:

  1. You are using Angular reactive forms, and are using some built in features, like out of the box Validators.
  2. You use something like the following.
      form.forEach((formField: any) => {
        let validatorsArray = formField.validators?.reduce((validators: any, validator: any) => {
          switch(validator.type){
            case 'required':
              validators.push(Validators.required);
              break;
            case 'pattern':
              validators.push(Validators.pattern(new RegExp(validator.value)));
              break;

where each validator is an object I've defined on our server like so (for example):

{
"name": "lowercaseandunderscore"
"value": "^[a-z_]*$",
"type": "pattern",
"error": "Column names must only contain lower case letters and underscores"
}

Now, one would assume this would inherently work using reactive forms, but it does not! It falls into the same pit as the post in 2021 below. Don't be fooled!

I came across this today.

Oddly, what works is the following:

      form.forEach((formField: any) => {
        let validatorsArray = formField.validators?.reduce((validators: any, validator: any) => {
          switch(validator.type){
            case 'required':
              validators.push(Validators.required);
              break;
            case 'pattern':
              validators.push(Validators.pattern(new RegExp(/^[a-z_]*$/)));
              break;

This does not fail every other time!

But, if you are creating a Regex from using the class, using a STRING... watch out. You'll have to do something like this.

      form.forEach((formField: any) => {
        let validatorsArray = formField.validators?.reduce((validators: any, validator: any) => {
          switch(validator.type){
            case 'required':
              validators.push(Validators.required);
              break;
            case 'pattern':
              validators.push(this.testValue(validator));
              break;

where your testValue function is the following:

private testValue(validator: any): ValidatorFn {
    return (control: AbstractControl) => {
      let returnVal = null;
      if(control.value){
        //NOTE the use of parenthesis around the RegExp below.
        if((new RegExp(validator.value, 'gm').test(control.value))){
          return null;
        } else {
          return {[validator.name]: validator.error};
        }
      }
      return returnVal;
    }
  }

As I was working through this today, I honestly thought "surely I can't be facing that same exact problem with regexes...". Turns out, I was.

There is something to be said for every time you create a RegExp with a string. Just wrap it in parenthesis. For whatever reason, creating it without a string literal works like it should.

I hope this hammers that point home.

Essentially, regexes seem to misbehave when created from string literals, and seem to be just fine when created NOT from a string.

///End of update

This seems to still be an issue in August of 2021... I just want to share some things I have learned before stumbling upon this question and answer. I was baffled by this problem and had no meaningful way forward - until now.

  1. It doesn't matter whether you use exec() or test() or match(). The regex still doesn't work properly on every other occurrence.

  2. It doesn't matter if you set the regex with

let reg = new RegExp(/<table(\s*[^>]*)>/g);

or with const. Doesn't matter if you set it globally or locally either...

What does work to bypass this problem is wrapping your regex statement in parenthesis in your loop like so:

Object.keys(table).forEach(key => {
    if((new RegExp(/<table(\s*[^>]*)>/g)).test(___your test string___)){
        //Do what you need to do
    }
});

Remove the parenthesis around the Regex, and watch every other one fail....

Thank you so much for the answer @Nick Craver and the comment @prototype!

This exact Regex was what was giving me trouble. It would work for one object, and fail for the subsequent object, and it made no sense. I am only here to say that this is still very relevant in 2021.

Coddle answered 9/8, 2021 at 17:32 Comment(3)
Look @halfer I appreciate the edit, but I don't appreciate the removal of "Thank God". Seriously, I couldn't find an answer anywhere for days, and finally found this answer already posted. If things like this aren't a gift from God at times, then I don't know what is... I'd rather just delete this "answer" (which really only illustrates the spread of the regex problem to other areas) if you're going to strip God out of it.Coddle
I understand your view, and sympathise. We have had a few people here, over the years, wanting to add theistic, religious, superstitious, and other such material to their posts, and the community view is that it does not represent the technical, neutral view we are aiming for. We have a canonical discussion here.Inverness
The thing with Stack Overflow is that we're sort of creating documentation for many future readers, and this can come as a surprise to new members. I acknowledge that it takes a bit of getting used to, given that for some decades the "forum model" encouraged contributors to think of posts as their own material (over which they could exercise full control). We are perhaps more like a mutually editable wiki here.Inverness
G
0

for some reason, matchAll() works correctly even though test() doesnt

Great answered 26/1 at 3:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.