192

I have a very long regular expression, which I wish to split into multiple lines in my JavaScript code to keep each line length 80 characters according to JSLint rules. It's just better for reading, I think. Here's pattern sample:

var pattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
Nik Sumeiko
  • 8,263
  • 8
  • 50
  • 53
  • 5
    It seems you're (trying to) validate e-mail addresses. Why not simply do `/\S+@\S+\.\S+/` ? – Bart Kiers Sep 07 '12 at 11:21
  • 1
    You should probably look to find a way to do that without a regular expression, or with multiple smaller regular expressions. That would be much more readable than a regular expression that long. If your regular expression is more than about 20 characters, there's probably a better way to do it. – ForbesLindesay Sep 07 '12 at 11:22
  • 2
    Isn't 80 characters kind of obsolete nowadays with wide monitors? – Oleg V. Volkov Sep 07 '12 at 12:04
  • 9
    @OlegV.Volkov No. A person could be using split windows in vim, a virtual terminal in a server room. It is wrong to assume everyone will be coding in the same viewport as you. Furthermore, limiting your lines to 80 chars forces you to break up your code into smaller functions. – synic Oct 10 '12 at 21:23
  • Well, I certainly see your motivation for wanting to do this here - once this regex is split over multiple lines, as demonstrated by Koolilnc, it immediately becomes a perfect example of readable, self-documenting code. ¬_¬ – Mark Amery Jun 10 '14 at 13:41
  • @OlegV.Volkov, it is still can be convenient to be able to split a wide monitor into several windows. For instance in one window you have your text editor, in another unit tests run – d.k Aug 04 '16 at 10:13

11 Answers11

162

Extending @KooiInc answer, you can avoid manually escaping every special character by using the source property of the RegExp object.

Example:

