Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -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",
Expand All @@ -33,6 +33,7 @@
"jest": "^29.4.2",
"prettier": "^2.8.4",
"ts-jest": "^29.0.5",
"type-level-regexp": "^0.1.13",
"typescript": "^4.9.5"
}
}
59 changes: 57 additions & 2 deletions src/internals/strings/Strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,70 @@ 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 "/<pattern>/<flags>" or "<patttern>" syntax.
* @returns The matched object with match array and `index` and `groups` properties.
* ```ts
* type T0 = Call<S.Match<"/A(?<g1>[b-e]{1,2})F/i">, "12aBef34">; // ["aBef", "Be"] & { index: 2; groups: { g1: "Be" } }
* type T1 = Call<S.Match<"/a(?<g1>[b-e]{1,2})f/gi">, "12aBef34AeCf56">; // ["aBef", "AeCf"]
* ```
*/
export type Match<
RawRegExp extends string | unset | _ = unset,
Str = unset
> = RawRegExp extends RawRegExp
? PartialApply<MatchFn, [RawRegExp, Str]>
: never;

interface MatchFn extends Fn {
return: this["args"] extends [
infer RawRegExp extends string,
infer Str,
...any
]
? Call<Impl.Match, Str, RawRegExp>
: 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<S.MatchAll<"/a(?<g1>[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<MatchAllFn, [RawRegExp, Str]>
: never;

interface MatchAllFn extends Fn {
return: this["args"] extends [
infer RawRegExp extends string,
infer Str,
...any
]
? Call<Impl.MatchAll, Str, RawRegExp>
: 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 (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 T1 = Call<S.Replace<"/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,
Expand Down
101 changes: 101 additions & 0 deletions src/internals/strings/impl/match.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Call, Fn } from "../../core/Core";
import { Split } from "../../helpers";
import { T } from "../../..";

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

type SupportedRegExpReplaceFlags = "i" | "g" | "ig" | "gi";

type PrettifyRegExpMatchArray<RegExpMatchResult> = 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<RegExpMatchResult>
: never;
}

type ResovleRegExpMatchOrError<
Str extends string,
RegExp extends string,
FlagUnion extends Flag,
ParsedResult = ParseRegExp<RegExp>
> = ParsedResult extends Matcher[]
? "g" extends FlagUnion
? MatchRegExp<Str, ParsedResult, FlagUnion>
: PrettifyRegExpMatchArray<MatchRegExp<Str, ParsedResult, FlagUnion>>
: ParsedResult;

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}/`
? ResovleRegExpMatchOrError<Str, RegExp, never>
: RawRegExp extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}`
? RawRegExp extends `/${RegExp}/${infer Flags}`
? Split<Flags, "">[number] extends infer FlagsUnion extends Flag
? ResovleRegExpMatchOrError<Str, RegExp, FlagsUnion>
: never
: never
: ResovleRegExpMatchOrError<Str, RawRegExp, never>
: never
: never;
}

type ResovleRegExpMatchAllOrError<
Str extends string,
RegExp extends string,
FlagUnion extends Flag,
ParsedResult = ParseRegExp<RegExp>
> = ParsedResult extends Matcher[]
? MatchAllRegExp<Str, ParsedResult, FlagUnion> extends {
_matchedTuple: infer MatchTuple extends any[];
}
? Call<T.Map<PrettifyRegExpMatchArrayFn>, MatchTuple>
: null
: ParsedResult;

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`
? ResovleRegExpMatchAllOrError<Str, RegExp, "g">
: RawRegExp extends `/${infer RegExp}/${Exclude<
SupportedRegExpReplaceFlags,
"i"
>}`
? ResovleRegExpMatchAllOrError<
Str,
RegExp,
Split<
RawRegExp extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}`
? Flags
: never,
""
>[number]
>
: TypeError & {
msg: "MatchAll called with a non-global RegExp argument";
}
: never
: never;
}
38 changes: 37 additions & 1 deletion src/internals/strings/impl/replace.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { Fn } from "../../core/Core";
import { Split } from "../../helpers";

import {
Flag,
Matcher,
ParseRegExp,
ReplaceWithRegExp,
} from "type-level-regexp/regexp";

type SupportedRegExpReplaceFlags = "i" | "g" | "ig" | "gi";

export type Replace<
Str,
Expand All @@ -10,12 +20,38 @@ export type Replace<
: Str
: Str;

type ResovleRegExpReplaceOrError<
Str extends string,
RegExp extends string,
To extends string,
FlagUnion extends Flag,
ParsedResult = ParseRegExp<RegExp>
> = ParsedResult extends Matcher[]
? ReplaceWithRegExp<Str, ParsedResult, To, FlagUnion>
: ParsedResult;

export interface ReplaceReducer<To extends string> extends Fn {
return: this["args"] extends [
infer Str extends string,
infer From extends string,
...any
]
? Replace<Str, From, To>
? Str extends Str
? From extends `/${infer RegExp}/`
? ResovleRegExpReplaceOrError<Str, RegExp, To, never>
: From extends `/${infer RegExp}/${SupportedRegExpReplaceFlags}`
? ResovleRegExpReplaceOrError<
Str,
RegExp,
To,
Split<
From extends `/${RegExp}/${infer Flags extends SupportedRegExpReplaceFlags}`
? Flags
: never,
""
>[number]
>
: Replace<Str, From, To>
: never
: never;
}
1 change: 1 addition & 0 deletions src/internals/strings/impl/strings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./split";
export * from "./trim";
export * from "./match";
export * from "./replace";
export * from "./repeat";
export * from "./compare";
Expand Down
Loading