Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -33,6 +33,7 @@
"prettier": "^2.8.4",
"ts-jest": "^29.0.5",
"tsup": "^6.7.0",
"type-level-regexp": "^0.1.17",
"typescript": "^4.9.5"
}
}
88 changes: 80 additions & 8 deletions src/internals/strings/Strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<S.Match<S.RegExp<"A(?<g1>[b-e]{1,2})F", "i">>, "12aBef34">; // ["aBef", "Be"] & { index: 2; groups: { g1: "Be" } }
* type T1 = Call<S.Match<S.RegExp<"a(?<g1>[b-e]{1,2})f", "g" | "i">>, "12aBef34AeCf56">; // ["aBef", "AeCf"]
* ```
*/
export type RegExp<
Pattern extends string,
Flags extends Impl.SupportedRegExpFlags = never
> = Impl.RegExpStruct<Pattern, Flags>;

/**
* Get the length of a string.
* @param args[0] - The string to get the length of.
Expand Down Expand Up @@ -114,25 +130,76 @@ export namespace Strings {
: never;
}

/**
* 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 `Strings.RegExp` to match.
* @returns The matched object with match array and `index` and `groups` properties.
* ```ts
* type T0 = Call<S.Match<S.RegExp<"A(?<g1>[b-e]{1,2})F", "i">>, "12aBef34">; // ["aBef", "Be"] & { index: 2; groups: { g1: "Be" } }
* type T1 = Call<S.Match<S.RegExp<"a(?<g1>[b-e]{1,2})f", "g" | "i">>, "12aBef34AeCf56">; // ["aBef", "AeCf"]
* ```
*/
export type Match<
RE extends RegExp<string, any> | unset | _ = unset,
Str = unset
> = PartialApply<MatchFn, [RE, Str]>;

interface MatchFn extends Fn {
return: this["args"] extends [
infer RE extends RegExp<string, any>,
infer Str,
...any
]
? Call<Impl.Match, Str, RE>
: never;
}

/**
* 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 `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<S.MatchAll<S.RegExp<"a(?<g1>[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<
RE extends RegExp<string, any> | unset | _ = unset,
Str = unset
> = RE extends RE ? PartialApply<MatchAllFn, [RE, Str]> : never;

interface MatchAllFn extends Fn {
return: this["args"] extends [
infer RE extends RegExp<string, any>,
infer Str,
...any
]
? Call<Impl.MatchAll, Str, RE>
: never;
}

/**
* 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 `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
* ```ts
* type T0 = Call<Strings.Replace<".","/">,"a.b.c.d">; // "a/b/c/d"
* type T0 = Call<Strings.Replace<S.RegExp<"\\.", "g">, "/">, "a.b.c.d">; // "a/b/c/d"
* type T1 = Call<S.Replace<S.RegExp<"b(\\w+):\\s(?<year>\\d{4})/(?<month>\\d{1,2})/(?<day>\\d{1,2})", "i">, "My b$1 is $<month>.$<day>, $2">, "Birthday: 1991/9/15">; // "My birthday is 9.15, 1991"
* ```
*/
export type Replace<
from extends string | unset | _ = unset,
from extends string | RegExp<string, any> | unset | _ = unset,
to extends string | unset | _ = unset,
str = unset
> = PartialApply<ReplaceFn, [from, to, str]>;

