feat(String): RegExp `S.Replace`, `S.Match` and `S.MatchAll`
Updates
- Add type-level-regexp as dev-dependency, and import helper types from a subpath export
type-level-regexp/regexpto prevent global declaration side effects from the package. - Add
Strings.RegExpto create a RegExp object with pattern, flags and parsed matches to use with other RegExp related hotscript fn, and returnsRegExpSyntaxErrorerror type with detailed error message if the provided RegExp pattern contains some syntax error (currently only show few types of error, WIP) - Update
Strings.Replaceto also accept RegExp pattern (/<pattern>/) asfromarg to replace substring matched by the given pattern. Replace value also support special replacemnet patterns. - Add
Strings.Matchto match a string against a RegExp (supportiandgflags), returns a matched object with match array andindexandgroupsproperties. - Add
Strings.MatchAllto match a string against a RegExp, return an array of match objects, each with a match array andindexandgroupsproperties.
Note
I have kept the implementation of RegExp matching and replacing generic types in a separate package called type-level-regexp. This allows for faster iteration as it is still in the early stages, and more features and performance improvements are coming along the way.
Usage
// Strings.Match exmaple
type MatchResult = Call<
Strings.Match<Strings.RegExp<"a(?<g1>[b-e$]{1,4})\\W\\s\\b\\k<g1>(\\d+)">>,
"12ab$c- b$c56#"
>;
// type of `MatchResult` is:
// ["ab$c- b$c56", "b$c", "56"] & {
// index: 2;
// groups: {
// g1: "b$c";
// };
// }
// Strings.Replace exmaple
type ReplaceResult = Call<
// ^? type ReplaceResult = "The release day is 2.13, 2023"
Strings.Replace<
Strings.RegExp<"((?:\\w|\\s)+):\\s(?<year>\\d{4})/(?<month>\\d{1,2})/(?<day>\\d{1,2})">,
"The $1 is $<month>.$<day>, $2"
>,
"release day: 2023/2/13"
>;
// RegExp syntax error exmaple
type SyntaxErrTest = Strings.RegExp<"foo(?g1>bar)baz">
// type of `SyntaxErrTest` is:
// {
// type: "RegExpSyntaxError";
// message: "Invalid regular expression, invalid capture group name for capturing `bar`, possibly due to a missing opening
// '<' and group name";
// } & SyntaxError
Related issues
Resolve #33
Tasks
- [x] add/update tests
- [x] update JSDocs
Hi @gvergnaud , thanks for the suggestion and reporting the bug! I've fix the bug you mention in 0.1.16
// ✅ working now
type res2 = Call<Strings.Replace<"/([a-z][A-Z]|!)/g", "_$1_", "OaaObbOcc!">>;
type test2 = Expect<Equal<res2, "Oa_aO_b_bO_cc_!_">>;
for making the regex syntax opt-in, with
Call<
Strings.Replace<
Strings.Regex<"/([A-Z]|!)/g">,
"_$1_",
"OaaObbOcc!"
>
>;
I think this might only separate and make the parsing part opt-in but not the evaluation/matching part?
as currently we still check if the user pass in just string or a RegExp pattern in ReplaceReducer interface:
export interface ReplaceReducer<To extends string> extends Fn {
return: this["args"] extends [
infer Str extends string,
infer From extends string,
...any
]
? Str extends Str
? From extends `/${infer RegExp}/`
? ResovleRegExpReplaceOrError<Str, RegExp, To, never>
: From extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}`
? ResovleRegExpReplaceOrError<
Str,
RegExp,
To,
Split<
From extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}`
? Flags
: never,
""
>[number]
>
: Replace<Str, From, To>
: never
: never;
}
but I'm curious if this way is already making RegExp parsing and evaluation opt-in as the type-level RegExp will not infer type if user only pass in pure string?
Or if we want to fully separate the types, maybe we can achieve by introducing Strings.RegExpReplace, Strings.RegExpMatch, Strings.RegExpMatchAll and Strings.RegExpSplit?
What do you think? which way do you prefer?
I'll be working on adding the support for Strings.Split 👍
I think this might only separate and make the
parsingpart opt-in but not theevaluation/matchingpart? as currently we still check if the user pass in juststringor aRegExppattern inReplaceReducerinterface:
From my point of view the /${infer RegExp/ should be enough to not trigger any big perf issue. TS is really performant for inferring and parsing strings.
The issue i see is more about: what if i want to replace strings starting and ending with / without them being interpreted as regexp ?
The solution i see is the @gvergnaud one :
type Regex<Regexpr extends string, Modifiers extends SupportedRegExpReplaceFlags = ""> = {
type: RegexpSymbol;
regex: Regexpr;
modifiers: Modifiers
>
export interface ReplaceReducer<To extends string> extends Fn {
return: this["args"] extends [
infer Str extends string,
infer From extends string | Regex<string>,
...any
]
? From extends Regex<infer RegExp, infer Flags>
? ResovleRegExpReplaceOrError<Str, RegExp, To, Flags>
: Replace<Str, From, To>
: never;
}
@ecyrbe good point! I think this is the best way to go 😃