diff --git a/README.md b/README.md index 56390a5..79b1a4e 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ See [Quantifiers API doc](https://callstack.github.io/ts-regex-builder/api/quant | `anyOf('abc')` | `[abc]` | Any of provided characters | | `charRange('a', 'z')` | `[a-z]` | Character in a range | | `charClass(...)` | `[...]` | Union of multiple character classes | -| `inverted(...)` | `[^...]` | Negation of a given character class | +| `negated(...)` | `[^...]` | Negation of a given character class | See [Character Classes API doc](https://callstack.github.io/ts-regex-builder/api/character-classes) for more info. diff --git a/src/__tests__/example-find-suffixes.ts b/src/__tests__/example-find-suffixes.ts index b8f0fed..aec6195 100644 --- a/src/__tests__/example-find-suffixes.ts +++ b/src/__tests__/example-find-suffixes.ts @@ -1,10 +1,10 @@ -import { buildRegExp, choiceOf, notWordBoundary, wordBoundary } from '..'; +import { buildRegExp, choiceOf, nonWordBoundary, wordBoundary } from '..'; test('example: find words with suffix', () => { const suffixesToFind = ['acy', 'ism']; const regex = buildRegExp([ - notWordBoundary, // match suffixes only + nonWordBoundary, // match suffixes only choiceOf(...suffixesToFind), wordBoundary, ]); diff --git a/src/constructs/__tests__/anchors.test.tsx b/src/constructs/__tests__/anchors.test.tsx index 54bdc31..b78a02d 100644 --- a/src/constructs/__tests__/anchors.test.tsx +++ b/src/constructs/__tests__/anchors.test.tsx @@ -2,8 +2,8 @@ import { buildRegExp, digit, endOfString, - notWhitespace, - notWordBoundary, + nonWhitespace, + nonWordBoundary, oneOrMore, startOfString, wordBoundary, @@ -35,28 +35,28 @@ test('`wordBoundary` pattern', () => { test('`wordBoundary` matching', () => { expect( - buildRegExp([wordBoundary, 'a', zeroOrMore(notWhitespace)], { global: true }), + buildRegExp([wordBoundary, 'a', zeroOrMore(nonWhitespace)], { global: true }), ).toMatchGroups('a ba ab aa', ['a', 'ab', 'aa']); expect( - buildRegExp([zeroOrMore(notWhitespace), 'a', wordBoundary], { global: true }), + buildRegExp([zeroOrMore(nonWhitespace), 'a', wordBoundary], { global: true }), ).toMatchGroups('a ba ab aa', ['a', 'ba', 'aa']); }); -test('`notWordBoundary` pattern', () => { - expect(notWordBoundary).toEqualRegex(/\B/); - expect([notWordBoundary, 'a', 'b']).toEqualRegex(/\Bab/); - expect(['a', notWordBoundary, 'b']).toEqualRegex(/a\Bb/); - expect(['a', 'b', notWordBoundary]).toEqualRegex(/ab\B/); +test('`nonWordBoundary` pattern', () => { + expect(nonWordBoundary).toEqualRegex(/\B/); + expect([nonWordBoundary, 'a', 'b']).toEqualRegex(/\Bab/); + expect(['a', nonWordBoundary, 'b']).toEqualRegex(/a\Bb/); + expect(['a', 'b', nonWordBoundary]).toEqualRegex(/ab\B/); }); -test('`notWordBoundary` matching', () => { - expect(buildRegExp([notWordBoundary, 'abc', digit], { global: true })).toMatchGroups( +test('`nonWordBoundary` matching', () => { + expect(buildRegExp([nonWordBoundary, 'abc', digit], { global: true })).toMatchGroups( 'abc1 xabc2 xxabc3', ['abc2', 'abc3'], ); - expect(buildRegExp([digit, 'abc', notWordBoundary], { global: true })).toMatchGroups( + expect(buildRegExp([digit, 'abc', nonWordBoundary], { global: true })).toMatchGroups( '1abc 2abcx 3abcxx', ['2abc', '3abc'], ); diff --git a/src/constructs/__tests__/character-class.test.ts b/src/constructs/__tests__/character-class.test.ts index bc06abd..d2ddb9f 100644 --- a/src/constructs/__tests__/character-class.test.ts +++ b/src/constructs/__tests__/character-class.test.ts @@ -5,10 +5,10 @@ import { charClass, charRange, digit, - inverted, - notDigit, - notWhitespace, - notWord, + negated, + nonDigit, + nonWhitespace, + nonWord, oneOrMore, optional, whitespace, @@ -30,12 +30,12 @@ test('`digit` character class', () => { expect(digit).not.toMatchString('A'); }); -test('`notDigit` character class', () => { - expect(notDigit).toEqualRegex(/\D/); - expect(['x', notDigit]).toEqualRegex(/x\D/); - expect(['x', notDigit, 'x']).toEqualRegex(/x\Dx/); - expect(notDigit).not.toMatchString('1'); - expect(notDigit).toMatchString('A'); +test('`nonDigit` character class', () => { + expect(nonDigit).toEqualRegex(/\D/); + expect(['x', nonDigit]).toEqualRegex(/x\D/); + expect(['x', nonDigit, 'x']).toEqualRegex(/x\Dx/); + expect(nonDigit).not.toMatchString('1'); + expect(nonDigit).toMatchString('A'); }); test('`word` character class', () => { @@ -47,13 +47,13 @@ test('`word` character class', () => { expect(word).not.toMatchString('$'); }); -test('`notWord` character class', () => { - expect(notWord).toEqualRegex(/\W/); - expect(['x', notWord]).toEqualRegex(/x\W/); - expect(['x', notWord, 'x']).toEqualRegex(/x\Wx/); - expect(notWord).not.toMatchString('A'); - expect(notWord).not.toMatchString('1'); - expect(notWord).toMatchString('$'); +test('`nonWord` character class', () => { + expect(nonWord).toEqualRegex(/\W/); + expect(['x', nonWord]).toEqualRegex(/x\W/); + expect(['x', nonWord, 'x']).toEqualRegex(/x\Wx/); + expect(nonWord).not.toMatchString('A'); + expect(nonWord).not.toMatchString('1'); + expect(nonWord).toMatchString('$'); }); test('`whitespace` character class', () => { @@ -66,14 +66,14 @@ test('`whitespace` character class', () => { expect(whitespace).not.toMatchString('1'); }); -test('`notWhitespace` character class', () => { - expect(notWhitespace).toEqualRegex(/\S/); - expect(['x', notWhitespace]).toEqualRegex(/x\S/); - expect(['x', notWhitespace, 'x']).toEqualRegex(/x\Sx/); - expect(notWhitespace).not.toMatchString(' '); - expect(notWhitespace).not.toMatchString('\t'); - expect(notWhitespace).toMatchString('A'); - expect(notWhitespace).toMatchString('1'); +test('`nonWhitespace` character class', () => { + expect(nonWhitespace).toEqualRegex(/\S/); + expect(['x', nonWhitespace]).toEqualRegex(/x\S/); + expect(['x', nonWhitespace, 'x']).toEqualRegex(/x\Sx/); + expect(nonWhitespace).not.toMatchString(' '); + expect(nonWhitespace).not.toMatchString('\t'); + expect(nonWhitespace).toMatchString('A'); + expect(nonWhitespace).toMatchString('1'); }); test('`charClass` base cases', () => { @@ -83,9 +83,9 @@ test('`charClass` base cases', () => { expect(charClass(charRange('a', 'z'), whitespace, anyOf('05'))).toEqualRegex(/[a-z\s05]/); }); -test('`charClass` throws on inverted arguments', () => { - expect(() => charClass(inverted(whitespace))).toThrowErrorMatchingInlineSnapshot( - `"\`charClass\` should receive only non-inverted character classes"`, +test('`charClass` throws on negated arguments', () => { + expect(() => charClass(negated(whitespace))).toThrowErrorMatchingInlineSnapshot( + `"\`charClass\` should receive only non-negated character classes"`, ); }); @@ -141,30 +141,30 @@ test('`anyOf` throws on empty text', () => { ); }); -test('`inverted` character class pattern', () => { - expect(inverted(anyOf('a'))).toEqualRegex(/[^a]/); - expect(inverted(anyOf('abc'))).toEqualRegex(/[^abc]/); +test('`negated` character class pattern', () => { + expect(negated(anyOf('a'))).toEqualRegex(/[^a]/); + expect(negated(anyOf('abc'))).toEqualRegex(/[^abc]/); }); -test('`inverted` character class pattern double inversion', () => { - expect(inverted(inverted(anyOf('a')))).toEqualRegex(/a/); - expect(inverted(inverted(anyOf('abc')))).toEqualRegex(/[abc]/); +test('`negated` character class pattern double inversion', () => { + expect(negated(negated(anyOf('a')))).toEqualRegex(/a/); + expect(negated(negated(anyOf('abc')))).toEqualRegex(/[abc]/); }); -test('`inverted` character class matching', () => { - expect(inverted(anyOf('a'))).not.toMatchString('aa'); - expect(inverted(anyOf('a'))).toMatchGroups('aba', ['b']); +test('`negated` character class matching', () => { + expect(negated(anyOf('a'))).not.toMatchString('aa'); + expect(negated(anyOf('a'))).toMatchGroups('aba', ['b']); }); test('`encodeCharacterClass` throws on empty text', () => { expect(() => buildRegExp( // @ts-expect-error - inverted({ + negated({ type: 'characterClass', chars: [], ranges: [], - isInverted: false, + isNegated: false, }), ), ).toThrowErrorMatchingInlineSnapshot( @@ -173,22 +173,22 @@ test('`encodeCharacterClass` throws on empty text', () => { }); test('showcase: negated character classes', () => { - expect(notDigit).toEqualRegex(/\D/); - expect(notWord).toEqualRegex(/\W/); - expect(notWhitespace).toEqualRegex(/\S/); - - expect(notDigit).toMatchString('A'); - expect(notDigit).not.toMatchString('1'); - expect(notDigit).toMatchString(' '); - expect(notDigit).toMatchString('#'); - - expect(notWord).not.toMatchString('A'); - expect(notWord).not.toMatchString('1'); - expect(notWord).toMatchString(' '); - expect(notWord).toMatchString('#'); - - expect(notWhitespace).toMatchString('A'); - expect(notWhitespace).toMatchString('1'); - expect(notWhitespace).not.toMatchString(' '); - expect(notWhitespace).toMatchString('#'); + expect(nonDigit).toEqualRegex(/\D/); + expect(nonWord).toEqualRegex(/\W/); + expect(nonWhitespace).toEqualRegex(/\S/); + + expect(nonDigit).toMatchString('A'); + expect(nonDigit).not.toMatchString('1'); + expect(nonDigit).toMatchString(' '); + expect(nonDigit).toMatchString('#'); + + expect(nonWord).not.toMatchString('A'); + expect(nonWord).not.toMatchString('1'); + expect(nonWord).toMatchString(' '); + expect(nonWord).toMatchString('#'); + + expect(nonWhitespace).toMatchString('A'); + expect(nonWhitespace).toMatchString('1'); + expect(nonWhitespace).not.toMatchString(' '); + expect(nonWhitespace).toMatchString('#'); }); diff --git a/src/constructs/anchors.ts b/src/constructs/anchors.ts index d2ab45e..718e7d6 100644 --- a/src/constructs/anchors.ts +++ b/src/constructs/anchors.ts @@ -24,12 +24,17 @@ export const wordBoundary: Anchor = { encode: encodeAnchor, }; -export const notWordBoundary: Anchor = { +export const nonWordBoundary: Anchor = { type: 'anchor', symbol: '\\B', encode: encodeAnchor, }; +/** + * @deprecated Renamed to `nonWordBoundary`. + */ +export const notWordBoundary = nonWordBoundary; + function encodeAnchor(this: Anchor): EncodeResult { return { precedence: 'sequence', diff --git a/src/constructs/character-class.ts b/src/constructs/character-class.ts index 0e4894a..880e52d 100644 --- a/src/constructs/character-class.ts +++ b/src/constructs/character-class.ts @@ -5,7 +5,7 @@ export interface CharacterClass extends RegexConstruct { type: 'characterClass'; chars: string[]; ranges: CharacterRange[]; - isInverted: boolean; + isNegated: boolean; encode: () => EncodeResult; } @@ -21,7 +21,7 @@ export const any: CharacterClass = { type: 'characterClass', chars: ['.'], ranges: [], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; @@ -29,15 +29,15 @@ export const digit: CharacterClass = { type: 'characterClass', chars: ['\\d'], ranges: [], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; -export const notDigit: CharacterClass = { +export const nonDigit: CharacterClass = { type: 'characterClass', chars: ['\\D'], ranges: [], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; @@ -45,15 +45,15 @@ export const word: CharacterClass = { type: 'characterClass', chars: ['\\w'], ranges: [], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; -export const notWord: CharacterClass = { +export const nonWord: CharacterClass = { type: 'characterClass', chars: ['\\W'], ranges: [], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; @@ -61,22 +61,37 @@ export const whitespace: CharacterClass = { type: 'characterClass', chars: ['\\s'], ranges: [], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; -export const notWhitespace: CharacterClass = { +export const nonWhitespace: CharacterClass = { type: 'characterClass', chars: ['\\S'], ranges: [], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; +/** + * @deprecated Renamed to `nonDigit`. + */ +export const notDigit = nonDigit; + +/** + * @deprecated Renamed to `nonWord`. + */ +export const notWord = nonWord; + +/** + * @deprecated Renamed to `nonWhitespace`. + */ +export const notWhitespace = nonWhitespace; + export function charClass(...elements: CharacterClass[]): CharacterClass { elements.forEach((element) => { - if (element.isInverted) { - throw new Error('`charClass` should receive only non-inverted character classes'); + if (element.isNegated) { + throw new Error('`charClass` should receive only non-negated character classes'); } }); @@ -84,7 +99,7 @@ export function charClass(...elements: CharacterClass[]): CharacterClass { type: 'characterClass', chars: elements.map((c) => c.chars).flat(), ranges: elements.map((c) => c.ranges).flat(), - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; } @@ -106,7 +121,7 @@ export function charRange(start: string, end: string): CharacterClass { type: 'characterClass', chars: [], ranges: [{ start, end }], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; } @@ -122,28 +137,33 @@ export function anyOf(characters: string): CharacterClass { type: 'characterClass', chars, ranges: [], - isInverted: false, + isNegated: false, encode: encodeCharacterClass, }; } -export function inverted(element: CharacterClass): CharacterClass { +export function negated(element: CharacterClass): CharacterClass { return { type: 'characterClass', chars: element.chars, ranges: element.ranges, - isInverted: !element.isInverted, + isNegated: !element.isNegated, encode: encodeCharacterClass, }; } +/** + * @deprecated Renamed to `negated`. + */ +export const inverted = negated; + function encodeCharacterClass(this: CharacterClass): EncodeResult { if (this.chars.length === 0 && this.ranges.length === 0) { throw new Error('Character class should contain at least one character or character range'); } // Direct rendering for single-character class - if (this.chars.length === 1 && this.ranges?.length === 0 && !this.isInverted) { + if (this.chars.length === 1 && this.ranges?.length === 0 && !this.isNegated) { return { precedence: 'atom', pattern: this.chars[0]!, @@ -157,9 +177,9 @@ function encodeCharacterClass(this: CharacterClass): EncodeResult { const caret = this.chars.includes('^') ? '^' : ''; const otherChars = this.chars.filter((c) => c !== '-' && c !== '^').join(''); const ranges = this.ranges.map(({ start, end }) => `${start}-${end}`).join(''); - const isInverted = this.isInverted ? '^' : ''; + const negation = this.isNegated ? '^' : ''; - let pattern = `[${isInverted}${ranges}${otherChars}${caret}${hyphen}]`; + let pattern = `[${negation}${ranges}${otherChars}${caret}${hyphen}]`; if (pattern === '[^-]') pattern = '[\\^-]'; return { diff --git a/src/index.ts b/src/index.ts index 1ba53a5..d07b03b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,13 @@ export type * from './types'; export { buildPattern, buildRegExp } from './builders'; -export { endOfString, notWordBoundary, startOfString, wordBoundary } from './constructs/anchors'; +export { + endOfString, + nonWordBoundary, + notWordBoundary, + startOfString, + wordBoundary, +} from './constructs/anchors'; export { capture } from './constructs/capture'; export { any, @@ -10,7 +16,11 @@ export { charClass, charRange, digit, + negated, inverted, + nonDigit, + nonWhitespace, + nonWord, notDigit, notWhitespace, notWord, diff --git a/website/docs/Examples.md b/website/docs/Examples.md index 43ff6a6..4de78ab 100644 --- a/website/docs/Examples.md +++ b/website/docs/Examples.md @@ -280,7 +280,7 @@ Ignoring cases where given word is part of a bigger word. const suffixesToFind = ['acy', 'ism']; const regex = buildRegExp([ - notWordBoundary, // match suffixes only + nonWordBoundary, // match suffixes only choiceOf(...suffixesToFind), wordBoundary, ]); diff --git a/website/docs/api/assertions.md b/website/docs/api/assertions.md index 2761d05..aacc76d 100644 --- a/website/docs/api/assertions.md +++ b/website/docs/api/assertions.md @@ -23,11 +23,11 @@ _This API was added in version 1.3.0._ ```ts const wordBoundary: Anchor; -const notWordBoundary: Anchor; +const nonWordBoundary: Anchor; ``` - `wordBoundary` matches the positions where a word character is not followed or preceded by another word character, effectively indicating the start or end of a word. Regex syntax: `\b`. -- `notWordBoundary` matches the positions where a word character is followed or preceded by another word character, indicating that it is not at the start or end of a word. Regex syntax: `\B`. +- `nonWordBoundary` matches the positions where a word character is followed or preceded by another word character, indicating that it is not at the start or end of a word. Regex syntax: `\B`. Note: word characters are letters, digits, and underscore (`_`). Other special characters like `#`, `$`, etc are not considered word characters. diff --git a/website/docs/api/character-classes.md b/website/docs/api/character-classes.md index 5c8ccb9..732c346 100644 --- a/website/docs/api/character-classes.md +++ b/website/docs/api/character-classes.md @@ -10,20 +10,20 @@ Character classes are a set of characters that match any one of the characters i ```ts const any: CharacterClass; const word: CharacterClass; -const notWord: CharacterClass; +const nonWord: CharacterClass; const digit: CharacterClass; -const notDigit: CharacterClass; +const nonDigit: CharacterClass; const whitespace: CharacterClass; -const notWhitespace: CharacterClass; +const nonWhitespace: CharacterClass; ``` - `any` matches any character except newline characters. Regex syntax: `*`. - `word` matches any word character (letters, digits & underscore). Regex syntax: `\w`. -- `notWord` matches any character **except** word characters (letters, digits & underscore). Regex syntax: `\W`. +- `nonWord` matches any character **except** word characters (letters, digits & underscore). Regex syntax: `\W`. - `digit` matches any digit. Regex syntax: `\d`. -- `notDigit` matches any character **except** digits. Regex syntax: `\D`. +- `nonDigit` matches any character **except** digits. Regex syntax: `\D`. - `whitespace` matches any whitespace character (spaces, tabs, line breaks). Regex syntax: `\s`. -- `notWhitespace` matches any character **except** whitespace characters (spaces, tabs, line breaks). Regex syntax: `\S`. +- `nonWhitespace` matches any character **except** whitespace characters (spaces, tabs, line breaks). Regex syntax: `\S`. ### `anyOf()` @@ -68,17 +68,17 @@ Examples: - `charClass(charRange('a', 'f'), digit)` will match all lowercase hex digits (`0` to `9` and `a` to `f`). - `charClass(charRange('a', 'z'), digit, anyOf("._-"))` will match any digit, lowercase Latin letter from `a` to `z`, and either of `.`, `_`, and `-` characters. -### `inverted()` +### `negated()` ```ts -function inverted(element: CharacterClass): CharacterClass; +function negated(element: CharacterClass): CharacterClass; ``` Regex syntax: `[^...]`. -The `inverted` construct creates a new character class that matches any character not present in the passed character class. +The `negated` construct creates a new character class that matches any character not present in the passed character class. Examples: -- `inverted(digit)` matches any character that is not a digit -- `inverted(anyOf('aeiou'))` matches any character that is not a lowercase vowel. +- `negated(digit)` matches any character that is not a digit +- `negated(anyOf('aeiou'))` matches any character that is not a lowercase vowel. diff --git a/website/docs/api/overview.md b/website/docs/api/overview.md index 4342194..64bab53 100644 --- a/website/docs/api/overview.md +++ b/website/docs/api/overview.md @@ -84,7 +84,7 @@ See [Quantifiers](./api/quantifiers) for more info. | `anyOf('abc')` | `[abc]` | Any of provided characters | | `charRange('a', 'z')` | `[a-z]` | Character in a range | | `charClass(...)` | `[...]` | Union of multiple character classes | -| `inverted(...)` | `[^...]` | Negation of a given character class | +| `negated(...)` | `[^...]` | Negation of a given character class | See [Character Classes](./api/character-classes) for more info.