UNPKG

ts-regexp

Version:

A RegExp wrapper providing stronger type safety.

312 lines (310 loc) 20.9 kB
type As<T, _Infer extends T> = unknown; type Head<T extends unknown[]> = T[0]; type Tail<T extends unknown[]> = T extends [unknown, ...infer MyTail] ? MyTail : never; type AsLinked<T extends unknown[], InferFirst extends Head<T>, InferRest extends Tail<T>> = InferRest extends unknown[] ? unknown : never; type RangeInternal<T extends number, TArr extends number[]> = TArr['length'] extends T ? TArr : RangeInternal<T, [...TArr, TArr['length']]>; type Range<T extends number> = RangeInternal<T, []>; type Increment<T extends number> = [...Range<T>, unknown]['length'] & number; type Decrement<T extends number> = Range<T> extends [unknown, ...infer Rest] ? Rest['length'] : never; type Add<T extends number, T2 extends number> = T2 extends 0 ? T : Increment<Add<T, Decrement<T2>>>; type Prettify<T> = { [K in keyof T]: T[K]; } & {}; type ToTupleInternal<TRecord extends Record<number, unknown>, Index extends number> = keyof TRecord extends never ? [] : [ ...(Index extends keyof TRecord ? [TRecord[Index]] : []), ...ToTupleInternal<Omit<TRecord, Index>, Increment<Index>> ]; type ToTuple<TRecord extends Record<number, unknown>> = ToTupleInternal<TRecord, 0>; type IsSatisfied<T, TCandidate extends T> = TCandidate; type Is<T extends boolean, TIf, TElse> = T extends true ? TIf : TElse; type CharOfInternal<T extends string> = T extends `${infer First}${infer Rest}` ? First | CharOfInternal<Rest> : never; type CharOf<T extends string> = string extends T ? string : CharOfInternal<T>; type FirstMatch<T extends string, TSource extends string> = TSource extends `${infer First}${infer Rest}` ? First extends T ? First : FirstMatch<T, Rest> : never; type ToNattyNumber<T extends string> = T extends `0${infer Rest extends `${number}`}` ? ToNattyNumber<Rest> : T extends `-${string}` | `${string}.${string}` ? never : T extends `${infer N extends number}` ? number extends N ? never : N : never; type Fallback<T, TFall> = [T] extends [never] ? TFall : T; type Exists<T> = T extends never ? never : unknown; type AsSkippedEscape<T extends string, Infer extends T extends `\\${string}${infer Skipped}` ? Skipped : never> = Exists<Infer>; type AsSkippedCharacterClass<T extends string, Infer extends unknown extends AsSkippedEscape<T, infer Skipped> ? Skipped : T extends `[${infer Rest}` ? ResolveCharacterClass<Rest> : never> = Exists<Infer>; type AsSkippedGroup<T extends string, Infer extends unknown extends AsSkippedCharacterClass<T, infer Skipped> ? Skipped : T extends `(${infer Rest}` ? ResolveGroup<Rest> : never> = Exists<Infer>; type ResolveCharacterClass<T extends string> = T extends `${infer First}${infer Rest}` ? unknown extends AsSkippedEscape<T, infer Skipped> ? ResolveCharacterClass<Skipped> : First extends ']' ? Rest : ResolveCharacterClass<Rest> : never; type ResolveGroup<T extends string> = T extends `${infer First}${infer Rest}` ? unknown extends AsSkippedGroup<T, infer Skipped> ? ResolveGroup<Skipped> : First extends ')' ? Rest : ResolveGroup<Rest> : never; type ResolveAlternation<T extends string> = T extends `${infer First}${infer Rest}` ? unknown extends AsSkippedGroup<T, infer Skipped> ? ResolveAlternation<Skipped> : First extends '|' ? Rest : ResolveAlternation<Rest> : never; type InferMin<S extends string> = S extends `${infer Min},${infer Max}` ? Max extends '' ? ToNattyNumber<Min> : ToNattyNumber<Max> extends never ? never : ToNattyNumber<Min> : ToNattyNumber<S>; type GroupPatterns<T extends string> = T extends `?<${infer Name}>${infer TheRest}` ? { value: { isCaptured: true; isNamed: true; name: Name; }; rest: TheRest; } : T extends `?${':' | `${'<' | ''}${'=' | '!'}`}${infer TheRest}` ? { value: { isCaptured: false; }; rest: TheRest; } : { value: { isCaptured: true; isNamed: false; }; rest: T; }; type GroupsTree<T extends string> = T extends `${string}${infer Rest}` ? unknown extends AsSkippedCharacterClass<T, infer Skipped> ? GroupsTree<Skipped> : unknown extends As<ResolveGroup<Rest>, infer Tail> ? T extends `(${infer Content})${Tail}` ? [ GroupPatterns<Content>['value'] & { isOptional: Tail extends `${'?' | '*'}${string}` ? true : Tail extends `{${infer ModRange}}${string}` ? 0 extends InferMin<ModRange> ? true : false : false; inner: TokenTree<GroupPatterns<Content>['rest']>; }, ...GroupsTree<Tail> ] : GroupsTree<Rest> : never : []; type TokenTree<T extends string> = unknown extends As<ResolveAlternation<T>, infer Right> ? T extends `${infer Left}|${Right}` ? { type: 'alternation'; left: TokenTree<Left>; right: TokenTree<Right>; } : { type: 'groups'; groups: GroupsTree<T>; } : never; type Token = IsSatisfied<{ type: string; }, { type: 'alternation'; left: Token; right: Token; } | { type: 'groups'; groups: ({ isOptional: boolean; inner: Token; } & IsSatisfied<{ isCaptured: boolean; }, { isCaptured: false; } | ({ isCaptured: true; isNamed: boolean; } & IsSatisfied<{ isNamed: boolean; }, { isNamed: false; } | { isNamed: true; name: string; }>)>)[]; }>; type Groups = (Token & { type: 'groups'; })['groups']; type FlattenGroups<TGroups extends Groups> = unknown extends AsLinked<TGroups, infer First, infer Rest> ? [ First, ...FlattenToken<First['inner']>, ...FlattenGroups<Rest> ] : []; type FlattenTokenInternal<TToken extends Token, Limit extends unknown[]> = Limit extends [unknown, ...infer L] ? TToken extends { type: 'alternation'; } ? [ ...FlattenTokenInternal<TToken['left'], L>, ...FlattenTokenInternal<TToken['right'], L> ] : TToken extends { type: 'groups'; } ? FlattenGroups<TToken['groups']> : never : never; type FlattenToken<TToken extends Token> = FlattenTokenInternal<TToken, Range<20>>; type IndexGroups<TGroups extends Groups, TIndex extends number> = unknown extends AsLinked<TGroups, infer First, infer Rest> ? [ { index: TIndex; value: Omit<First, 'inner'> & { inner: IndexTokenInternal<First['inner'], Increment<TIndex>>; }; }, ...IndexGroups<Rest, Add<TIndex, FlattenGroups<[First]>['length'] & number>> ] : []; type IndexTokenInternal<TToken extends Token, TIndex extends number> = TToken extends { type: 'alternation'; } ? { type: 'alternation'; left: IndexTokenInternal<TToken['left'], TIndex>; right: IndexTokenInternal<TToken['right'], Add<TIndex, FlattenToken<TToken['left']>['length'] & number>>; } : TToken extends { type: 'groups'; } ? { type: 'groups'; groups: IndexGroups<TToken['groups'], TIndex>; } : never; type IndexToken<TToken extends Token> = IndexTokenInternal<TToken, 0>; type TokenWithIndex = IsSatisfied<{ type: string; }, { type: 'alternation'; left: TokenWithIndex; right: TokenWithIndex; } | { type: 'groups'; groups: { index: number; value: { isOptional: boolean; inner: TokenWithIndex; } & IsSatisfied<{ isCaptured: boolean; }, { isCaptured: false; } | ({ isCaptured: true; } & IsSatisfied<{ isNamed: boolean; }, { isNamed: false; } | { isNamed: true; name: string; }>)>; }[]; }>; type GroupWithIndexes = (TokenWithIndex & { type: 'groups'; })['groups']; type GroupWithIndex = GroupWithIndexes[number]; type ContextualValue<T extends GroupWithIndex, TValue> = Record<T['index'], { value: TValue; reference: T['value']; }>; type UnsetGroups<TGroups extends GroupWithIndexes> = unknown extends AsLinked<TGroups, infer First, infer Rest> ? ContextualValue<First, never> & UnsetToken<First['value']['inner']> & UnsetGroups<Rest> : {}; type UnsetToken<TToken extends TokenWithIndex> = TToken extends { type: 'alternation'; } ? UnsetToken<TToken['left']> & UnsetToken<TToken['right']> : TToken extends { type: 'groups'; } ? UnsetGroups<TToken['groups']> : never; type ContextualizeGroups<TGroups extends GroupWithIndexes> = unknown extends AsLinked<TGroups, infer First, infer Rest> ? ((First['value']['isOptional'] extends true ? UnsetGroups<[First]> : never) | (ContextualValue<First, string> & ContextualizeToken<First['value']['inner']>)) & ContextualizeGroups<Rest> : {}; type ContextualizeToken<TToken extends TokenWithIndex> = TToken extends { type: 'alternation'; } ? ((ContextualizeToken<TToken['left']> & UnsetToken<TToken['right']>) | (ContextualizeToken<TToken['right']> & UnsetToken<TToken['left']>)) : TToken extends { type: 'groups'; } ? ContextualizeGroups<TToken['groups']> : never; type Distribute<T extends Record<keyof T & GroupWithIndex['index'], { value: string | undefined; reference: GroupWithIndex['value']; }>> = T extends unknown ? unknown extends As<{ [K in keyof T as T[K]['reference']['isCaptured'] extends false ? never : K]: T[K]; }, infer CaptureRecord> ? { captures: ToTuple<{ [K in keyof CaptureRecord]: Fallback<CaptureRecord[K]['value'], undefined>; }>; namedCaptures: { [K in keyof CaptureRecord as CaptureRecord[K]['reference'] extends { name: infer Name; } ? Name & string : never]: Fallback<CaptureRecord[K]['value'], undefined>; }; } : never : never; type Parse<T extends string> = string extends T ? { captures: [string, ...(string | undefined)[]]; namedCaptures: Record<string, string | undefined>; } : Distribute<ContextualizeToken<IndexToken<TokenTree<`(\\\\${T})`>>>>; type Remove<Ts extends unknown[], TMatch extends Ts[number]> = unknown extends AsLinked<Ts, infer First, infer Rest> ? TMatch extends First ? Rest : [First, ...Remove<Rest, TMatch>] : []; type Flags = ['d', 'g', 'i', 'm', 's', 'u' | 'v', 'y']; type Flag = Flags[number]; type GetFlagsInternal<T extends string, TFlags extends Flag[]> = unknown extends AsLinked<TFlags, infer First, infer Rest> ? `${Fallback<FirstMatch<First, T>, ''>}${GetFlagsInternal<T, Rest>}` : ''; type GetFlags<T extends string> = string extends T ? string : GetFlagsInternal<T, Flags>; type AreFlagsValid<TSource extends string, TFlags extends Flag[]> = TSource extends `${infer First}${infer Rest}` ? First extends TFlags[number] ? AreFlagsValid<Rest, Remove<TFlags, First>> : false : true; type ValidatedFlags<T extends string> = AreFlagsValid<T, Flags> extends true ? T : never; declare const typedRegExp: <TPattern extends string, TFlags extends string = never>(pattern: TPattern, flags?: ValidatedFlags<TFlags>) => Prettify<Omit<{ matchIn: <T extends string>(source: T) => (Is<string extends ([TFlags] extends [never] ? "" : TFlags) ? boolean : "g" extends infer T_1 ? T_1 extends "g" ? T_1 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never, [Head<Parse<TPattern>["captures"]>, ...Head<Parse<TPattern>["captures"]>[]], Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: T; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_2 extends Parse<TPattern>["captures"] ? { [K in keyof T_2 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never)>) | null; replaceIn: { <T extends string>(string: T, replacer: (...p: [match: Head<Parse<TPattern>["captures"]>, ...p: Tail<Parse<TPattern>["captures"]>, offset: number, string: T, ...keyof Parse<TPattern>["namedCaptures"] extends never ? [] : [groups: Parse<TPattern>["namedCaptures"]]]) => string): string; <T extends string>(string: T, replaceValue: string): string; }; searchIn: (source: string) => number; splitIn: (source: string, limit?: number | undefined) => string[]; exec: <T extends string>(string: T) => (Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: T; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_1 extends Parse<TPattern>["captures"] ? { [K in keyof T_1 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never)) | null; test: (string: string) => boolean; source: TPattern extends "" ? "(?:)" : TPattern; global: string extends ([TFlags] extends [never] ? "" : TFlags) ? boolean : "g" extends infer T ? T extends "g" ? T extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never; ignoreCase: string extends ([TFlags] extends [never] ? "" : TFlags) ? boolean : "i" extends infer T_1 ? T_1 extends "i" ? T_1 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never; multiline: boolean; lastIndex: number; compile: (pattern: string, flags?: string) => RegExp; flags: GetFlags<[TFlags] extends [never] ? "" : TFlags>; sticky: string extends ([TFlags] extends [never] ? "" : TFlags) ? boolean : "y" extends infer T_2 ? T_2 extends "y" ? T_2 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never; unicode: string extends ([TFlags] extends [never] ? "" : TFlags) ? boolean : "u" extends infer T_3 ? T_3 extends "u" ? T_3 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never; dotAll: string extends ([TFlags] extends [never] ? "" : TFlags) ? boolean : "s" extends infer T_4 ? T_4 extends "s" ? T_4 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never; hasIndices: string extends ([TFlags] extends [never] ? "" : TFlags) ? boolean : "d" extends infer T_5 ? T_5 extends "d" ? T_5 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never; unicodeSets: string extends ([TFlags] extends [never] ? "" : TFlags) ? boolean : "v" extends infer T_6 ? T_6 extends "v" ? T_6 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never; regExp: RegExp; } & ((string extends "g" | ([TFlags] extends [never] ? "" : TFlags) ? boolean : "g" extends infer T_7 ? T_7 extends "g" ? T_7 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never) extends infer T_8 ? T_8 extends (string extends "g" | ([TFlags] extends [never] ? "" : TFlags) ? boolean : "g" extends infer T_9 ? T_9 extends "g" ? T_9 extends CharOf<[TFlags] extends [never] ? "" : TFlags> ? true : false : never : never) ? T_8 extends true ? { matchAllIn: <T_9 extends string>(source: T_9) => RegExpStringIterator<Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: T_9; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_10 extends Parse<TPattern>["captures"] ? { [K in keyof T_10 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never)>; replaceAllIn: { <T_9 extends string>(string: T_9, replacer: (...p: [match: Head<Parse<TPattern>["captures"]>, ...p: Tail<Parse<TPattern>["captures"]>, offset: number, string: T_9, ...keyof Parse<TPattern>["namedCaptures"] extends never ? [] : [groups: Parse<TPattern>["namedCaptures"]]]) => string): string; <T_9 extends string>(string: T_9, replaceValue: string): string; }; } : {} : never : never), "exec" | "matchIn" | "matchAllIn"> & ((Omit<{ global: true; } & { matchAllIn: <T_10 extends string>(source: T_10) => RegExpStringIterator<Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: T_10; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_11 extends Parse<TPattern>["captures"] ? { [K in keyof T_11 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never)>; replaceAllIn: { <T_10 extends string>(string: T_10, replacer: (...p: [match: Head<Parse<TPattern>["captures"]>, ...p: Tail<Parse<TPattern>["captures"]>, offset: number, string: T_10, ...keyof Parse<TPattern>["namedCaptures"] extends never ? [] : [groups: Parse<TPattern>["namedCaptures"]]]) => string): string; <T_10 extends string>(string: T_10, replaceValue: string): string; }; }, "matchAllIn"> & { matchIn: (string: string) => [Head<Parse<TPattern>["captures"]>, ...Head<Parse<TPattern>["captures"]>[]] | null; } & (({ hasIndices: false; } & { matchAllIn: <TString extends string>(string: TString) => RegExpStringIterator<Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: TString; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_10 extends Parse<TPattern>["captures"] ? { [K in keyof T_10 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never)>; }) | ({ hasIndices: true; } & { matchAllIn: <TString extends string>(string: TString) => RegExpStringIterator<Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: TString; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_10 extends Parse<TPattern>["captures"] ? { [K in keyof T_10 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never) & { indices: NonNullable<RegExpExecArray["indices"]>; }>; }))) | (({ global: false; } & {}) & (({ hasIndices: false; } & { matchIn: <TString extends string>(string: TString) => (Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: TString; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_10 extends Parse<TPattern>["captures"] ? { [K in keyof T_10 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never)) | null; }) | ({ hasIndices: true; } & { matchIn: <TString extends string>(string: TString) => (Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: TString; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_10 extends Parse<TPattern>["captures"] ? { [K in keyof T_10 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never) & { indices: NonNullable<RegExpExecArray["indices"]>; }) | null; })))) & (({ hasIndices: false; } & { exec: <TString extends string>(string: TString) => (Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: TString; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_10 extends Parse<TPattern>["captures"] ? { [K in keyof T_10 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never)) | null; }) | ({ hasIndices: true; } & { exec: <TString extends string>(string: TString) => (Omit<Omit<RegExpExecArray, keyof unknown[] | "indices">, "groups" | "input"> & Pick<{ groups: keyof Parse<TPattern>["namedCaptures"] extends never ? undefined : Parse<TPattern>["namedCaptures"]; input: TString; }, "groups" | "input"> & (Parse<TPattern>["captures"] extends infer T_10 extends Parse<TPattern>["captures"] ? { [K in keyof T_10 as K extends number ? number extends Parse<TPattern>["captures"]["length"] ? K : never : K]: Parse<TPattern>["captures"][K]; } : never) & { indices: NonNullable<RegExpExecArray["indices"]>; }) | null; }))>; export { typedRegExp };