regex-partial-match 1.2.0 → 2.0.0

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 (122) hide show
  1. package/README.md +169 -178
  2. package/lib/core/corePartialMatchRegExp.d.ts +24 -0
  3. package/lib/core/corePartialMatchRegExp.js +22 -0
  4. package/lib/core/index.d.ts +4 -0
  5. package/lib/core/index.js +2 -0
  6. package/lib/{extend.d.ts → extend/index.d.ts} +6 -7
  7. package/lib/{extend.js → extend/index.js} +1 -1
  8. package/lib/features/index.d.ts +20 -0
  9. package/lib/features/index.js +23 -0
  10. package/lib/hitEnd/index.d.ts +45 -0
  11. package/lib/hitEnd/index.js +57 -0
  12. package/lib/hitEnd/matchHitEnd.d.ts +4 -0
  13. package/lib/hitEnd/matchHitEnd.js +51 -0
  14. package/lib/hitEnd/partRole.d.ts +3 -0
  15. package/lib/hitEnd/partRole.js +17 -0
  16. package/lib/hitEnd/truncationProbe.d.ts +8 -0
  17. package/lib/hitEnd/truncationProbe.js +187 -0
  18. package/lib/hitEnd/truncationProbeCache.d.ts +14 -0
  19. package/lib/hitEnd/truncationProbeCache.js +1 -0
  20. package/lib/index.d.ts +4 -0
  21. package/lib/index.js +4 -0
  22. package/lib/modules/backreferences/asNativeAtom.d.ts +2 -0
  23. package/lib/modules/backreferences/asNativeAtom.js +10 -0
  24. package/lib/modules/backreferences/asPreScanPart.d.ts +2 -0
  25. package/lib/modules/backreferences/asPreScanPart.js +8 -0
  26. package/lib/modules/backreferences/backreferenceRecorder.d.ts +2 -0
  27. package/lib/modules/backreferences/backreferenceRecorder.js +38 -0
  28. package/lib/modules/backreferences/caseFoldFlags.d.ts +1 -0
  29. package/lib/modules/backreferences/caseFoldFlags.js +3 -0
  30. package/lib/modules/backreferences/compileDynamic.d.ts +4 -0
  31. package/lib/modules/backreferences/compileDynamic.js +53 -0
  32. package/lib/modules/backreferences/compiledBackreferences.d.ts +9 -0
  33. package/lib/modules/backreferences/compiledBackreferences.js +9 -0
  34. package/lib/modules/backreferences/constants.d.ts +2 -0
  35. package/lib/modules/backreferences/constants.js +2 -0
  36. package/lib/modules/backreferences/execDynamic.d.ts +2 -0
  37. package/lib/modules/backreferences/execDynamic.js +43 -0
  38. package/lib/modules/backreferences/flagsAtBackreference.d.ts +2 -0
  39. package/lib/modules/backreferences/flagsAtBackreference.js +4 -0
  40. package/lib/modules/backreferences/groupName.d.ts +2 -0
  41. package/lib/modules/backreferences/groupName.js +8 -0
  42. package/lib/modules/backreferences/index.d.ts +10 -0
  43. package/lib/modules/backreferences/index.js +20 -0
  44. package/lib/modules/backreferences/longestBakedPrefixEndingInput.d.ts +1 -0
  45. package/lib/modules/backreferences/longestBakedPrefixEndingInput.js +46 -0
  46. package/lib/modules/backreferences/renderParts.d.ts +2 -0
  47. package/lib/modules/backreferences/renderParts.js +9 -0
  48. package/lib/modules/backreferences/resolvedFromScan.d.ts +2 -0
  49. package/lib/modules/backreferences/resolvedFromScan.js +10 -0
  50. package/lib/modules/backreferences/startsWithUnderFlags.d.ts +1 -0
  51. package/lib/modules/backreferences/startsWithUnderFlags.js +7 -0
  52. package/lib/modules/carets/appendMultilineCaret.d.ts +7 -0
  53. package/lib/modules/carets/appendMultilineCaret.js +181 -0
  54. package/lib/modules/carets/caretRecorder.d.ts +2 -0
  55. package/lib/modules/carets/caretRecorder.js +164 -0
  56. package/lib/modules/carets/caretSyntax.d.ts +3 -0
  57. package/lib/modules/carets/caretSyntax.js +11 -0
  58. package/lib/modules/carets/index.d.ts +9 -0
  59. package/lib/modules/carets/index.js +18 -0
  60. package/lib/modules/carets/lineTerminator.d.ts +1 -0
  61. package/lib/modules/carets/lineTerminator.js +31 -0
  62. package/lib/modules/carets/needsCaretRules.d.ts +1 -0
  63. package/lib/modules/carets/needsCaretRules.js +15 -0
  64. package/lib/modules/carets/quantifierBounds.d.ts +3 -0
  65. package/lib/modules/carets/quantifierBounds.js +18 -0
  66. package/lib/modules/index.d.ts +3 -0
  67. package/lib/modules/index.js +2 -0
  68. package/lib/partialMatchRegExp/atomSyntax.d.ts +17 -0
  69. package/lib/partialMatchRegExp/atomSyntax.js +27 -0
  70. package/lib/partialMatchRegExp/backreferenceExpansion.d.ts +5 -0
  71. package/lib/partialMatchRegExp/backreferenceExpansion.js +1 -0
  72. package/lib/partialMatchRegExp/backreferenceHook.d.ts +11 -0
  73. package/lib/partialMatchRegExp/backreferenceHook.js +1 -0
  74. package/lib/partialMatchRegExp/caretHook.d.ts +17 -0
  75. package/lib/partialMatchRegExp/caretHook.js +5 -0
  76. package/lib/partialMatchRegExp/compilePartial/compileWith.d.ts +3 -0
  77. package/lib/partialMatchRegExp/compilePartial/compileWith.js +36 -0
  78. package/lib/partialMatchRegExp/compilePartial/compiled.d.ts +29 -0
  79. package/lib/partialMatchRegExp/compilePartial/compiled.js +14 -0
  80. package/lib/partialMatchRegExp/compilePartial/constants.d.ts +4 -0
  81. package/lib/partialMatchRegExp/compilePartial/constants.js +4 -0
  82. package/lib/partialMatchRegExp/compilePartial/groupShape.d.ts +4 -0
  83. package/lib/partialMatchRegExp/compilePartial/groupShape.js +9 -0
  84. package/lib/partialMatchRegExp/compilePartial/toStatic.d.ts +3 -0
  85. package/lib/partialMatchRegExp/compilePartial/toStatic.js +4 -0
  86. package/lib/partialMatchRegExp/constants.d.ts +5 -0
  87. package/lib/partialMatchRegExp/constants.js +5 -0
  88. package/lib/partialMatchRegExp/createPartialMatchRegExp.d.ts +3 -0
  89. package/lib/partialMatchRegExp/createPartialMatchRegExp.js +21 -0
  90. package/lib/partialMatchRegExp/execFrom.d.ts +1 -0
  91. package/lib/partialMatchRegExp/execFrom.js +4 -0
  92. package/lib/partialMatchRegExp/index.d.ts +49 -0
  93. package/lib/partialMatchRegExp/index.js +31 -0
  94. package/lib/partialMatchRegExp/legacyEscape.d.ts +2 -0
  95. package/lib/partialMatchRegExp/legacyEscape.js +18 -0
  96. package/lib/partialMatchRegExp/moduleHooks.d.ts +14 -0
  97. package/lib/partialMatchRegExp/moduleHooks.js +15 -0
  98. package/lib/partialMatchRegExp/opaqueModule.d.ts +9 -0
  99. package/lib/partialMatchRegExp/opaqueModule.js +1 -0
  100. package/lib/partialMatchRegExp/part.d.ts +15 -0
  101. package/lib/partialMatchRegExp/part.js +2 -0
  102. package/lib/partialMatchRegExp/partialMatchInternals.d.ts +3 -0
  103. package/lib/partialMatchRegExp/partialMatchInternals.js +4 -0
  104. package/lib/partialMatchRegExp/quantifier.d.ts +6 -0
  105. package/lib/partialMatchRegExp/quantifier.js +9 -0
  106. package/lib/partialMatchRegExp/rawLookaroundInfo.d.ts +11 -0
  107. package/lib/partialMatchRegExp/rawLookaroundInfo.js +1 -0
  108. package/lib/partialMatchRegExp/regexFeatures.d.ts +2 -0
  109. package/lib/partialMatchRegExp/regexFeatures.js +41 -0
  110. package/lib/partialMatchRegExp/scope.d.ts +12 -0
  111. package/lib/partialMatchRegExp/scope.js +45 -0
  112. package/lib/partialMatchRegExp/walk.d.ts +16 -0
  113. package/lib/partialMatchRegExp/walk.js +412 -0
  114. package/lib/partialMatchRegExp/withModules.d.ts +13 -0
  115. package/lib/partialMatchRegExp/withModules.js +27 -0
  116. package/package.json +46 -53
  117. package/lib/compilePartial.d.ts +0 -26
  118. package/lib/compilePartial.js +0 -397
  119. package/lib/partialMatchRegExp.d.ts +0 -52
  120. package/lib/partialMatchRegExp.js +0 -126
  121. /package/lib/{escapeAtom.d.ts → modules/backreferences/escapeAtom.d.ts} +0 -0
  122. /package/lib/{escapeAtom.js → modules/backreferences/escapeAtom.js} +0 -0
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, each `PartialMatchRegExp` also exposes a [`features`](#partialmatchregexpprototypefeatures-readonlysetregexfeature) set naming the syntactic constructs its 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
 
@@ -46,35 +46,102 @@ const partial = /^hello world/.toPartialMatchRegex();
46
46
  partial.test("hel"); // true
47
47
  ```
48
48
 
49
- ## ⚙️ How It Works
49
+ ### Telling a settled match from one still reading
50
50
 
51
- The library transforms a regular expression by wrapping each [atomic element](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions#atoms) in a [non-capturing group](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Non-capturing_group) with a [disjunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Disjunction) to a true-end-of-input sentinel (`$(?![\s\S])`[^1]):
51
+ `test()` and `exec()` answer "could this match?", which is `true` for a prefix and for a complete match alike. [`hitEnd()`](#hitend) reports whether the engine reached the end of the input while producing the match, so more input could still change it [^1]. Testing the original pattern instead doesn't answer that [^2]. Together they give progressive validation its three states:
52
52
 
53
53
  ```javascript
54
- /abc/ → /(?:a|$(?![\s\S]))(?:b|$(?![\s\S]))(?:c|$(?![\s\S]))/
54
+ import PartialMatchRegExp, { hitEnd } from "regex-partial-match";
55
+
56
+ const partial = new PartialMatchRegExp(/^\d{4}-\d{2}-\d{2}/);
57
+
58
+ function state(input) {
59
+ const match = partial.exec(input);
60
+
61
+ if (match === null) return "invalid";
62
+ return hitEnd(partial, match) ? "incomplete" : "complete";
63
+ }
64
+
65
+ state("20xx"); // 'invalid' - reject
66
+ state("2024"); // 'incomplete' - no error, keep typing
67
+ state("2024-06"); // 'incomplete' - no error, keep typing
68
+ state("2024-06-15"); // 'complete' - accept, enable submit
55
69
  ```
56
70
 
57
- This allows the pattern to match prefixes of the original pattern, enabling validation of incomplete input.
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.
73
+
74
+ Upgrading from 1.x, where this was `isComplete()`? See the [migration guide](./codemods/transforms/v1-v2/README.md).
58
75
 
59
- Since the library accepts only valid regular expressions [^2], this enables the algorithm to make lots of unguarded assumptions about the source of the expression.
76
+ [^1]:
77
+ Mimicking JDK's [`Matcher.hitEnd()`](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#hitEnd--)
60
78
 
61
- The library has been stress-tested with various regular expression features in isolation, and some in likely combination, but obviously it's an unbounded test space, and syntactically valid regular expressions nevertheless support contradictory patterns e.g.
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.
62
81
 
63
- - `/\b\B/` - impossible to match both a word boundary and a non-word boundary
64
- - `/$^/` - end cannot come before start
65
- - `x{2}?` - lazy quantifiers are mutually exclusive to fixed-length assertions
82
+ ### A note on Tree-Shaking
66
83
 
67
- Such combinations have not been tested.
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:
68
85
 
69
- > [!NOTE]
70
- > See [Partial Match Parity](/docs/partial-match-parity.md) for full details on how the library compares to reference implementations
86
+ ```javascript
87
+ import PartialMatchRegExp from "regex-partial-match/partialMatchRegExp";
88
+ import hitEnd from "regex-partial-match/hitEnd";
89
+ import features from "regex-partial-match/features";
90
+ ```
91
+
92
+ [`hitEnd`](#hitend) and [`features`](#features) accept an instance of any `PartialMatchRegExp` class, including those from [`regex-partial-match/core`](#the-lean-entry-regex-partial-matchcore).
93
+
94
+ ### The lean entry: `regex-partial-match/core`
95
+
96
+ `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`:
71
97
 
72
- ### Patterns with backreferences
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 |
102
+
103
+ Both are also named exports of `regex-partial-match/modules`.
104
+
105
+ ```javascript
106
+ import PartialMatchRegExp, { withModules } from "regex-partial-match/core";
107
+ import carets from "regex-partial-match/modules/carets";
108
+ import backreferences from "regex-partial-match/modules/backreferences";
109
+
110
+ new PartialMatchRegExp(/^\d{4}-\d{2}/).test("20"); // true
111
+
112
+ const WithBoth = withModules(carets, backreferences);
113
+ new WithBoth(/(a|b)\1/).test("a"); // true
114
+ ```
115
+
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.
117
+
118
+ 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
+
120
+ #### Choosing an entry point
121
+
122
+ 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
+
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 |
129
+
130
+ The error names every module the pattern needs, and none that is already bound.
131
+
132
+ **Without `carets`**, a `^` is accepted outside every group and positive lookahead and, under the `m` flag, only at the start of the pattern or of a top-level alternative, with nothing before it but `^`, `$`, `\b`, `\B`, a negative lookahead or a lookbehind: `/^a|^b/m` and `/\b^a/m` compile. A `^` inside a lookbehind or negative lookahead is always accepted, since those are kept as written: `/(?<=^)a/` compiles. Any other `^` throws `TypeError: Needs the carets module` when constructed, as `/x^a/m` and `/(?=a)^a/m` do. So does a `^` inside a group or positive lookahead, even without `m`, as in `/(^a|^b)/` and `/(?m:^a)/`, since the rule that moves such a caret in front of its group is in the carets module. The rule stays there to keep `core` small. An unquantified group whose every alternative starts with `^` matches the same with the caret written once in front of it, and `core` accepts `/^(a|b)/` where it refuses `/(^a|^b)/`. Every pattern `core` accepts is transformed exactly as by the default class.
133
+
134
+ **Without `backreferences`**, a pattern whose backreference must be matched at run time throws `TypeError: Needs the backreferences module` when constructed, as `/(a)\1/` does. An escape that resolves without it still compiles: in `/x\8y/` there is no group 8, so `\8` is the literal `8`.
135
+
136
+ ## ⚙️ How It Works
73
137
 
74
- Backreferences cannot be handled by the `|$(?![\s\S])` transform alone because they are atomic — `\1` must match the entire captured string or fail, and its length is only known at runtime. `PartialMatchRegExp` first tries a full match natively, but that native result only wins outright if nothing earlier in the input could still be a viable partial — a cheap bound check settles that without needing to resolve the backreference's actual value, so the common case (no earlier partial exists) stays fast. Otherwise it runs a "capture scan": a variant of the pattern with each backreference swapped for a lazy `(?:[\s\S]*?)` wildcard, so the group it depends on can still capture against a partial input — matching anything, or nothing at all, without needing to already know the backreference's value.
138
+ The library wraps each [atomic element](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions#atoms) in a non-capturing group with a disjunction to the true end of the input, so the pattern matches any prefix of what the original would match:
75
139
 
76
- Whatever that scan captures (or leaves `undefined`, if the group hasn't been reached yet) is then used to build a fresh partial-matching regex for this specific input, expanding the backreference character-by-character from the captured value with the same per-atom transform as the rest of the pattern. See [docs/backreferences.md](./docs/backreferences.md) for the full algorithm, including the prefer-longer post-processing that preserves correct captures for groups inside quantifiers.
140
+ ```javascript
141
+ /abc/ → /(?:a|$(?![\s\S]))(?:b|$(?![\s\S]))(?:c|$(?![\s\S]))/
142
+ ```
77
143
 
144
+ See [How It Works](./docs/how-it-works.md) for the full transform, where it over-accepts, how `^` is handled in groups and under the `m` flag, and how backreferences are matched.
78
145
 
79
146
  ## ✅ Supported Features
80
147
 
@@ -87,19 +154,19 @@ Whatever that scan captures (or leaves `undefined`, if the group hasn't been rea
87
154
  - 🔢 [Quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Quantifier) (`*`, `+`, `?`, `{n}`, `{n,}`, `{n,m}`)
88
155
  - 🔀 [Disjunction](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Disjunction) (`a|b`)
89
156
  - 👥 [Groups](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Capturing_group) (capturing and non-capturing) (`(?:abc)`, `(abc)`, `(?<named>abc)`)
90
- - 🔙 [Backreferences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Backreference) (`\1`, `\k<name>`) (See [caveats](#backreferences) for known limitations)
157
+ - 🔙 [Backreferences](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Backreference) (`\1`, `\k<name>`) (See [caveats](./docs/caveats.md#backreferences) for known limitations)
91
158
  - 👉 [Lookahead assertions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookahead_assertion) (`(?=...)`, `(?!...)`)
92
159
  - 👈 [Lookbehind assertions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Lookbehind_assertion) (`(?<=...)`, `(?<!...)`)
93
160
  - ⚓ [Input Boundaries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Input_boundary_assertion) (`^`, `$`)
94
161
  - 🆒 [Word Boundaries](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Word_boundary_assertion) (`\b`, `\B`)
95
- - 🏴 [Flags](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags): `g`, `i`, `m`, `s`, `u`, `d`, `y` (See [caveats](#sticky-flag-y) for `y`)
162
+ - 🏴 [Flags](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags): `g`, `i`, `m`, `s`, `u`, `d`, `y` (See [caveats](./docs/caveats.md#sticky-flag-y) for `y`)
96
163
  - 🎚️ [Modifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Modifier) (`(?ims:...)`, `(?-ims:...)`, `(?im-s:...)`)
97
164
 
98
165
  ## 🚫 Unsupported Features
99
166
 
100
167
  The following regex features are **not currently supported**:
101
168
 
102
- - ⚠️ [Character class substrings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Character_class#matching_strings) (`\q{abc}`) - When used independently, rather than to modify, can be included, but can't partially match. See [caveats](#caveats).
169
+ - ⚠️ [Character class substrings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Character_class#matching_strings) (`\q{abc}`) - When used independently, rather than to modify, can be included, but can't partially match. See [caveats](./docs/caveats.md#string-properties).
103
170
 
104
171
  ## 🌐 Browser Compatibility
105
172
 
@@ -113,154 +180,20 @@ The library is compiled to **ES2015** (ECMAScript 6). Certain regular expression
113
180
  - [**`v` (unicodeSets) flag**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicodeSets) - ES2024+
114
181
  - [**Modifiers**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Modifier) (`(?ims:...)`, `(?-ims:...)`, `(?i-ms:...)`) - ES2025+
115
182
 
116
- ## ⚠️ Caveats
117
-
118
- ### [`.test()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) behaviour and non-matching results from [`.exec()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec) and [`.match()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match)
119
-
120
- For unanchored patterns (no `^` and not using the `y` flag), the library produces an expression that always matches an empty string at the true end of the input — see [How It Works](#how-it-works). Feasibly, this is the start of a new partial match.
121
-
122
- Hence:
123
-
124
- ```js
125
- /x/.test("a") === false; /* untransformed regex */
126
- /(?:x|$(?![\s\S]))/.test("a") === true; /* new PartialMatchRegExp(/x/), internally */
127
- ```
128
-
129
- To mitigate, a start anchor (`^`) can prevent the engine from scanning forward to match the empty-string fallback at the end of the input:
130
-
131
- ```js
132
- /* new PartialMatchRegExp(/^x/) matches as if it were /^(?:x|$(?![\s\S]))/ */
133
- /^(?:x|$(?![\s\S]))/.test("") === true;
134
- /^(?:x|$(?![\s\S]))/.test("x") === true;
135
- /^(?:x|$(?![\s\S]))/.test("a") === false;
136
- ```
137
-
138
- > [!CAUTION]
139
- > In [multiline mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline), `^` still matches at the start of the string and immediately after each `\n`, so the transformed regex can attempt the empty-string fallback at the start of any line — but, since the fallback requires strict end-of-input, it only succeeds if that line start is *also* genuinely where the input ends:
140
- >
141
- > ```js
142
- > /^(?:x|$(?![\s\S]))/m.test("x") === true;
143
- > /^(?:x|$(?![\s\S]))/m.test("a\n") === true; /* '^' matches after '\n', and input truly ends there */
144
- > /^(?:x|$(?![\s\S]))/m.test("a\nb") === false; /* '^' matches after '\n', but "b" remains — not genuine end-of-input */
145
- > ```
146
-
147
- The [`y` flag](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky) prevents matching ahead from the [`lastIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex) (defaulting to `0` for a new `RegExp`):
148
-
149
- ```js
150
- /(?:x|$(?![\s\S]))/y.test("x") === true;
151
- /(?:x|$(?![\s\S]))/y.test("a") === false;
152
- ```
153
-
154
- > [!CAUTION]
155
- > See [caveats](#sticky-flag-y) re: resetting `lastIndex` when incrementally matching
156
-
157
- On this basis, `.test()` should be used with caution, and a match of an empty string at the true end of the input should instead be considered "no match", if validating that which came before.
158
-
159
- e.g.
160
-
161
- ```js
162
- /(?:x|$(?![\s\S]))/.exec("a"); // ['', index: 1, input: "a", groups: undefined];
163
- "a".match(/(?:x|$(?![\s\S]))/); // ['', index: 1, input: "a", groups: undefined];
164
- ```
165
-
166
- > [!NOTE]
167
- > A more ergonomic `test()` / `exec()` output [was explored](https://github.com/TomStrepsil/regex-partial-match/pull/51), but proved a complex problem space.
168
-
169
- ### Backreferences
170
-
171
- `PartialMatchRegExp` supports partial matching of backreferences (`\1`, `\k<name>`) — see [Patterns with backreferences](#patterns-with-backreferences) above and [docs/backreferences.md](./docs/backreferences.md) for the algorithm. A backreference is inherently atomic — `\1` must match the complete captured text or fail — but the library resolves what each group captured from a partial input and expands the backreference into per-character partial form so matching can still proceed character-by-character in the common case.
172
-
173
- The following cases remain atomic (full native value or exactly at true end of input, no mid-value partial matching):
174
-
175
- - **Backreferences inside lookbehinds and negative lookarounds.** These are verbatim contexts — the value a lookbehind or negative lookahead requires must be fully present or fully absent, so there's no partial-prefix position to expand into.
176
- - **`\k<name>` with no named capturing groups in the pattern.** [Annex B](https://tc39.es/ecma262/#sec-regular-expressions-patterns) tolerates this as the literal characters `k<a>`, but it's still treated as if it were a named backreference and matched atomically — `"k"` and `"k<"` will not partially match. A `\k` not immediately followed by a well-formed `<name>` reference is treated as the literal `k` and partially matches as usual.
177
- - **A backreference whose captured value can't be determined from a partial input.** This only affects the backreference site itself; it's strictly better than rejecting the input outright, and never accepts anything unsound.
178
-
179
- #### Prefix-ambiguous top-level alternation
180
-
181
- When a pattern uses top-level alternation where one branch is a strict prefix of another (e.g. `^(ab)\1|^(abc)\2`), the internal capture scan may select the shorter branch — because it uses `(?:[\s\S]*?)` which accepts zero characters — causing the final partial regex to fail for inputs that are valid prefixes of the longer branch. In such cases `exec` returns `null` even though the input is a valid partial match:
182
-
183
- ```javascript
184
- const partial = new PartialMatchRegExp(/^(ab)\1|^(abc)\2/);
185
-
186
- partial.test("abca"); // false — but "abca" is a valid prefix of "abcabc" via the second branch
187
- ```
188
-
189
- > [!TIP]
190
- > If alternate branches share a prefix, list the longer one first. The capture scan tries branches in order and stops at the first that accepts the partial input, so putting the longer branch first ensures it's the one selected:
191
- >
192
- > ```javascript
193
- > const partial = new PartialMatchRegExp(/^(abc)\2|^(ab)\1/);
194
- >
195
- > partial.test("abca"); // true
196
- > ```
197
-
198
- See [docs/backreferences.md](./docs/backreferences.md) for why this happens (the internal capture scan resolving the wrong alternative first).
199
-
200
- [^1]:
201
- A bare `$` alone isn't sufficient here: under the `m` (multiline) flag — including one turned on locally via a `(?m:...)` [modifier](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Modifier) — `$` also matches immediately before *any* line terminator, not just the true end of input. That would let a `"\n"` the source pattern never allowed for be silently accepted as if the input had simply run out, e.g. `new PartialMatchRegExp(/^foobar/m)` would wrongly accept `"foo\nbaz"`. Appending `(?![\s\S])` narrows the disjunction down to strict end-of-input, regardless of multiline state.
202
-
203
- See [chromium issue 536420076](https://issues.chromium.org/u/2/issues/536420076) for the underlying V8 bug that requires `$` to precede `(?![\s\S])` rather than using the lookahead alone.
204
-
205
- A shorter option, `(?-m:$)` — disabling multiline locally so `$` means strict end-of-input on its own — also sidesteps the bug and saves a few bytes per atom. However, modifier groups are new enough that support isn't universal, and feature-detecting them would add a fallback branch the test suite can't exercise honestly, since every engine that can realistically be tested against already supports them.
206
-
207
- [^2]:
208
- To remain lightweight, no runtime type validation is applied, so non-TypeScript consumers will be reliant on underlying errors thrown if used incorrectly.
209
-
210
- ### Positive Lookbehinds
211
-
212
- Whilst forming a match, a positive lookbehind must match in entirety, for the pattern to match. This is inherent in the concept of non-matching groups, since they are not match-worthy themselves, but just qualify matching atoms.
213
-
214
- e.g.
215
-
216
- ```js
217
- /(?<=foo)bar/;
218
- ```
219
-
220
- "f" through "foo" is not a match, but "foob" is.
221
-
222
- ### Surrogate Pair Matching
223
-
224
- In [unicode-aware mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode) (`u` flag), **only whole astral characters are supported**. Partial matching of individual surrogate pairs is not supported. For example, `/😀/u` will match the complete emoji character, but not the first surrogate pair in isolation. Hence, if partially matching a byte stream, be sure to pipe via a [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) first.
225
-
226
- ### [Sticky](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky) Flag (`y`)
227
-
228
- The sticky flag is fully supported for its intended use case: **scanning within a single fixed string**. Partial matches are found only at `lastIndex`; the engine does not scan forward, and `lastIndex` advances on success or resets to `0` on failure — exactly as native sticky regexes behave.
229
-
230
- ```javascript
231
- import PartialMatchRegExp from "regex-partial-match";
232
-
233
- const partial = new PartialMatchRegExp(/hello/y);
234
-
235
- partial.lastIndex = 2;
236
- partial.test("xyhello"); // true — partial match at position 2
237
- partial.test("xyworld"); // false — no match at position 2, no forward scan
238
- partial.lastIndex = 2;
239
- partial.test("xyhel"); // true — partial prefix "hel" at position 2
240
- ```
241
-
242
- **Limitation — progressive input validation:** Because a successful match advances `lastIndex`, testing a sequence of growing strings against the same instance does not work as expected:
243
-
244
- ```javascript
245
- const partial = new PartialMatchRegExp(/hello/y);
246
-
247
- partial.test("h"); // true, lastIndex → 1
248
- partial.test("he"); // false — sticky requires a match at position 1 of "he",
249
- // but "e" is not a valid start of the pattern
250
- partial.test("hel"); // true (lastIndex was reset to 0 by the previous failure)
251
- ```
252
-
253
- There is no way to distinguish "scanning forward in the same string" from "testing a new, longer string", so this cannot be fixed in code. For progressive input validation, use a regex **without** the `y` flag and always test against the full input so far.
254
-
255
- The `gy` flag combination is also fully supported: `exec()`/`test()` behave as sticky, while `match()`, `matchAll()`, `replace()`, and `replaceAll()` iterate via `exec()` as global — matching [the language specification](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky).
256
-
257
- ### "String properties"
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.
258
184
 
259
- As with surrogate pair matching, grapheme clusters / string properties can only match atomically.
185
+ ## ⚠️ Caveats
260
186
 
261
- Hence, `[\p{RGI_Emoji_Flag_Sequence}]` will match `🇺🇳` as a whole, but not as the individual code points of which it's comprised.
187
+ See [Caveats](./docs/caveats.md) for details, examples and workarounds.
262
188
 
263
- In [`v` mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicodeSets) expressions, where `[\q{abc}]` syntax is used in isolation (rather than its canonical use-case as a subtraction/intersection of another character class), this will also only match entirely or not at all. i.e. `abc` can match, but not partially.
189
+ - [**`.test()` and empty matches at the end of the input**](./docs/caveats.md#test-behaviour-and-non-matching-results-from-exec-and-match): an unanchored pattern always matches `""` at the end of the input; anchor with `^`, or ask [`hitEnd()`](#hitend)
190
+ - [**Backreferences**](./docs/caveats.md#backreferences): some backreferences stay atomic
191
+ - [Captures on a partial match](./docs/caveats.md#captures-on-a-partial-match)
192
+ - [Prefix-ambiguous top-level alternation](./docs/caveats.md#prefix-ambiguous-top-level-alternation)
193
+ - [**Positive lookbehinds**](./docs/caveats.md#positive-lookbehinds): must match in their entirety
194
+ - [**Surrogate pair matching**](./docs/caveats.md#surrogate-pair-matching): only whole astral characters match
195
+ - [**Sticky flag (`y`)**](./docs/caveats.md#sticky-flag-y): not suited to progressive input validation
196
+ - [**"String properties"**](./docs/caveats.md#string-properties): match atomically
264
197
 
265
198
  ## 💡 Examples
266
199
 
@@ -341,11 +274,15 @@ Useful for parsing log files, network streams, or any chunked data where records
341
274
 
342
275
  ## 🔌 API
343
276
 
344
- ### `new PartialMatchRegExp(pattern: RegExp | string, flags?: string)`
277
+ ### `new PartialMatchRegExp()`
278
+
279
+ ```typescript
280
+ new PartialMatchRegExp(pattern: RegExp | string, flags?: string)
281
+ ```
345
282
 
346
- Extends `RegExp`. An instance behaves like a normal `RegExp` — `instanceof RegExp` is `true`, and `.test()`, `.exec()`, `.match()`, `.matchAll()`, `.replace()`, etc. all work as expected — but also matches any input string that is a valid prefix of the original pattern, in addition to full matches.
283
+ Extends `RegExp`. An instance behaves like a normal `RegExp`; `instanceof RegExp` is `true`, and `.test()`, `.exec()`, `.match()`, `.matchAll()`, `.replace()` etc. all work as expected, but also matches any input string that is a valid prefix of the original pattern, in addition to full matches.
347
284
 
348
- Available via the default entry point of the package.
285
+ Available via the default entry point of the package, or via `regex-partial-match/partialMatchRegExp`; see [A note on Tree-Shaking](#a-note-on-tree-shaking).
349
286
 
350
287
  **Parameters:**
351
288
 
@@ -356,7 +293,11 @@ Available via the default entry point of the package.
356
293
 
357
294
  - A `PartialMatchRegExp` instance that matches partial strings of the original pattern
358
295
 
359
- ### `RegExp.prototype.toPartialMatchRegex(): PartialMatchRegExp`
296
+ ### `toPartialMatchRegex()`
297
+
298
+ ```typescript
299
+ RegExp.prototype.toPartialMatchRegex(): PartialMatchRegExp
300
+ ```
360
301
 
361
302
  When using `import 'regex-partial-match/extend'`, this method is added to `RegExp.prototype`.
362
303
 
@@ -364,26 +305,76 @@ When using `import 'regex-partial-match/extend'`, this method is added to `RegEx
364
305
 
365
306
  - A new `PartialMatchRegExp` that matches partial strings, created from the `RegExp` instance the method was called on.
366
307
 
367
- ### `PartialMatchRegExp.prototype.features: ReadonlySet<RegexFeature>`
308
+ ### `hitEnd()`
309
+
310
+ ```typescript
311
+ hitEnd(partial: PartialMatchRegExp, match: RegExpExecArray): boolean
312
+ ```
313
+
314
+ Reports whether the engine **reached the end of the input** while producing `match`, so that more input could change it — the contract of the JDK's [`Matcher.hitEnd()`](https://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html#hitEnd--): when it returns `false`, no continuation of the input changes the match's index or text. `exec()` alone cannot say: it returns the same shape of array for `"h"`, `"hello"` and `"hello world"` against `/hello world/`.
315
+
316
+ Available as a named export of the default entry point, `import { hitEnd } from 'regex-partial-match'`, or as the default export of `regex-partial-match/hitEnd`.
317
+
318
+ **Parameters:**
319
+
320
+ - `partial` - The `PartialMatchRegExp` instance that produced `match`
321
+ - `match` - A match returned by `partial.exec()`
322
+
323
+ **Returns:**
324
+
325
+ - `true` when the match read the end of the input: an atom ran out of input and took one of the `|$(?![\s\S])` branches described in [How It Works](#how-it-works); a greedy quantifier stopped at the end because there was nothing left to read; or `$`, `\b` or `\B` held there. More input could extend the match, change which alternative wins, or invalidate it, and its captures are the closest available rather than final — see [Captures on a partial match](./docs/caveats.md#captures-on-a-partial-match).
326
+ - `false` when every atom matched literally and nothing read past the last character consumed. No continuation of the input changes the match's index or text, and the captures are the ones the original pattern produces — except the two cases in [What it cannot see](#what-it-cannot-see) below, where a read of the end leaves no marker and `false` is reported despite it.
327
+
328
+ ```javascript
329
+ import PartialMatchRegExp, { hitEnd } from "regex-partial-match";
330
+
331
+ const partial = new PartialMatchRegExp(/hello world/);
332
+
333
+ hitEnd(partial, partial.exec("hello")); // true — ran out of input inside "world"
334
+ hitEnd(partial, partial.exec("hello world!")); // false — settled short of the end
335
+ hitEnd(partial, partial.exec("hello world")); // false — consumed the last character without reading past it
336
+
337
+ const greedy = new PartialMatchRegExp(/hello \w+/);
338
+
339
+ hitEnd(greedy, greedy.exec("hello world")); // true — \w+ read the end looking for more
340
+ ```
341
+
342
+ > [!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.
368
344
 
369
- Building the partial-match regex requires walking the entire source pattern once. As a side effect of that same walk, each instance records which syntactic constructs its pattern actually uses, exposed as a `features` set — no separate scan of the source is performed to produce it.
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.
346
+
347
+ #### What it cannot see
348
+
349
+ - **A read of the end inside a lookahead in an earlier iteration of a quantified group.** The probe's [markers](./docs/how-it-works.md#recording-a-read-of-the-end) are capturing groups, and [`RepeatMatcher`](https://tc39.es/ecma262/#sec-runtime-semantics-repeatmatcher-abstract-operation) resets a quantified group's captures at the start of every iteration. `/(?:a(?=bcd)|b)+/` on `"abc"` reads the end inside `(?=bcd)` in its first iteration, matches `b` in its second, and reports `false` — although `"abcx"` changes the match to `"b"` at index 1. Nothing placed inside the repeated atom survives the reset, so this is a limit of the marker approach rather than an oversight, and it is pinned by a test.
350
+ - **A read of the end inside a raw lookaround.** Negative lookaheads and both lookbehinds are kept verbatim (see [Caveats](./docs/caveats.md)), so a read of the end inside them leaves no marker: `/^a(?!b)/` on `"a"` reports `false`, although `"ab"` invalidates the match. A scanner whose output must not depend on where its input was chunked should refuse or buffer patterns that use them.
351
+
352
+ ### `features()`
353
+
354
+ ```typescript
355
+ features(partial: PartialMatchRegExp): ReadonlySet<RegexFeature>
356
+ ```
357
+
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.
359
+
360
+ 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).
370
361
 
371
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:
372
363
 
373
- - **Flagging patterns likely to hit one of the [caveats](#caveats) documented above.** For example, a pattern combining `backreference` with `lookbehind`, `negativeLookahead`, or `negativeLookbehind` is a candidate for the [atomic-backreference caveat](#backreferences); one combining `backreference` with `disjunction` is a candidate for the [prefix-ambiguous top-level alternation caveat](#prefix-ambiguous-top-level-alternation). A consumer accepting user-supplied patterns can surface a warning instead of letting the edge case surprise someone later.
374
- - **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.
375
- - **Deciding at construction time whether a pattern needs a careful path.** A capture nested inside a lookaround is decided by the assertion rather than by the consumed text, so its value can vary with how far the input has been seen. `features.has("lookaroundCapture")` isolates exactly those patterns, where `features.has("lookahead") && features.has("capturingGroup")` would also catch the ordinary `/(\w+)(?= END)/`.
364
+ - **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
+ - **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.
366
+ - **Deciding at construction time whether a pattern needs a careful path.** A capture nested inside a lookaround is decided by the assertion rather than by the consumed text, so its value can vary with how far the input has been seen. `features(partial).has("lookaroundCapture")` isolates exactly those patterns, where checking for both `lookahead` and `capturingGroup` would also catch the ordinary `/(\w+)(?= END)/`.
376
367
 
377
368
  ```javascript
378
- import PartialMatchRegExp from "regex-partial-match";
369
+ import PartialMatchRegExp, { features } from "regex-partial-match";
379
370
 
380
371
  const partial = new PartialMatchRegExp(/^[a-z]+(?<domain>\.[a-z]+)\1/);
381
372
 
382
- partial.features; // Set { "startAnchor", "backreference", "namedGroup", "capturingGroup", "characterClass", "quantifier", "otherEscape" }
383
- partial.features.has("backreference"); // true
373
+ features(partial); // Set { "startAnchor", "backreference", "namedGroup", "capturingGroup", "characterClass", "quantifier", "otherEscape" }
374
+ features(partial).has("backreference"); // true
384
375
  ```
385
376
 
386
- `RegexFeature` is a string union, exported alongside `PartialMatchRegExp`:
377
+ `RegexFeature` is a string union, exported from `regex-partial-match` and `regex-partial-match/features`:
387
378
 
388
379
  | Feature | Matches | Notes |
389
380
  | --------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------- |
@@ -416,7 +407,7 @@ partial.features.has("backreference"); // true
416
407
  | `controlLetterEscape` | `\cX` | |
417
408
  | `hexEscapeSequence` | `\xXX` | |
418
409
  | `unicodeEscapeSequence` | `\uXXXX`, `\u{...}` | |
419
- | `otherEscape` | Any other `\X`, e.g. `\.`, `\0` | |
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) |
420
411
 
421
412
  Three things worth knowing about how these tags line up with the grammar:
422
413
 
@@ -0,0 +1,24 @@
1
+ import type { PartialMatchRegExpConstructor } from "../partialMatchRegExp/index.ts";
2
+ /**
3
+ * `PartialMatchRegExp` without the rules that place a `^` in a group or after
4
+ * other parts under the `m` flag, or match a backreference, so that a bundle
5
+ * importing only `regex-partial-match/core` leaves them out.
6
+ *
7
+ * A pattern that needs either throws one `TypeError` naming every module it
8
+ * needs; every other pattern is transformed exactly as the class exported
9
+ * from `regex-partial-match` transforms it.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import PartialMatchRegExp from "regex-partial-match/core";
14
+ *
15
+ * new PartialMatchRegExp(/^hello world/).test("hello"); // true
16
+ * new PartialMatchRegExp(/(a)\1/); // throws TypeError
17
+ * ```
18
+ *
19
+ * @see {@link https://github.com/TomStrepsil/regex-partial-match#readme | Documentation}
20
+ */
21
+ declare const PartialMatchRegExp: PartialMatchRegExpConstructor;
22
+ type PartialMatchRegExp = InstanceType<typeof PartialMatchRegExp>;
23
+ export default PartialMatchRegExp;
24
+ export type { PartialMatchRegExp, PartialMatchRegExpConstructor };
@@ -0,0 +1,22 @@
1
+ import withModules from "../partialMatchRegExp/withModules.js";
2
+ /**
3
+ * `PartialMatchRegExp` without the rules that place a `^` in a group or after
4
+ * other parts under the `m` flag, or match a backreference, so that a bundle
5
+ * importing only `regex-partial-match/core` leaves them out.
6
+ *
7
+ * A pattern that needs either throws one `TypeError` naming every module it
8
+ * needs; every other pattern is transformed exactly as the class exported
9
+ * from `regex-partial-match` transforms it.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import PartialMatchRegExp from "regex-partial-match/core";
14
+ *
15
+ * new PartialMatchRegExp(/^hello world/).test("hello"); // true
16
+ * new PartialMatchRegExp(/(a)\1/); // throws TypeError
17
+ * ```
18
+ *
19
+ * @see {@link https://github.com/TomStrepsil/regex-partial-match#readme | Documentation}
20
+ */
21
+ const PartialMatchRegExp = withModules();
22
+ export default PartialMatchRegExp;
@@ -0,0 +1,4 @@
1
+ export { default } from "./corePartialMatchRegExp.ts";
2
+ export type { PartialMatchRegExp, PartialMatchRegExpConstructor } from "./corePartialMatchRegExp.ts";
3
+ export { default as withModules } from "../partialMatchRegExp/withModules.ts";
4
+ export type { Module } from "../partialMatchRegExp/opaqueModule.ts";
@@ -0,0 +1,2 @@
1
+ export { default } from "./corePartialMatchRegExp.js";
2
+ export { default as withModules } from "../partialMatchRegExp/withModules.js";
@@ -1,12 +1,11 @@
1
- import PartialMatchRegExp from "./partialMatchRegExp.ts";
1
+ import PartialMatchRegExp from "../partialMatchRegExp/index.ts";
2
2
  declare global {
3
3
  interface RegExp {
4
4
  /**
5
5
  * Transforms this regular expression to support partial matching.
6
6
  *
7
- * This method wraps each atomic element of the regex pattern in a non-capturing group
8
- * with an alternation to end-of-input (`$`), allowing the pattern to match prefixes
9
- * of the original pattern. This enables validation of incomplete input strings.
7
+ * The result matches any prefix of the original pattern, which enables
8
+ * validation of incomplete input strings.
10
9
  *
11
10
  * @returns A new PartialMatchRegExp that matches partial strings of the original pattern
12
11
  *
@@ -23,9 +22,9 @@ declare global {
23
22
  * ```
24
23
  *
25
24
  * @remarks
26
- * - The transformed regex will always match an empty string at the end of input
27
- * - Use with a start anchor (`^`) to prevent false positives from empty string matches
28
- * - The `y` (sticky) flag may not behave as expected in partial matching scenarios
25
+ * The transformed pattern always matches an empty string at the end of input;
26
+ * use a start anchor (`^`) to prevent false positives from empty string matches.
27
+ * See the documentation for how the `y` (sticky) flag behaves.
29
28
  *
30
29
  * @see {@link https://github.com/TomStrepsil/regex-partial-match#readme | Documentation}
31
30
  */
@@ -1,4 +1,4 @@
1
- import PartialMatchRegExp from "./partialMatchRegExp.js";
1
+ import PartialMatchRegExp from "../partialMatchRegExp/index.js";
2
2
  RegExp.prototype.toPartialMatchRegex = function () {
3
3
  return new PartialMatchRegExp(this);
4
4
  };
@@ -0,0 +1,20 @@
1
+ import type PartialMatchRegExp from "../partialMatchRegExp/index.ts";
2
+ import { type RegexFeature } from "../partialMatchRegExp/regexFeatures.ts";
3
+ export type { RegexFeature };
4
+ /**
5
+ * The syntactic constructs `partial`'s original pattern uses.
6
+ *
7
+ * The set iterates in `RegexFeature` declaration order, not the order the
8
+ * constructs appear in the pattern.
9
+ *
10
+ * @param partial - A `PartialMatchRegExp` instance, from any entry point
11
+ * @returns The features the original pattern contains
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import PartialMatchRegExp, { features } from "regex-partial-match";
16
+ *
17
+ * features(new PartialMatchRegExp(/^[a-z]+/)).has("startAnchor"); // true
18
+ * ```
19
+ */
20
+ export default function features(partial: PartialMatchRegExp): ReadonlySet<RegexFeature>;
@@ -0,0 +1,23 @@
1
+ import { compiledOf } from "../partialMatchRegExp/partialMatchInternals.js";
2
+ import { featureSet } from "../partialMatchRegExp/regexFeatures.js";
3
+ /**
4
+ * The syntactic constructs `partial`'s original pattern uses.
5
+ *
6
+ * The set iterates in `RegexFeature` declaration order, not the order the
7
+ * constructs appear in the pattern.
8
+ *
9
+ * @param partial - A `PartialMatchRegExp` instance, from any entry point
10
+ * @returns The features the original pattern contains
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import PartialMatchRegExp, { features } from "regex-partial-match";
15
+ *
16
+ * features(new PartialMatchRegExp(/^[a-z]+/)).has("startAnchor"); // true
17
+ * ```
18
+ */
19
+ export default function features(partial) {
20
+ var _a;
21
+ const compiled = compiledOf(partial);
22
+ return ((_a = compiled.features) !== null && _a !== void 0 ? _a : (compiled.features = featureSet(compiled.featureMask)));
23
+ }