From 8fd51735832c1a3fea4a0d59e47ce5ff1094d308 Mon Sep 17 00:00:00 2001 From: Alexey Iskhakov Date: Thu, 20 Nov 2025 12:51:18 +0200 Subject: [PATCH 1/3] Feature: allow type-only processing of capture groups Having a type-safe function is amazing and all, but sometimes all you need is just the types. --- src/index.ts | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index db1ff55..fbaf116 100644 --- a/src/index.ts +++ b/src/index.ts @@ -374,7 +374,25 @@ type Distribute = string extends T +type DistributeCaptures> = T extends unknown + ? unknown extends As<{ + [K in keyof T as T[K]['reference']['isCaptured'] extends false + ? never + : K + ]: T[K] + }, infer CaptureRecord> + ? ToTuple<{ [K in keyof CaptureRecord]: Fallback }> + : never + : never +; + +export type Parse = string extends T ? { captures: [string, ...(string | undefined)[]], namedCaptures: Record; @@ -391,6 +409,20 @@ type Parse = string extends T }>>> ; +export type ParseCaptures = string extends T + ? (string | undefined)[] + // @ts-expect-error: this should terminate + : DistributeCaptures + }] + }>>> +; + type Remove = unknown extends AsLinked ? TMatch extends First ? Rest @@ -594,4 +626,5 @@ export const typedRegExp = < & (GlobalFalseIndicesBehavior | GlobalFalseIndicesBehavior) ) ) & (IndicesBehavior | IndicesBehavior)>; -}; \ No newline at end of file + +}; From efd92674196739847e15b8cfbf4043c5c2f14fb6 Mon Sep 17 00:00:00 2001 From: Alexey Iskhakov Date: Thu, 20 Nov 2025 13:45:48 +0200 Subject: [PATCH 2/3] Update ParseCaptures: do not include the root string token into the resulting tuple --- src/index.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index fbaf116..feb2ca2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -412,15 +412,7 @@ export type Parse = string extends T export type ParseCaptures = string extends T ? (string | undefined)[] // @ts-expect-error: this should terminate - : DistributeCaptures - }] - }>>> + : DistributeCaptures>>> ; type Remove = unknown extends AsLinked @@ -628,3 +620,4 @@ export const typedRegExp = < ) & (IndicesBehavior | IndicesBehavior)>; }; + From 2c3eb30e970dc1c76f64f89fd2b6babaee8cdebb Mon Sep 17 00:00:00 2001 From: Raiondesu Date: Sun, 23 Nov 2025 04:08:32 +0200 Subject: [PATCH 3/3] Refactor ParseCaptures: simplify for maintainability Add ParseNamedCaptures for parity Add Tails to expoted types to address the common use-case of omitting the input string from captures --- src/index.ts | 67 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/src/index.ts b/src/index.ts index feb2ca2..b987b5c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,17 @@ // Utils // Compensation type As = unknown; -type Head = T[0]; -type Tail = T extends [infer _, ...infer Rest] +export type Head = T[0]; +/** + * Grab the tail of a list (all elements except the first) + * + * @example + * ```ts + * // Will omit the 0-th capture, leaving only the capture groups + * type Captures = Tail>; + * ``` + */ +export type Tail = T extends [infer _, ...infer Rest] ? Rest : never ; @@ -273,7 +282,7 @@ type IndexTokenInternal = TToken ex left: IndexTokenInternal, right: IndexTokenInternal['length'] + FlattenToken['length'] & number >> } : TToken extends { type: 'groups' } ? { @@ -283,7 +292,7 @@ type IndexTokenInternal = TToken ex ; type IndexToken = IndexTokenInternal; // TokenWithIndex type -type TokenWithIndex = IsSatisfied<{type: string}, +type TokenWithIndex = IsSatisfied<{type: string}, { type: 'alternation', left: TokenWithIndex, @@ -374,24 +383,9 @@ type Distribute> = T extends unknown - ? unknown extends As<{ - [K in keyof T as T[K]['reference']['isCaptured'] extends false - ? never - : K - ]: T[K] - }, infer CaptureRecord> - ? ToTuple<{ [K in keyof CaptureRecord]: Fallback }> - : never - : never -; - +/** + * Parse the capture groups from a regex-like string literal type + */ export type Parse = string extends T ? { captures: [string, ...(string | undefined)[]], @@ -409,11 +403,29 @@ export type Parse = string extends T }>>> ; -export type ParseCaptures = string extends T - ? (string | undefined)[] - // @ts-expect-error: this should terminate - : DistributeCaptures>>> -; +/** + * Get the list of indexed captures from a regex-like string literal type + * + * @example + * ```ts + * // Will get all non-named captures, including the string itself at index 0 + * type AllCaptures = ParseCaptures; + * + * // Will omit the 0-th capture, leaving only the capture groups + * type OnlyGroups = Tail>; + * ``` + */ +export type ParseCaptures = Parse['captures']; + +/** + * Get the dictionary of named captures from a regex-like string literal type + * + * @example + * ```ts + * type AllNamedCaptures = ParseNamedCaptures; + * ``` + */ +export type ParseNamedCaptures = Parse['namedCaptures']; type Remove = unknown extends AsLinked ? TMatch extends First @@ -620,4 +632,3 @@ export const typedRegExp = < ) & (IndicesBehavior | IndicesBehavior)>; }; -