Skip to content

Commit b4e2aa2

Browse files
authored
feat: add times.atMost function (#227)
1 parent 6e3bcc7 commit b4e2aa2

File tree

4 files changed

+21
-2
lines changed

4 files changed

+21
-2
lines changed

docs/content/2.getting-started/2.usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ All of the helpers above return an object of type `Input` that can be chained wi
5454
| `and` | this adds a new pattern to the current input, or you can use `and.referenceTo(groupName)` to adds a new pattern referencing to a named group. |
5555
| `or` | this provides an alternative to the current input. |
5656
| `after`, `before`, `notAfter` and `notBefore` | these activate positive/negative lookahead/lookbehinds. 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). |
57-
| `times` | this is a function you can call directly to repeat the previous pattern an exact number of times, or you can use `times.between(min, max)` to specify a range, `times.atLeast(num)` to indicate it must repeat x times or `times.any()` to indicate it can repeat any number of times, _including none_. |
57+
| `times` | this is a function you can call directly to repeat the previous pattern an exact number of times, or you can use `times.between(min, max)` to specify a range, `times.atLeast(x)` to indicate it must repeat at least x times, `times.atMost(x)` to indicate it must repeat at most x times or `times.any()` to indicate it can repeat any number of times, _including none_. |
5858
| `optionally` | this is a function you can call to mark the current input as optional. |
5959
| `as` | alias for `groupedAs` |
6060
| `groupedAs` | 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()`. |

src/core/internal.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,14 @@ export interface Input<
5050
<N extends number>(number: N): Input<IfUnwrapped<V, `(?:${V}){${N}}`, `${V}{${N}}`>, G, C>
5151
/** specify that the expression can repeat any number of times, _including none_ */
5252
any: () => Input<IfUnwrapped<V, `(?:${V})*`, `${V}*`>, G, C>
53-
/** specify that the expression must occur at least x times */
53+
/** specify that the expression must occur at least `N` times */
5454
atLeast: <N extends number>(
5555
number: N
5656
) => Input<IfUnwrapped<V, `(?:${V}){${N},}`, `${V}{${N},}`>, G, C>
57+
/** specify that the expression must occur at most `N` times */
58+
atMost: <N extends number>(
59+
number: N
60+
) => Input<IfUnwrapped<V, `(?:${V}){0,${N}}`, `${V}{0,${N}}`>, G, C>
5761
/** specify a range of times to repeat the previous pattern */
5862
between: <Min extends number, Max extends number>(
5963
min: Min,
@@ -115,6 +119,7 @@ export const createInput = <
115119
times: Object.assign((number: number) => createInput(`${wrap(s)}{${number}}`) as any, {
116120
any: () => createInput(`${wrap(s)}*`) as any,
117121
atLeast: (min: number) => createInput(`${wrap(s)}{${min},}`) as any,
122+
atMost: (max: number) => createInput(`${wrap(s)}{0,${max}}`) as any,
118123
between: (min: number, max: number) => createInput(`${wrap(s)}{${min},${max}}`) as any,
119124
}),
120125
optionally: () => createInput(`${wrap(s)}?`) as any,

test/index.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ describe('inputs', () => {
8686
})
8787
it('times', () => {
8888
expect(exactly('test').times.between(1, 3).toString()).toMatchInlineSnapshot('"(?:test){1,3}"')
89+
expect(exactly('test').times.atLeast(3).toString()).toMatchInlineSnapshot('"(?:test){3,}"')
90+
expect(exactly('test').times.atMost(3).toString()).toMatchInlineSnapshot('"(?:test){0,3}"')
8991
expect(exactly('test').times(4).or('foo').toString()).toMatchInlineSnapshot(
9092
'"(?:(?:test){4}|foo)"'
9193
)

test/inputs.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,18 @@ describe('chained inputs', () => {
266266
expect(regexp2).toMatchInlineSnapshot('/\\(\\?:ab\\)\\{2,\\}/')
267267
expectTypeOf(extractRegExp(val2)).toEqualTypeOf<'(?:ab){2,}'>()
268268
})
269+
it('times.atMost', () => {
270+
const val = input.times.atMost(2)
271+
const regexp = new RegExp(val as any)
272+
expect(regexp).toMatchInlineSnapshot('/\\\\\\?\\{0,2\\}/')
273+
expectTypeOf(extractRegExp(val)).toEqualTypeOf<'\\?{0,2}'>()
274+
275+
const val2 = multichar.times.atMost(2)
276+
const regexp2 = new RegExp(val2 as any)
277+
expect(regexp2).toMatchInlineSnapshot('/\\(\\?:ab\\)\\{0,2\\}/')
278+
expectTypeOf(extractRegExp(val2)).toEqualTypeOf<'(?:ab){0,2}'>()
279+
})
280+
269281
it('times.between', () => {
270282
const val = input.times.between(3, 5)
271283
const regexp = new RegExp(val as any)

0 commit comments

Comments
 (0)