Home > Net >  Javascript Regex test vs match hack
Javascript Regex test vs match hack

Time:11-12

So I have seen this happen in multiple projects and I have no idea what is going on here. Please help me demystify this.

Let's say you have a regex:

/^(hello|goodbye)?\s*world$/gi

And you want to test it against the following strings:

...
    "hello world", // PASSES
    "goodbye world", // PASSES
    "hello goodbye", // FAILS
    "world world" // FAILS
...

So you use the test method to evaluate the strings and whether or not they pass:

...
/^(hello|goodbye)?\s*world$/gi.test(searchTexts[0])

And you get the results you expect. However, now you need to assign the regex into a variable (you know, to reuse it):

...
const helloWorldRegex = /^(hello|goodbye)?\s*world$/gi

And all of a sudden, this doesn't work:

...
new RegExp(helloWorldRegex).test(searchTexts[0]) // fails
helloWorldRegex.test(searchTexts[0]) // fails

What gives? I DON'T KNOW

All I know is that THE HACK BELOW MAKES NO SENSE and I want an explanation:

...
const hackedRegex = new RegExp(/^(hello|goodbye)?\s*world$/gi)
searchTexts[0].match(hackedRegex)
const hackedRegexPasses = hackedRegex.test(searchTexts[0])

The code below is isomorphic and will work in the browser or in node, whichever you prefer or run here: https://jsfiddle.net/qLm5679z/72/

const poutput = (text) => {
    if (typeof window !== 'undefined') {
        if (!document.getElementById("out")) document.body.innerHTML  = "<div id='out'></div>"
        const outputEl = document.getElementById("out")
        outputEl.innerHTML  = `<p>${(Array.isArray(text) ? text : [text]).join('<br>')}</p>`
    }

    console.log(text)
}

const searchTexts = [
    // Using the regex = /^(hello|goodbye)?\s*world$/gi
    "hello world", // PASSES
    "goodbye world", // PASSES
    "hello goodbye", // FAILS
    "world world" // FAILS
]

const assignedRegex = /^(hello|goodbye)?\s*world$/gi
const constructedRegex = new RegExp(/^(hello|goodbye)?\s*world$/gi)
const nonLiteral = new RegExp("^(hello|goodbye)?\\s*world$", "gi")
const nonGlobal = new RegExp("^(hello|goodbye)?\\s*world$", "i")

for (const search of searchTexts) {
    const inlineRegexPasses = /^(hello|goodbye)?\s*world$/gi.test(search)
    const assignedRegexPasses = assignedRegex.test(search)
    const constructedRegexPasses = constructedRegex.test(search)
    const nonLiteralRegexPasses = nonLiteral.test(search)
    const nonGlobalRegexPasses = nonGlobal.test(search)

    const hackedRegex = new RegExp(/^(hello|goodbye)?\s*world$/gi)
    search.match(hackedRegex)
    const hackedRegexPasses = hackedRegex.test(search)

    poutput([
        `${search}::inlineRegexPasses::${inlineRegexPasses ? 'PASS' : 'FAIL'}`,
        `${search}::nonGlobalRegexPasses::${nonGlobalRegexPasses ? 'PASS' : 'FAIL'}`,
        `${search}::hackedRegexPasses::${hackedRegexPasses ? 'PASS' : 'FAIL'}`,
        `${search}::assignedRegexPasses::${assignedRegexPasses ? 'PASS' : 'FAIL'}`,
        `${search}::constructedRegexPasses::${constructedRegexPasses ? 'PASS' : 'FAIL'}`,
        `${search}::nonLiteralRegexPasses::${nonLiteralRegexPasses ? 'PASS' : 'FAIL'}`
    ])
}

CodePudding user response:

Answered here: Why does a RegExp with global flag give wrong results?

Short explanation: the "g" flag makes the RegExp stateful and keeps the last index from the last match/test.

Also, calling the constructor with a literal makes it share the state of the literal. If you want the constructor to create a new RegExp with a clean state, you need to call it with the string pattern and the flags parameters like so:

new RegExp("^(hello|goodbye)?\\s*world$", "i")

CodePudding user response:

I use https://regexr.com/ for problems with regex. I do not keep this information, when I need it, I use the validator that appears on this site.

  • Related