From c2e31b8ba5666d13e3fe0dec14273edbbafb94ff Mon Sep 17 00:00:00 2001 From: David Tai Date: Mon, 17 Apr 2023 20:08:29 +0800 Subject: [PATCH 1/8] feat: add `RegExp` pattern for `S.replace` --- package-lock.json | 7 +++++++ package.json | 1 + src/internals/strings/Strings.ts | 6 ++++-- src/internals/strings/impl/replace.ts | 23 ++++++++++++++++++++++- tsconfig.json | 1 + 5 files changed, 35 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index a72f9ae..d40ad46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "jest": "^29.4.2", "prettier": "^2.8.4", "ts-jest": "^29.0.5", + "type-level-regexp": "^0.1.10", "typescript": "^4.9.5" } }, @@ -3432,6 +3433,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-level-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.10.tgz", + "integrity": "sha512-EIC9Y+hkLqL2z7GVWMN/z3R03/xIUJNc05xZGo+Q6cT8sePO77KO+QvLcxypn0snD3N5exO12prsP0GUQhhFxw==", + "dev": true + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", diff --git a/package.json b/package.json index 1133e8d..1df18b3 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "jest": "^29.4.2", "prettier": "^2.8.4", "ts-jest": "^29.0.5", + "type-level-regexp": "^0.1.10", "typescript": "^4.9.5" } } diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index c918c44..7b7b8ba 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -118,12 +118,14 @@ export namespace Strings { /** * Replace all instances of a substring in a string. * @param args[0] - The string to replace. - * @param from - The substring to replace. - * @param to - The substring to replace with. + * @param from - The substring to replace or a RegExp pattern (support `i` flag). + * @param to - The substring to replace with, can include special replacement patterns when replacing with a RegExp. see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_the_replacement for more details. * @returns The replaced string. * @example * ```ts * type T0 = Call,"a.b.c.d">; // "a/b/c/d" + * type T1 = Call\\d{4})/(?\\d{1,2})/(?\\d{1,2})/i", "My b$1 is $.$, $2">, "Birthday: 1991/9/15">; // "My birthday is 9.15, 1991" + * ``` */ export type Replace< from extends string | unset | _ = unset, diff --git a/src/internals/strings/impl/replace.ts b/src/internals/strings/impl/replace.ts index f892c42..8667c56 100644 --- a/src/internals/strings/impl/replace.ts +++ b/src/internals/strings/impl/replace.ts @@ -1,4 +1,9 @@ import { Fn } from "../../core/Core"; +import { Split } from "../../helpers"; + +import { ParseRegExp, ReplaceWithRegExp } from "type-level-regexp/regexp"; + +type SupportedRegExpReplaceFlags = "i" | "g" | "ig" | "gi"; export type Replace< Str, @@ -16,6 +21,22 @@ export interface ReplaceReducer extends Fn { infer From extends string, ...any ] - ? Replace + ? Str extends Str + ? From extends `/${infer RegExp}/` + ? ReplaceWithRegExp, To, never> + : From extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}` + ? ReplaceWithRegExp< + Str, + ParseRegExp, + To, + Split< + From extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}` + ? Flags + : never, + "" + >[number] + > + : Replace + : never : never; } diff --git a/tsconfig.json b/tsconfig.json index c3dfd85..f128827 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "module": "ESNext", + "moduleResolution": "node", "declaration": true, "esModuleInterop": true, "target": "ESNext", From e5e166d9019cbe058b54de767e436a16bddcdda9 Mon Sep 17 00:00:00 2001 From: David Tai Date: Mon, 17 Apr 2023 20:08:29 +0800 Subject: [PATCH 2/8] feat: add `S.match` and `S.matchAll` --- src/internals/strings/Strings.ts | 63 ++++++++++++++++++- src/internals/strings/impl/match.ts | 90 +++++++++++++++++++++++++++ src/internals/strings/impl/strings.ts | 1 + 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 src/internals/strings/impl/match.ts diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index 7b7b8ba..2ae3f66 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -1,4 +1,12 @@ -import { Call, ComposeLeft, Fn, PartialApply, unset, _ } from "../core/Core"; +import { + Apply, + Call, + ComposeLeft, + Fn, + PartialApply, + unset, + _, +} from "../core/Core"; import { Std } from "../std/Std"; import { Tuples } from "../tuples/Tuples"; import * as H from "../helpers"; @@ -115,6 +123,59 @@ export namespace Strings { : never; } + /** + * Match a string against a regular expression (support `i` and `g` flags). + * @param args[0] - The string to match. + * @param RawRegExp - The regular expression to match. Support both "//" or "" syntax. + * @returns The matched object with match array and `index` and `groups` properties. + * ```ts + * type T0 = Call[b-e]{1,2})F/i">, "12aBef34">; // ["aBef", "Be"] & { index: 2; groups: { g1: "Be" } } + * type T1 = Call[b-e]{1,2})f/gi">, "12aBef34AeCf56">; // ["aBef", "AeCf"] + * ``` + */ + export type Match< + RawRegExp extends string | unset | _ = unset, + Str = unset + > = RawRegExp extends RawRegExp + ? PartialApply + : never; + + interface MatchFn extends Fn { + return: this["args"] extends [ + infer RawRegExp extends string, + infer Str, + ...any + ] + ? Apply + : never; + } + + /** + * Match a string against a regular expression, return an array of match objects. + * @param args[0] - The string to match. + * @param RawRegExp - The regular expression to match, `g` flag is required (also support `i` flag). + * @returns Array of matched object, each with a match array and `index` and `groups` properties. + * ```ts + * type T0 = Call[b-e]{1,2})f/gi">, "12aBef34AeCf56">; // [["aBef", "Be"] & { index: 2; groups: { g1: "Be"; }; }, ["AeCf", "eC"] & { index: 8; groups: { g1: "eC"; }; }] + * ``` + */ + export type MatchAll< + RawRegExp extends string | unset | _ = unset, + Str = unset + > = RawRegExp extends RawRegExp + ? PartialApply + : never; + + interface MatchAllFn extends Fn { + return: this["args"] extends [ + infer RawRegExp extends string, + infer Str, + ...any + ] + ? Apply + : never; + } + /** * Replace all instances of a substring in a string. * @param args[0] - The string to replace. diff --git a/src/internals/strings/impl/match.ts b/src/internals/strings/impl/match.ts new file mode 100644 index 0000000..3f870fd --- /dev/null +++ b/src/internals/strings/impl/match.ts @@ -0,0 +1,90 @@ +import { Call, Fn } from "../../core/Core"; +import { Split } from "../../helpers"; +import { T } from "../../.."; + +import { + ParseRegExp, + MatchRegExp, + MatchAllRegExp, + Flag, +} from "type-level-regexp/regexp"; + +type SupportedRegExpReplaceFlags = "i" | "g" | "ig" | "gi"; + +type PrettifyRegExpMatchArray = RegExpMatchResult extends { + _matchArray: infer MatchArray; + index: infer Index; + groups: infer Groups; +} + ? MatchArray & { index: Index; groups: Groups } + : null; + +interface PrettifyRegExpMatchArrayFn extends Fn { + return: this["args"] extends [infer RegExpMatchResult, ...any] + ? PrettifyRegExpMatchArray + : never; +} + +export interface Match extends Fn { + return: this["args"] extends [ + infer Str extends string, + infer RawRegExp extends string, + ...any + ] + ? Str extends Str + ? RawRegExp extends `/${infer RegExp}/` + ? PrettifyRegExpMatchArray, never>> + : RawRegExp extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}` + ? RawRegExp extends `/${RegExp}/${infer Flags}` + ? Split[number] extends infer FlagsUnion extends Flag + ? "g" extends FlagsUnion + ? MatchRegExp, FlagsUnion> + : PrettifyRegExpMatchArray< + MatchRegExp, FlagsUnion> + > + : never + : never + : PrettifyRegExpMatchArray< + MatchRegExp, never> + > + : never + : never; +} + +export interface MatchAll extends Fn { + return: this["args"] extends [ + infer Str extends string, + infer RawRegExp extends string, + ...any + ] + ? Str extends Str + ? RawRegExp extends `/${infer RegExp}/g` + ? MatchAllRegExp, never> extends { + _matchedTuple: infer MatchTuple extends any[]; + } + ? Call, MatchTuple> + : null + : RawRegExp extends `/${infer RegExp}/${Exclude< + SupportedRegExpReplaceFlags, + "i" + >}` + ? MatchAllRegExp< + Str, + ParseRegExp, + Split< + RawRegExp extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}` + ? Flags + : never, + "" + >[number] + > extends { + _matchedTuple: infer MatchTuple extends any[]; + } + ? Call, MatchTuple> + : null + : TypeError & { + msg: "MatchAll called with a non-global RegExp argument"; + } + : never + : never; +} diff --git a/src/internals/strings/impl/strings.ts b/src/internals/strings/impl/strings.ts index cbb0dec..70ee29e 100644 --- a/src/internals/strings/impl/strings.ts +++ b/src/internals/strings/impl/strings.ts @@ -1,5 +1,6 @@ export * from "./split"; export * from "./trim"; +export * from "./match"; export * from "./replace"; export * from "./repeat"; export * from "./compare"; From 58d28fafef2027bacb1ec3e64b77a35893108295 Mon Sep 17 00:00:00 2001 From: David Tai Date: Mon, 17 Apr 2023 20:08:29 +0800 Subject: [PATCH 3/8] test: update tests --- test/strings.test.ts | 200 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) diff --git a/test/strings.test.ts b/test/strings.test.ts index 39a7ac4..412633e 100644 --- a/test/strings.test.ts +++ b/test/strings.test.ts @@ -45,6 +45,163 @@ describe("Strings", () => { type test2 = Expect>; }); + describe("Match", () => { + it("support most basic RegExp tokens", () => { + type res1 = $< + // ^? + Strings.Match<"/a(?[b-e$]{1,4})\\W\\s\\b\\k(\\d+)/">, + "12ab$c- b$c56#" + >; + type test1 = Expect< + Equal< + res1, + ["ab$c- b$c56", "b$c", "56"] & { + index: 2; + groups: { + g1: "b$c"; + }; + } + > + >; + }); + + it("support pattern without wrapping with `//`", () => { + type res2 = $< + // ^? + Strings.Match<"a(?[b-e$]{1,4})\\W\\s\\b\\k(\\d+)">, + "12ab$c- b$c56#" + >; + type test2 = Expect< + Equal< + res2, + ["ab$c- b$c56", "b$c", "56"] & { + index: 2; + groups: { + g1: "b$c"; + }; + } + > + >; + }); + + it("support RegExp global `g` flag", () => { + type res3 = $< + // ^? + Strings.Match<"/c\\w{2,6}/g">, + "cats and cows ride in a car with cozy couch that's made for comfort." + >; + type test3 = Expect< + Equal + >; + }); + + it("support RegExp case case insensitive `i` flag", () => { + type res4 = $< + // ^? + Strings.Match<"/C[a-z]+/ig">, + "Cats and coWs ride in a CAR with cozY cOUch that's made for Comfort." + >; + type test4 = Expect< + Equal + >; + }); + }); + + describe("MatchAll", () => { + it("support most basic RegExp tokens", () => { + type res1 = $< + // ^? + Strings.MatchAll<"/c(?[a-z]+)/g">, + "my cats love to play with toy car under the couch." + >; + type test1 = Expect< + Equal< + res1, + [ + ["cats", "ats"] & { + index: 3; + groups: { + letters: "ats"; + }; + }, + ["car", "ar"] & { + index: 30; + groups: { + letters: "ar"; + }; + }, + ["couch", "ouch"] & { + index: 44; + groups: { + letters: "ouch"; + }; + } + ] + > + >; + }); + + it("return `null` if no match", () => { + type res2 = $< + // ^? + Strings.MatchAll<"/z(?[a-z]+)/g">, + "my cats love to play with toy car under the couch." + >; + + type test2 = Expect>; + }); + + it("require global `g`", () => { + type res3 = $< + // ^? + Strings.MatchAll<"/c(?[a-z]+)/">, + "my cats love to play with toy car under the couch." + >; + + type test3 = Expect< + Equal< + res3, + TypeError & { + msg: "MatchAll called with a non-global RegExp argument"; + } + > + >; + }); + + it("support RegExp case insensitive `i` flag", () => { + type res4 = $< + // ^? + Strings.MatchAll<"/c(?[a-z]+)/gi">, + "my Cats love to play with toy CAR under the cOucH." + >; + type test4 = Expect< + Equal< + res4, + [ + ["Cats", "ats"] & { + index: 3; + groups: { + letters: "ats"; + }; + }, + ["CAR", "AR"] & { + index: 30; + groups: { + letters: "AR"; + }; + }, + ["cOucH", "OucH"] & { + index: 44; + groups: { + letters: "OucH"; + }; + } + ] + > + >; + }); + }); + describe("Replace", () => { it("replaces single letters", () => { type res1 = $, "abc">; @@ -86,6 +243,49 @@ describe("Strings", () => { >; type test4 = Expect>; }); + + it("support using RegExp pattern", () => { + type res8 = $< + // ^? + Strings.Replace< + "/((?:\\w|\\s)+):\\s(?\\d{4})/(?\\d{1,2})/(?\\d{1,2})/", + "The $1 is $.$, $2" + >, + "release day: 2023/2/13" + >; + type test8 = Expect>; + }); + + it("support using union of RegExp pattern", () => { + type res9 = $< + // ^? + Strings.Replace< + "/42\\d{2}(?:-\\d{4}){3}/" | "/token-[a-zA-Z0-9_]+/", + "" + >, + "credit card number: 4232-3242-5823-8421, myToken: token-shekh23xz2jd_32jd213" + >; + type test9 = Expect< + Equal, myToken: "> + >; + }); + + it("support using RegExp pattern with flags", () => { + type res10 = $< + // ^? + Strings.Replace< + '/(<(?:\\/)?)(?\\w{2,16})((?:\\s(?:\\w|=|\\")+)?>)/gi', + "$1My$$3" + >, + 'HotScript X type-level RegExp!

Type level madness.

READ MORE
' + >; + type test10 = Expect< + Equal< + res10, + 'HotScript X type-level RegExp!

Type level madness.

READ MORE
' + > + >; + }); }); it("Slice", () => { From 45189c2cae57053fab37cc8a406edb1c6a2ca305 Mon Sep 17 00:00:00 2001 From: David Tai Date: Tue, 18 Apr 2023 00:58:12 +0800 Subject: [PATCH 4/8] fix: use `Call` instead of `Apply` --- src/internals/strings/Strings.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index 2ae3f66..7dfd936 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -1,5 +1,4 @@ import { - Apply, Call, ComposeLeft, Fn, @@ -146,7 +145,7 @@ export namespace Strings { infer Str, ...any ] - ? Apply + ? Call : never; } @@ -172,7 +171,7 @@ export namespace Strings { infer Str, ...any ] - ? Apply + ? Call : never; } From 76e61bad73635f435bd4318be7d8764151b68041 Mon Sep 17 00:00:00 2001 From: David Tai Date: Tue, 18 Apr 2023 00:58:25 +0800 Subject: [PATCH 5/8] chore: update `build` script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1df18b3..4f4bb9b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "main": "./dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "tsc src/index.ts -d --emitDeclarationOnly --outDir dist", + "build": "tsc -d --emitDeclarationOnly --outDir dist", "prepublishOnly": "npm run test && npm run build", "test": "jest", "clear-test": "jest --clearCache", From ae25d44fa0b2c5e98bbc6c72a1b9e3f436bfae41 Mon Sep 17 00:00:00 2001 From: David Tai Date: Tue, 18 Apr 2023 19:01:38 +0800 Subject: [PATCH 6/8] feat: return RegExp syntax erro and msg --- src/internals/strings/Strings.ts | 9 +-- src/internals/strings/impl/match.ts | 53 +++++++----- src/internals/strings/impl/replace.ts | 23 +++++- test/strings.test.ts | 112 ++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 33 deletions(-) diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index 7dfd936..2140f07 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -1,11 +1,4 @@ -import { - Call, - ComposeLeft, - Fn, - PartialApply, - unset, - _, -} from "../core/Core"; +import { Call, ComposeLeft, Fn, PartialApply, unset, _ } from "../core/Core"; import { Std } from "../std/Std"; import { Tuples } from "../tuples/Tuples"; import * as H from "../helpers"; diff --git a/src/internals/strings/impl/match.ts b/src/internals/strings/impl/match.ts index 3f870fd..7cedc7c 100644 --- a/src/internals/strings/impl/match.ts +++ b/src/internals/strings/impl/match.ts @@ -7,6 +7,7 @@ import { MatchRegExp, MatchAllRegExp, Flag, + Matcher, } from "type-level-regexp/regexp"; type SupportedRegExpReplaceFlags = "i" | "g" | "ig" | "gi"; @@ -25,6 +26,17 @@ interface PrettifyRegExpMatchArrayFn extends Fn { : never; } +type ResovleRegExpMatchOrError< + Str extends string, + RegExp extends string, + FlagUnion extends Flag, + ParsedResult = ParseRegExp +> = ParsedResult extends Matcher[] + ? "g" extends FlagUnion + ? MatchRegExp + : PrettifyRegExpMatchArray> + : ParsedResult; + export interface Match extends Fn { return: this["args"] extends [ infer Str extends string, @@ -33,24 +45,31 @@ export interface Match extends Fn { ] ? Str extends Str ? RawRegExp extends `/${infer RegExp}/` - ? PrettifyRegExpMatchArray, never>> + ? ResovleRegExpMatchOrError : RawRegExp extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}` ? RawRegExp extends `/${RegExp}/${infer Flags}` ? Split[number] extends infer FlagsUnion extends Flag - ? "g" extends FlagsUnion - ? MatchRegExp, FlagsUnion> - : PrettifyRegExpMatchArray< - MatchRegExp, FlagsUnion> - > + ? ResovleRegExpMatchOrError : never : never - : PrettifyRegExpMatchArray< - MatchRegExp, never> - > + : ResovleRegExpMatchOrError : never : never; } +type ResovleRegExpMatchAllOrError< + Str extends string, + RegExp extends string, + FlagUnion extends Flag, + ParsedResult = ParseRegExp +> = ParsedResult extends Matcher[] + ? MatchAllRegExp extends { + _matchedTuple: infer MatchTuple extends any[]; + } + ? Call, MatchTuple> + : null + : ParsedResult; + export interface MatchAll extends Fn { return: this["args"] extends [ infer Str extends string, @@ -59,29 +78,21 @@ export interface MatchAll extends Fn { ] ? Str extends Str ? RawRegExp extends `/${infer RegExp}/g` - ? MatchAllRegExp, never> extends { - _matchedTuple: infer MatchTuple extends any[]; - } - ? Call, MatchTuple> - : null + ? ResovleRegExpMatchAllOrError : RawRegExp extends `/${infer RegExp}/${Exclude< SupportedRegExpReplaceFlags, "i" >}` - ? MatchAllRegExp< + ? ResovleRegExpMatchAllOrError< Str, - ParseRegExp, + RegExp, Split< RawRegExp extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}` ? Flags : never, "" >[number] - > extends { - _matchedTuple: infer MatchTuple extends any[]; - } - ? Call, MatchTuple> - : null + > : TypeError & { msg: "MatchAll called with a non-global RegExp argument"; } diff --git a/src/internals/strings/impl/replace.ts b/src/internals/strings/impl/replace.ts index 8667c56..598f829 100644 --- a/src/internals/strings/impl/replace.ts +++ b/src/internals/strings/impl/replace.ts @@ -1,7 +1,12 @@ import { Fn } from "../../core/Core"; import { Split } from "../../helpers"; -import { ParseRegExp, ReplaceWithRegExp } from "type-level-regexp/regexp"; +import { + Flag, + Matcher, + ParseRegExp, + ReplaceWithRegExp, +} from "type-level-regexp/regexp"; type SupportedRegExpReplaceFlags = "i" | "g" | "ig" | "gi"; @@ -15,6 +20,16 @@ export type Replace< : Str : Str; +type ResovleRegExpReplaceOrError< + Str extends string, + RegExp extends string, + To extends string, + FlagUnion extends Flag, + ParsedResult = ParseRegExp +> = ParsedResult extends Matcher[] + ? ReplaceWithRegExp + : ParsedResult; + export interface ReplaceReducer extends Fn { return: this["args"] extends [ infer Str extends string, @@ -23,11 +38,11 @@ export interface ReplaceReducer extends Fn { ] ? Str extends Str ? From extends `/${infer RegExp}/` - ? ReplaceWithRegExp, To, never> + ? ResovleRegExpReplaceOrError : From extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}` - ? ReplaceWithRegExp< + ? ResovleRegExpReplaceOrError< Str, - ParseRegExp, + RegExp, To, Split< From extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}` diff --git a/test/strings.test.ts b/test/strings.test.ts index 412633e..9d509a2 100644 --- a/test/strings.test.ts +++ b/test/strings.test.ts @@ -105,6 +105,53 @@ describe("Strings", () => { Equal >; }); + + it("return RegExp syntax errors and hints", () => { + type res5 = $< + // ^? + Strings.Match<"/foo(b(ar)baz/">, + "basicRegExp_foobarbaz" + >; + type test5 = Expect< + Equal< + res5, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, missing closing `)`"; + } & SyntaxError + > + >; + + type res6 = $< + // ^? + Strings.Match<"foo(?g1>bar)baz">, + "noWrapWith/_foobarbaz" + >; + type test6 = Expect< + Equal< + res6, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, invalid capture group name for capturing `bar`, possibly due to a missing opening '<' and group name"; + } & SyntaxError + > + >; + + type res7 = $< + // ^? + Strings.Match<"/foo[a-zbar/g">, + "withFlag_fooabar" + >; + type test7 = Expect< + Equal< + res7, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, missing closing `]`"; + } & SyntaxError + > + >; + }); }); describe("MatchAll", () => { @@ -200,6 +247,53 @@ describe("Strings", () => { > >; }); + + it("return RegExp syntax errors and hints", () => { + type res5 = $< + // ^? + Strings.MatchAll<"/foo(b(ar)baz/g">, + "basicRegExp_foobarbaz" + >; + type test5 = Expect< + Equal< + res5, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, missing closing `)`"; + } & SyntaxError + > + >; + + type res6 = $< + // ^? + Strings.MatchAll<"/foo(?, + "foobarbaz" + >; + type test6 = Expect< + Equal< + res6, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, invalid capture group name of `g1bar`, possibly due to a missing closing '>' for group name"; + } & SyntaxError + > + >; + + type res7 = $< + // ^? + Strings.Match<"/foo?{2}bar/g">, + "fooabar" + >; + type test7 = Expect< + Equal< + res7, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, the preceding token to {2} is not quantifiable"; + } & SyntaxError + > + >; + }); }); describe("Replace", () => { @@ -286,6 +380,24 @@ describe("Strings", () => { > >; }); + + it("return RegExp syntax errors and hints", () => { + type res11 = $< + // ^? + Strings.Replace<"/(foo)+*baz/">, + "foobarbaz", + "replace" + >; + type test11 = Expect< + Equal< + res11, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, the preceding token to * is not quantifiable"; + } & SyntaxError + > + >; + }); }); it("Slice", () => { From 8bfade1810166257895f8a437c4e619ee3bb4649 Mon Sep 17 00:00:00 2001 From: David Tai Date: Wed, 19 Apr 2023 01:56:57 +0800 Subject: [PATCH 7/8] chore: bump `type-level-regexp` version --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d40ad46..52ac9fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "jest": "^29.4.2", "prettier": "^2.8.4", "ts-jest": "^29.0.5", - "type-level-regexp": "^0.1.10", + "type-level-regexp": "^0.1.12", "typescript": "^4.9.5" } }, @@ -3434,9 +3434,9 @@ } }, "node_modules/type-level-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.10.tgz", - "integrity": "sha512-EIC9Y+hkLqL2z7GVWMN/z3R03/xIUJNc05xZGo+Q6cT8sePO77KO+QvLcxypn0snD3N5exO12prsP0GUQhhFxw==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.12.tgz", + "integrity": "sha512-8w8mTz1Cn4oIHXP41nrL+vUDV3PmXgvXqhXOYI5Dmx/4l3AELZsJbeED0WxkRF5g+rpORNLHa6s0+bub3KvUTQ==", "dev": true }, "node_modules/typescript": { diff --git a/package.json b/package.json index 4f4bb9b..47589a4 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "jest": "^29.4.2", "prettier": "^2.8.4", "ts-jest": "^29.0.5", - "type-level-regexp": "^0.1.10", + "type-level-regexp": "^0.1.12", "typescript": "^4.9.5" } } From 20564900e69269647bbff0fe6d18cda1335c98c6 Mon Sep 17 00:00:00 2001 From: David Tai Date: Wed, 19 Apr 2023 02:28:44 +0800 Subject: [PATCH 8/8] chore: bump `type-level-regexp` version to 0.1.13 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52ac9fb..075aa2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "jest": "^29.4.2", "prettier": "^2.8.4", "ts-jest": "^29.0.5", - "type-level-regexp": "^0.1.12", + "type-level-regexp": "^0.1.13", "typescript": "^4.9.5" } }, @@ -3434,9 +3434,9 @@ } }, "node_modules/type-level-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.12.tgz", - "integrity": "sha512-8w8mTz1Cn4oIHXP41nrL+vUDV3PmXgvXqhXOYI5Dmx/4l3AELZsJbeED0WxkRF5g+rpORNLHa6s0+bub3KvUTQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.13.tgz", + "integrity": "sha512-nNq3nayYK6HUdPuhMTn+c/IMmxv7YsMgHHAOIJBo3nzPzrR0vlDgmAAloGQ+bU03xYNDyDLukBb9lh/fr4glog==", "dev": true }, "node_modules/typescript": { diff --git a/package.json b/package.json index 47589a4..cb14ae7 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "jest": "^29.4.2", "prettier": "^2.8.4", "ts-jest": "^29.0.5", - "type-level-regexp": "^0.1.12", + "type-level-regexp": "^0.1.13", "typescript": "^4.9.5" } }