interface ReplaceFn extends Fn {
return: this["args"] extends [
infer From extends string,
infer From extends string | RegExp<string, any>,
infer To extends string,
infer Str,
...any
Expand Down Expand Up @@ -162,21 +229,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<Strings.Split<",">,"a,b,c">; // ["a","b","c"]
* type T1 = Call<Strings.Split<Strings.RegExp<"-{2,4}|\\.">>, "1--2-3.4..5">; // ["1", "2-3", "4", "5"]
* ```
*/
export type Split<
Sep extends string | unset | _ = unset,
Sep extends string | RegExp<string, any> | unset | _ = unset,
Str extends string | unset | _ = unset
> = PartialApply<SplitFn, [Sep, Str]>;

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<string, any>,
infer Str,
...any
]
? Impl.Split<Str, Sep>
: never;
}
Expand Down
53 changes: 53 additions & 0 deletions src/internals/strings/impl/match.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Fn } from "../../core/Core";

import { RegExpStruct } from "./regexp";

import { MatchRegExp, MatchAllRegExp } from "type-level-regexp/regexp";

type PrettifyRegExpMatchArray<RegExpMatchResult> = RegExpMatchResult extends {
_matchArray: infer MatchArray;
index: infer Index;
groups: infer Groups;
}
? MatchArray & { index: Index; groups: Groups }
: null;

export interface Match extends Fn {
return: this["args"] extends [
infer Str extends string,
infer RE extends RegExpStruct<string, any>,
...any
]
? Str extends Str
? "g" extends RE["flags"]
? MatchRegExp<Str, NonNullable<RE["parsedMatchers"]>, RE["flags"]>
: PrettifyRegExpMatchArray<
MatchRegExp<Str, NonNullable<RE["parsedMatchers"]>, RE["flags"]>
>
: never
: never;
}

export interface MatchAll extends Fn {
return: this["args"] extends [
infer Str extends string,
infer RE extends RegExpStruct<string, any>,
...any
]
? Str extends Str
? "g" extends RE["flags"]
? MatchAllRegExp<Str, RE["parsedMatchers"], RE["flags"]> extends {
_matchedTuple: infer MatchTuple extends any[];
}
? {
[Key in keyof MatchTuple]: PrettifyRegExpMatchArray<
MatchTuple[Key]
>;
}
: null
: TypeError & {
msg: "MatchAll called with a non-global RegExp argument";
}
: never
: never;
}
20 changes: 20 additions & 0 deletions src/internals/strings/impl/regexp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +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,
ParsedMatchersOrError = string extends Pattern
? Matcher[]
: ParseRegExp<Pattern>
> = ParsedMatchersOrError extends Matcher[]
? {
type: RegExpSymbol;
pattern: Pattern;
flags: Flags;
parsedMatchers: ParsedMatchersOrError;
}
: ParsedMatchersOrError;
16 changes: 14 additions & 2 deletions src/internals/strings/impl/replace.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Fn } from "../../core/Core";
import { RegExpStruct } from "./regexp";

import { ReplaceWithRegExp } from "type-level-regexp/regexp";

export type Replace<
Str,
Expand All @@ -13,9 +16,18 @@ export type Replace<
export interface ReplaceReducer<To extends string> extends Fn {
return: this["args"] extends [
infer Str extends string,
infer From extends string,
infer From extends string | RegExpStruct<string, any>,
...any
]
? Replace<Str, From, To>
? Str extends Str
? keyof RegExpStruct<string> extends keyof From
? ReplaceWithRegExp<
Str,
Exclude<From, string>["parsedMatchers"],
To,
Exclude<From, string>["flags"]
>
: Replace<Str, Extract<From, string>, To>
: never
: never;
}
29 changes: 25 additions & 4 deletions src/internals/strings/impl/split.ts
Original file line number Diff line number Diff line change
@@ -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[],
Expand All @@ -22,14 +26,31 @@ 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<Sep>
> = Seps extends string[]
Sep extends string | RegExpStruct<string, any>,
Seps = keyof RegExpStruct<string> extends keyof Sep
? H.UnionToTuple<Sep> extends infer REs extends RegExpStruct<string, any>[]
? H.UnionToTuple<
Call<
Tuples.FlatMap<PartialApply<Match, [Str]>>,
{
[K in keyof REs]: RegExpStruct<
REs[K]["pattern"],
REs[K]["flags"] | "g",
REs[K]["parsedMatchers"]
>;
}
>[number]
>
: never
: H.UnionToTuple<Sep>
> = H.IsNever<Seps> extends true
? [...(Str extends "" ? [] : [Str])]
: Seps extends string[]
? Str extends string
? SplitManySep<Str, Seps>
: []
Expand Down
2 changes: 2 additions & 0 deletions src/internals/strings/impl/strings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from "./split";
export * from "./trim";
export * from "./match";
export * from "./replace";
export * from "./repeat";
export * from "./compare";
export * from "./length";
export * from "./regexp";
Loading