🔤🔍 Type-Level RegExp (WIP)
TypeScript type-level RegExp parser and matcher implemented using template literals.
👉 Try on TypeScript Playground
or see examples in Playground
and test
folders.
🚧 Work In Progress, PRs and issues are welcome 🚧
Quick Setup
- Add
type-level-regexp
dependency to your project
pnpm i -D type-level-regexp
npm i -D type-level-regexp
- Import
createRegExp
function, pass in a RegExp string pattern to it creates a TypedRegExp
, passing this TypedRegExp
to String.match()
, String.matchAll()
or String.replace()
functions to get fully typed match result.
Basic Usage
match result will be fully typed if match against a literal stirng, or shows emumerated results if match against a dynamic string.
import { createRegExp, spreadRegExpIterator } from 'type-level-regexp'
const regExp = createRegExp('foO(?<g1>b[a-g]r)(?:BAz|(?<g2>qux))', ['i'])
const matchResult = 'prefix foobarbaz suffix'.match(regExp)
matchResult[0]
matchResult[1]
matchResult[3]
matchResult.length
matchResult.index
matchResult.groups
const regExp2 = createRegExp('(\\d{4})[-.](?<month>\\w{3,4})[-.](\\d{1,2})')
const replaceResult = '1991-Sept-15'.replace(regExp2, '$<month> $3, $1')
replaceResult
const regExp3 = createRegExp('c[a-z]{2}', ['g'])
const matchALlIterator = 'cat car caw cay caw cay'.matchAll(regExp3)
const spreadedResult = spreadRegExpIterator(matchALlIterator)
spreadedResult[2][0]
spreadedResult[3].index
const InvalidRegExp = createRegExp('foo(bar')
For TypeScript library authors, you can also import individual generic types to parse and match RegExp string at type-level and combine with your library's type-level features.
import { ParseRegExp, MatchRegExp } from 'type-level-regexp'
type MatchResult = MatchRegExp<'fooBAR42', ParseRegExp<'Fo[a-z](Bar)\\d{2}'>, 'i'>
type Matched = MatchResult[0]
type First = MatchResult[1]
type RegExpAST = ParseRegExp<'foo(?<g1>bar)'>
Origin & Notice
The main purpose of this project is to test and demonstrate the possibility and limitations of writing a RegExp parser/matcher in TypeScript's type-level. Note that this may not be practically useful, but rather an interesting showcase.
The idea for this project originated while I was working on improving the type hints of string.match and replace in magic-regexp (created by the most inspiring, resourceful, and kind Daniel Roe from Nuxt, definitely check it out if you are working with RegExp and TypeScript!).
As the complexity grows, I start working on this separated repo to increase development speed and try out different iterations. It will be incorporate and use in magic-regexp, and Gabriel Vergnaud's awesome hotscript very soon.
❤️ Testing, feedbacks and PRs are welcome!
Features
- Export
createRegExp
function to create aTypedRegExp
that replace your original /regex_pattern/
regex object, which can be pass to String.match()
, String.matchAll()
and String.replace()
functions and gets fully typed result. - Shows
RegExpSyntaxError
if the provided RegExp pattern is invalid. - Enhance types of RegExp related
String
functions (.match
, matchAll
, .replace
...) for literal or dynamic typed string. - Result of
String
functions matched exactly as runtime result. - Support all common RegExp tokens (incl. Lookarounds, Backreferences...etc), quantifiers (incl. greedy/lazy) and (
g
,i
) flags. - Export helper functions
spreadRegExpMatchArray
and spreadRegExpIterator
to get tuple type of match results and iterators. - Provide generic type
ParseRegExp
to parse and RegExp string to AST. - Provide generic type
MatchRegExp
to match giving string with a parsed RegExp. - Provide generic type
ResolvePermutation
to permutation all possible matching string of given RegExp if possible (due to TypeScript type-level limitation) - More details please try on TypeScript Playground, or see tests files in Tests and Stackblitz. (examples in index.test-d.ts)
Example - type-safe args in replacing function of string.replace()
Example - spreaded string.matchAll()
with union of RegExp pattern remain as tuple
RegExp Tokens & Flags
Tokens | Description | Support |
---|
. | Matches any single character. | ✅ |
* , *? | Matches zero or more occurrences (Greedy/Lazy). | ✅ |
+ , *? | Matches one or more occurrences (Greedy/Lazy). | ✅ |
? , ?? | Matches zero or one occurrence (Greedy/Lazy). | ✅ |
^ | Matches the start of a line. | ✅ |
$ | Matches the end of a line. | ✅ |
\s , \S | Matches any whitespace, non-whitespace character. | ✅ |
\d , \D | Matches any digit, non-digit character. | ✅ |
\w , \W | Matches any word, non-word character. | ✅ |
\b , \B | Matches a word-boundary, non-word-boundary. | ✅ |
[abc] | Matches any character in the set. | ✅ |
[^abc] | Matches any character not in the set. | ✅ |
() | Creates a capturing group. | ✅ |
(?:) | Creates a non-capturing group. | ✅ |
(?<name>) | Creates a named-capturing group. | ✅ |
| | Matches either the expression before or after the vertical bar. | ✅ |
{n} | Matches exactly n occurrences. | ✅ |
{n,} | Matches at least n occurrences. | ✅ |
{n,m} | Matches between n and m occurrences. | ✅ |
(?=) , (?!) | Positive/Negative lookahead. | ✅ |
(?<=) , (?<!) | Positive/Negative lookbehind. | ✅ |
Flags | Description | Support |
---|
g | Global matching (matches all occurrences). | ✅ |
i | Case-insensitive matching. | ✅ |
💻 Development
- Clone this repository
- Enable Corepack using
corepack enable
(use npm i -g corepack
for Node.js < 16.10) - Install dependencies using
pnpm install
- Run interactive tests using
pnpm dev
License
Made with 🔥 and ❤️
Published under MIT License.