From c2e31b8ba5666d13e3fe0dec14273edbbafb94ff Mon Sep 17 00:00:00 2001 From: David Tai Date: Mon, 17 Apr 2023 20:08:29 +0800 Subject: [PATCH 01/18] 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 02/18] 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 03/18] 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 04/18] 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 05/18] 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 06/18] 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 07/18] 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 08/18] 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" } } From b289b605a250ed7c02c038875e2c7d7d59dfb8a9 Mon Sep 17 00:00:00 2001 From: David Tai Date: Sat, 22 Apr 2023 23:51:07 +0800 Subject: [PATCH 09/18] chore: bump `type-level-regexp` version to 0.1.14 --- 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 075aa2a..2089abe 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.13", + "type-level-regexp": "^0.1.14", "typescript": "^4.9.5" } }, @@ -3434,9 +3434,9 @@ } }, "node_modules/type-level-regexp": { - "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==", + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.14.tgz", + "integrity": "sha512-JkUWaTheK8AWMjxYDrFneAEGGSdybPa/+nCytkR1upnA7cmEotdE9U6vXWTGk4EfmczI7rGVIPQjNBhFw6XedA==", "dev": true }, "node_modules/typescript": { diff --git a/package.json b/package.json index cb14ae7..e6d7c81 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.13", + "type-level-regexp": "^0.1.14", "typescript": "^4.9.5" } } From fd4af5b3280996cf11ffb5e16c32b0d72d919449 Mon Sep 17 00:00:00 2001 From: David Tai Date: Sat, 22 Apr 2023 23:51:19 +0800 Subject: [PATCH 10/18] test: add `strings` test --- test/strings.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/strings.test.ts b/test/strings.test.ts index 9d509a2..6a3427b 100644 --- a/test/strings.test.ts +++ b/test/strings.test.ts @@ -63,6 +63,26 @@ describe("Strings", () => { } > >; + + type res2 = $< + // ^? + Strings.Match<"/a(?\\w*)(?\\d+)(?\\w*)(?\\w+)/">, + "abcd1234xyz" + >; + type test2 = Expect< + Equal< + res2, + ["abcd1234xyz", "bcd123", "4", "xy", "z"] & { + index: 0; + groups: { + g1: "bcd123"; + g2: "4"; + g3: "xy"; + g4: "z"; + }; + } + > + >; }); it("support pattern without wrapping with `//`", () => { From 91f52a7ca4fa0a83e4eb56c18d16218c7b9b4fb4 Mon Sep 17 00:00:00 2001 From: David Tai Date: Wed, 26 Apr 2023 22:10:59 +0800 Subject: [PATCH 11/18] chore: bump type-level-regexp version to 0.1.16 --- 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 2089abe..52e96d8 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.14", + "type-level-regexp": "^0.1.16", "typescript": "^4.9.5" } }, @@ -3434,9 +3434,9 @@ } }, "node_modules/type-level-regexp": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.14.tgz", - "integrity": "sha512-JkUWaTheK8AWMjxYDrFneAEGGSdybPa/+nCytkR1upnA7cmEotdE9U6vXWTGk4EfmczI7rGVIPQjNBhFw6XedA==", + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.16.tgz", + "integrity": "sha512-/I0EfCQFR3fcRhSutqyc496s2DY9wPJDRNpV4qe3CNuOyMOna3a2OAGVb5pmMx1aOyNL5JSGmqG7EAYnb6UaFA==", "dev": true }, "node_modules/typescript": { diff --git a/package.json b/package.json index e6d7c81..426cf34 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.14", + "type-level-regexp": "^0.1.16", "typescript": "^4.9.5" } } From fecd9c71df487dc37b8284a8864a7a65b3cfbb70 Mon Sep 17 00:00:00 2001 From: David Tai Date: Sat, 29 Apr 2023 22:08:46 +0800 Subject: [PATCH 12/18] add `S.RegExp` --- src/internals/strings/Strings.ts | 50 ++++++++++------- src/internals/strings/impl/match.ts | 65 ++++++--------------- src/internals/strings/impl/regexp.ts | 12 ++++ src/internals/strings/impl/replace.ts | 41 ++++---------- src/internals/strings/impl/strings.ts | 1 + test/strings.test.ts | 81 ++++++++++++--------------- 6 files changed, 108 insertions(+), 142 deletions(-) create mode 100644 src/internals/strings/impl/regexp.ts diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index 53cdb61..6877f66 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -13,6 +13,22 @@ export namespace Strings { | null | undefined; + /** + * Create a RegExp object with the given pattern and flags, use for `Strings.Replace`, `Strings.Match`, `Strings.MatchAll`. + * @param Pattern - The pattern of the RegExp. + * @param Flags - The flags of the RegExp. + * @returns A RegExp object. + * @example + * ```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", "g" | "i">>, "12aBef34AeCf56">; // ["aBef", "AeCf"] + * ``` + */ + export type RegExp< + Pattern extends string, + Flags extends Impl.SupportedRegExpFlags = never + > = Impl.RegExp; + /** * Get the length of a string. * @param args[0] - The string to get the length of. @@ -120,24 +136,22 @@ export namespace Strings { * @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"] + * 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", "g" | "i">>, "12aBef34AeCf56">; // ["aBef", "AeCf"] * ``` */ export type Match< - RawRegExp extends string | unset | _ = unset, + RE extends RegExp | unset | _ = unset, Str = unset - > = RawRegExp extends RawRegExp - ? PartialApply - : never; + > = PartialApply; interface MatchFn extends Fn { return: this["args"] extends [ - infer RawRegExp extends string, + infer RE extends RegExp, infer Str, ...any ] - ? Call + ? Call : never; } @@ -147,23 +161,21 @@ export namespace Strings { * @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"; }; }] + * type T0 = Call[b-e]{1,2})f", "g" | "i">>, "12aBef34AeCf56">; // [["aBef", "Be"] & { index: 2; groups: { g1: "Be"; }; }, ["AeCf", "eC"] & { index: 8; groups: { g1: "eC"; }; }] * ``` */ export type MatchAll< - RawRegExp extends string | unset | _ = unset, + RE extends RegExp | unset | _ = unset, Str = unset - > = RawRegExp extends RawRegExp - ? PartialApply - : never; + > = RE extends RE ? PartialApply : never; interface MatchAllFn extends Fn { return: this["args"] extends [ - infer RawRegExp extends string, + infer RE extends RegExp, infer Str, ...any ] - ? Call + ? Call : never; } @@ -175,19 +187,19 @@ export namespace Strings { * @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" + * 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, + from extends string | RegExp | unset | _ = unset, to extends string | unset | _ = unset, str = unset > = PartialApply; interface ReplaceFn extends Fn { return: this["args"] extends [ - infer From extends string, + infer From extends string | RegExp, infer To extends string, infer Str, ...any diff --git a/src/internals/strings/impl/match.ts b/src/internals/strings/impl/match.ts index 7cedc7c..84010f7 100644 --- a/src/internals/strings/impl/match.ts +++ b/src/internals/strings/impl/match.ts @@ -1,6 +1,6 @@ import { Call, Fn } from "../../core/Core"; -import { Split } from "../../helpers"; import { T } from "../../.."; +import { RegExp } from "./regexp"; import { ParseRegExp, @@ -10,8 +10,6 @@ import { Matcher, } from "type-level-regexp/regexp"; -type SupportedRegExpReplaceFlags = "i" | "g" | "ig" | "gi"; - type PrettifyRegExpMatchArray = RegExpMatchResult extends { _matchArray: infer MatchArray; index: infer Index; @@ -20,43 +18,30 @@ type PrettifyRegExpMatchArray = RegExpMatchResult extends { ? MatchArray & { index: Index; groups: Groups } : null; -interface PrettifyRegExpMatchArrayFn extends Fn { - return: this["args"] extends [infer RegExpMatchResult, ...any] - ? PrettifyRegExpMatchArray - : 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, - infer RawRegExp extends string, + RegExp, ...any ] ? Str extends Str - ? RawRegExp extends `/${infer RegExp}/` - ? ResovleRegExpMatchOrError - : RawRegExp extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}` - ? RawRegExp extends `/${RegExp}/${infer Flags}` - ? Split[number] extends infer FlagsUnion extends Flag - ? ResovleRegExpMatchOrError - : never - : never - : ResovleRegExpMatchOrError + ? ParseRegExp extends infer ParsedResult + ? ParsedResult extends Matcher[] + ? "g" extends Flags + ? MatchRegExp + : PrettifyRegExpMatchArray> + : ParsedResult + : never : never : never; } +interface PrettifyRegExpMatchArrayFn extends Fn { + return: this["args"] extends [infer RegExpMatchResult, ...any] + ? PrettifyRegExpMatchArray + : never; +} + type ResovleRegExpMatchAllOrError< Str extends string, RegExp extends string, @@ -73,26 +58,12 @@ type ResovleRegExpMatchAllOrError< export interface MatchAll extends Fn { return: this["args"] extends [ infer Str extends string, - infer RawRegExp extends string, + RegExp, ...any ] ? Str extends Str - ? RawRegExp extends `/${infer RegExp}/g` - ? ResovleRegExpMatchAllOrError - : RawRegExp extends `/${infer RegExp}/${Exclude< - SupportedRegExpReplaceFlags, - "i" - >}` - ? ResovleRegExpMatchAllOrError< - Str, - RegExp, - Split< - RawRegExp extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}` - ? Flags - : never, - "" - >[number] - > + ? "g" extends Flags + ? ResovleRegExpMatchAllOrError : TypeError & { msg: "MatchAll called with a non-global RegExp argument"; } diff --git a/src/internals/strings/impl/regexp.ts b/src/internals/strings/impl/regexp.ts new file mode 100644 index 0000000..c57e312 --- /dev/null +++ b/src/internals/strings/impl/regexp.ts @@ -0,0 +1,12 @@ +declare const RegExpSymbol: unique symbol; +type RegExpSymbol = typeof RegExpSymbol; +export type SupportedRegExpFlags = "i" | "g"; + +export type RegExp< + Pattern extends string, + Flags extends SupportedRegExpFlags = never +> = { + type: RegExpSymbol; + pattern: Pattern; + flags: Flags; +}; diff --git a/src/internals/strings/impl/replace.ts b/src/internals/strings/impl/replace.ts index 598f829..e910c5d 100644 --- a/src/internals/strings/impl/replace.ts +++ b/src/internals/strings/impl/replace.ts @@ -1,15 +1,12 @@ import { Fn } from "../../core/Core"; -import { Split } from "../../helpers"; +import { RegExp } from "./regexp"; import { - Flag, Matcher, ParseRegExp, ReplaceWithRegExp, } from "type-level-regexp/regexp"; -type SupportedRegExpReplaceFlags = "i" | "g" | "ig" | "gi"; - export type Replace< Str, From extends string, @@ -20,38 +17,22 @@ 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, - infer From extends string, + infer From extends string | RegExp, ...any ] ? Str extends Str - ? From extends `/${infer RegExp}/` - ? ResovleRegExpReplaceOrError - : From extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}` - ? ResovleRegExpReplaceOrError< - Str, - RegExp, - To, - Split< - From extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}` - ? Flags - : never, - "" - >[number] - > - : Replace + ? From extends RegExp + ? ParseRegExp extends infer ParsedResult + ? ParsedResult extends Matcher[] + ? ReplaceWithRegExp + : ParsedResult + : never + : From extends string + ? Replace + : never : never : never; } diff --git a/src/internals/strings/impl/strings.ts b/src/internals/strings/impl/strings.ts index 70ee29e..33a3148 100644 --- a/src/internals/strings/impl/strings.ts +++ b/src/internals/strings/impl/strings.ts @@ -5,3 +5,4 @@ export * from "./replace"; export * from "./repeat"; export * from "./compare"; export * from "./length"; +export * from "./regexp"; diff --git a/test/strings.test.ts b/test/strings.test.ts index 5ac51de..150febb 100644 --- a/test/strings.test.ts +++ b/test/strings.test.ts @@ -49,7 +49,9 @@ describe("Strings", () => { it("support most basic RegExp tokens", () => { type res1 = $< // ^? - Strings.Match<"/a(?[b-e$]{1,4})\\W\\s\\b\\k(\\d+)/">, + Strings.Match< + Strings.RegExp<"a(?[b-e$]{1,4})\\W\\s\\b\\k(\\d+)"> + >, "12ab$c- b$c56#" >; type test1 = Expect< @@ -64,40 +66,23 @@ describe("Strings", () => { > >; - type res2 = $< - // ^? - Strings.Match<"/a(?\\w*)(?\\d+)(?\\w*)(?\\w+)/">, - "abcd1234xyz" - >; - type test2 = Expect< - Equal< - res2, - ["abcd1234xyz", "bcd123", "4", "xy", "z"] & { - index: 0; - groups: { - g1: "bcd123"; - g2: "4"; - g3: "xy"; - g4: "z"; - }; - } - > - >; - }); - - it("support pattern without wrapping with `//`", () => { type res2 = $< // ^? - Strings.Match<"a(?[b-e$]{1,4})\\W\\s\\b\\k(\\d+)">, - "12ab$c- b$c56#" + Strings.Match< + Strings.RegExp<"a(?\\w*)(?\\d+)(?\\w*)(?\\w+)"> + >, + "abcd1234xyz" >; type test2 = Expect< Equal< res2, - ["ab$c- b$c56", "b$c", "56"] & { - index: 2; + ["abcd1234xyz", "bcd123", "4", "xy", "z"] & { + index: 0; groups: { - g1: "b$c"; + g1: "bcd123"; + g2: "4"; + g3: "xy"; + g4: "z"; }; } > @@ -107,7 +92,7 @@ describe("Strings", () => { it("support RegExp global `g` flag", () => { type res3 = $< // ^? - Strings.Match<"/c\\w{2,6}/g">, + Strings.Match>, "cats and cows ride in a car with cozy couch that's made for comfort." >; type test3 = Expect< @@ -118,7 +103,7 @@ describe("Strings", () => { it("support RegExp case case insensitive `i` flag", () => { type res4 = $< // ^? - Strings.Match<"/C[a-z]+/ig">, + Strings.Match>, "Cats and coWs ride in a CAR with cozY cOUch that's made for Comfort." >; type test4 = Expect< @@ -129,8 +114,8 @@ describe("Strings", () => { it("return RegExp syntax errors and hints", () => { type res5 = $< // ^? - Strings.Match<"/foo(b(ar)baz/">, - "basicRegExp_foobarbaz" + Strings.Match>, + "missingClosing_foobarbaz" >; type test5 = Expect< Equal< @@ -144,8 +129,8 @@ describe("Strings", () => { type res6 = $< // ^? - Strings.Match<"foo(?g1>bar)baz">, - "noWrapWith/_foobarbaz" + Strings.Matchbar)baz">>, + "invalidGroupName_foobarbaz" >; type test6 = Expect< Equal< @@ -159,7 +144,7 @@ describe("Strings", () => { type res7 = $< // ^? - Strings.Match<"/foo[a-zbar/g">, + Strings.Match>, "withFlag_fooabar" >; type test7 = Expect< @@ -178,7 +163,7 @@ describe("Strings", () => { it("support most basic RegExp tokens", () => { type res1 = $< // ^? - Strings.MatchAll<"/c(?[a-z]+)/g">, + Strings.MatchAll[a-z]+)", "g">>, "my cats love to play with toy car under the couch." >; type test1 = Expect< @@ -211,7 +196,7 @@ describe("Strings", () => { it("return `null` if no match", () => { type res2 = $< // ^? - Strings.MatchAll<"/z(?[a-z]+)/g">, + Strings.MatchAll[a-z]+)", "g">>, "my cats love to play with toy car under the couch." >; @@ -221,7 +206,7 @@ describe("Strings", () => { it("require global `g`", () => { type res3 = $< // ^? - Strings.MatchAll<"/c(?[a-z]+)/">, + Strings.MatchAll[a-z]+)">>, "my cats love to play with toy car under the couch." >; @@ -238,7 +223,7 @@ describe("Strings", () => { it("support RegExp case insensitive `i` flag", () => { type res4 = $< // ^? - Strings.MatchAll<"/c(?[a-z]+)/gi">, + Strings.MatchAll[a-z]+)", "g" | "i">>, "my Cats love to play with toy CAR under the cOucH." >; type test4 = Expect< @@ -271,7 +256,7 @@ describe("Strings", () => { it("return RegExp syntax errors and hints", () => { type res5 = $< // ^? - Strings.MatchAll<"/foo(b(ar)baz/g">, + Strings.MatchAll>, "basicRegExp_foobarbaz" >; type test5 = Expect< @@ -286,7 +271,7 @@ describe("Strings", () => { type res6 = $< // ^? - Strings.MatchAll<"/foo(?, + Strings.MatchAll>, "foobarbaz" >; type test6 = Expect< @@ -301,7 +286,7 @@ describe("Strings", () => { type res7 = $< // ^? - Strings.Match<"/foo?{2}bar/g">, + Strings.Match>, "fooabar" >; type test7 = Expect< @@ -362,7 +347,7 @@ describe("Strings", () => { type res8 = $< // ^? Strings.Replace< - "/((?:\\w|\\s)+):\\s(?\\d{4})/(?\\d{1,2})/(?\\d{1,2})/", + Strings.RegExp<"((?:\\w|\\s)+):\\s(?\\d{4})/(?\\d{1,2})/(?\\d{1,2})">, "The $1 is $.$, $2" >, "release day: 2023/2/13" @@ -374,7 +359,8 @@ describe("Strings", () => { type res9 = $< // ^? Strings.Replace< - "/42\\d{2}(?:-\\d{4}){3}/" | "/token-[a-zA-Z0-9_]+/", + | Strings.RegExp<"42\\d{2}(?:-\\d{4}){3}"> + | Strings.RegExp<"token-[a-zA-Z0-9_]+">, "" >, "credit card number: 4232-3242-5823-8421, myToken: token-shekh23xz2jd_32jd213" @@ -388,7 +374,10 @@ describe("Strings", () => { type res10 = $< // ^? Strings.Replace< - '/(<(?:\\/)?)(?\\w{2,16})((?:\\s(?:\\w|=|\\")+)?>)/gi', + Strings.RegExp< + '(<(?:\\/)?)(?[a-z]{2,16})((?:\\s(?:\\w|=|\\")+)?>)', + "g" | "i" + >, "$1My$$3" >, 'HotScript X type-level RegExp!

Type level madness.

READ MORE
' @@ -404,7 +393,7 @@ describe("Strings", () => { it("return RegExp syntax errors and hints", () => { type res11 = $< // ^? - Strings.Replace<"/(foo)+*baz/">, + Strings.Replace>, "foobarbaz", "replace" >; From ab1652c2a7d9ed8e853d16604e1085a1d87f665d Mon Sep 17 00:00:00 2001 From: David Tai Date: Fri, 5 May 2023 23:20:28 +0800 Subject: [PATCH 13/18] refactor: `S.RegExp`, `S.Replace` and `S.Match` --- src/internals/strings/Strings.ts | 12 ++++++++++-- src/internals/strings/impl/match.ts | 7 ++++--- src/internals/strings/impl/regexp.ts | 2 +- src/internals/strings/impl/replace.ts | 28 ++++++++++++++++----------- 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index 6877f66..14207c2 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -27,7 +27,7 @@ export namespace Strings { export type RegExp< Pattern extends string, Flags extends Impl.SupportedRegExpFlags = never - > = Impl.RegExp; + > = Impl.RegExpStruct; /** * Get the length of a string. @@ -204,7 +204,15 @@ export namespace Strings { infer Str, ...any ] - ? Call, Str>, H.UnionToTuple> + ? keyof RegExp extends keyof From + ? Call< + Tuples.Reduce, Str>, + H.UnionToTuple + > + : Call< + Tuples.Reduce, Str>, + H.UnionToTuple + > : never; } diff --git a/src/internals/strings/impl/match.ts b/src/internals/strings/impl/match.ts index 84010f7..55e4295 100644 --- a/src/internals/strings/impl/match.ts +++ b/src/internals/strings/impl/match.ts @@ -1,6 +1,7 @@ import { Call, Fn } from "../../core/Core"; import { T } from "../../.."; -import { RegExp } from "./regexp"; + +import { RegExpStruct } from "./regexp"; import { ParseRegExp, @@ -21,7 +22,7 @@ type PrettifyRegExpMatchArray = RegExpMatchResult extends { export interface Match extends Fn { return: this["args"] extends [ infer Str extends string, - RegExp, + RegExpStruct, ...any ] ? Str extends Str @@ -58,7 +59,7 @@ type ResovleRegExpMatchAllOrError< export interface MatchAll extends Fn { return: this["args"] extends [ infer Str extends string, - RegExp, + RegExpStruct, ...any ] ? Str extends Str diff --git a/src/internals/strings/impl/regexp.ts b/src/internals/strings/impl/regexp.ts index c57e312..55baedb 100644 --- a/src/internals/strings/impl/regexp.ts +++ b/src/internals/strings/impl/regexp.ts @@ -2,7 +2,7 @@ declare const RegExpSymbol: unique symbol; type RegExpSymbol = typeof RegExpSymbol; export type SupportedRegExpFlags = "i" | "g"; -export type RegExp< +export type RegExpStruct< Pattern extends string, Flags extends SupportedRegExpFlags = never > = { diff --git a/src/internals/strings/impl/replace.ts b/src/internals/strings/impl/replace.ts index e910c5d..d508b3b 100644 --- a/src/internals/strings/impl/replace.ts +++ b/src/internals/strings/impl/replace.ts @@ -1,5 +1,5 @@ import { Fn } from "../../core/Core"; -import { RegExp } from "./regexp"; +import { RegExpStruct } from "./regexp"; import { Matcher, @@ -20,19 +20,25 @@ export type Replace< export interface ReplaceReducer extends Fn { return: this["args"] extends [ infer Str extends string, - infer From extends string | RegExp, + infer From extends string, ...any ] ? Str extends Str - ? From extends RegExp - ? ParseRegExp extends infer ParsedResult - ? ParsedResult extends Matcher[] - ? ReplaceWithRegExp - : ParsedResult - : never - : From extends string - ? Replace - : never + ? Replace + : never + : never; +} + +export interface ReplaceWithRegExpReducer extends Fn { + return: this["args"] extends [ + infer Str extends string, + RegExpStruct, + ...any + ] + ? ParseRegExp extends infer ParsedResult + ? ParsedResult extends Matcher[] + ? ReplaceWithRegExp + : ParsedResult : never : never; } From 8618c2e9d72be90f2a8a18d60dfc093a67604b55 Mon Sep 17 00:00:00 2001 From: David Tai Date: Sun, 7 May 2023 15:17:48 +0800 Subject: [PATCH 14/18] feat: add RegExp support for `Strings.Split` --- src/internals/strings/Strings.ts | 11 ++++++++--- src/internals/strings/impl/split.ts | 30 +++++++++++++++++++++++++---- test/strings.test.ts | 15 +++++++++++++++ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index 14207c2..992362e 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -237,21 +237,26 @@ export namespace Strings { /** * Split a string into a tuple of strings. * @param args[0] - The string to split. - * @param sep - The separator to split the string with. + * @param sep - The separator to split the string with, can be a union of strings or RegExp pattern `Strings.RegExp` (support `i` flag) * @returns The split string. * @warning - 🔥 using an empty sep with emojis in the string will destroy the emoji 🔥 * @example * ```ts * type T0 = Call,"a,b,c">; // ["a","b","c"] + * type T1 = Call>, "1--2-3.4..5">; // ["1", "2-3", "4", "5"] * ``` */ export type Split< - Sep extends string | unset | _ = unset, + Sep extends string | RegExp | unset | _ = unset, Str extends string | unset | _ = unset > = PartialApply; export interface SplitFn extends Fn { - return: this["args"] extends [infer Sep extends string, infer Str, ...any] + return: this["args"] extends [ + infer Sep extends string | RegExp, + infer Str, + ...any + ] ? Impl.Split : never; } diff --git a/src/internals/strings/impl/split.ts b/src/internals/strings/impl/split.ts index ebe5592..7b4a63b 100644 --- a/src/internals/strings/impl/split.ts +++ b/src/internals/strings/impl/split.ts @@ -1,4 +1,8 @@ +import { Call, PartialApply } from "../../core/Core"; import * as H from "../../helpers"; +import { Tuples } from "../../tuples/Tuples"; +import { Match } from "./match"; +import { RegExpStruct } from "./regexp"; type ConcatSplits< Parts extends string[], @@ -22,14 +26,32 @@ type SplitManySep< /** * Split a string into a tuple. * @param Str - The string to split. - * @param Sep - The separator to split on, can be a union of strings of more than one character. + * @param Sep - The separator to split on, can be a union of strings of more than one character, or a union of RegExp pattern `Strings.RegExp` (support `i` flag) * @returns The tuple of each split. if sep is an empty string, returns a tuple of each character. */ export type Split< Str, - Sep extends string, - Seps = H.UnionToTuple -> = Seps extends string[] + Sep extends string | RegExpStruct, + Seps = keyof RegExpStruct extends keyof Sep + ? H.UnionToTuple extends infer REs + ? H.UnionToTuple< + Call< + Tuples.FlatMap>, + { + [K in keyof REs]: REs[K] extends RegExpStruct< + infer Pattern, + infer Flags + > + ? RegExpStruct + : never; + } + >[number] + > + : never + : H.UnionToTuple +> = H.IsNever extends true + ? [...(Str extends "" ? [] : [Str])] + : Seps extends string[] ? Str extends string ? SplitManySep : [] diff --git a/test/strings.test.ts b/test/strings.test.ts index 150febb..f6fa09f 100644 --- a/test/strings.test.ts +++ b/test/strings.test.ts @@ -437,6 +437,21 @@ describe("Strings", () => { type res4 = $, "1--2-3.4..5">; // ^? type test4 = Expect>; + + it("supports RegExp pattern", () => { + type res5 = $>, "1--2-3.4..5">; + // ^? + type test5 = Expect>; + }); + + it("supports union of RegExp pattern", () => { + type res6 = $< + // ^? + Strings.Split | Strings.RegExp<"[*-.:]">>, + "{name:'foo',age:18}" + >; + type test6 = Expect>; + }); }); it("Repeat", () => { From 3b9664f56c94a3172bbd142a2d77cb43fa0f9a70 Mon Sep 17 00:00:00 2001 From: David Tai Date: Sun, 7 May 2023 15:23:17 +0800 Subject: [PATCH 15/18] docs: update JSDoc --- src/internals/strings/Strings.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index 992362e..06cfe54 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -131,9 +131,9 @@ export namespace Strings { } /** - * Match a string against a regular expression (support `i` and `g` flags). + * Match a string against a regular expression `Strings.RegExp` (support `i` and `g` flags). * @param args[0] - The string to match. - * @param RawRegExp - The regular expression to match. Support both "//" or "" syntax. + * @param RawRegExp - The regular expression `Strings.RegExp` to match. * @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" } } @@ -156,9 +156,9 @@ export namespace Strings { } /** - * Match a string against a regular expression, return an array of match objects. + * Match a string against a regular expression `Strings.RegExp`, 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). + * @param RawRegExp - The regular expression `Strings.RegExp` 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", "g" | "i">>, "12aBef34AeCf56">; // [["aBef", "Be"] & { index: 2; groups: { g1: "Be"; }; }, ["AeCf", "eC"] & { index: 8; groups: { g1: "eC"; }; }] @@ -182,7 +182,7 @@ 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 or a RegExp pattern (support `i` flag). + * @param from - The substring to replace or a RegExp pattern `Strings.RegExp` (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 From 06e83ae67eb2f7bff46374008235f8b20602c877 Mon Sep 17 00:00:00 2001 From: David Tai Date: Mon, 8 May 2023 20:15:21 +0800 Subject: [PATCH 16/18] refactor: parse pattern early at RegExp type --- package-lock.json | 8 +- package.json | 2 +- src/internals/strings/Strings.ts | 10 +- src/internals/strings/impl/match.ts | 58 +++---- src/internals/strings/impl/regexp.ts | 20 ++- src/internals/strings/impl/replace.ts | 31 ++-- src/internals/strings/impl/split.ts | 13 +- test/strings.test.ts | 233 +++++++++++++------------- 8 files changed, 176 insertions(+), 199 deletions(-) diff --git a/package-lock.json b/package-lock.json index 52e96d8..969a8e2 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.16", + "type-level-regexp": "^0.1.17", "typescript": "^4.9.5" } }, @@ -3434,9 +3434,9 @@ } }, "node_modules/type-level-regexp": { - "version": "0.1.16", - "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.16.tgz", - "integrity": "sha512-/I0EfCQFR3fcRhSutqyc496s2DY9wPJDRNpV4qe3CNuOyMOna3a2OAGVb5pmMx1aOyNL5JSGmqG7EAYnb6UaFA==", + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/type-level-regexp/-/type-level-regexp-0.1.17.tgz", + "integrity": "sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==", "dev": true }, "node_modules/typescript": { diff --git a/package.json b/package.json index 426cf34..de77569 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.16", + "type-level-regexp": "^0.1.17", "typescript": "^4.9.5" } } diff --git a/src/internals/strings/Strings.ts b/src/internals/strings/Strings.ts index 06cfe54..816d3c7 100644 --- a/src/internals/strings/Strings.ts +++ b/src/internals/strings/Strings.ts @@ -204,15 +204,7 @@ export namespace Strings { infer Str, ...any ] - ? keyof RegExp extends keyof From - ? Call< - Tuples.Reduce, Str>, - H.UnionToTuple - > - : Call< - Tuples.Reduce, Str>, - H.UnionToTuple - > + ? Call, Str>, H.UnionToTuple> : never; } diff --git a/src/internals/strings/impl/match.ts b/src/internals/strings/impl/match.ts index 55e4295..9949d0f 100644 --- a/src/internals/strings/impl/match.ts +++ b/src/internals/strings/impl/match.ts @@ -1,15 +1,8 @@ -import { Call, Fn } from "../../core/Core"; -import { T } from "../../.."; +import { Fn } from "../../core/Core"; import { RegExpStruct } from "./regexp"; -import { - ParseRegExp, - MatchRegExp, - MatchAllRegExp, - Flag, - Matcher, -} from "type-level-regexp/regexp"; +import { MatchRegExp, MatchAllRegExp } from "type-level-regexp/regexp"; type PrettifyRegExpMatchArray = RegExpMatchResult extends { _matchArray: infer MatchArray; @@ -22,49 +15,36 @@ type PrettifyRegExpMatchArray = RegExpMatchResult extends { export interface Match extends Fn { return: this["args"] extends [ infer Str extends string, - RegExpStruct, + infer RE extends RegExpStruct, ...any ] ? Str extends Str - ? ParseRegExp extends infer ParsedResult - ? ParsedResult extends Matcher[] - ? "g" extends Flags - ? MatchRegExp - : PrettifyRegExpMatchArray> - : ParsedResult - : never + ? "g" extends RE["flags"] + ? MatchRegExp, RE["flags"]> + : PrettifyRegExpMatchArray< + MatchRegExp, RE["flags"]> + > : never : never; } -interface PrettifyRegExpMatchArrayFn extends Fn { - return: this["args"] extends [infer RegExpMatchResult, ...any] - ? PrettifyRegExpMatchArray - : 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, - RegExpStruct, + infer RE extends RegExpStruct, ...any ] ? Str extends Str - ? "g" extends Flags - ? ResovleRegExpMatchAllOrError + ? "g" extends RE["flags"] + ? MatchAllRegExp extends { + _matchedTuple: infer MatchTuple extends any[]; + } + ? { + [Key in keyof MatchTuple]: PrettifyRegExpMatchArray< + MatchTuple[Key] + >; + } + : null : TypeError & { msg: "MatchAll called with a non-global RegExp argument"; } diff --git a/src/internals/strings/impl/regexp.ts b/src/internals/strings/impl/regexp.ts index 55baedb..f6bf054 100644 --- a/src/internals/strings/impl/regexp.ts +++ b/src/internals/strings/impl/regexp.ts @@ -1,12 +1,20 @@ +import { Matcher, ParseRegExp } from "type-level-regexp/regexp"; + declare const RegExpSymbol: unique symbol; type RegExpSymbol = typeof RegExpSymbol; export type SupportedRegExpFlags = "i" | "g"; export type RegExpStruct< Pattern extends string, - Flags extends SupportedRegExpFlags = never -> = { - type: RegExpSymbol; - pattern: Pattern; - flags: Flags; -}; + Flags extends SupportedRegExpFlags = never, + ParsedMatchersOrError = string extends Pattern + ? Matcher[] + : ParseRegExp +> = ParsedMatchersOrError extends Matcher[] + ? { + type: RegExpSymbol; + pattern: Pattern; + flags: Flags; + parsedMatchers: ParsedMatchersOrError; + } + : ParsedMatchersOrError; diff --git a/src/internals/strings/impl/replace.ts b/src/internals/strings/impl/replace.ts index d508b3b..5936abd 100644 --- a/src/internals/strings/impl/replace.ts +++ b/src/internals/strings/impl/replace.ts @@ -1,11 +1,7 @@ import { Fn } from "../../core/Core"; import { RegExpStruct } from "./regexp"; -import { - Matcher, - ParseRegExp, - ReplaceWithRegExp, -} from "type-level-regexp/regexp"; +import { ReplaceWithRegExp } from "type-level-regexp/regexp"; export type Replace< Str, @@ -20,25 +16,18 @@ export type Replace< export interface ReplaceReducer extends Fn { return: this["args"] extends [ infer Str extends string, - infer From extends string, + infer From extends string | RegExpStruct, ...any ] ? Str extends Str - ? Replace - : never - : never; -} - -export interface ReplaceWithRegExpReducer extends Fn { - return: this["args"] extends [ - infer Str extends string, - RegExpStruct, - ...any - ] - ? ParseRegExp extends infer ParsedResult - ? ParsedResult extends Matcher[] - ? ReplaceWithRegExp - : ParsedResult + ? keyof RegExpStruct extends keyof From + ? ReplaceWithRegExp< + Str, + Exclude["parsedMatchers"], + To, + Exclude["flags"] + > + : Replace, To> : never : never; } diff --git a/src/internals/strings/impl/split.ts b/src/internals/strings/impl/split.ts index 7b4a63b..4c3bfe8 100644 --- a/src/internals/strings/impl/split.ts +++ b/src/internals/strings/impl/split.ts @@ -33,17 +33,16 @@ export type Split< Str, Sep extends string | RegExpStruct, Seps = keyof RegExpStruct extends keyof Sep - ? H.UnionToTuple extends infer REs + ? H.UnionToTuple extends infer REs extends RegExpStruct[] ? H.UnionToTuple< Call< Tuples.FlatMap>, { - [K in keyof REs]: REs[K] extends RegExpStruct< - infer Pattern, - infer Flags - > - ? RegExpStruct - : never; + [K in keyof REs]: RegExpStruct< + REs[K]["pattern"], + REs[K]["flags"] | "g", + REs[K]["parsedMatchers"] + >; } >[number] > diff --git a/test/strings.test.ts b/test/strings.test.ts index f6fa09f..0954c31 100644 --- a/test/strings.test.ts +++ b/test/strings.test.ts @@ -45,6 +45,127 @@ describe("Strings", () => { type test2 = Expect>; }); + describe("RegExp", () => { + it("with valid RegExp Pattern return a object with `pattern`, `flags` and `parsedMatchers` props", () => { + type res1 = Omit< + Strings.RegExp<"(?:\\w{2,3})(?[a-z]+)\\k", "g" | "i">, + "type" + >; + // ^? + type test1 = Expect< + Equal< + res1, + { + pattern: "(?:\\w{2,3})(?[a-z]+)\\k"; + flags: "g" | "i"; + parsedMatchers: [ + { + type: "repeat"; + greedy: true; + from: "2"; + to: "3"; + value: [ + { + type: "char"; + } + ]; + }, + { + type: "namedCapture"; + name: "g1"; + value: [ + { + type: "oneOrMore"; + greedy: true; + value: [ + { + type: "charSet"; + value: "a-z"; + } + ]; + } + ]; + }, + { + type: "backreference"; + value: "g1"; + } + ]; + } + > + >; + }); + + it("return `SyntaxError` with error message when RegExp pattern is invalid", () => { + type res1 = Strings.RegExp<"foo(b(ar)baz">; + // ^? + + type test1 = Expect< + Equal< + res1, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, missing closing `)`"; + } & SyntaxError + > + >; + + type res2 = $< + // ^? + Strings.Match< + //@ts-expect-error - { message: "Invalid regular expression, opening `(`"; type: "RegExpSyntaxError"; } & SyntaxError + Strings.RegExp<"foobar)baz"> + >, + "foobarbaz" + >; + + type res3 = Strings.RegExp<"foo[a-zbar", "g">; + // ^? + type test3 = Expect< + Equal< + res3, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, missing closing `]`"; + } & SyntaxError + > + >; + type res4 = Strings.RegExp<"foo?{2}bar", "g">; + // ^? + type test4 = Expect< + Equal< + res4, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, the preceding token to {2} is not quantifiable"; + } & SyntaxError + > + >; + type res5 = Strings.RegExp<"foo(?g1>bar)baz">; + // ^? + type test5 = Expect< + Equal< + res5, + { + type: "RegExpSyntaxError"; + message: "Invalid regular expression, invalid capture group name for capturing `bar`, possibly due to a missing opening '<' and group name"; + } & SyntaxError + > + >; + type res6 = Strings.RegExp<"foo(?; + // ^? + 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 + > + >; + }); + }); + describe("Match", () => { it("support most basic RegExp tokens", () => { type res1 = $< @@ -110,53 +231,6 @@ describe("Strings", () => { Equal >; }); - - it("return RegExp syntax errors and hints", () => { - type res5 = $< - // ^? - Strings.Match>, - "missingClosing_foobarbaz" - >; - type test5 = Expect< - Equal< - res5, - { - type: "RegExpSyntaxError"; - message: "Invalid regular expression, missing closing `)`"; - } & SyntaxError - > - >; - - type res6 = $< - // ^? - Strings.Matchbar)baz">>, - "invalidGroupName_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>, - "withFlag_fooabar" - >; - type test7 = Expect< - Equal< - res7, - { - type: "RegExpSyntaxError"; - message: "Invalid regular expression, missing closing `]`"; - } & SyntaxError - > - >; - }); }); describe("MatchAll", () => { @@ -252,53 +326,6 @@ describe("Strings", () => { > >; }); - - it("return RegExp syntax errors and hints", () => { - type res5 = $< - // ^? - Strings.MatchAll>, - "basicRegExp_foobarbaz" - >; - type test5 = Expect< - Equal< - res5, - { - type: "RegExpSyntaxError"; - message: "Invalid regular expression, missing closing `)`"; - } & SyntaxError - > - >; - - type res6 = $< - // ^? - Strings.MatchAll>, - "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>, - "fooabar" - >; - type test7 = Expect< - Equal< - res7, - { - type: "RegExpSyntaxError"; - message: "Invalid regular expression, the preceding token to {2} is not quantifiable"; - } & SyntaxError - > - >; - }); }); describe("Replace", () => { @@ -389,24 +416,6 @@ describe("Strings", () => { > >; }); - - it("return RegExp syntax errors and hints", () => { - type res11 = $< - // ^? - Strings.Replace>, - "foobarbaz", - "replace" - >; - type test11 = Expect< - Equal< - res11, - { - type: "RegExpSyntaxError"; - message: "Invalid regular expression, the preceding token to * is not quantifiable"; - } & SyntaxError - > - >; - }); }); it("Slice", () => { From 47494fdb13f164eac023d3b946d06b8fdf03209b Mon Sep 17 00:00:00 2001 From: David Tai Date: Tue, 9 May 2023 01:02:44 +0800 Subject: [PATCH 17/18] fix: test case --- test/strings.test.ts | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/test/strings.test.ts b/test/strings.test.ts index 0954c31..704ce87 100644 --- a/test/strings.test.ts +++ b/test/strings.test.ts @@ -447,20 +447,16 @@ describe("Strings", () => { // ^? type test4 = Expect>; - it("supports RegExp pattern", () => { - type res5 = $>, "1--2-3.4..5">; - // ^? - type test5 = Expect>; - }); + type res5 = $>, "1--2-3.4..5">; + // ^? + type test5 = Expect>; - it("supports union of RegExp pattern", () => { - type res6 = $< - // ^? - Strings.Split | Strings.RegExp<"[*-.:]">>, - "{name:'foo',age:18}" - >; - type test6 = Expect>; - }); + type res6 = $< + // ^? + Strings.Split | Strings.RegExp<"[*-.:]">>, + "{name:'foo',age:18}" + >; + type test6 = Expect>; }); it("Repeat", () => { From 5d19d8a04a7a9eeb64d146575d531f110495e0bb Mon Sep 17 00:00:00 2001 From: David Tai Date: Tue, 9 May 2023 01:12:20 +0800 Subject: [PATCH 18/18] chore: update build script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8a0344..f69aecd 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "module": "./dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "tsup src/index.ts --format esm,cjs && tsc src/index.ts --declaration --emitDeclarationOnly --outDir dist", + "build": "tsup src/index.ts --format esm,cjs && tsc --declaration --emitDeclarationOnly --outDir dist", "prepublishOnly": "npm run test && npm run build", "test": "jest", "clear-test": "jest --clearCache",