Skip to content

refactor(rfc): options args order #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ const hexColor = buildRegex(
optionally('#'),
capture(
choiceOf(
repeat({ count: 6 }, hexDigit), // #rrggbb
repeat({ count: 3 }, hexDigit), // #rgb
repeat(hexDigit, { count: 6 }), // #rrggbb
repeat(hexDigit, { count: 3 }), // #rgb
),
),
endOfString,
Expand Down Expand Up @@ -90,7 +90,7 @@ const currencyAmount = buildRegex([
| Regex Component | Regex Pattern | Description |
| --------------------------------------- | ------------- | ----------------------------------- |
| `buildRegex(...)` | `/.../` | Create `RegExp` instance |
| `buildRegex({ ignoreCase: true }, ...)` | `/.../i` | Create `RegExp` instance with flags |
| `buildRegex(..., { ignoreCase: true })` | `/.../i` | Create `RegExp` instance with flags |

### Components

Expand All @@ -111,9 +111,9 @@ Notes:
| `zeroOrMore(x)` | `x*` | Zero or more occurence of a pattern |
| `oneOrMore(x)` | `x+` | One or more occurence of a pattern |
| `optionally(x)` | `x?` | Zero or one occurence of a pattern |
| `repeat({ count: n }, x)` | `x{n}` | Pattern repeats exact number of times |
| `repeat({ min: n, }, x)` | `x{n,}` | Pattern repeats at least given number of times |
| `repeat({ min: n, max: n2 }, x)` | `x{n1,n2}` | Pattern repeats between n1 and n2 number of times |
| `repeat(x, { count: n })` | `x{n}` | Pattern repeats exact number of times |
| `repeat(x, { min: n, })` | `x{n,}` | Pattern repeats at least given number of times |
| `repeat(x, { min: n, max: n2 })` | `x{n1,n2}` | Pattern repeats between n1 and n2 number of times |

All quantifiers accept sequence of elements

Expand All @@ -133,7 +133,7 @@ All quantifiers accept sequence of elements
Notes:

- `any`, `word`, `digit`, `whitespace` are objects, no need to call them
- `anyof` accepts a single string of characters to match
- `anyOf` accepts a single string of characters to match
- `charRange` accepts exactly **two single character** strings representing range start and end (inclusive)
- `charClass` accepts a variable number of character classes to join into a single class
- `inverted` accepts a single character class to be inverted
Expand Down
20 changes: 4 additions & 16 deletions docs/Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,9 @@ const octet = choiceOf(

// Match
const regex = buildRegex([
startOfString,
capture(octet),
'.',
capture(octet),
'.',
capture(octet),
'.',
capture(octet),
startOfString, //
repeat([octet, '.'], { count: 3 }),
octet,
endOfString,
]);
```
Expand All @@ -30,12 +25,5 @@ This code generates the following regex pattern:

```ts
const regex =
/^(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/;
```

This pattern uses repetition of the `capture(octet)` elements to generate capture groups for each of the IPv4 octets:

```ts
// Matched groups ['192.168.0.1', '192', '168', '0', '1',]
const match = regex.exec('192.168.0.1');
/^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/;
```
30 changes: 18 additions & 12 deletions src/__tests__/builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@ import { buildRegex } from '../builders';

test('`regexBuilder` flags', () => {
expect(buildRegex('a').flags).toBe('');
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('a', { global: true }).flags).toBe('g');
expect(buildRegex('a', { global: false }).flags).toBe('');

expect(buildRegex({ ignoreCase: true }, 'a').flags).toBe('i');
expect(buildRegex({ ignoreCase: false }, 'a').flags).toBe('');
expect(buildRegex('a', { ignoreCase: true }).flags).toBe('i');
expect(buildRegex('a', { ignoreCase: false }).flags).toBe('');

expect(buildRegex({ multiline: true }, 'a').flags).toBe('m');
expect(buildRegex({ multiline: false }, 'a').flags).toBe('');
expect(buildRegex('a', { multiline: true }).flags).toBe('m');
expect(buildRegex('a', { multiline: false }).flags).toBe('');

expect(buildRegex({ hasIndices: true }, 'a').flags).toBe('d');
expect(buildRegex({ hasIndices: false }, 'a').flags).toBe('');
expect(buildRegex('a', { hasIndices: true }).flags).toBe('d');
expect(buildRegex('a', { hasIndices: false }).flags).toBe('');

expect(buildRegex({ sticky: true }, 'a').flags).toBe('y');
expect(buildRegex({ sticky: false }, 'a').flags).toBe('');
expect(buildRegex('a', { sticky: true }).flags).toBe('y');
expect(buildRegex('a', { sticky: false }).flags).toBe('');

expect(buildRegex({ global: true, ignoreCase: true, multiline: false }, 'a').flags).toBe('gi');
expect(
buildRegex('a', {
global: true, //
ignoreCase: true,
multiline: false,
}).flags
).toBe('gi');
});
43 changes: 43 additions & 0 deletions src/__tests__/examples.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
buildRegex,
charRange,
choiceOf,
digit,
endOfString,
repeat,
startOfString,
} from '../index';

test('example: IPv4 address validator', () => {
const octet = choiceOf(
[digit],
[charRange('1', '9'), digit],
['1', repeat(digit, { count: 2 })],
['2', charRange('0', '4'), digit],
['25', charRange('0', '5')]
);

const regex = buildRegex([
startOfString, //
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused comment?

Suggested change
startOfString, //
startOfString,

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, inserting comment this way prevents prettier from collapsing complex expression into single line. Multiline expressions are imo more readable.

repeat([octet, '.'], { count: 3 }),
octet,
endOfString,
]);

expect(regex).toMatchString('0.0.0.0');
expect(regex).toMatchString('192.168.0.1');
expect(regex).toMatchString('1.99.100.249');
expect(regex).toMatchString('255.255.255.255');
expect(regex).toMatchString('123.45.67.89');

expect(regex).not.toMatchString('0.0.0.');
expect(regex).not.toMatchString('0.0.0.0.');
expect(regex).not.toMatchString('0.-1.0.0');
expect(regex).not.toMatchString('0.1000.0.0');
expect(regex).not.toMatchString('0.0.300.0');
expect(regex).not.toMatchString('255.255.255.256');

expect(regex).toHavePattern(
/^(?:(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])$/
);
});
49 changes: 0 additions & 49 deletions src/__tests__/examples.ts

This file was deleted.

19 changes: 2 additions & 17 deletions src/builders.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { RegexSequence } from './types';
import { encodeSequence } from './encoder/encoder';
import { asNodeArray } from './utils/nodes';
import { optionalFirstArg } from './utils/optional-arg';

export interface RegexFlags {
/** Global search. */
Expand All @@ -21,27 +20,13 @@ export interface RegexFlags {
}

/**
* Generate RegExp object from elements.
*
* @param elements Single regex element or array of elements
* @returns
*/
export function buildRegex(sequence: RegexSequence): RegExp;

/**
* Generate RegExp object from elements with passed flags.
* Generate RegExp object from elements with optional flags.
*
* @param elements Single regex element or array of elements
* @param flags RegExp flags object
* @returns RegExp object
*/
export function buildRegex(flags: RegexFlags, sequence: RegexSequence): RegExp;

export function buildRegex(first: any, second?: any): RegExp {
return _buildRegex(...optionalFirstArg(first, second));
}

export function _buildRegex(flags: RegexFlags, sequence: RegexSequence): RegExp {
export function buildRegex(sequence: RegexSequence, flags?: RegexFlags): RegExp {
const pattern = encodeSequence(asNodeArray(sequence)).pattern;
const flagsString = encodeFlags(flags ?? {});
return new RegExp(pattern, flagsString);
Expand Down
2 changes: 1 addition & 1 deletion src/components/__tests__/choice-of.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test('`choiceOf` with sequence options', () => {

test('`choiceOf` using nested regex', () => {
expect(choiceOf(oneOrMore('a'), zeroOrMore('b'))).toHavePattern(/a+|b*/);
expect(choiceOf(repeat({ min: 1, max: 3 }, 'a'), repeat({ count: 5 }, 'bx'))).toHavePattern(
expect(choiceOf(repeat('a', { min: 1, max: 3 }), repeat('bx', { count: 5 }))).toHavePattern(
/a{1,3}|(?:bx){5}/
);
});
Expand Down
18 changes: 9 additions & 9 deletions src/components/__tests__/repeat.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import { oneOrMore, zeroOrMore } from '../quantifiers';
import { repeat } from '../repeat';

test('`repeat` quantifier', () => {
expect(['a', repeat({ min: 1, max: 5 }, 'b')]).toHavePattern(/ab{1,5}/);
expect(['a', repeat({ min: 1 }, 'b')]).toHavePattern(/ab{1,}/);
expect(['a', repeat({ count: 1 }, 'b')]).toHavePattern(/ab{1}/);
expect(['a', repeat('b', { min: 1, max: 5 })]).toHavePattern(/ab{1,5}/);
expect(['a', repeat('b', { min: 1 })]).toHavePattern(/ab{1,}/);
expect(['a', repeat('b', { count: 1 })]).toHavePattern(/ab{1}/);

expect(['a', repeat({ count: 1 }, ['a', zeroOrMore('b')])]).toHavePattern(/a(?:ab*){1}/);
expect(repeat({ count: 5 }, ['text', ' ', oneOrMore('d')])).toHavePattern(/(?:text d+){5}/);
expect(['a', repeat(['a', zeroOrMore('b')], { count: 1 })]).toHavePattern(/a(?:ab*){1}/);
expect(repeat(['text', ' ', oneOrMore('d')], { count: 5 })).toHavePattern(/(?:text d+){5}/);
});

test('`repeat` optimizes grouping for atoms', () => {
expect(repeat({ count: 2 }, digit)).toHavePattern(/\d{2}/);
expect(repeat({ min: 2 }, digit)).toHavePattern(/\d{2,}/);
expect(repeat({ min: 1, max: 5 }, digit)).toHavePattern(/\d{1,5}/);
expect(repeat(digit, { count: 2 })).toHavePattern(/\d{2}/);
expect(repeat(digit, { min: 2 })).toHavePattern(/\d{2,}/);
expect(repeat(digit, { min: 1, max: 5 })).toHavePattern(/\d{1,5}/);
});

test('`repeat` throws on no children', () => {
expect(() => repeat({ count: 1 }, [])).toThrowErrorMatchingInlineSnapshot(
expect(() => repeat([], { count: 1 })).toThrowErrorMatchingInlineSnapshot(
`"\`repeat\` should receive at least one element"`
);
});
2 changes: 1 addition & 1 deletion src/components/repeat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export interface Repeat extends RegexElement {

export type RepeatOptions = { count: number } | { min: number; max?: number };

export function repeat(options: RepeatOptions, sequence: RegexSequence): Repeat {
export function repeat(sequence: RegexSequence, options: RepeatOptions): Repeat {
const children = asNodeArray(sequence);

if (children.length === 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/encoder/__tests__/encoder.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ test('basic quantifies', () => {
expect(['a', oneOrMore('bc')]).toHavePattern(/a(?:bc)+/);
expect(['a', oneOrMore('bc')]).toHavePattern(/a(?:bc)+/);

expect(['a', repeat({ min: 1, max: 5 }, 'b')]).toHavePattern(/ab{1,5}/);
expect(['a', repeat('b', { min: 1, max: 5 })]).toHavePattern(/ab{1,5}/);

expect(['a', zeroOrMore('b')]).toHavePattern(/ab*/);
expect(['a', zeroOrMore('bc')]).toHavePattern(/a(?:bc)*/);
Expand Down
34 changes: 0 additions & 34 deletions src/utils/optional-arg.ts

This file was deleted.