From e57b322a4cffc1f4578eba5966479838620234f4 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 4 Jun 2024 15:27:04 +0100 Subject: [PATCH 1/3] feat: add `orChar`/`from` chain for `charIn`/`charNotIn` --- src/core/inputs.ts | 23 +++++++++++++++++------ test/inputs.test.ts | 30 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/core/inputs.ts b/src/core/inputs.ts index 6616612c..e9b5fb6e 100644 --- a/src/core/inputs.ts +++ b/src/core/inputs.ts @@ -10,16 +10,27 @@ export type { Input } const ESCAPE_REPLACE_RE = /[.*+?^${}()|[\]\\/]/g -/** This matches any character in the string provided */ -export function charIn(chars: T) { - return createInput(`[${chars.replace(/[-\\^\]]/g, '\\$&')}]`) as Input<`[${EscapeChar}]`> +function createCharInput (raw: T) { + const input = createInput(`[${raw}]`) + const from = (charFrom: From, charTo: To) => createCharInput(`${raw}${escapeCharInput(charFrom)}-${escapeCharInput(charTo)}`) + const orChar = Object.assign(((chars: T) => createCharInput(`${raw}${escapeCharInput(chars)}`)), { from }) + return Object.assign(input, { orChar, from }) } -/** This matches any character that is not in the string provided */ -export function charNotIn(chars: T) { - return createInput(`[^${chars.replace(/[-\\^\]]/g, '\\$&')}]`) as Input<`[^${EscapeChar}]`> +function escapeCharInput (raw: T) { + return raw.replace(/[-\\^\]]/g, '\\$&') as EscapeChar } +/** This matches any character in the string provided */ +export const charIn = Object.assign((chars: T) => { + return createCharInput(escapeCharInput(chars)) +}, createCharInput('')) + +/** This matches any character that is not in the string provided */ +export const charNotIn = Object.assign((chars: T) => { + return createCharInput(`^${escapeCharInput(chars)}`) +}, createCharInput('^')) + /** * This takes a variable number of inputs and matches any of them * @example diff --git a/test/inputs.test.ts b/test/inputs.test.ts index b61b5e18..3068fc6d 100644 --- a/test/inputs.test.ts +++ b/test/inputs.test.ts @@ -30,11 +30,41 @@ describe('inputs', () => { expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[fo\\\\\\]\\\\\\^\\]/') expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[fo\\]\\^]'>() }) + it('charIn.orChar', () => { + const input = charIn('a').orChar('b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[ab\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[ab]'>() + }) + it('charIn.orChar.from', () => { + const input = charIn('a').orChar.from('a', 'b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[aa-b\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[aa-b]'>() + }) + it('charIn.from', () => { + const input = charIn.from('a', 'b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[a-b\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[a-b]'>() + }) it('charNotIn', () => { const input = charNotIn('fo^-') expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^fo\\\\\\^\\\\-\\]/') expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^fo\\^\\-]'>() }) + it('charNotIn.orChar', () => { + const input = charNotIn('a').orChar('b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^ab\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^ab]'>() + }) + it('charNotIn.orChar.from', () => { + const input = charNotIn('a').orChar.from('a', 'b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^aa-b\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^aa-b]'>() + }) + it('charNotIn.from', () => { + const input = charNotIn.from('a', 'b') + expect(new RegExp(input as any)).toMatchInlineSnapshot('/\\[\\^a-b\\]/') + expectTypeOf(extractRegExp(input)).toEqualTypeOf<'[^a-b]'>() + }) it('anyOf', () => { const values = ['fo/o', 'bar', 'baz', oneOrMore('this')] as const const input = anyOf(...values) From fc3e203f4c34e05c82750e98bdc704c3799235f2 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 4 Jun 2024 15:47:27 +0100 Subject: [PATCH 2/3] fix: decrease type complexity somewhat --- src/core/inputs.ts | 4 ++-- src/core/internal.ts | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/inputs.ts b/src/core/inputs.ts index e9b5fb6e..18d60620 100644 --- a/src/core/inputs.ts +++ b/src/core/inputs.ts @@ -1,4 +1,4 @@ -import type { Input } from './internal' +import type { CharInput, Input } from './internal' import { createInput } from './internal' import type { EscapeChar } from './types/escape' import type { Join } from './types/join' @@ -14,7 +14,7 @@ function createCharInput (raw: T) { const input = createInput(`[${raw}]`) const from = (charFrom: From, charTo: To) => createCharInput(`${raw}${escapeCharInput(charFrom)}-${escapeCharInput(charTo)}`) const orChar = Object.assign(((chars: T) => createCharInput(`${raw}${escapeCharInput(chars)}`)), { from }) - return Object.assign(input, { orChar, from }) + return Object.assign(input, { orChar, from }) as CharInput } function escapeCharInput (raw: T) { diff --git a/src/core/internal.ts b/src/core/internal.ts index eb373543..df5e2a7a 100644 --- a/src/core/internal.ts +++ b/src/core/internal.ts @@ -1,4 +1,5 @@ import { exactly } from './inputs' +import { EscapeChar } from './types/escape' import type { Join } from './types/join' import type { InputSource, MapToCapturedGroupsArr, MapToGroups, MapToValues } from './types/sources' import type { IfUnwrapped } from './wrap' @@ -134,6 +135,11 @@ export interface Input< toString: () => string } +export interface CharInput extends Input<`[${T}]`> { + orChar: ((chars: Or) => CharInput<`${T}${EscapeChar}`>) & CharInput + from: (charFrom: From, charTo: To) => CharInput<`${T}${EscapeChar}-${EscapeChar}`> +} + export function createInput< Value extends string, Groups extends string = never, From 4091f0854190a8f9ab3f5403a1ca265f2b7a60d1 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 4 Jun 2024 15:49:45 +0100 Subject: [PATCH 3/3] chore: import type --- src/core/internal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/internal.ts b/src/core/internal.ts index df5e2a7a..38efe447 100644 --- a/src/core/internal.ts +++ b/src/core/internal.ts @@ -1,5 +1,5 @@ import { exactly } from './inputs' -import { EscapeChar } from './types/escape' +import type { EscapeChar } from './types/escape' import type { Join } from './types/join' import type { InputSource, MapToCapturedGroupsArr, MapToGroups, MapToValues } from './types/sources' import type { IfUnwrapped } from './wrap'