var urlRegex = new RegExp(
  /(?:(?:(https?|ftp):)?\/\/)/.source       // protocol
  + /(?:([^:\n\r]+):([^@\n\r]+)@)?/.source  // user:pass
  + /(?:(?:www.)?([^/\n\r]+))/.source       // domain
  + /(\/[^?\n\r]+)?/.source                 // request
  + /(\?[^#\n\r]*)?/.source                 // query
  + /(#?[^\n\r]*)?/.source                  // anchor
);

or if you want to avoid repeating the .source property you can do it using the Array.map() function:

var urlRegex = new RegExp([
  /(?:(?:(https?|ftp):)?\/\/)/,     // protocol
  /(?:([^:\n\r]+):([^@\n\r]+)@)?/,  // user:pass
  /(?:(?:www.)?([^/\n\r]+))/,       // domain
  /(\/[^?\n\r]+)?/,                 // request
  /(\?[^#\n\r]*)?/,                 // query
  /(#?[^\n\r]*)?/,                  // anchor
].map(function (r) { return r.source; }).join(''));

In ES6 the map function can be reduced to: .map(r => r.source).

Géry Ogam
  • 6,336
  • 4
  • 38
  • 67
korun
  • 2,211
  • 1
  • 16
  • 12
  • 3
    Exactly what I was looking for, super-clean. Thanks! – Marian Zagoruiko Mar 25 '16 at 10:36
  • 13
    This is really convenient for adding comments to a long regexp. However, it is limited by having matching parentheses on the same line. – Nathan S. Watson-Haigh Jul 12 '18 at 02:22
  • Definitely, this! Super nice with the ability to comment each sub-regex. – GaryO Mar 26 '19 at 00:52
  • Thanks, it helped putting source in regex function – Code Jan 15 '20 at 06:17
  • 1
    Very clever. Thanks, this idea helped me a lot. Just as a side note: I encapsulated the whole thing in an function to make it even cleaner: `combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))` Usage: `combineRegex(/regex1/, /regex2/, ...)` – Scindix Apr 30 '20 at 12:33
  • Also you should probably add the limitations of this method. (I.e. matching parenthesis, etc.) – Scindix Apr 30 '20 at 12:35
141

[Edit 2022/08] Created a small github repository to create regular expressions with spaces, comments and templating.


You could convert it to a string and create the expression by calling new RegExp():

var myRE = new RegExp (['^(([^<>()[\]\\.,;:\\s@\"]+(\\.[^<>(),[\]\\.,;:\\s@\"]+)*)',
                        '|(\\".+\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                        '[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\\.)+',
                        '[a-zA-Z]{2,}))$'].join(''));

Notes:

  1. when converting the expression literal to a string you need to escape all backslashes as backslashes are consumed when evaluating a string literal. (See Kayo's comment for more detail.)

  2. RegExp accepts modifiers as a second parameter

    /regex/g => new RegExp('regex', 'g')

[Addition ES20xx (tagged template)]

In ES20xx you can use tagged templates. See the snippet.

Note:

  • Disadvantage here is that you can't use plain whitespace in the regular expression string (always use \s, \s+, \s{1,x}, \t, \n etc).

(() => {
  const createRegExp = (str, opts) => 
    new RegExp(str.raw[0].replace(/\s/gm, ""), opts || "");
  const yourRE = createRegExp`
    ^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|
    (\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|
    (([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`;
  console.log(yourRE);
  const anotherLongRE = createRegExp`
    (\byyyy\b)|(\bm\b)|(\bd\b)|(\bh\b)|(\bmi\b)|(\bs\b)|(\bms\b)|
    (\bwd\b)|(\bmm\b)|(\bdd\b)|(\bhh\b)|(\bMI\b)|(\bS\b)|(\bMS\b)|
    (\bM\b)|(\bMM\b)|(\bdow\b)|(\bDOW\b)
    ${"gi"}`;
  console.log(anotherLongRE);
})();
KooiInc
  • 119,216
  • 31
  • 141
  • 177
  • 5
    A `new RegExp` is a great way for multiline regular expressions. Instead of joining arrays, you can just use a string concatenation operator: `var reg = new RegExp('^([a-' + 'z]+)$','i');` – dakab Apr 22 '14 at 12:29
  • 49
    **Caution:** A long *regular expression literal* could be broken into multiple lines using the above answer. However it needs care because you can't simply copy the regular expression literal (defined with `//`) and paste it as the string argument to the RegExp constructor. This is because backslash characters get consumed when evaluating the *string literal*. Example: `/Hey\sthere/` cannot be replaced by `new RegExp("Hey\sthere")`. Instead it should be replaced by `new RegExp("Hey\\sthere")` Note the extra backslash! Hence I prefer to just leave a long regex literal on one long line – Kayo Apr 27 '14 at 04:37
  • 5
    An even clearer way to do this is to create named variables holding meaningful subsections, and joining *those* as strings or in an array. That lets you construct the `RegExp` in a way that is much easier to understand. – Chris Krycho Oct 03 '14 at 17:12
  • Also MDN recommends to use literal notation when the regex will remain constant, versus the constructor notation when the regex can change. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp – Akin Hwan Sep 15 '21 at 17:02
  • Replacing `.replace(/\s/gm, "")` with `.replace(/( #.*|\s)/gm, "")` will also enable the `#` comments (like [ruby](https://github.com/rubocop/ruby-style-guide#multi-line-regexes)), which requies at least one space before the `#`. – Míng Apr 11 '22 at 07:28
  • @AkinHwan I don't believe it says that anymore. But in any case, I would almost always optimize for maintainability. It's very easy (and pretty much normalized) to create regexes that look like hieroglyphics. It's almost a badge of honor. Far better to write something that can easily be parsed by Future Me, let alone another dev. – acjay Dec 18 '22 at 04:09
33

Using strings in new RegExp is awkward because you must escape all the backslashes. You may write smaller regexes and concatenate them.

Let's split this regex

/^foo(.*)\bar$/

We will use a function to make things more beautiful later

function multilineRegExp(regs, options) {
    return new RegExp(regs.map(
        function(reg){ return reg.source; }
    ).join(''), options);
}

And now let's rock

var r = multilineRegExp([
     /^foo/,  // we can add comments too
     /(.*)/,
     /\bar$/
]);

Since it has a cost, try to build the real regex just once and then use that.

Riccardo Galli
  • 12,419
  • 6
  • 64
  • 62
  • 1
    This is very cool -- not only you don't have to do additional escaping, but also you keep the special syntax highlight for the sub-regexes! – quezak Jul 24 '20 at 13:41
  • 1
    one caveat though: you need to make sure your sub-regexes are self-contained, or wrap each in a new bracket group. Example: `multilineRegExp([/a|b/, /c|d])` results in `/a|bc|d/`, while you meant `(a|b)(c|d)`. – quezak Jul 24 '20 at 13:52
  • this makes it impossible to break a big, complex regex group in multiple lines, as @quezak mentioned, as you can't do `multilineRegExp([/a (/, /cold/, /|hot/, /) drink/]) – ProblemsLoop Jan 31 '22 at 11:01
18

Thanks to the wonderous world of template literals you can now write big, multi-line, well-commented, and even semantically nested regexes in ES6.

//build regexes without worrying about
// - double-backslashing
// - adding whitespace for readability
// - adding in comments
let clean = (piece) => (piece
    .replace(/((^|\n)(?:[^\/\\]|\/[^*\/]|\\.)*?)\s*\/\*(?:[^*]|\*[^\/])*(\*\/|)/g, '$1')
    .replace(/((^|\n)(?:[^\/\\]|\/[^\/]|\\.)*?)\s*\/\/[^\n]*/g, '$1')
    .replace(/\n\s*/g, '')
);
window.regex = ({raw}, ...interpolations) => (
    new RegExp(interpolations.reduce(
        (regex, insert, index) => (regex + insert + clean(raw[index + 1])),
        clean(raw[0])
    ))
);

Using this you can now write regexes like this:

let re = regex`I'm a special regex{3} //with a comment!`;

Outputs

/I'm a special regex{3}/

Or what about multiline?

'123hello'
    .match(regex`
        //so this is a regex

        //here I am matching some numbers
        (\d+)

        //Oh! See how I didn't need to double backslash that \d?
        ([a-z]{1,3}) /*note to self, this is group #2*/
    `)
    [2]

Outputs hel, neat!
"What if I need to actually search a newline?", well then use \n silly!
Working on my Firefox and Chrome.


Okay, "how about something a little more complex?"
Sure, here's a piece of an object destructuring JS parser I was working on:

regex`^\s*
    (
        //closing the object
        (\})|

        //starting from open or comma you can...
        (?:[,{]\s*)(?:
            //have a rest operator
            (\.\.\.)
            |
            //have a property key
            (
                //a non-negative integer
                \b\d+\b
                |
                //any unencapsulated string of the following
                \b[A-Za-z$_][\w$]*\b
                |
                //a quoted string
                //this is #5!
                ("|')(?:
                    //that contains any non-escape, non-quote character
                    (?!\5|\\).
                    |
                    //or any escape sequence
                    (?:\\.)
                //finished by the quote
                )*\5
            )
            //after a property key, we can go inside
            \s*(:|)
      |
      \s*(?={)
        )
    )
    ((?:
        //after closing we expect either
        // - the parent's comma/close,
        // - or the end of the string
        \s*(?:[,}\]=]|$)
        |
        //after the rest operator we expect the close
        \s*\}
        |
        //after diving into a key we expect that object to open
        \s*[{[:]
        |
        //otherwise we saw only a key, we now expect a comma or close
        \s*[,}{]
    ).*)
$`

It outputs /^\s*((\})|(?:[,{]\s*)(?:(\.\.\.)|(\b\d+\b|\b[A-Za-z$_][\w$]*\b|("|')(?:(?!\5|\\).|(?:\\.))*\5)\s*(:|)|\s*(?={)))((?:\s*(?:[,}\]=]|$)|\s*\}|\s*[{[:]|\s*[,}{]).*)$/

And running it with a little demo?

let input = '{why, hello, there, "you   huge \\"", 17, {big,smelly}}';
for (
    let parsed;
    parsed = input.match(r);
    input = parsed[parsed.length - 1]
) console.log(parsed[1]);

Successfully outputs

{why
, hello
, there
, "you   huge \""
, 17
,
{big
,smelly
}
}

Note the successful capturing of the quoted string.
I tested it on Chrome and Firefox, works a treat!

If curious you can checkout what I was doing, and its demonstration.
Though it only works on Chrome, because Firefox doesn't support backreferences or named groups. So note the example given in this answer is actually a neutered version and might get easily tricked into accepting invalid strings.

Hashbrown
  • 12,091
  • 8
  • 72
  • 95
  • 3
    you should think of exporting this as a NodeJS package, it is marvelous – rmobis May 26 '20 at 04:20
  • 1
    Although I've never done it myself, there's a pretty thorough tutorial here: https://zellwk.com/blog/publish-to-npm. I'd suggest checking np, at the end of the page. I've never used it, but Sindre Sorhus is a magician with these things, so I wouldn't pass it up. – rmobis May 26 '20 at 08:14
  • Hey @Hashbrown , do you mind if I make this a package? I'll give you attribution of course – Siddharth Shyniben May 16 '21 at 05:04
  • 1
    @Siddharth go for it. I haven't seemed to get around to it. [Hashbrown777](https://gist.github.com/Hashbrown777) on github too – Hashbrown May 16 '21 at 09:56
  • 1
    @Siddharth [I've already got a gist using it in practice](https://gist.github.com/Hashbrown777/5b316892a12670d0c4cc3a072bc39a71#gistcomment-3746702) – Hashbrown May 18 '21 at 12:33
  • How would you add regex flags here? – ProblemsLoop Jan 31 '22 at 11:10
  • @ProblemsLoop well [this is what I actually use](https://codepen.io/Hashbrown/pen/vYWVYLx). See how I use the last value as an [optional] options object? So if your case was needing 'g' flag it would be `{global:true}` but you could change the code slightly to just accept a string (`'g'}` directly I suppose. You could even add other objects elsewhere for instance to change how `clean()` works, maybe temporarily disabling it mid-regex. As an example of something like that I inject an array which is just turned into `or`d strings. – Hashbrown Feb 27 '22 at 12:10
11

There are good answers here, but for completeness someone should mention Javascript's core feature of inheritance with the prototype chain. Something like this illustrates the idea:

RegExp.prototype.append = function(re) {
  return new RegExp(this.source + re.source, this.flags);
};

let regex = /[a-z]/g
.append(/[A-Z]/)
.append(/[0-9]/);

console.log(regex); //=> /[a-z][A-Z][0-9]/g
Jamesfo
  • 524
  • 6
  • 11
  • 2
    This is the best answer here. – parttimeturtle Apr 06 '20 at 19:41
  • 1
    This creates compiles the RegExp object each time you use `.append`, so the other answers which compile the given combined array in one go is slightly better. The difference is insignificant, I guess, but it's worth noting it. – ProblemsLoop Jan 31 '22 at 10:37
  • 1
    @ProblemsLoop This is true. In my tests it's about 80% slower than the accepted solution on my 8-year-old workstation with a 6-line multiline regex. Still, my PC came in at ~220,000 ops/sec https://jsbench.me/sfkz4e7mjf/2 – Jamesfo Feb 01 '22 at 23:04
6

The regex above is missing some black slashes which isn't working properly. So, I edited the regex. Please consider this regex which works 99.99% for email validation.

let EMAIL_REGEXP = 
new RegExp (['^(([^<>()[\\]\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\.,;:\\s@\"]+)*)',
                    '|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                    '[0-9]{1,3}\])|(([a-zA-Z\\-0-9]+\\.)+',
                    '[a-zA-Z]{2,}))$'].join(''));
Anvesh Reddy
  • 131
  • 2
  • 4
2

To avoid the Array join, you can also use the following syntax:

var pattern = new RegExp('^(([^<>()[\]\\.,;:\s@\"]+' +
  '(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@' +
  '((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' +
  '(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');
andreasonny83
  • 1,213
  • 11
  • 19
2

You can simply use string operation.

var pattenString = "^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|"+
"(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|"+
"(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$";
var patten = new RegExp(pattenString);
Mubeena
  • 21
  • 2
2

I tried improving korun's answer by encapsulating everything and implementing support for splitting capturing groups and character sets - making this method much more versatile.

To use this snippet you need to call the variadic function combineRegex whose arguments are the regular expression objects you need to combine. Its implementation can be found at the bottom.

Capturing groups can't be split directly that way though as it would leave some parts with just one parenthesis. Your browser would fail with an exception.

Instead I'm simply passing the contents of the capture group inside an array. The parentheses are automatically added when combineRegex encounters an array.

Furthermore quantifiers need to follow something. If for some reason the regular expression needs to be split in front of a quantifier you need to add a pair of parentheses. These will be removed automatically. The point is that an empty capture group is pretty useless and this way quantifiers have something to refer to. The same method can be used for things like non-capturing groups (/(?:abc)/ becomes [/()?:abc/]).

This is best explained using a simple example:

var regex = /abcd(efghi)+jkl/;

would become:

var regex = combineRegex(
    /ab/,
    /cd/,
    [
        /ef/,
        /ghi/
    ],
    /()+jkl/    // Note the added '()' in front of '+'
);

If you must split character sets you can use objects ({"":[regex1, regex2, ...]}) instead of arrays ([regex1, regex2, ...]). The key's content can be anything as long as the object only contains one key. Note that instead of () you have to use ] as dummy beginning if the first character could be interpreted as quantifier. I.e. /[+?]/ becomes {"":[/]+?/]}

Here is the snippet and a more complete example:

function combineRegexStr(dummy, ...regex)
{
    return regex.map(r => {
        if(Array.isArray(r))
            return "("+combineRegexStr(dummy, ...r).replace(dummy, "")+")";
        else if(Object.getPrototypeOf(r) === Object.getPrototypeOf({}))
            return "["+combineRegexStr(/^\]/, ...(Object.entries(r)[0][1]))+"]";
        else 
            return r.source.replace(dummy, "");
    }).join("");
}
function combineRegex(...regex)
{
    return new RegExp(combineRegexStr(/^\(\)/, ...regex));
}

//Usage:
//Original:
console.log(/abcd(?:ef[+A-Z0-9]gh)+$/.source);
//Same as:
console.log(
  combineRegex(
    /ab/,
    /cd/,
    [
      /()?:ef/,
      {"": [/]+A-Z/, /0-9/]},
      /gh/
    ],
    /()+$/
  ).source
);
Scindix
  • 1,254
  • 2
  • 15
  • 32
1

Personally, I'd go for a less complicated regex:

/\S+@\S+\.\S+/

Sure, it is less accurate than your current pattern, but what are you trying to accomplish? Are you trying to catch accidental errors your users might enter, or are you worried that your users might try to enter invalid addresses? If it's the first, I'd go for an easier pattern. If it's the latter, some verification by responding to an e-mail sent to that address might be a better option.

However, if you want to use your current pattern, it would be (IMO) easier to read (and maintain!) by building it from smaller sub-patterns, like this:

var box1 = "([^<>()[\]\\\\.,;:\s@\"]+(\\.[^<>()[\\]\\\\.,;:\s@\"]+)*)";
var box2 = "(\".+\")";

var host1 = "(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])";
var host2 = "(([a-zA-Z\-0-9]+\\.)+[a-zA-Z]{2,})";

var regex = new RegExp("^(" + box1 + "|" + box2 + ")@(" + host1 + "|" + host2 + ")$");
Bart Kiers
  • 166,582
  • 36
  • 299
  • 288
  • 23
    Downvoting - Although your comments about reducing regex complexity are valid, OP specifically is asking how to "split long regex over multiple lines". So although your advice is valid, it has been given for the wrong reasons. e.g. changing business logic to work around a programming language. Furthermore, the code example you gave is quite ugly. – SleepyCal Oct 14 '14 at 15:13
  • 6
    @sleepycal I think Bart has answered the question. See the last section of his answer. He has answered the question as well as given an alternative. – Nidhin David Jan 14 '16 at 06:14
1

@Hashbrown's great answer got me on the right track. Here's my version, also inspired by this blog.

function regexp(...args) {
  function cleanup(string) {
    // remove whitespace, single and multi-line comments
    return string.replace(/\s+|\/\/.*|\/\*[\s\S]*?\*\//g, '');
  }

  function escape(string) {
    // escape regular expression
    return string.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&');
  }

  function create(flags, strings, ...values) {
    let pattern = '';
    for (let i = 0; i < values.length; ++i) {
      pattern += cleanup(strings.raw[i]);  // strings are cleaned up
      pattern += escape(values[i]);        // values are escaped
    }
    pattern += cleanup(strings.raw[values.length]);
    return RegExp(pattern, flags);
  }

  if (Array.isArray(args[0])) {
    // used as a template tag (no flags)
    return create('', ...args);
  }

  // used as a function (with flags)
  return create.bind(void 0, args[0]);
}

Use it like this:

regexp('i')`
  //so this is a regex

  //here I am matching some numbers
  (\d+)

  //Oh! See how I didn't need to double backslash that \d?
  ([a-z]{1,3}) /*note to self, this is group #2*/
`

To create this RegExp object:

/(\d+)([a-z]{1,3})/i
Nuno Cruces
  • 1,584
  • 15
  • 18