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..13ad226 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 { isRegexElement } 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,22 @@ import { encodeSequence } from './encoder/encoder'; * @param elements * @returns */ -export function buildRegex(...elements: Array): RegExp { - const pattern = encodeSequence(elements).pattern; - return new RegExp(pattern); +export function buildRegex(...elements: Array): RegExp; +export function buildRegex( + flags: RegexFlags, + ...elements: Array +): RegExp; +export function buildRegex( + first: RegexFlags | RegexElement | string, + ...rest: Array +): RegExp { + if (typeof first === 'string' || isRegexElement(first)) { + return buildRegex({}, first, ...rest); + } + + const pattern = encodeSequence(rest).pattern; + const flags = encodeFlags(first); + return new RegExp(pattern, flags); } /** @@ -22,3 +49,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..d0d930d 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'; /** @@ -29,6 +30,10 @@ 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