From 87413cffe0285f030b9c22d041a06d14439d5bb1 Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 18 Dec 2023 18:37:43 +0100 Subject: [PATCH 1/2] feat: support core regex flags --- src/__tests__/builder.test.ts | 25 ++++++++++++++++++ src/builders.ts | 49 +++++++++++++++++++++++++++++++++-- src/utils.ts | 16 ++++++++++++ 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/__tests__/builder.test.ts diff --git a/src/__tests__/builder.test.ts b/src/__tests__/builder.test.ts new file mode 100644 index 0000000..f9ed336 --- /dev/null +++ b/src/__tests__/builder.test.ts @@ -0,0 +1,25 @@ +import { buildRegex } from '../builders'; + +test('"regexBuilder" flags', () => { + expect(buildRegex('a').flags).toBe(''); + expect(buildRegex({}, 'a').flags).toBe(''); + + expect(buildRegex({ global: true }, 'a').flags).toBe('g'); + expect(buildRegex({ global: false }, 'a').flags).toBe(''); + + expect(buildRegex({ ignoreCase: true }, 'a').flags).toBe('i'); + expect(buildRegex({ ignoreCase: false }, 'a').flags).toBe(''); + + expect(buildRegex({ multiline: true }, 'a').flags).toBe('m'); + expect(buildRegex({ multiline: false }, 'a').flags).toBe(''); + + expect(buildRegex({ hasIndices: true }, 'a').flags).toBe('d'); + expect(buildRegex({ hasIndices: false }, 'a').flags).toBe(''); + + expect(buildRegex({ sticky: true }, 'a').flags).toBe('y'); + expect(buildRegex({ sticky: false }, 'a').flags).toBe(''); + + expect( + buildRegex({ global: true, ignoreCase: true, multiline: false }, 'a').flags + ).toBe('gi'); +}); diff --git a/src/builders.ts b/src/builders.ts index 0f17bd5..1b11b00 100644 --- a/src/builders.ts +++ b/src/builders.ts @@ -1,5 +1,19 @@ import type { RegexElement } from './components/types'; import { encodeSequence } from './encoder/encoder'; +import { extractPropsParam } from './utils'; + +export interface RegexFlags { + /** Global search. */ + global?: boolean; + /** Case-insensitive search. */ + ignoreCase?: boolean; + /** Allows ^ and $ to match newline characters. */ + multiline?: boolean; + /** Generate indices for substring matches. */ + hasIndices?: boolean; + /** Perform a "sticky" search that matches starting at the current position in the target string. */ + sticky?: boolean; +} /** * Generate RegExp object for elements. @@ -7,9 +21,19 @@ import { encodeSequence } from './encoder/encoder'; * @param elements * @returns */ -export function buildRegex(...elements: Array): RegExp { +export function buildRegex(...elements: Array): RegExp; +export function buildRegex( + flags: RegexFlags, + ...elements: Array +): RegExp; +export function buildRegex( + ..._elements: Array +): RegExp { + const [flagsObject, elements] = extractPropsParam(_elements); + const pattern = encodeSequence(elements).pattern; - return new RegExp(pattern); + const flags = encodeFlags(flagsObject); + return new RegExp(pattern, flags); } /** @@ -22,3 +46,24 @@ export function buildPattern( ): string { return encodeSequence(elements).pattern; } + +function encodeFlags(flags: RegexFlags): string { + let result = ''; + if (flags.global) { + result += 'g'; + } + if (flags.ignoreCase) { + result += 'i'; + } + if (flags.multiline) { + result += 'm'; + } + if (flags.hasIndices) { + result += 'd'; + } + if (flags.sticky) { + result += 'y'; + } + + return result; +} diff --git a/src/utils.ts b/src/utils.ts index a1ae03e..da12682 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,4 @@ +import type { RegexElement } from './components/types'; import { type EncoderNode, EncoderPrecedence } from './encoder/types'; /** @@ -33,3 +34,18 @@ export function concatNodes(nodes: EncoderNode[]): EncoderNode { export function escapeText(text: string) { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } + +export function extractPropsParam( + args: Array +) { + const first = args[0]; + if (typeof first === 'string' || isRegexElement(first)) { + return [{}, args] as [Props, Array]; + } + + return [first, args.slice(1)] as [Props, Array]; +} + +function isRegexElement(element: unknown): element is RegexElement { + return typeof element === 'object' && element !== null && 'type' in element; +} From 2cd85d300b291d865da7c7f1a8da5633b45f5d9c Mon Sep 17 00:00:00 2001 From: Maciej Jastrzebski Date: Mon, 18 Dec 2023 21:42:16 +0100 Subject: [PATCH 2/2] chore: self code review --- src/builders.ts | 13 ++++++++----- src/utils.ts | 19 ++++--------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/builders.ts b/src/builders.ts index 1b11b00..13ad226 100644 --- a/src/builders.ts +++ b/src/builders.ts @@ -1,6 +1,6 @@ import type { RegexElement } from './components/types'; import { encodeSequence } from './encoder/encoder'; -import { extractPropsParam } from './utils'; +import { isRegexElement } from './utils'; export interface RegexFlags { /** Global search. */ @@ -27,12 +27,15 @@ export function buildRegex( ...elements: Array ): RegExp; export function buildRegex( - ..._elements: Array + first: RegexFlags | RegexElement | string, + ...rest: Array ): RegExp { - const [flagsObject, elements] = extractPropsParam(_elements); + if (typeof first === 'string' || isRegexElement(first)) { + return buildRegex({}, first, ...rest); + } - const pattern = encodeSequence(elements).pattern; - const flags = encodeFlags(flagsObject); + const pattern = encodeSequence(rest).pattern; + const flags = encodeFlags(first); return new RegExp(pattern, flags); } diff --git a/src/utils.ts b/src/utils.ts index da12682..d0d930d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,22 +30,11 @@ export function concatNodes(nodes: EncoderNode[]): EncoderNode { }; } +export function isRegexElement(element: unknown): element is RegexElement { + return typeof element === 'object' && element !== null && 'type' in element; +} + // Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping export function escapeText(text: string) { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } - -export function extractPropsParam( - args: Array -) { - const first = args[0]; - if (typeof first === 'string' || isRegexElement(first)) { - return [{}, args] as [Props, Array]; - } - - return [first, args.slice(1)] as [Props, Array]; -} - -function isRegexElement(element: unknown): element is RegexElement { - return typeof element === 'object' && element !== null && 'type' in element; -}