### Content/1.Guide/1.Index --- title: Setup description: Install magic-regexp from npm and (optionally) enable the build-time transform via a plugin. --- First, install `magic-regexp`: :pm-install{name="magic-regexp"} --- Second, optionally, you can enable the included transform, [which enables zero-runtime usage](/guide/usage#build-time-transform). ::code-group ```js [nuxt.config.ts] // Nuxt 3 import { defineNuxtConfig } from 'nuxt' export default defineNuxtConfig({ // This will also enable auto-imports of magic-regexp helpers modules: ['magic-regexp/nuxt'], }) ``` ```js [vite.config.ts] import { MagicRegExpTransformPlugin } from 'magic-regexp/transform' import { defineConfig } from 'vite' export default defineConfig({ plugins: [MagicRegExpTransformPlugin.vite()], }) ``` ```js [next.config.mjs] // or, if using next.config.js // const { MagicRegExpTransformPlugin } = require('magic-regexp/transform') import { MagicRegExpTransformPlugin } from 'magic-regexp/transform' export default { webpack(config) { config.plugins = config.plugins || [] config.plugins.push(MagicRegExpTransformPlugin.webpack()) return config }, } ``` ```js [build.config.ts ] import { MagicRegExpTransformPlugin } from 'magic-regexp/transform' // unbuild import { defineBuildConfig } from 'unbuild' export default defineBuildConfig({ hooks: { 'rollup:options': (options, config) => { config.plugins.push(MagicRegExpTransformPlugin.rollup()) }, }, }) ``` :: --- ### Content/1.Guide/2.Usage --- title: Usage --- ```js import { createRegExp, exactly } from 'magic-regexp' const regExp = createRegExp(exactly('foo/test.js').after('bar/')) console.log(regExp) // /(?<=bar\/)foo\/test\.js/ ``` ## createRegExp Every pattern you create with the library should be wrapped in `createRegExp`, which enables the build-time transform. `createRegExp` accepts an arbitrary number of arguments of type `string` or `Input` (built up using helpers from `magic-regexp`), and an optional final argument of an array of flags or a flags string. It creates a `MagicRegExp`, which concatenates all the patterns from the arguments that were passed in. ```js import { createRegExp, exactly, global, maybe, multiline } from 'magic-regexp' createRegExp(exactly('foo').or('bar')) createRegExp('string-to-match', [global, multiline]) // you can also pass flags directly as strings or Sets createRegExp('string-to-match', ['g', 'm']) // or pass in multiple `string` and `input patterns`, // all inputs will be concatenated to one RegExp pattern createRegExp( 'foo', maybe('bar').groupedAs('g1'), 'baz', [global, multiline] ) // equivalent to /foo(?(?:bar)?)baz/gm ``` ::alert By default, all helpers from `magic-regexp` assume that input that is passed should be escaped - so no special RegExp characters apply. So `createRegExp('foo.\d')` will not match `food3` but only `foo.\d` exactly. :: ## Creating inputs There are a range of helpers that can be used to activate pattern matching, and they can be chained. Each one of these returns an object of type `Input` that can be passed directly to `new RegExp`, `createRegExp`, to another helper or chained to produce more complex patterns. | | | | ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `charIn`, `charNotIn` | this matches or doesn't match any character in the string provided. | | `anyOf` | this takes a variable number of inputs and matches any of them. | | `char`, `word`, `wordChar`, `wordBoundary`, `digit`, `whitespace`, `letter`, `letter.lowercase`, `letter.uppercase`, `tab`, `linefeed` and `carriageReturn` | these are helpers for specific RegExp characters. | | `not` | this can prefix `word`, `wordChar`, `wordBoundary`, `digit`, `whitespace`, `letter`, `letter.lowercase`, `letter.uppercase`, `tab`, `linefeed` or `carriageReturn`. For example `createRegExp(not.letter)`. | | `maybe` | equivalent to `?` - this takes a variable number of inputs and marks them as optional. | | `oneOrMore` | Equivalent to `+` - this takes a variable number of inputs and marks them as repeatable, any number of times but at least once. | | `exactly` | This takes a variable number of inputs and concatenate their patterns, and escapes string inputs to match it exactly. | ::alert All helpers that takes `string` and `Input` are variadic functions, so you can pass in one or multiple arguments of `string` or `Input` to them and they will be concatenated to one pattern. for example, `exactly('foo', maybe('bar'))` is equivalent to `exactly('foo').and(maybe('bar'))`. :: ## Chaining inputs All of the helpers above return an object of type `Input` that can be chained with the following helpers: | | | | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `and` | this takes a variable number of inputs and adds them as new pattern to the current input, or you can use `and.referenceTo(groupName)` to adds a new pattern referencing to a named group. | | `or` | this takes a variable number of inputs and provides as an alternative to the current input. | | `after`, `before`, `notAfter` and `notBefore` | these takes a variable number of inputs and activate positive/negative lookahead/lookbehinds. Make sure to check [browser support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#browser_compatibility) as not all browsers support lookbehinds (notably Safari). | | `times` | this is a function you can call directly to repeat the previous pattern an exact number of times, or you can use `times.between(min, max)` to specify a range, `times.atLeast(x)` to indicate it must repeat at least x times, `times.atMost(x)` to indicate it must repeat at most x times or `times.any()` to indicate it can repeat any number of times, _including none_. | | `optionally` | this is a function you can call to mark the current input as optional. | | `as` | alias for `groupedAs` | | `groupedAs` | this defines the entire input so far as a named capture group. You will get type safety when using the resulting RegExp with `String.match()`. | | `grouped` | this defines the entire input so far as an anonymous group. | | `at` | this allows you to match beginning/ends of lines with `at.lineStart()` and `at.lineEnd()`. | ::alert By default, for better regex performance, creation input helpers such as `anyOf`, `maybe`, `oneOrMore`, and chaining input helpers such as `or`, `times(.between/atLeast/any)`, or `optionally` will wrap the input in a non-capturing group with `(?:)`. You can use chaining input helper `grouped` after any `Input` type to capture it as an anonymous group. :: ## Debugging When using `magic-regexp`, a TypeScript generic is generated for you that should show the RegExp that you are constructing, as you go. This is true not just for the final RegExp, but also for the pieces you create along the way. So, for example: ```ts import { exactly } from 'magic-regexp' exactly('test.mjs') // (alias) exactly<"test.mjs">(input: "test.mjs"): Input<"test\\.mjs", never> exactly('test.mjs').or('something.else') // (property) Input<"test\\.mjs", never>.or: <"something.else">(input: "something.else") => Input<"(?:test\\.mjs|something\\.else)", never> ``` Each function, if you hover over it, shows what's going in, and what's coming out by way of regular expression You can also call `.toString()` on any input to see the same information at runtime. ## Type-Level match result (experimental) We also provide an experimental feature that allows you to obtain the type-level results of a RegExp match or replace in string literals. To try this feature, please import all helpers from a subpath export `magic-regexp/further-magic` instead of `magic-regexp`. ```ts import { createRegExp, digit, exactly } from 'magic-regexp/further-magic' ``` This feature is especially useful when you want to obtain the type of the matched groups or test if your RegExp matches and captures from a given string as expected. This feature works best for matching literal strings such as ```ts 'foo'.match(createRegExp(exactly('foo').groupedAs('g1'))) ``` which will return a matched result of type `['foo', 'foo']`. `result.groups` of type `{ g1: 'foo' }`, `result.index` of type `0` and `result.length` of type `2`. If matching with dynamic string, such as ```ts myString.match(createRegExp(exactly('foo').or('bar').groupedAs('g1'))) ``` the type of the matched result will be `null`, or array of union of possible matches `["bar", "bar"] | ["foo", "foo"]` and `result.groups` will be type `{ g1: "bar" } | { g1: "foo" }`. ::alert For more usage details please see the [usage examples](3.examples.md#type-level-regexp-match-and-replace-result-experimental) or [test](https://github.com/danielroe/magic-regexp/blob/main/test/further-magic.test.ts). For type-related issues, please report them to [type-level-regexp](https://github.com/didavid61202/type-level-regexp). :: --- ### Content/1.Guide/3.Examples --- title: Examples --- ### Quick-and-dirty semver ```js import { char, createRegExp, digit, maybe, oneOrMore } from 'magic-regexp' createRegExp( oneOrMore(digit).groupedAs('major'), '.', oneOrMore(digit).groupedAs('minor'), maybe('.', oneOrMore(char).groupedAs('patch')) ) // /(?\d+)\.(?\d+)(?:\.(?.+))?/ ``` ### References to previously captured groups using the group name ```js import assert from 'node:assert' import { char, createRegExp, oneOrMore, wordChar } from 'magic-regexp' const TENET_RE = createRegExp( wordChar .groupedAs('firstChar') .and(wordChar.groupedAs('secondChar')) .and(oneOrMore(char)) .and.referenceTo('secondChar') .and.referenceTo('firstChar') ) // /(?\w)(?\w).+\k\k/ assert.equal(TENET_RE.test('TEN<==O==>NET'), true) ``` ### Type-level RegExp match and replace result (experimental) ::alert This feature is still experimental, to try it please import `createRegExp ` and all `Input` helpers from `magic-regexp/further-magic` instead of `magic-regexp`. :: When matching or replacing with literal string such as `magic-regexp v3.2.5.beta.1 just release!` ```ts import { anyOf, createRegExp, digit, exactly, oneOrMore, wordChar } from 'magic-regexp/further-magic' const literalString = 'magic-regexp 3.2.5.beta.1 just release!' const semverRegExp = createRegExp( oneOrMore(digit) .as('major') .and('.') .and(oneOrMore(digit).as('minor')) .and( exactly('.') .and(oneOrMore(anyOf(wordChar, '.')).groupedAs('patch')) .optionally() ) ) // `String.match()` example const matchResult = literalString.match(semverRegExp) matchResult[0] // "3.2.5.beta.1" matchResult[3] // "5.beta.1" matchResult.length // 4 matchResult.index // 14 matchResult.groups // groups: { // major: "3"; // minor: "2"; // patch: "5.beta.1"; // } // `String.replace()` example const replaceResult = literalString.replace( semverRegExp, `minor version "$2" brings many great DX improvements, while patch "$" fix some bugs and it's` ) replaceResult // "magic-regexp minor version \"2\" brings many great DX improvements, while patch \"5.beta.1\" fix some bugs and it's just release!" ``` When matching dynamic string, the result will be union of possible matches ```ts const myString = 'dynamic' const RegExp = createRegExp(exactly('foo').or('bar').groupedAs('g1')) const matchAllResult = myString.match(RegExp) matchAllResult // null | RegExpMatchResult<{ // matched: ["bar", "bar"] | ["foo", "foo"]; // namedCaptures: ["g1", "bar"] | ["g1", "foo"]; // input: string; // restInput: undefined; // }> matchAllResult?.[0] // ['foo', 'foo'] | ['bar', 'bar'] matchAllResult?.length // 2 | undefined matchAllResult?.groups // groups: { // g1: "foo" | "bar"; // } | undefined ``` --- ### Content/1.Guide/4.Transform --- title: Transform --- The best way to use `magic-regexp` is by making use of the included build-time transform. ```js const beforeTransform = createRegExp(exactly('foo/test.js').after('bar/')) // => gets _compiled_ to const afterTransform = /(?<=bar\/)foo\/test\.js/ ``` Of course, this only works with non-dynamic regexps. Within the `createRegExp` block you have to include all the helpers you are using from `magic-regexp` - and not rely on any external variables. This, for example, will not statically compile into a RegExp, although it will still continue to work with a minimal runtime: ```js const someString = 'test' const regExp = createRegExp(exactly(someString)) ``` --- ### Content/1.Guide/5.Converter --- title: Converter (experimental) --- It is also possible to convert existing regular expressions to `magic-regexp` syntax. ```ts import { convert } from 'magic-regexp/converter' convert(/[abc]/) // createRegExp(exactly('a').or('b').or('c')) convert(/(foo)bar\d+/) // createRegExp(exactly('foo').grouped(), 'bar', oneOrMore(digit)) ``` ### Options - `argsOnly` (boolean) _Default: `false`_ Only show arguments without `createRegExp` ```ts convert(/\w+@\w\.com/, { argsOnly: true }) // oneOrMore(wordChar), '@', wordChar, '.com' ``` --- ### Content/2.Contribution --- title: Contribution --- ## Roadmap **Future ideas** ::list{type=info} - More TypeScript guard-rails - More complex RegExp features/syntax - Instrumentation for accurately getting coverage on RegExps - Hybrid/partially-compiled RegExps for better dynamic support :: ## Development - Clone this repository - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` - Install dependencies using `pnpm install` - Run interactive tests using `pnpm dev` --- ### Content/Index --- title: Home navigation: false --- ::hero --- actions: - name: Documentation leftIcon: 'lucide:rocket' to: /guide - name: Try it out leftIcon: 'lucide:play' variant: outline to: https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAbzgYwBYEMoBoVQKbox4BKeA5gKIAeYOAJsGcDDnlesjADYCeOI6HgCM8OCADs8AeSgBZaKLgB3aHQDCGKHAC+cAGZQIIOAHIBTZAFp8ZNmBMAoBwHoAVK4dxXKDONtwYCE9vUEhYRAA6KN0DI1NzYCsbO2c9AFdYVDwoSwTkRy8AiACoHgCeMDw4LjwANzwuOFJKGjgBGDQ4fABnNK4YbrgACjts0DxxGHQuAEpg5ydnZzgAFQq8OhR0MBgMqrJDNLBuh2QJbvgASQARAH1iCjgAXlwCImbqMBH2Tl4hk2AdEsJhmEXQ4joQwYTBgERg426QwArKCDhAjhsAIKIgF0EEzOZncQXOBoo6DF4mbpGKqAywAJgAzAAWACcAAY4Fl8Mp9hATBF2mghjd7hQZgB+CJk44uZZwBVwAB6EsWywAimlEgBrSzgoEMWBlbp4ED1KCnc7wADKFFkADUKMQxc9XoQSORPkNPHAJNI5AooYxmKjDpU6Nj-gIAFbQEFYH0CkwJhV+mTyfBBmGh9HhyNmYDiOMzFNtQQif4RZO+yTpwNoTA5jERnFgQhofEJuYOW0Op1iuXK1WW4nwHp9eAvZD4d0fGjfDjcHj-PQQCDOIgXCLR7ogsF6IhQf5CTDOfFwvAXY+n1frzewncgtVNPB6bITZCXopwMD4WrAdFul4LYdj2TYZUGNJukLMgAiyUkwzgcR0BAPARxJFYKAAOQoFYXSnGd3k9ecfTYRc-hUKB1E0aUwyxHE9GAKALg0TB42UVRWKgWjc3o-4TSJai2JLGt-QzPAhgbKACR9BUwQhCJ8DffBxE-FYIH4vBBK4p9FTgeS6EU1931UvB1JXJiWM0eMHG7IkSXHfp6VdTCcLwh4LyvExXIAHieJ4pH8gA+NyQSAA --- #title magic-regexp #description A compiled-away, type-safe, readable RegExp alternative. :: ::card-group ::card --- icon: 'heroicons-cube-transparent' icon-size: 26 --- #title Lightweight runtime #description Zero-dependency, minimal runtime if no transform is used. :: ::card --- icon: 'heroicons-wrench' icon-size: 26 --- #title ... or pure RegExp #description Ships with transform to compile to pure regular expression. :: ::card --- icon: 'heroicons-shield-check' icon-size: 26 --- #title Type-safe #description Automatically typed capture groups, with generated RegExp displaying on hover. :: ::card --- icon: 'heroicons-book-open' icon-size: 26 --- #title Intuitive syntax #description Natural language syntax regular expression builder. :: :: --- ### README # 🦄 magic-regexp [![npm version][npm-version-src]][npm-version-href] [![npm downloads][npm-downloads-src]][npm-downloads-href] [![Github Actions][github-actions-src]][github-actions-href] [![Codecov][codecov-src]][codecov-href] [![Bundlephobia][bundlephobia-src]][bundlephobia-href] [](https://nuxt.care/?search=magic-regexp) > A compiled-away, type-safe, readable RegExp alternative - [✨  Changelog](https://github.com/danielroe/magic-regexp/blob/main/CHANGELOG.md) - [📖  Documentation](https://regexp.dev) - [▶️  Online playground](https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAbzgYwBYEMoBoVQKbox4BKeA5gKIAeYOAJsGcDDnlesjADYCeOI6HgCM8OCADs8AeSgBZaKLgB3aHQDCGKHAC+cAGZQIIOAHIBTZAFp8ZNmBMAoBwHoAVK4dxXKDONtwYCE9vUEhYRAA6KN0DI1NzYCsbO2c9AFdYVDwoSwTkRy8AiACoHgCeMDw4LjwANzwuOFJKGjgBGDQ4fABnNK4YbrgACjts0DxxGHQuAEpg5ydnZzgAFQq8OhR0MBgMqrJDNLBuh2QJbvgASQARAH1iCjgAXlwCImbqMBH2Tl4hk2AdEsJhmEXQ4joQwYTBgERg426QwArKCDhAjhsAIKIgF0EEzOZncQXOBoo6DF4mbpGKqAywAJgAzAAWACcAAY4Fl8Mp9hATBF2mghjd7hQZgB+CJk44uZZwBVwAB6EsWywAimlEgBrSzgoEMWBlbp4ED1KCnc7wADKFFkADUKMQxc9XoQSORPkNPHAJNI5AooYxmKjDpU6Nj-gIAFbQEFYH0CkwJhV+mTyfBBmGh9HhyNmYDiOMzFNtQQif4RZO+yTpwNoTA5jERnFgQhofEJuYOW0Op1iuXK1WW4nwHp9eAvZD4d0fGjfDjcHj-PQQCDOIgXCLR7ogsF6IhQf5CTDOfFwvAXY+n1frzewncgtVNPB6bITZCXopwMD4WrAdFul4LYdj2TYZUGNJukLMgAiyUkwzgcR0BAPARxJFYKAAOQoFYXSnGd3k9ecfTYRc-hUKB1E0aUwyxHE9GAKALg0TB42UVRWKgWjc3o-4TSJai2JLGt-QzPAhgbKACR9BUwQhCJ8DffBxE-FYIH4vBBK4p9FTgeS6EU1931UvB1JXJiWM0eMHG7IkSXHfp6VdTCcLwh4LyvExXIAHieJ4pH8gA+NyQSAA) ## Features - Runtime is zero-dependency and ultra-minimal - Ships with transform to compile to pure RegExp - Automatically typed capture groups - Natural language syntax - Generated RegExp displays on hover [📖  Read more](https://regexp.dev) ## 💻 Development - Clone this repository - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable` (use `npm i corepack` for Node.js < 16.10) - Install dependencies using `pnpm install` - Run interactive tests using `pnpm dev` ## Similar packages - [verbal-expressions](http://verbalexpressions.github.io/) - [typed-regex](https://github.com/phenax/typed-regex/) ## License Made with ❤️ Published under [MIT License](./LICENCE). [npm-version-src]: https://npmx.dev/api/registry/badge/version/magic-regexp [npm-version-href]: https://npmx.dev/package/magic-regexp [npm-downloads-src]: https://npmx.dev/api/registry/badge/downloads/magic-regexp [npm-downloads-href]: https://npmx.dev/package/magic-regexp [github-actions-src]: https://img.shields.io/github/actions/workflow/status/danielroe/magic-regexp/ci.yml?branch=main&style=flat-square [github-actions-href]: https://github.com/danielroe/magic-regexp/actions?query=workflow%3Aci [codecov-src]: https://img.shields.io/codecov/c/gh/danielroe/magic-regexp/main?style=flat-square [codecov-href]: https://codecov.io/gh/danielroe/magic-regexp [bundlephobia-src]: https://img.shields.io/bundlephobia/minzip/magic-regexp?style=flat-square [bundlephobia-href]: https://bundlephobia.com/package/magic-regexp ---