regex-partial-match 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +54 -54
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -10,7 +10,7 @@ Unlike C/C++ (via [PCRE/PCRE2](https://www.pcre.org/original/doc/html/pcrepartia
10
10
 
11
11
  This library transforms regular expressions to best-effort support **partial matching**, allowing you to test if an incomplete string could potentially match the full pattern. This is particularly useful for real-time input validation, autocomplete systems, progressive form validation, stream chunk matching, etc.
12
12
 
13
- As a side effect of the parse this requires, [`features()`](#features) names the syntactic constructs a `PartialMatchRegExp`'s pattern uses — useful for consumers that need to reason about a pattern without writing their own regex parser. For many features, a simple search in the [source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source) would be insufficient.
13
+ As a side effect of the parse this requires, [`features()`](#features) names the syntactic constructs a `PartialMatchRegExp`'s pattern uses — useful for consumers that need to reason about a pattern without writing their own regex parser. For many features, a simple search in the [source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source) would be insufficient.
14
14
 
15
15
  **Based on an algorithm created by [Lucas Trzesniewski](https://github.com/ltrzesniewski)**, re-created for NPM via ISC license, with permission.
16
16
 
@@ -69,19 +69,17 @@ state("2024-06-15"); // 'complete' - accept, enable submit
69
69
  ```
70
70
 
71
71
  > [!NOTE]
72
- > `hitEnd()` answers "could more input change this match?", not "is this text a match?". `/hello \w+/` matches `"hello world"` in full, and `hitEnd()` is still `true`: `\w+` read the end of the input looking for more. For an exact-length pattern like the date above the two questions coincide, which is what makes the three states work; a validator for an open-ended pattern that wants "valid so far" should test the original pattern as well.
72
+ > [`hitEnd()`](#hitend) answers "could more input change this match?", not "is this text a match?". `/hello \w+/` matches `"hello world"` in full, and `hitEnd()` is still `true`: `\w+` read the end of the input looking for more. For an exact-length pattern like the date above the two questions coincide, which is what makes the three states work; a validator for an open-ended pattern that wants "valid so far" should test the original pattern as well.
73
73
 
74
74
  Upgrading from 1.x, where this was `isComplete()`? See the [migration guide](./codemods/transforms/v1-v2/README.md).
75
75
 
76
- [^1]:
77
- Mimicking JDK's [`Matcher.hitEnd()`](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#hitEnd--)
76
+ [^1]: Mimicking JDK's [`Matcher.hitEnd()`](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#hitEnd--)
78
77
 
79
- [^2]:
80
- Testing the original, untransformed pattern looks like it should answer this — "did the input fully satisfy the original pattern?" — but it asks a different question: whether the original matches *at all* here, not whether *this* match reached the end of the input on its way. The two agree almost always, but a read of the end inside a zero-width assertion can make both return an identical result by different paths. See [Why the question can't be answered from the outside](https://github.com/TomStrepsil/regex-partial-match/blob/main/docs/how-it-works.md#why-the-question-cant-be-answered-from-the-outside) for the case where they diverge.
78
+ [^2]: Testing the original, untransformed pattern looks like it should answer this — "did the input fully satisfy the original pattern?" — but it asks a different question: whether the original matches _at all_ here, not whether _this_ match reached the end of the input on its way. The two agree almost always, but a read of the end inside a zero-width assertion can make both return an identical result by different paths. See [Why the question can't be answered from the outside](https://github.com/TomStrepsil/regex-partial-match/blob/main/docs/how-it-works.md#why-the-question-cant-be-answered-from-the-outside) for the case where they diverge.
81
79
 
82
80
  ### A note on Tree-Shaking
83
81
 
84
- If your environment doesn't tree-shake (e.g. Deno, or unbundled Node), the default entry loads `hitEnd` and `features` with the class. Import each from its own subpath instead, and only the ones you use:
82
+ If your environment doesn't tree-shake (e.g. Deno, or unbundled Node), the default entry loads [`hitEnd`](#hitend) and [`features`](#features) with the class. Import each from its own subpath instead, and only the ones you use:
85
83
 
86
84
  ```javascript
87
85
  import PartialMatchRegExp from "regex-partial-match/partialMatchRegExp";
@@ -95,10 +93,10 @@ import features from "regex-partial-match/features";
95
93
 
96
94
  `regex-partial-match/core` exports a `PartialMatchRegExp` without two sets of rules, so a bundle that doesn't need them can leave them out. Each set is a module you bind back with `withModules`:
97
95
 
98
- | Module | Import | Supplies |
99
- |---|---|---|
100
- | `carets` | `regex-partial-match/modules/carets` | the rules for a `^` under the `m` flag that has something before it, and for a `^` in a group or positive lookahead |
101
- | `backreferences` | `regex-partial-match/modules/backreferences` | matching a backreference against the text its group captured |
96
+ | Module | Import | Supplies |
97
+ | ---------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
98
+ | `carets` | `regex-partial-match/modules/carets` | the rules for a `^` under the `m` flag that has something before it, and for a `^` in a group or positive lookahead |
99
+ | `backreferences` | `regex-partial-match/modules/backreferences` | matching a backreference against the text its group captured |
102
100
 
103
101
  Both are also named exports of `regex-partial-match/modules`.
104
102
 
@@ -113,7 +111,7 @@ const WithBoth = withModules(carets, backreferences);
113
111
  new WithBoth(/(a|b)\1/).test("a"); // true
114
112
  ```
115
113
 
116
- `withModules` returns a `PartialMatchRegExp` class extending `RegExp`, and the same set of modules in any order returns the same class: `withModules()` is the `core` class, and `withModules(carets, backreferences)` is the default `PartialMatchRegExp`, so `instanceof` holds between them. [`hitEnd()`](#hitend) and [`features()`](#features) accept instances of any of these classes.
114
+ `withModules` returns a `PartialMatchRegExp` class extending `RegExp`, and the same set of modules in any order returns the same class: `withModules()` is the `core` class, and `withModules(carets, backreferences)` is the default `PartialMatchRegExp`, so `instanceof` holds between them. [`hitEnd`](#hitend) and [`features`](#features) accept instances of any of these classes.
117
115
 
118
116
  A module's type is `Module`, exported from both `regex-partial-match/core` and `regex-partial-match/modules`, for annotating a list of modules: `const modules: Module[] = [carets, backreferences]`. It is opaque, so what a module holds is not part of the API, and `withModules` throws a `TypeError` for anything that isn't a module.
119
117
 
@@ -121,11 +119,11 @@ A module's type is `Module`, exported from both `regex-partial-match/core` and `
121
119
 
122
120
  If your patterns aren't known when you build, because they come from users or configuration, use `regex-partial-match`. If they are fixed, use `core`: construct each pattern once, and bind the modules construction asks for.
123
121
 
124
- | Error when constructed | Bind |
125
- |---|---|
126
- | `TypeError: Needs the carets module` | `carets` |
127
- | `TypeError: Needs the backreferences module` | `backreferences` |
128
- | `TypeError: Needs the carets and backreferences modules` | both |
122
+ | Error when constructed | Bind |
123
+ | -------------------------------------------------------- | ---------------- |
124
+ | `TypeError: Needs the carets module` | `carets` |
125
+ | `TypeError: Needs the backreferences module` | `backreferences` |
126
+ | `TypeError: Needs the carets and backreferences modules` | both |
129
127
 
130
128
  The error names every module the pattern needs, and none that is already bound.
131
129
 
@@ -180,7 +178,7 @@ The library is compiled to **ES2015** (ECMAScript 6). Certain regular expression
180
178
  - [**`v` (unicodeSets) flag**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicodeSets) - ES2024+
181
179
  - [**Modifiers**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Modifier) (`(?ims:...)`, `(?-ims:...)`, `(?i-ms:...)`) - ES2025+
182
180
 
183
- Each of these applies only when the *original* pattern uses the feature — everything else, including construction, `exec()`, `test()` and [`hitEnd()`](#hitend), holds to the ES2015 floor.
181
+ Each of these applies only when the _original_ pattern uses the feature — everything else, including construction, `exec()`, `test()` and [`hitEnd()`](#hitend), holds to the ES2015 floor.
184
182
 
185
183
  ## ⚠️ Caveats
186
184
 
@@ -340,9 +338,9 @@ hitEnd(greedy, greedy.exec("hello world")); // true — \w+ read the end lookin
340
338
  ```
341
339
 
342
340
  > [!NOTE]
343
- > Where the JDK is exact, `hitEnd()` is conservative in one place: a bounded greedy quantifier (`?`, `{n,m}`) fully taken at the end of the input reports `true`, although the engine attempted no further read there — on a *group* (`/(ab)?/` on `"ab"` is `true` here and `false` in Java), and the same way for an unequal-bound `{n,m}` directly on a single atom once it's saturated at its maximum (`/a{1,2}/` on `"aa"` is `true`, though no continuation can add a third `a`). Outside the two limits in [What it cannot see](#what-it-cannot-see), it is never wrong in the other direction.
341
+ > Where the JDK is exact, [`hitEnd`](#hitend) is conservative in one place: a bounded greedy quantifier (`?`, `{n,m}`) fully taken at the end of the input reports `true`, although the engine attempted no further read there — on a _group_ (`/(ab)?/` on `"ab"` is `true` here and `false` in Java), and the same way for an unequal-bound `{n,m}` directly on a single atom once it's saturated at its maximum (`/a{1,2}/` on `"aa"` is `true`, though no continuation can add a third `a`). Outside the two limits in [What it cannot see](#what-it-cannot-see), it is never wrong in the other direction.
344
342
 
345
- See [How It Works](./docs/how-it-works.md#why-the-question-cant-be-answered-from-the-outside) for why this can't be worked out from the match alone, and how `hitEnd()` records a read of the end.
343
+ See [How It Works](./docs/how-it-works.md#why-the-question-cant-be-answered-from-the-outside) for why this can't be worked out from the match alone, and how [`hitEnd`](#hitend) records a read of the end.
346
344
 
347
345
  #### What it cannot see
348
346
 
@@ -355,11 +353,11 @@ See [How It Works](./docs/how-it-works.md#why-the-question-cant-be-answered-from
355
353
  features(partial: PartialMatchRegExp): ReadonlySet<RegexFeature>
356
354
  ```
357
355
 
358
- Building the partial-match regex requires walking the entire source pattern, usually once. The [carets module](./docs/modules/carets.md) guesses from a cheap look at the source whether a pattern needs its rules, and when it guesses wrong the pattern is walked a second time with them. As a side effect of the walk that builds the regex, each instance records which syntactic constructs its pattern actually uses, and `features()` names them as a set — no separate scan of the source is performed to produce it. The set is built on the first call for an instance and the same set is returned after that.
356
+ Building the partial-match regex requires walking the entire source pattern, usually once. [^3] As a side effect of the walk that builds the regex, each instance records which syntactic constructs its pattern actually uses, and `features()` names them as a set — no separate scan of the source is performed to produce it. The set is built on the first call for an instance and the same set is returned after that.
359
357
 
360
358
  Available as a named export of the default entry point, `import { features } from 'regex-partial-match'`, or as the default export of `regex-partial-match/features`. It accepts an instance of any `PartialMatchRegExp` class, including those from [`regex-partial-match/core`](#the-lean-entry-regex-partial-matchcore).
361
359
 
362
- This is useful for consumers building on top of `PartialMatchRegExp` who need to reason about which constructs a *specific* pattern uses, without writing their own regex parser to find out. Two concrete cases:
360
+ This is useful for consumers building on top of `PartialMatchRegExp` who need to reason about which constructs a _specific_ pattern uses, without writing their own regex parser to find out. Two concrete cases:
363
361
 
364
362
  - **Flagging patterns likely to hit one of the [caveats](./docs/caveats.md).** For example, a pattern combining `backreference` with `lookbehind`, `negativeLookahead`, or `negativeLookbehind` is a candidate for the [atomic-backreference caveat](./docs/caveats.md#backreferences); one combining `backreference` with `disjunction` is a candidate for the [prefix-ambiguous top-level alternation caveat](./docs/caveats.md#prefix-ambiguous-top-level-alternation). A consumer accepting user-supplied patterns can surface a warning instead of letting the edge case surprise someone later.
365
363
  - **Restricting which constructs a product surface allows.** e.g. a system that only wants to accept "simple" patterns (no lookaround, no backreferences) from untrusted input can check `features()` against an allow-list and reject the rest, without needing to hand-roll that check against the raw pattern source.
@@ -376,38 +374,38 @@ features(partial).has("backreference"); // true
376
374
 
377
375
  `RegexFeature` is a string union, exported from `regex-partial-match` and `regex-partial-match/features`:
378
376
 
379
- | Feature | Matches | Notes |
380
- | --------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------- |
381
- | `patternCharacter` | An ordinary literal character | |
382
- | `startAnchor` | Top-level `^` | |
383
- | `endAnchor` | Top-level `$` | |
384
- | `wordBoundary` | Top-level `\b` | |
385
- | `nonWordBoundary` | Top-level `\B` | |
386
- | `lookahead` | `(?=...)` | |
387
- | `negativeLookahead` | `(?!...)` | |
388
- | `lookbehind` | `(?<=...)` | |
389
- | `negativeLookbehind` | `(?<!...)` | |
390
- | `backreference` | `\1` | |
391
- | `namedBackreference` | `\k<name>` | |
392
- | `namedGroup` | `(?<name>...)` | Always accompanied by `capturingGroup` — see below |
393
- | `capturingGroup` | `(...)`, including named groups | |
394
- | `lookaroundCapture` | A capturing group inside any lookaround | Nesting, not a construct — always accompanied by `capturingGroup` |
395
- | `nonCapturingGroup` | `(?:...)` | |
396
- | `modifierGroup` | `(?ims:...)` | |
397
- | `modifierGroupWithRemoval` | `(?ims-ims:...)` | Mutually exclusive with `modifierGroup` |
398
- | `characterClass` | `[...]` | |
399
- | `nestedCharacterClass` | `[...[...]...]` | `v` flag only |
400
- | `classIntersection` | `&&` inside a character class | `v` flag only |
401
- | `classSubtraction` | `--` inside a character class | `v` flag only |
402
- | `disjunction` | `\|` | |
403
- | `quantifier` | `*`, `+`, `?`, `{n}`, `{n,}`, `{n,m}` | |
404
- | `unicodePropertyEscape` | `\p{...}`, `\P{...}` | `u`/`v` flag only — otherwise tagged `otherEscape` |
405
- | `characterClassEscape` | `\d`, `\D`, `\w`, `\W`, `\s`, `\S` | |
406
- | `controlEscape` | `\f`, `\n`, `\r`, `\t`, `\v` | |
407
- | `controlLetterEscape` | `\cX` | |
408
- | `hexEscapeSequence` | `\xXX` | |
409
- | `unicodeEscapeSequence` | `\uXXXX`, `\u{...}` | |
410
- | `otherEscape` | Any other `\X`, e.g. `\.` | `\0` alone is tagged `otherEscape` only under `u`/`v`; otherwise `backreference`, [like any other digit escape](docs/modules/backreferences.md) |
377
+ | Feature | Matches | Notes |
378
+ | -------------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
379
+ | `patternCharacter` | An ordinary literal character | |
380
+ | `startAnchor` | Top-level `^` | |
381
+ | `endAnchor` | Top-level `$` | |
382
+ | `wordBoundary` | Top-level `\b` | |
383
+ | `nonWordBoundary` | Top-level `\B` | |
384
+ | `lookahead` | `(?=...)` | |
385
+ | `negativeLookahead` | `(?!...)` | |
386
+ | `lookbehind` | `(?<=...)` | |
387
+ | `negativeLookbehind` | `(?<!...)` | |
388
+ | `backreference` | `\1` | |
389
+ | `namedBackreference` | `\k<name>` | |
390
+ | `namedGroup` | `(?<name>...)` | Always accompanied by `capturingGroup` — see below |
391
+ | `capturingGroup` | `(...)`, including named groups | |
392
+ | `lookaroundCapture` | A capturing group inside any lookaround | Nesting, not a construct — always accompanied by `capturingGroup` |
393
+ | `nonCapturingGroup` | `(?:...)` | |
394
+ | `modifierGroup` | `(?ims:...)` | |
395
+ | `modifierGroupWithRemoval` | `(?ims-ims:...)` | Mutually exclusive with `modifierGroup` |
396
+ | `characterClass` | `[...]` | |
397
+ | `nestedCharacterClass` | `[...[...]...]` | `v` flag only |
398
+ | `classIntersection` | `&&` inside a character class | `v` flag only |
399
+ | `classSubtraction` | `--` inside a character class | `v` flag only |
400
+ | `disjunction` | `\|` | |
401
+ | `quantifier` | `*`, `+`, `?`, `{n}`, `{n,}`, `{n,m}` | |
402
+ | `unicodePropertyEscape` | `\p{...}`, `\P{...}` | `u`/`v` flag only — otherwise tagged `otherEscape` |
403
+ | `characterClassEscape` | `\d`, `\D`, `\w`, `\W`, `\s`, `\S` | |
404
+ | `controlEscape` | `\f`, `\n`, `\r`, `\t`, `\v` | |
405
+ | `controlLetterEscape` | `\cX` | |
406
+ | `hexEscapeSequence` | `\xXX` | |
407
+ | `unicodeEscapeSequence` | `\uXXXX`, `\u{...}` | |
408
+ | `otherEscape` | Any other `\X`, e.g. `\.` | `\0` alone is tagged `otherEscape` only under `u`/`v`; otherwise `backreference`, [like any other digit escape](docs/modules/backreferences.md) |
411
409
 
412
410
  Three things worth knowing about how these tags line up with the grammar:
413
411
 
@@ -415,6 +413,8 @@ Three things worth knowing about how these tags line up with the grammar:
415
413
  - **A named capturing group always carries both `namedGroup` and `capturingGroup`.** The grammar treats a capturing group with a name and one without as the same production (`( GroupSpecifier? Disjunction )`), not two, so both tags are added together.
416
414
  - **`lookaroundCapture` records nesting, not a construct.** Every other tag names something the source contains; this one names where something sits — a capturing group appearing lexically inside `(?=...)`, `(?!...)`, `(?<=...)` or `(?<!...)`, at any depth of nested groups. It is the one relationship between constructs the flattened set cannot otherwise express: `/(\w+)(?= END)/` and `/a(?=(?:b(?:x|(c))d|b))/` both report `lookahead` and `capturingGroup`, but only the second has a capture whose value the assertion decides.
417
415
 
416
+ [^3]: The [carets module](./docs/modules/carets.md) guesses from a cheap look at the source whether a pattern needs its rules, and when it guesses wrong the pattern is walked a second time with them.
417
+
418
418
  ## 📜 License
419
419
 
420
420
  ISC License - see [LICENSE](./LICENSE) file for details.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "regex-partial-match",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "A regular expression transform for partial matching",
5
5
  "keywords": [
6
6
  "regexp",