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.
- package/README.md +169 -178
- package/lib/core/corePartialMatchRegExp.d.ts +24 -0
- package/lib/core/corePartialMatchRegExp.js +22 -0
- package/lib/core/index.d.ts +4 -0
- package/lib/core/index.js +2 -0
- package/lib/{extend.d.ts → extend/index.d.ts} +6 -7
- package/lib/{extend.js → extend/index.js} +1 -1
- package/lib/features/index.d.ts +20 -0
- package/lib/features/index.js +23 -0
- package/lib/hitEnd/index.d.ts +45 -0
- package/lib/hitEnd/index.js +57 -0
- package/lib/hitEnd/matchHitEnd.d.ts +4 -0
- package/lib/hitEnd/matchHitEnd.js +51 -0
- package/lib/hitEnd/partRole.d.ts +3 -0
- package/lib/hitEnd/partRole.js +17 -0
- package/lib/hitEnd/truncationProbe.d.ts +8 -0
- package/lib/hitEnd/truncationProbe.js +187 -0
- package/lib/hitEnd/truncationProbeCache.d.ts +14 -0
- package/lib/hitEnd/truncationProbeCache.js +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/modules/backreferences/asNativeAtom.d.ts +2 -0
- package/lib/modules/backreferences/asNativeAtom.js +10 -0
- package/lib/modules/backreferences/asPreScanPart.d.ts +2 -0
- package/lib/modules/backreferences/asPreScanPart.js +8 -0
- package/lib/modules/backreferences/backreferenceRecorder.d.ts +2 -0
- package/lib/modules/backreferences/backreferenceRecorder.js +38 -0
- package/lib/modules/backreferences/caseFoldFlags.d.ts +1 -0
- package/lib/modules/backreferences/caseFoldFlags.js +3 -0
- package/lib/modules/backreferences/compileDynamic.d.ts +4 -0
- package/lib/modules/backreferences/compileDynamic.js +53 -0
- package/lib/modules/backreferences/compiledBackreferences.d.ts +9 -0
- package/lib/modules/backreferences/compiledBackreferences.js +9 -0
- package/lib/modules/backreferences/constants.d.ts +2 -0
- package/lib/modules/backreferences/constants.js +2 -0
- package/lib/modules/backreferences/execDynamic.d.ts +2 -0
- package/lib/modules/backreferences/execDynamic.js +43 -0
- package/lib/modules/backreferences/flagsAtBackreference.d.ts +2 -0
- package/lib/modules/backreferences/flagsAtBackreference.js +4 -0
- package/lib/modules/backreferences/groupName.d.ts +2 -0
- package/lib/modules/backreferences/groupName.js +8 -0
- package/lib/modules/backreferences/index.d.ts +10 -0
- package/lib/modules/backreferences/index.js +20 -0
- package/lib/modules/backreferences/longestBakedPrefixEndingInput.d.ts +1 -0
- package/lib/modules/backreferences/longestBakedPrefixEndingInput.js +46 -0
- package/lib/modules/backreferences/renderParts.d.ts +2 -0
- package/lib/modules/backreferences/renderParts.js +9 -0
- package/lib/modules/backreferences/resolvedFromScan.d.ts +2 -0
- package/lib/modules/backreferences/resolvedFromScan.js +10 -0
- package/lib/modules/backreferences/startsWithUnderFlags.d.ts +1 -0
- package/lib/modules/backreferences/startsWithUnderFlags.js +7 -0
- package/lib/modules/carets/appendMultilineCaret.d.ts +7 -0
- package/lib/modules/carets/appendMultilineCaret.js +181 -0
- package/lib/modules/carets/caretRecorder.d.ts +2 -0
- package/lib/modules/carets/caretRecorder.js +164 -0
- package/lib/modules/carets/caretSyntax.d.ts +3 -0
- package/lib/modules/carets/caretSyntax.js +11 -0
- package/lib/modules/carets/index.d.ts +9 -0
- package/lib/modules/carets/index.js +18 -0
- package/lib/modules/carets/lineTerminator.d.ts +1 -0
- package/lib/modules/carets/lineTerminator.js +31 -0
- package/lib/modules/carets/needsCaretRules.d.ts +1 -0
- package/lib/modules/carets/needsCaretRules.js +15 -0
- package/lib/modules/carets/quantifierBounds.d.ts +3 -0
- package/lib/modules/carets/quantifierBounds.js +18 -0
- package/lib/modules/index.d.ts +3 -0
- package/lib/modules/index.js +2 -0
- package/lib/partialMatchRegExp/atomSyntax.d.ts +17 -0
- package/lib/partialMatchRegExp/atomSyntax.js +27 -0
- package/lib/partialMatchRegExp/backreferenceExpansion.d.ts +5 -0
- package/lib/partialMatchRegExp/backreferenceExpansion.js +1 -0
- package/lib/partialMatchRegExp/backreferenceHook.d.ts +11 -0
- package/lib/partialMatchRegExp/backreferenceHook.js +1 -0
- package/lib/partialMatchRegExp/caretHook.d.ts +17 -0
- package/lib/partialMatchRegExp/caretHook.js +5 -0
- package/lib/partialMatchRegExp/compilePartial/compileWith.d.ts +3 -0
- package/lib/partialMatchRegExp/compilePartial/compileWith.js +36 -0
- package/lib/partialMatchRegExp/compilePartial/compiled.d.ts +29 -0
- package/lib/partialMatchRegExp/compilePartial/compiled.js +14 -0
- package/lib/partialMatchRegExp/compilePartial/constants.d.ts +4 -0
- package/lib/partialMatchRegExp/compilePartial/constants.js +4 -0
- package/lib/partialMatchRegExp/compilePartial/groupShape.d.ts +4 -0
- package/lib/partialMatchRegExp/compilePartial/groupShape.js +9 -0
- package/lib/partialMatchRegExp/compilePartial/toStatic.d.ts +3 -0
- package/lib/partialMatchRegExp/compilePartial/toStatic.js +4 -0
- package/lib/partialMatchRegExp/constants.d.ts +5 -0
- package/lib/partialMatchRegExp/constants.js +5 -0
- package/lib/partialMatchRegExp/createPartialMatchRegExp.d.ts +3 -0
- package/lib/partialMatchRegExp/createPartialMatchRegExp.js +21 -0
- package/lib/partialMatchRegExp/execFrom.d.ts +1 -0
- package/lib/partialMatchRegExp/execFrom.js +4 -0
- package/lib/partialMatchRegExp/index.d.ts +49 -0
- package/lib/partialMatchRegExp/index.js +31 -0
- package/lib/partialMatchRegExp/legacyEscape.d.ts +2 -0
- package/lib/partialMatchRegExp/legacyEscape.js +18 -0
- package/lib/partialMatchRegExp/moduleHooks.d.ts +14 -0
- package/lib/partialMatchRegExp/moduleHooks.js +15 -0
- package/lib/partialMatchRegExp/opaqueModule.d.ts +9 -0
- package/lib/partialMatchRegExp/opaqueModule.js +1 -0
- package/lib/partialMatchRegExp/part.d.ts +15 -0
- package/lib/partialMatchRegExp/part.js +2 -0
- package/lib/partialMatchRegExp/partialMatchInternals.d.ts +3 -0
- package/lib/partialMatchRegExp/partialMatchInternals.js +4 -0
- package/lib/partialMatchRegExp/quantifier.d.ts +6 -0
- package/lib/partialMatchRegExp/quantifier.js +9 -0
- package/lib/partialMatchRegExp/rawLookaroundInfo.d.ts +11 -0
- package/lib/partialMatchRegExp/rawLookaroundInfo.js +1 -0
- package/lib/partialMatchRegExp/regexFeatures.d.ts +2 -0
- package/lib/partialMatchRegExp/regexFeatures.js +41 -0
- package/lib/partialMatchRegExp/scope.d.ts +12 -0
- package/lib/partialMatchRegExp/scope.js +45 -0
- package/lib/partialMatchRegExp/walk.d.ts +16 -0
- package/lib/partialMatchRegExp/walk.js +412 -0
- package/lib/partialMatchRegExp/withModules.d.ts +13 -0
- package/lib/partialMatchRegExp/withModules.js +27 -0
- package/package.json +46 -53
- package/lib/compilePartial.d.ts +0 -26
- package/lib/compilePartial.js +0 -397
- package/lib/partialMatchRegExp.d.ts +0 -52
- package/lib/partialMatchRegExp.js +0 -126
- /package/lib/{escapeAtom.d.ts → modules/backreferences/escapeAtom.d.ts} +0 -0
- /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,
|
|
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
|
-
|
|
49
|
+
### Telling a settled match from one still reading
|
|
50
50
|
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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](#
|
|
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
|
-
|
|
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
|
-
|
|
185
|
+
## ⚠️ Caveats
|
|
260
186
|
|
|
261
|
-
|
|
187
|
+
See [Caveats](./docs/caveats.md) for details, examples and workarounds.
|
|
262
188
|
|
|
263
|
-
|
|
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(
|
|
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
|
|
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
|
-
### `
|
|
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
|
-
### `
|
|
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
|
-
|
|
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](
|
|
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 `
|
|
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
|
|
383
|
-
partial.
|
|
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
|
|
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.
|
|
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";
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import PartialMatchRegExp from "
|
|
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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
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
|
*/
|
|
@@ -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
|
+
}
|