ts-regexp
Version:
A RegExp wrapper providing stronger type safety.
312 lines (310 loc) • 20.9 kB
text/typescript
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 };