Composing regular expressions via re-template-tag

[2017-07-22] dev, javascript, regexp, template literals
(Ad, please don’t block)

I’ve written the small library re-template-tag that provides a template tag function for composing regular expressions. This blog post explains how it works.

The basics  

The library implements the template tag re for regular expressions:

Install:

npm install re-template-tag

Import:

import {re} from 're-template-tag';

Use: The following two expressions produce the same regular expression.

re`/abc/gu`
/abc/gu

A tour of the features  

The unit tests are a good tour of the features of re-template-tag.

Main features  

You can use re to assemble a regular expression from fragments:

test('Composing regular expressions', t => {
    const RE_YEAR = /([0-9]{4})/;
    const RE_MONTH = /([0-9]{2})/;
    const RE_DAY = /([0-9]{2})/;
    const RE_DATE = re`/^${RE_YEAR}-${RE_MONTH}-${RE_DAY}$/u`;
    t.is(RE_DATE.source, '^([0-9]{4})-([0-9]{2})-([0-9]{2})$');
});

Any strings you insert are escaped properly:

test('Escaping special characters in strings', t => {
    t.is(re`/-${'.'}-/u`.source, '-\\.-');
});

Flag-less mode:

test('Simple, flag-less mode', t => {
    const regexp = re`abc`;
    t.true(regexp instanceof RegExp);
    t.is(regexp.source, 'abc');
    t.is(regexp.flags, '');
});

Details and advanced features  

You can use the backslash as you would inside regular expression literals:

test('Use “raw” backslashes like in regular expressions', t => {
    t.is(re`/\./u`.source, '\\.');
});

Regular expression flags (such as /u in the previous example) can also be computed:

test('Computed flags', t => {
    const regexp = re`/abc/${'g'+'u'}`;
    t.true(regexp instanceof RegExp);
    t.is(regexp.source, 'abc');
    t.is(regexp.flags, 'gu');
});

Why is this useful?  

  • You can compose regular expressions from fragments and document the fragments via comments. That makes regular expressions easier to understand.
  • You can define constants for regular expression fragments and reuse them.
  • You can define plain text constants via strings and insert them into regular expressions and they are escaped as necessary.

re and named capture groups  

re profits from the upcoming regular expression feature “named capture groups”, because it makes the fragments more independent.

Without named groups:

const RE_YEAR = /([0-9]{4})/;
const RE_MONTH = /([0-9]{2})/;
const RE_DAY = /([0-9]{2})/;
const RE_DATE = re`/^${RE_YEAR}-${RE_MONTH}-${RE_DAY}$/u`;

const match = RE_DATE.exec('2017-01-27');
console.log(match[1]); // 2017

With named groups:

const RE_YEAR = re`(?<year>[0-9]{4})`;
const RE_MONTH = re`(?<month>[0-9]{2})`;
const RE_DAY = re`(?<day>[0-9]{2})`;
const RE_DATE = re`/${RE_YEAR}-${RE_MONTH}-${RE_DAY}/u`;

const match = RE_DATE.exec('2017-01-27');
console.log(match.groups.year); // 2017