ECMAScript feature: regular expression pattern modifiers

[2025-01-10] dev, javascript, es proposal
(Ad, please don’t block)

Traditionally, we could only apply regular expression flags such as i (for ignoring case) to all of a regular expression. The ECMAScript feature “Regular Expression Pattern Modifiers” (by Ron Buckton) enables us to apply them to only part of a regular expression. In this blog post we examine how they work and what their use cases are.

Regular expression pattern modifiers attributes reached stage 4 in October 2024 and will probably be part of ECMAScript 2025.

What are pattern modifiers?  

How a regular expression works is influenced by so-called flags – e.g., the flag i ignores case during matching:

> /yes/i.test('yes')
true
> /yes/i.test('YES')
true

Pattern modifiers let us apply flags to parts of a regular expression (vs. all of the regular expression) – for example, in the following regular expression, the flag i is only applied to “HELLO”:

> /^x(?i:HELLO)x$/.test('xHELLOx')
true
> /^x(?i:HELLO)x$/.test('xhellox')
true
> /^x(?i:HELLO)x$/.test('XhelloX')
false

In a way, pattern modifiers are inline flags.

Syntax  

This is what the syntax looks like:

(?ims-ims:pattern)
(?ims:pattern)
(?-ims:pattern)

Notes:

  • A flag that follows the question mark (?) is activated.
  • A flag that follows the hyphen (-) is deactivated.
  • A flag cannot appear in both the “activation section” and the “deactivation section”.
  • Without any flags, this syntax is simply a non-capturing group: (?:pattern)

Let’s change the previous example: Now all of the regular expression is case-insensitive – except for “HELLO”:

> /^x(?-i:HELLO)x$/i.test('xHELLOx')
true
> /^x(?-i:HELLO)x$/i.test('XHELLOX')
true
> /^x(?-i:HELLO)x$/i.test('XhelloX')
false

Which flags are supported?  

The following flags can be used in pattern modifiers:

Literal flag Property name ES Description
i ignoreCase ES3 Match case-insensitively
m multiline ES3 ^ and $ match per line
s dotAll ES2018 Dot matches line terminators

For more information, see the section on flags in “Exploring JavaScript”.

The remaining flags are not supported because they would either make regular expression semantics too complicated (e.g. flag v) or because they only make sense if applied to the whole regular expression (e.g. flag g).

What are the use cases for pattern modifiers?  

Use case: changing flags for part of a regular expression  

It’s sometimes useful if you can change flags for part of a regular expression. For example, Ron Buckton explains that changing flag m helps with matching a Markdown frontmatter block at the start of a file (I slightly edited his version):

const re = /(?-m:^)---\r?\n((?:^(?!---$).*\r?\n)*)^---$/m;
assert.equal(re.test('---a'), false);
assert.equal(re.test('---\n---'), true);
assert.equal(
  re.exec('---\n---')[1],
  ''
);
assert.equal(
  re.exec('---\na: b\n---')[1],
  'a: b\n'
);

How does this regular expression work?

  • By default, flag m is on and the anchor ^ matches at the beginning of a line and the anchor $ matches at the end of a line.
  • The very first ^ is different: It must match at the beginning of a string. That’s why we use a pattern modifier there and switch flag m off.

This is the regular expression, annotated with insignificant whitespace and explanatory comments:

(?-m:^)---\r?\n  # first line of string
(  # capturing group for the frontmatter
  (?:  # pattern for one line (non-capturing group)
    ^(?!---$)  # line must not start with "---" + EOL (lookahead)
    .*\r?\n
  )*
)
^---$  # closing delimiter of frontmatter

Use case: inlining flags  

In some situations, flags being outside the actual regular expressions is inconvenient. Then pattern modifiers help. Examples include:

  • Storing regular expressions in configuration files, e.g. in JSON format.
  • The Regex+ library provides a template literal that makes creating regular expressions much more convenient. The syntax for specifying flags adds a bit of clutter that can be avoided via pattern modifiers (if they support the required flags):
    regex('i')`world`
    regex`(?i:world)`
    

Use case: regular expression fragments can change flags  

In complex applications, it helps if you can compose large regular expressions out of smaller regular expressions. The aforementioned Regex+ library supports that. If a smaller regular expression needs different flags (e.g. because it wants to ignore case) then it can – thanks to pattern modifiers.

Further reading