diff --git a/docs/content/2.getting-started/2.usage.md b/docs/content/2.getting-started/2.usage.md index 2d329dce..1ef21f97 100644 --- a/docs/content/2.getting-started/2.usage.md +++ b/docs/content/2.getting-started/2.usage.md @@ -51,7 +51,7 @@ All of the helpers above return an object of type `Input` that can be chained wi | | | | --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `and` | this adds a new pattern to the current input. | +| `and` | this adds a new pattern to the current input, or you can use `and.referenceToGroup(groupName)` to adds a new pattern referencing to a named group. | | `or` | this provides an alternative to the current input. | | `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). | | `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_. | diff --git a/src/core/internal.ts b/src/core/internal.ts index c9a0a9b4..db29973e 100644 --- a/src/core/internal.ts +++ b/src/core/internal.ts @@ -3,10 +3,15 @@ import type { GetValue } from './types/escape' import type { InputSource } from './types/sources' export interface Input { - /** this adds a new pattern to the current input */ - and: >( - input: I - ) => Input<`${V}${GetValue}`, G | (I extends Input ? NewGroups : never)> + and: { + /** this adds a new pattern to the current input */ + >(input: I): Input< + `${V}${GetValue}`, + G | (I extends Input ? NewGroups : never) + > + /** this adds a new pattern to the current input, with the pattern reference to a named group. */ + referenceToGroup: (groupName: N) => Input<`${V}\\k<${N}>`, G> + } /** this provides an alternative to the current input */ or: >( input: I @@ -52,7 +57,9 @@ export const createInput = ): Input => { return { toString: () => s.toString(), - and: input => createInput(`${s}${exactly(input)}`), + and: Object.assign((input: InputSource) => createInput(`${s}${exactly(input)}`), { + referenceToGroup: (groupName: string) => createInput(`${s}\\k<${groupName}>`), + }), or: input => createInput(`(${s}|${exactly(input)})`), after: input => createInput(`(?<=${exactly(input)})${s}`), before: input => createInput(`${s}(?=${exactly(input)})`), diff --git a/test/index.test.ts b/test/index.test.ts index 3c42916e..ad7dd12b 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -101,4 +101,24 @@ describe('inputs', () => { ) )?.groups?.id }) + it('named backreference to capture groups', () => { + const pattern = exactly('foo') + .as('fooGroup') + .and(exactly('bar').as('barGroup')) + .and('baz') + .and.referenceToGroup('barGroup') + .and.referenceToGroup('fooGroup') + .and.referenceToGroup('barGroup') + + expect('foobarbazbarfoobar'.match(createRegExp(pattern))).toMatchInlineSnapshot(` + [ + "foobarbazbarfoobar", + "foo", + "bar", + ] + `) + expectTypeOf(pattern.and.referenceToGroup).toBeCallableWith('barGroup') + // @ts-expect-error + pattern.and.referenceToGroup('bazgroup') + }) }) diff --git a/test/inputs.test.ts b/test/inputs.test.ts index 87a83c08..2446324a 100644 --- a/test/inputs.test.ts +++ b/test/inputs.test.ts @@ -135,6 +135,12 @@ describe('chained inputs', () => { expect(regexp).toMatchInlineSnapshot('/\\\\\\?test\\\\\\.js/') expectTypeOf(extractRegExp(val)).toMatchTypeOf<'\\?test\\.js'>() }) + it('and.referenceToGroup', () => { + const val = input.as('namedGroup').and(exactly('any')).and.referenceToGroup('namedGroup') + const regexp = new RegExp(val as any) + expect(regexp).toMatchInlineSnapshot('/\\(\\?\\\\\\?\\)any\\\\k/') + expectTypeOf(extractRegExp(val)).toMatchTypeOf<'(?\\?)any\\k'>() + }) it('or', () => { const val = input.or('test.js') const regexp = new RegExp(val as any)