|
1 | 1 | import { exactly } from './inputs' |
2 | 2 | import type { GetValue } from './types/escape' |
3 | | -import type { InputSource } from './types/sources' |
| 3 | +import type { GetCapturedGroupsArr, InputSource } from './types/sources' |
4 | 4 | import { IfUnwrapped, wrap } from './wrap' |
5 | 5 |
|
6 | 6 | const GROUPED_AS_REPLACE_RE = /^(?:\(\?:(.+)\)|(\(?.+\)?))$/ |
7 | | -const GROUPED_REPLACE_RE = /^(?:\(\??:?(.+)\)([?+*]|{[\d,]+})?|(.+))$/ |
| 7 | +const GROUPED_REPLACE_RE = /^(?:\(\?:(.+)\)([?+*]|{[\d,]+})?|(.+))$/ |
8 | 8 |
|
9 | | -export interface Input<V extends string, G extends string = never> { |
| 9 | +export interface Input< |
| 10 | + V extends string, |
| 11 | + G extends string = never, |
| 12 | + C extends (string | undefined)[] = [] |
| 13 | +> { |
10 | 14 | and: { |
11 | 15 | /** this adds a new pattern to the current input */ |
12 | 16 | <I extends InputSource<string, any>>(input: I): Input< |
13 | 17 | `${V}${GetValue<I>}`, |
14 | | - G | (I extends Input<any, infer NewGroups> ? NewGroups : never) |
| 18 | + G | (I extends Input<any, infer NewGroups> ? NewGroups : never), |
| 19 | + [...C, ...GetCapturedGroupsArr<I>] |
15 | 20 | > |
16 | 21 | /** this adds a new pattern to the current input, with the pattern reference to a named group. */ |
17 | | - referenceTo: <N extends G>(groupName: N) => Input<`${V}\\k<${N}>`, G> |
| 22 | + referenceTo: <N extends G>(groupName: N) => Input<`${V}\\k<${N}>`, G, C> |
18 | 23 | } |
19 | 24 | /** this provides an alternative to the current input */ |
20 | 25 | or: <I extends InputSource<string, any>>( |
21 | 26 | input: I |
22 | 27 | ) => Input< |
23 | 28 | `(?:${V}|${GetValue<I>})`, |
24 | | - G | (I extends Input<any, infer NewGroups> ? NewGroups : never) |
| 29 | + G | (I extends Input<any, infer NewGroups> ? NewGroups : never), |
| 30 | + [...C, ...GetCapturedGroupsArr<I>] |
25 | 31 | > |
26 | 32 | /** this is a positive lookbehind. Make sure to check [browser support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#browser_compatibility) as not all browsers support lookbehinds (notably Safari) */ |
27 | | - after: <I extends InputSource<string>>(input: I) => Input<`(?<=${GetValue<I>})${V}`, G> |
| 33 | + after: <I extends InputSource<string>>( |
| 34 | + input: I |
| 35 | + ) => Input<`(?<=${GetValue<I>})${V}`, G, [...GetCapturedGroupsArr<I>, ...C]> |
28 | 36 | /** this is a positive lookahead */ |
29 | | - before: <I extends InputSource<string>>(input: I) => Input<`${V}(?=${GetValue<I>})`, G> |
| 37 | + before: <I extends InputSource<string>>( |
| 38 | + input: I |
| 39 | + ) => Input<`${V}(?=${GetValue<I>})`, G, [...C, ...GetCapturedGroupsArr<I>]> |
30 | 40 | /** these is a negative lookbehind. Make sure to check [browser support](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#browser_compatibility) as not all browsers support lookbehinds (notably Safari) */ |
31 | | - notAfter: <I extends InputSource<string>>(input: I) => Input<`(?<!${GetValue<I>})${V}`, G> |
| 41 | + notAfter: <I extends InputSource<string>>( |
| 42 | + input: I |
| 43 | + ) => Input<`(?<!${GetValue<I>})${V}`, G, [...GetCapturedGroupsArr<I, true>, ...C]> |
32 | 44 | /** this is a negative lookahead */ |
33 | | - notBefore: <I extends InputSource<string>>(input: I) => Input<`${V}(?!${GetValue<I>})`, G> |
| 45 | + notBefore: <I extends InputSource<string>>( |
| 46 | + input: I |
| 47 | + ) => Input<`${V}(?!${GetValue<I>})`, G, [...C, ...GetCapturedGroupsArr<I, true>]> |
34 | 48 | times: { |
35 | 49 | /** repeat the previous pattern an exact number of times */ |
36 | 50 | <N extends number>(number: N): IfUnwrapped< |
37 | 51 | V, |
38 | | - Input<`(?:${V}){${N}}`, G>, |
39 | | - Input<`${V}{${N}}`, G> |
| 52 | + Input<`(?:${V}){${N}}`, G, C>, |
| 53 | + Input<`${V}{${N}}`, G, C> |
40 | 54 | > |
41 | 55 | /** specify that the expression can repeat any number of times, _including none_ */ |
42 | | - any: () => IfUnwrapped<V, Input<`(?:${V})*`, G>, Input<`${V}*`, G>> |
| 56 | + any: () => IfUnwrapped<V, Input<`(?:${V})*`, G>, Input<`${V}*`, G, C>> |
43 | 57 | /** specify that the expression must occur at least x times */ |
44 | 58 | atLeast: <N extends number>( |
45 | 59 | number: N |
46 | | - ) => IfUnwrapped<V, Input<`(?:${V}){${N},}`, G>, Input<`${V}{${N},}`, G>> |
| 60 | + ) => IfUnwrapped<V, Input<`(?:${V}){${N},}`, G>, Input<`${V}{${N},}`, G, C>> |
47 | 61 | /** specify a range of times to repeat the previous pattern */ |
48 | 62 | between: <Min extends number, Max extends number>( |
49 | 63 | min: Min, |
50 | 64 | max: Max |
51 | | - ) => IfUnwrapped<V, Input<`(?:${V}){${Min},${Max}}`, G>, Input<`${V}{${Min},${Max}}`, G>> |
| 65 | + ) => IfUnwrapped<V, Input<`(?:${V}){${Min},${Max}}`, G, C>, Input<`${V}{${Min},${Max}}`, G, C>> |
52 | 66 | } |
53 | 67 | /** this defines the entire input so far as a named capture group. You will get type safety when using the resulting RegExp with `String.match()`. Alias for `groupedAs` */ |
54 | 68 | as: <K extends string>( |
55 | 69 | key: K |
56 | | - ) => Input<`(?<${K}>${V extends `(?:${infer S extends string})` ? S : V})`, G | K> |
| 70 | + ) => Input< |
| 71 | + `(?<${K}>${V extends `(?:${infer S})` ? S : V})`, |
| 72 | + G | K, |
| 73 | + [`(?<${K}>${V extends `(?:${infer S})` ? S : V})`, ...C] |
| 74 | + > |
57 | 75 | /** this defines the entire input so far as a named capture group. You will get type safety when using the resulting RegExp with `String.match()` */ |
58 | 76 | groupedAs: <K extends string>( |
59 | 77 | key: K |
60 | | - ) => Input<`(?<${K}>${V extends `(?:${infer S extends string})` ? S : V})`, G | K> |
| 78 | + ) => Input< |
| 79 | + `(?<${K}>${V extends `(?:${infer S})` ? S : V})`, |
| 80 | + G | K, |
| 81 | + [`(?<${K}>${V extends `(?:${infer S})` ? S : V})`, ...C] |
| 82 | + > |
61 | 83 | /** this capture the entire input so far as an anonymous group */ |
62 | | - grouped: () => Input<V extends `(?:${infer S})${infer E}` ? `(${S})${E}` : `(${V})`, G> |
| 84 | + grouped: () => Input< |
| 85 | + V extends `(?:${infer S})${infer E}` ? `(${S})${E}` : `(${V})`, |
| 86 | + G, |
| 87 | + [V extends `(?:${infer S})${'' | '?' | '+' | '*' | `{${string}}`}` ? `(${S})` : `(${V})`, ...C] |
| 88 | + > |
63 | 89 | /** this allows you to match beginning/ends of lines with `at.lineStart()` and `at.lineEnd()` */ |
64 | 90 | at: { |
65 | | - lineStart: () => Input<`^${V}`, G> |
66 | | - lineEnd: () => Input<`${V}$`, G> |
| 91 | + lineStart: () => Input<`^${V}`, G, C> |
| 92 | + lineEnd: () => Input<`${V}$`, G, C> |
67 | 93 | } |
68 | 94 | /** this allows you to mark the input so far as optional */ |
69 | | - optionally: () => IfUnwrapped<V, Input<`(?:${V})?`, G>, Input<`${V}?`, G>> |
| 95 | + optionally: () => IfUnwrapped<V, Input<`(?:${V})?`, G, C>, Input<`${V}?`, G, C>> |
70 | 96 | toString: () => string |
71 | 97 | } |
72 | 98 |
|
73 | | -export const createInput = <Value extends string, Groups extends string = never>( |
74 | | - s: Value | Input<Value, Groups> |
75 | | -): Input<Value, Groups> => { |
| 99 | +export const createInput = < |
| 100 | + Value extends string, |
| 101 | + Groups extends string = never, |
| 102 | + CaptureGroupsArr extends (string | undefined)[] = [] |
| 103 | +>( |
| 104 | + s: Value | Input<Value, Groups, CaptureGroupsArr> |
| 105 | +): Input<Value, Groups, CaptureGroupsArr> => { |
76 | 106 | const groupedAsFn = (key: string) => |
77 | 107 | createInput(`(?<${key}>${`${s}`.replace(GROUPED_AS_REPLACE_RE, '$1$2')})`) |
78 | 108 |
|
|
0 commit comments