💄 Fancy 💅 JS/TS regexes with whitespace, comments, and interpolation!
regex.<flags>`...`
creates a native JavaScript RegExp
object. Regexes created with this function strip all literal white space, as well as all comments starting with a #
.
import { regex } from 'fancy-regex'
import { assertEquals, assertMatch, assertNotEquals, assertThrows } from 'std/assert/mod.ts'
const myFancyRegex = regex.v`
hello,\ 🌎! # space escaped with backslash becomes a literal space
`
assertEquals(myFancyRegex, /hello, 🌎!/v)
You can use _
to create a regex with no flags.
const flaglessRe = regex._`flagless`
assertEquals(flaglessRe, /flagless/)
If you're using TypeScript, flags must be supplied in alphabetical order to pass type checking.
// OK!
regex.gimsvy`👍`
// @ts-expect-error Property 'yvsmig' does not exist on type <...>
regex.yvsmig`⛔`
If you like, you can pass string flags or use an options object instead.
const globalRe = regex('gv')`
💩+ # with unicode enabled, this matches by codepoint
`
assertEquals(globalRe, /💩+/gv)
const withOptionsObject = regex({
unicodeSets: true,
global: true,
})`
^
💩+ # with unicode enabled, this matches by codepoint
$
`
assertEquals(withOptionsObject, /^💩+$/gv)
Interpolation is simple, with escaping of interpolated strings handled automatically.
const interpolatedRe = regex.iv`
${'[abc]'} # seamlessly interpolate strings...
.
${/[abc]/} # ...and other regexes
.
${/[abc]/g} # inner flags are ignored when interpolated
`
assertEquals(interpolatedRe, /\[abc\].[abc].[abc]/iv)
Regex escapes (\b
, \w
, etc.) are supported.
const escapedRe = regex.iv`
\w\d\b\0\\ # look Mom, no double escaping!
\r\n\t\x20 # "\x20" matches a literal space
`
assertEquals(escapedRe, /\w\d\b\0\\\r\n\t\x20/iv)
You can also escape literal white space, hash symbols #
, backticks `
, or the sequence ${
, by preceding them with a backslash.
const escapedRe2 = regex._`
\#
\ # a literal space
\`
\$\{
`
assertEquals(escapedRe2, /# `\$\{/)
assertMatch('# `${', escapedRe2)
Interpolated arrays are automatically converted to non-capturing groups, sorted by from longest to shortest. Duplicate values, false
, and nullish values are removed.
const withArray = regex.v`
${['aa', 'bbb', '.', false, null, undefined]}
`
assertEquals(withArray, /(?:bbb|aa|\.)/v)
If you want to interpolate a string you want to be interpreted as raw regex source fragment, you'll need to wrap it in a RegexFragment
first.
For example, we would need to use this approach if we wanted to dynamically interpolate create a quantifier {3}
that isn't syntactically valid as a standalone regex, such that the desired result is /^a{3}$/v
:
const expected = /^a{3}$/v
const attempt1 = regex.v`^a${'{3}'}$`
assertNotEquals(attempt1, expected)
assertEquals(attempt1, /^a\{3\}$/v)
assertThrows(
() => {
const attempt2 = regex.v`^a${regex.v`{3}`}$`
},
SyntaxError,
'Invalid regular expression: /{3}/v: Nothing to repeat',
)
import { RegexFragment } from 'fancy-regex'
const success = regex.v`^a${new RegexFragment('{3}')}$`
assertEquals(success, expected)
assertMatch('aaa', success)
fancy-regex
also provides the utility function unwrap
.
Removes start-of-string and end-of-string matchers from a regex. Useful for interpolating or repurposing single-match regexes:
import { unwrap } from 'fancy-regex'
const singleHex = /^[0-9a-f]$/vi
const hex = unwrap(singleHex)
assertEquals(hex, /[0-9a-f]/vi)
const singleUuid = regex.v`
^
${hex}{8}
-
${hex}{4}
-
${hex}{4}
-
${hex}{4}
-
${hex}{12}
$
`
assertEquals(singleUuid, /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/v)
const multipleUuid = unwrap(singleUuid, 'gv')
assertEquals(multipleUuid, /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gv)