Skip to content

Commit c9707a2

Browse files
authored
fix: update interface Input type parameter to contravariant type (#79)
1 parent ad29caf commit c9707a2

File tree

6 files changed

+47
-50
lines changed

6 files changed

+47
-50
lines changed

src/core/inputs.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const charNotIn = <T extends string>(chars: T) =>
2424
createInput(`[^${chars.replace(/[-\\^\]]/g, '\\$&')}]`) as Input<`[^${EscapeChar<T>}]`>
2525

2626
/** This takes an array of inputs and matches any of them */
27-
export const anyOf = <New extends InputSource<string, string>[]>(...args: New) =>
27+
export const anyOf = <New extends InputSource[]>(...args: New) =>
2828
createInput(`(?:${args.map(a => exactly(a)).join('|')})`) as Input<
2929
`(?:${Join<MapToValues<New>>})`,
3030
MapToGroups<New>,
@@ -54,23 +54,23 @@ export const not = {
5454
}
5555

5656
/** Equivalent to `?` - this marks the input as optional */
57-
export const maybe = <New extends InputSource<string>>(str: New) =>
58-
createInput(`${wrap(exactly(str))}?`) as IfUnwrapped<
59-
GetValue<New>,
60-
Input<`(?:${GetValue<New>})?`, GetGroup<New>, GetCapturedGroupsArr<New>>,
61-
Input<`${GetValue<New>}?`, GetGroup<New>, GetCapturedGroupsArr<New>>
57+
export const maybe = <New extends InputSource>(str: New) =>
58+
createInput(`${wrap(exactly(str))}?`) as Input<
59+
IfUnwrapped<GetValue<New>, `(?:${GetValue<New>})?`, `${GetValue<New>}?`>,
60+
GetGroup<New>,
61+
GetCapturedGroupsArr<New>
6262
>
6363

6464
/** This escapes a string input to match it exactly */
65-
export const exactly = <New extends InputSource<string>>(
65+
export const exactly = <New extends InputSource>(
6666
input: New
6767
): Input<GetValue<New>, GetGroup<New>, GetCapturedGroupsArr<New>> =>
6868
typeof input === 'string' ? (createInput(input.replace(ESCAPE_REPLACE_RE, '\\$&')) as any) : input
6969

7070
/** Equivalent to `+` - this marks the input as repeatable, any number of times but at least once */
71-
export const oneOrMore = <New extends InputSource<string>>(str: New) =>
72-
createInput(`${wrap(exactly(str))}+`) as IfUnwrapped<
73-
GetValue<New>,
74-
Input<`(?:${GetValue<New>})+`, GetGroup<New>, GetCapturedGroupsArr<New>>,
75-
Input<`${GetValue<New>}+`, GetGroup<New>, GetCapturedGroupsArr<New>>
71+
export const oneOrMore = <New extends InputSource>(str: New) =>
72+
createInput(`${wrap(exactly(str))}+`) as Input<
73+
IfUnwrapped<GetValue<New>, `(?:${GetValue<New>})+`, `${GetValue<New>}+`>,
74+
GetGroup<New>,
75+
GetCapturedGroupsArr<New>
7676
>

src/core/internal.ts

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ const GROUPED_AS_REPLACE_RE = /^(?:\(\?:(.+)\)|(\(?.+\)?))$/
77
const GROUPED_REPLACE_RE = /^(?:\(\?:(.+)\)([?+*]|{[\d,]+})?|(.+))$/
88

99
export interface Input<
10-
V extends string,
10+
in V extends string,
1111
G extends string = never,
1212
C extends (string | undefined)[] = []
1313
> {
14+
/** this adds a new pattern to the current input */
1415
and: {
15-
/** this adds a new pattern to the current input */
16-
<I extends InputSource<string, any>>(input: I): Input<
16+
<I extends InputSource>(input: I): Input<
1717
`${V}${GetValue<I>}`,
1818
G | (I extends Input<any, infer NewGroups> ? NewGroups : never),
1919
[...C, ...GetCapturedGroupsArr<I>]
@@ -22,63 +22,59 @@ export interface Input<
2222
referenceTo: <N extends G>(groupName: N) => Input<`${V}\\k<${N}>`, G, C>
2323
}
2424
/** this provides an alternative to the current input */
25-
or: <I extends InputSource<string, any>>(
25+
or: <I extends InputSource>(
2626
input: I
2727
) => Input<
2828
`(?:${V}|${GetValue<I>})`,
2929
G | (I extends Input<any, infer NewGroups> ? NewGroups : never),
3030
[...C, ...GetCapturedGroupsArr<I>]
3131
>
3232
/** 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) */
33-
after: <I extends InputSource<string>>(
33+
after: <I extends InputSource>(
3434
input: I
3535
) => Input<`(?<=${GetValue<I>})${V}`, G, [...GetCapturedGroupsArr<I>, ...C]>
3636
/** this is a positive lookahead */
37-
before: <I extends InputSource<string>>(
37+
before: <I extends InputSource>(
3838
input: I
3939
) => Input<`${V}(?=${GetValue<I>})`, G, [...C, ...GetCapturedGroupsArr<I>]>
4040
/** 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) */
41-
notAfter: <I extends InputSource<string>>(
41+
notAfter: <I extends InputSource>(
4242
input: I
4343
) => Input<`(?<!${GetValue<I>})${V}`, G, [...GetCapturedGroupsArr<I, true>, ...C]>
4444
/** this is a negative lookahead */
45-
notBefore: <I extends InputSource<string>>(
45+
notBefore: <I extends InputSource>(
4646
input: I
4747
) => Input<`${V}(?!${GetValue<I>})`, G, [...C, ...GetCapturedGroupsArr<I, true>]>
48+
/** repeat the previous pattern an exact number of times */
4849
times: {
49-
/** repeat the previous pattern an exact number of times */
50-
<N extends number>(number: N): IfUnwrapped<
51-
V,
52-
Input<`(?:${V}){${N}}`, G, C>,
53-
Input<`${V}{${N}}`, G, C>
54-
>
50+
<N extends number>(number: N): Input<IfUnwrapped<V, `(?:${V}){${N}}`, `${V}{${N}}`>, G, C>
5551
/** specify that the expression can repeat any number of times, _including none_ */
56-
any: () => IfUnwrapped<V, Input<`(?:${V})*`, G>, Input<`${V}*`, G, C>>
52+
any: () => Input<IfUnwrapped<V, `(?:${V})*`, `${V}*`>, G, C>
5753
/** specify that the expression must occur at least x times */
5854
atLeast: <N extends number>(
5955
number: N
60-
) => IfUnwrapped<V, Input<`(?:${V}){${N},}`, G>, Input<`${V}{${N},}`, G, C>>
56+
) => Input<IfUnwrapped<V, `(?:${V}){${N},}`, `${V}{${N},}`>, G, C>
6157
/** specify a range of times to repeat the previous pattern */
6258
between: <Min extends number, Max extends number>(
6359
min: Min,
6460
max: Max
65-
) => IfUnwrapped<V, Input<`(?:${V}){${Min},${Max}}`, G, C>, Input<`${V}{${Min},${Max}}`, G, C>>
61+
) => Input<IfUnwrapped<V, `(?:${V}){${Min},${Max}}`, `${V}{${Min},${Max}}`>, G, C>
6662
}
6763
/** 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` */
6864
as: <K extends string>(
6965
key: K
7066
) => Input<
71-
`(?<${K}>${V extends `(?:${infer S})` ? S : V})`,
67+
V extends `(?:${infer S})` ? `(?<${K}>${S})` : `(?<${K}>${V})`,
7268
G | K,
73-
[`(?<${K}>${V extends `(?:${infer S})` ? S : V})`, ...C]
69+
[V extends `(?:${infer S})` ? `(?<${K}>${S})` : `(?<${K}>${V})`, ...C]
7470
>
7571
/** 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()` */
7672
groupedAs: <K extends string>(
7773
key: K
7874
) => Input<
79-
`(?<${K}>${V extends `(?:${infer S})` ? S : V})`,
75+
V extends `(?:${infer S})` ? `(?<${K}>${S})` : `(?<${K}>${V})`,
8076
G | K,
81-
[`(?<${K}>${V extends `(?:${infer S})` ? S : V})`, ...C]
77+
[V extends `(?:${infer S})` ? `(?<${K}>${S})` : `(?<${K}>${V})`, ...C]
8278
>
8379
/** this capture the entire input so far as an anonymous group */
8480
grouped: () => Input<
@@ -92,7 +88,7 @@ export interface Input<
9288
lineEnd: () => Input<`${V}$`, G, C>
9389
}
9490
/** this allows you to mark the input so far as optional */
95-
optionally: () => IfUnwrapped<V, Input<`(?:${V})?`, G, C>, Input<`${V}?`, G, C>>
91+
optionally: () => Input<IfUnwrapped<V, `(?:${V})?`, `${V}?`>, G, C>
9692
toString: () => string
9793
}
9894

@@ -108,7 +104,7 @@ export const createInput = <
108104

109105
return {
110106
toString: () => s.toString(),
111-
and: Object.assign((input: InputSource<string, any>) => createInput(`${s}${exactly(input)}`), {
107+
and: Object.assign((input: InputSource) => createInput(`${s}${exactly(input)}`), {
112108
referenceTo: (groupName: string) => createInput(`${s}\\k<${groupName}>`),
113109
}),
114110
or: input => createInput(`(?:${s}|${exactly(input)})`),

src/core/types/escape.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type CharEscapeCharacter = '\\' | '^' | '-' | ']'
2222
export type EscapeChar<T extends string> = Escape<T, CharEscapeCharacter>
2323
export type StripEscapes<T extends string> = T extends `${infer A}\\${infer B}` ? `${A}${B}` : T
2424

25-
export type GetValue<T extends InputSource<string>> = T extends string
25+
export type GetValue<T extends InputSource> = T extends string
2626
? Escape<T, ExactEscapeChar>
2727
: T extends Input<infer R>
2828
? R

src/core/types/sources.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
import type { Input } from '../internal'
22
import type { GetValue } from './escape'
33

4-
export type InputSource<S extends string = never, T extends string = never> = S | Input<S, T>
5-
export type GetGroup<T extends InputSource<string>> = T extends Input<string, infer Group>
6-
? Group
7-
: never
4+
export type InputSource<S extends string = string, T extends string = never> = S | Input<any, T>
5+
export type GetGroup<T extends InputSource> = T extends Input<any, infer Group> ? Group : never
86
export type GetCapturedGroupsArr<
9-
T extends InputSource<string>,
7+
T extends InputSource,
108
MapToUndefined extends boolean = false
11-
> = T extends Input<string, any, infer CapturedGroupArr>
9+
> = T extends Input<any, any, infer CapturedGroupArr>
1210
? MapToUndefined extends true
1311
? { [K in keyof CapturedGroupArr]: undefined }
1412
: CapturedGroupArr
1513
: []
16-
export type MapToValues<T extends InputSource<any, any>[]> = T extends [infer First, ...infer Rest]
17-
? First extends InputSource<string>
14+
export type MapToValues<T extends InputSource[]> = T extends [
15+
infer First,
16+
...infer Rest extends InputSource[]
17+
]
18+
? First extends InputSource
1819
? [GetValue<First>, ...MapToValues<Rest>]
1920
: []
2021
: []
2122

22-
export type MapToGroups<T extends InputSource<any, string>[]> = T extends [
23+
export type MapToGroups<T extends InputSource[]> = T extends [
2324
infer First,
24-
...infer Rest
25+
...infer Rest extends InputSource[]
2526
]
2627
? First extends Input<any, infer K>
2728
? K | MapToGroups<Rest>
@@ -34,6 +35,6 @@ type Flatten<T extends any[]> = T extends [infer L, ...infer R]
3435
: [L, ...Flatten<R>]
3536
: []
3637

37-
export type MapToCapturedGroupsArr<T extends InputSource<any, string>[]> = Flatten<{
38+
export type MapToCapturedGroupsArr<T extends InputSource[]> = Flatten<{
3839
[K in keyof T]: T[K] extends Input<any, any, infer C> ? C : string[]
3940
}>

src/core/wrap.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { StripEscapes } from './types/escape'
33

44
export type IfUnwrapped<
55
Value extends string,
6-
Yes extends Input<string>,
7-
No extends Input<string>
6+
Yes extends string,
7+
No extends string
88
> = Value extends `(${string})`
99
? No
1010
: StripEscapes<Value> extends `${infer A}${infer B}`

test/utils/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import type { Input } from 'magic-regexp'
22

3-
export const extractRegExp = <T extends Input<any> = never>(input: T) =>
3+
export const extractRegExp = <T = never>(input: T) =>
44
input as T extends Input<infer RE> ? RE : never

0 commit comments

Comments
 (0)