regex-partial-match 1.1.2 → 1.2.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 CHANGED
@@ -372,13 +372,14 @@ This is useful for consumers building on top of `PartialMatchRegExp` who need to
372
372
 
373
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
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)/`.
375
376
 
376
377
  ```javascript
377
378
  import PartialMatchRegExp from "regex-partial-match";
378
379
 
379
380
  const partial = new PartialMatchRegExp(/^[a-z]+(?<domain>\.[a-z]+)\1/);
380
381
 
381
- partial.features; // Set { "startAnchor", "characterClass", "quantifier", "namedGroup", "capturingGroup", "backreference" }
382
+ partial.features; // Set { "startAnchor", "backreference", "namedGroup", "capturingGroup", "characterClass", "quantifier", "otherEscape" }
382
383
  partial.features.has("backreference"); // true
383
384
  ```
384
385
 
@@ -399,6 +400,7 @@ partial.features.has("backreference"); // true
399
400
  | `namedBackreference` | `\k<name>` | |
400
401
  | `namedGroup` | `(?<name>...)` | Always accompanied by `capturingGroup` — see below |
401
402
  | `capturingGroup` | `(...)`, including named groups | |
403
+ | `lookaroundCapture` | A capturing group inside any lookaround | Nesting, not a construct — always accompanied by `capturingGroup` |
402
404
  | `nonCapturingGroup` | `(?:...)` | |
403
405
  | `modifierGroup` | `(?ims:...)` | |
404
406
  | `modifierGroupWithRemoval` | `(?ims-ims:...)` | Mutually exclusive with `modifierGroup` |
@@ -416,10 +418,11 @@ partial.features.has("backreference"); // true
416
418
  | `unicodeEscapeSequence` | `\uXXXX`, `\u{...}` | |
417
419
  | `otherEscape` | Any other `\X`, e.g. `\.`, `\0` | |
418
420
 
419
- Two things worth knowing about how these tags line up with the grammar:
421
+ Three things worth knowing about how these tags line up with the grammar:
420
422
 
421
423
  - **One ECMA-262 production can map to several tags.** `Assertion` alone covers `^`, `$`, `\b`, `\B`, and all four lookarounds — `features` splits it by whichever discriminant is easiest to read off during the walk (`^` vs `$`, `=` vs `!` after `(?<`, etc.), since that information is free at the point each construct is recognised.
422
424
  - **A named capturing group always carries both `namedGroup` and `capturingGroup`.** The grammar treats a capturing group with a name and one without as the same production (`( GroupSpecifier? Disjunction )`), not two, so both tags are added together.
425
+ - **`lookaroundCapture` records nesting, not a construct.** Every other tag names something the source contains; this one names where something sits — a capturing group appearing lexically inside `(?=...)`, `(?!...)`, `(?<=...)` or `(?<!...)`, at any depth of nested groups. It is the one relationship between constructs the flattened set cannot otherwise express: `/(\w+)(?= END)/` and `/a(?=(?:b(?:x|(c))d|b))/` both report `lookahead` and `capturingGroup`, but only the second has a capture whose value the assertion decides.
423
426
 
424
427
  ## 📜 License
425
428
 
@@ -1,16 +1,26 @@
1
- export type RegexFeature = "patternCharacter" | "startAnchor" | "endAnchor" | "wordBoundary" | "nonWordBoundary" | "lookahead" | "negativeLookahead" | "lookbehind" | "negativeLookbehind" | "backreference" | "namedBackreference" | "namedGroup" | "capturingGroup" | "nonCapturingGroup" | "modifierGroup" | "modifierGroupWithRemoval" | "characterClass" | "nestedCharacterClass" | "classIntersection" | "classSubtraction" | "disjunction" | "quantifier" | "unicodePropertyEscape" | "characterClassEscape" | "controlEscape" | "controlLetterEscape" | "hexEscapeSequence" | "unicodeEscapeSequence" | "otherEscape";
1
+ declare const REGEX_FEATURES: readonly ["patternCharacter", "startAnchor", "endAnchor", "wordBoundary", "nonWordBoundary", "lookahead", "negativeLookahead", "lookbehind", "negativeLookbehind", "backreference", "namedBackreference", "namedGroup", "capturingGroup", "lookaroundCapture", "nonCapturingGroup", "modifierGroup", "modifierGroupWithRemoval", "characterClass", "nestedCharacterClass", "classIntersection", "classSubtraction", "disjunction", "quantifier", "unicodePropertyEscape", "characterClassEscape", "controlEscape", "controlLetterEscape", "hexEscapeSequence", "unicodeEscapeSequence", "otherEscape"];
2
+ export type RegexFeature = (typeof REGEX_FEATURES)[number];
2
3
  export interface DynamicPath {
3
4
  originalCaptureScan: RegExp;
4
5
  preScan: RegExp;
5
6
  expand: (capture: RegExpExecArray) => string;
6
7
  }
7
- export type CompiledPartial = ({
8
- kind: "static";
9
- regex: RegExp;
10
- } | {
11
- kind: "dynamic";
12
- dynamic: DynamicPath;
13
- }) & {
14
- features: Set<RegexFeature>;
15
- };
8
+ declare abstract class Compiled {
9
+ private readonly _featureMask;
10
+ private _features?;
11
+ constructor(_featureMask: number);
12
+ get features(): ReadonlySet<RegexFeature>;
13
+ }
14
+ declare class CompiledStatic extends Compiled {
15
+ readonly regex: RegExp;
16
+ readonly kind = "static";
17
+ constructor(regex: RegExp, featureMask: number);
18
+ }
19
+ declare class CompiledDynamic extends Compiled {
20
+ readonly dynamic: DynamicPath;
21
+ readonly kind = "dynamic";
22
+ constructor(dynamic: DynamicPath, featureMask: number);
23
+ }
24
+ export type CompiledPartial = CompiledStatic | CompiledDynamic;
16
25
  export declare const compilePartial: (regex: RegExp) => CompiledPartial;
26
+ export {};
@@ -3,18 +3,63 @@ const OCCURRENCES_REGEX = /\{\d+,?\d*\}/y;
3
3
  const NOT_NUMBERS_REGEX = /\D/g;
4
4
  const MAYBE_HAS_BACKREFERENCE_REGEX = /\\[1-9]|\\k</;
5
5
  const DISJUNCTION_TO_END_OF_INPUT = "|$(?![\\s\\S]))";
6
+ const ANY_CAPTURED_TEXT = "(?:[\\s\\S]*?)";
6
7
  const isBackreference = (part) => typeof part !== "string";
7
8
  const isNumericBackreference = (part) => isBackreference(part) && typeof part.ref === "number";
9
+ const REGEX_FEATURES = [
10
+ "patternCharacter",
11
+ "startAnchor",
12
+ "endAnchor",
13
+ "wordBoundary",
14
+ "nonWordBoundary",
15
+ "lookahead",
16
+ "negativeLookahead",
17
+ "lookbehind",
18
+ "negativeLookbehind",
19
+ "backreference",
20
+ "namedBackreference",
21
+ "namedGroup",
22
+ "capturingGroup",
23
+ "lookaroundCapture",
24
+ "nonCapturingGroup",
25
+ "modifierGroup",
26
+ "modifierGroupWithRemoval",
27
+ "characterClass",
28
+ "nestedCharacterClass",
29
+ "classIntersection",
30
+ "classSubtraction",
31
+ "disjunction",
32
+ "quantifier",
33
+ "unicodePropertyEscape",
34
+ "characterClassEscape",
35
+ "controlEscape",
36
+ "controlLetterEscape",
37
+ "hexEscapeSequence",
38
+ "unicodeEscapeSequence",
39
+ "otherEscape"
40
+ ];
41
+ const FEATURE_BIT = {};
42
+ for (let index = 0; index < REGEX_FEATURES.length; index++) {
43
+ FEATURE_BIT[REGEX_FEATURES[index]] = 1 << index;
44
+ }
45
+ function featureSet(mask) {
46
+ const features = new Set();
47
+ for (let index = 0; index < REGEX_FEATURES.length; index++) {
48
+ if (mask & (1 << index))
49
+ features.add(REGEX_FEATURES[index]);
50
+ }
51
+ return features;
52
+ }
8
53
  function walk(regex) {
9
54
  const source = regex.source;
10
55
  const isUnicode = regex.unicode || regex.unicodeSets;
11
56
  let i = 0;
12
57
  let groupCount = 0;
13
- const features = new Set();
58
+ let featureMask = 0;
14
59
  function extractSlice(length) {
15
60
  return source.slice(i, (i += length));
16
61
  }
17
- function process() {
62
+ function process(withinLookaround) {
18
63
  var _a;
19
64
  const result = [];
20
65
  function appendOptional(length) {
@@ -23,10 +68,10 @@ function walk(regex) {
23
68
  function appendRaw(length) {
24
69
  result.push(extractSlice(length));
25
70
  }
26
- function appendRawGroup(prefixLength) {
71
+ function appendRawLookaround(prefixLength) {
27
72
  const start = i;
28
73
  i += prefixLength;
29
- process();
74
+ process(true);
30
75
  result.push(source.slice(start, i));
31
76
  }
32
77
  while (i < source.length) {
@@ -34,7 +79,7 @@ function walk(regex) {
34
79
  case "\\":
35
80
  switch (source[i + 1]) {
36
81
  case "c":
37
- features.add("controlLetterEscape");
82
+ featureMask |= FEATURE_BIT.controlLetterEscape;
38
83
  appendOptional(3);
39
84
  break;
40
85
  case "k": {
@@ -43,7 +88,7 @@ function walk(regex) {
43
88
  appendOptional(2);
44
89
  }
45
90
  else {
46
- features.add("namedBackreference");
91
+ featureMask |= FEATURE_BIT.namedBackreference;
47
92
  const start = i;
48
93
  const ref = source.slice(i + 3, referenceEnd);
49
94
  i = referenceEnd + 1;
@@ -52,7 +97,7 @@ function walk(regex) {
52
97
  break;
53
98
  }
54
99
  case "u":
55
- features.add("unicodeEscapeSequence");
100
+ featureMask |= FEATURE_BIT.unicodeEscapeSequence;
56
101
  if (isUnicode && source[i + 2] === "{") {
57
102
  appendOptional(source.indexOf("}", i) - i + 1);
58
103
  }
@@ -63,7 +108,7 @@ function walk(regex) {
63
108
  case "p":
64
109
  case "P":
65
110
  if (isUnicode) {
66
- features.add("unicodePropertyEscape");
111
+ featureMask |= FEATURE_BIT.unicodePropertyEscape;
67
112
  appendOptional(source.indexOf("}", i) - i + 1);
68
113
  }
69
114
  else {
@@ -71,15 +116,15 @@ function walk(regex) {
71
116
  }
72
117
  break;
73
118
  case "x":
74
- features.add("hexEscapeSequence");
119
+ featureMask |= FEATURE_BIT.hexEscapeSequence;
75
120
  appendOptional(4);
76
121
  break;
77
122
  case "b":
78
- features.add("wordBoundary");
123
+ featureMask |= FEATURE_BIT.wordBoundary;
79
124
  appendOptional(2);
80
125
  break;
81
126
  case "B":
82
- features.add("nonWordBoundary");
127
+ featureMask |= FEATURE_BIT.nonWordBoundary;
83
128
  appendOptional(2);
84
129
  break;
85
130
  case "f":
@@ -87,7 +132,7 @@ function walk(regex) {
87
132
  case "r":
88
133
  case "t":
89
134
  case "v":
90
- features.add("controlEscape");
135
+ featureMask |= FEATURE_BIT.controlEscape;
91
136
  appendOptional(2);
92
137
  break;
93
138
  case "1":
@@ -99,7 +144,7 @@ function walk(regex) {
99
144
  case "7":
100
145
  case "8":
101
146
  case "9": {
102
- features.add("backreference");
147
+ featureMask |= FEATURE_BIT.backreference;
103
148
  NOT_NUMBERS_REGEX.lastIndex = i + 1;
104
149
  const nextNonDigit = NOT_NUMBERS_REGEX.exec(source);
105
150
  const start = i;
@@ -115,17 +160,17 @@ function walk(regex) {
115
160
  case "W":
116
161
  case "s":
117
162
  case "S":
118
- features.add("characterClassEscape");
163
+ featureMask |= FEATURE_BIT.characterClassEscape;
119
164
  appendOptional(2);
120
165
  break;
121
166
  default:
122
- features.add("otherEscape");
167
+ featureMask |= FEATURE_BIT.otherEscape;
123
168
  appendOptional(2);
124
169
  break;
125
170
  }
126
171
  break;
127
172
  case "[": {
128
- features.add("characterClass");
173
+ featureMask |= FEATURE_BIT.characterClass;
129
174
  let depth = 1, j = i + 1, previousSetOperatorCharacter;
130
175
  while (depth) {
131
176
  const character = source[j];
@@ -136,7 +181,7 @@ function walk(regex) {
136
181
  continue;
137
182
  case "[":
138
183
  if (regex.unicodeSets) {
139
- features.add("nestedCharacterClass");
184
+ featureMask |= FEATURE_BIT.nestedCharacterClass;
140
185
  depth++;
141
186
  }
142
187
  break;
@@ -145,12 +190,12 @@ function walk(regex) {
145
190
  break;
146
191
  case "&":
147
192
  if (regex.unicodeSets && previousSetOperatorCharacter === "&") {
148
- features.add("classIntersection");
193
+ featureMask |= FEATURE_BIT.classIntersection;
149
194
  }
150
195
  break;
151
196
  case "-":
152
197
  if (regex.unicodeSets && previousSetOperatorCharacter === "-") {
153
- features.add("classSubtraction");
198
+ featureMask |= FEATURE_BIT.classSubtraction;
154
199
  }
155
200
  break;
156
201
  }
@@ -161,28 +206,28 @@ function walk(regex) {
161
206
  break;
162
207
  }
163
208
  case "^":
164
- features.add("startAnchor");
209
+ featureMask |= FEATURE_BIT.startAnchor;
165
210
  appendRaw(1);
166
211
  break;
167
212
  case "$":
168
- features.add("endAnchor");
213
+ featureMask |= FEATURE_BIT.endAnchor;
169
214
  appendRaw(1);
170
215
  break;
171
216
  case "|":
172
- features.add("disjunction");
217
+ featureMask |= FEATURE_BIT.disjunction;
173
218
  appendRaw(1);
174
219
  break;
175
220
  case "*":
176
221
  case "+":
177
222
  case "?":
178
- features.add("quantifier");
223
+ featureMask |= FEATURE_BIT.quantifier;
179
224
  appendRaw(1);
180
225
  break;
181
226
  case "{": {
182
227
  OCCURRENCES_REGEX.lastIndex = i;
183
228
  const regExpExecArray = OCCURRENCES_REGEX.exec(source);
184
229
  if (regExpExecArray) {
185
- features.add("quantifier");
230
+ featureMask |= FEATURE_BIT.quantifier;
186
231
  appendRaw(regExpExecArray[0].length);
187
232
  }
188
233
  else {
@@ -194,16 +239,16 @@ function walk(regex) {
194
239
  if (source[i + 1] == "?") {
195
240
  switch (source[i + 2]) {
196
241
  case ":":
197
- features.add("nonCapturingGroup");
242
+ featureMask |= FEATURE_BIT.nonCapturingGroup;
198
243
  result.push("(?:");
199
244
  i += 3;
200
- result.push(...process(), DISJUNCTION_TO_END_OF_INPUT);
245
+ result.push(...process(withinLookaround), DISJUNCTION_TO_END_OF_INPUT);
201
246
  break;
202
247
  case "=":
203
- features.add("lookahead");
248
+ featureMask |= FEATURE_BIT.lookahead;
204
249
  result.push("(?=");
205
250
  i += 3;
206
- result.push(...process(), ")");
251
+ result.push(...process(true), ")");
207
252
  break;
208
253
  case "-":
209
254
  case "i":
@@ -211,63 +256,69 @@ function walk(regex) {
211
256
  case "m": {
212
257
  const flagsStart = i + 2, colonIndex = source.indexOf(":", flagsStart);
213
258
  const modifiers = source.slice(flagsStart, colonIndex);
214
- features.add(modifiers.includes("-")
215
- ? "modifierGroupWithRemoval"
216
- : "modifierGroup");
259
+ featureMask |= modifiers.includes("-")
260
+ ? FEATURE_BIT.modifierGroupWithRemoval
261
+ : FEATURE_BIT.modifierGroup;
217
262
  result.push("(?" + modifiers + ":");
218
263
  i = colonIndex + 1;
219
- result.push(...process(), ")");
264
+ result.push(...process(withinLookaround), ")");
220
265
  break;
221
266
  }
222
267
  case "!":
223
- features.add("negativeLookahead");
224
- appendRawGroup(3);
268
+ featureMask |= FEATURE_BIT.negativeLookahead;
269
+ appendRawLookaround(3);
225
270
  break;
226
271
  case "<":
227
272
  switch (source[i + 3]) {
228
273
  case "=":
229
- features.add("lookbehind");
230
- appendRawGroup(4);
274
+ featureMask |= FEATURE_BIT.lookbehind;
275
+ appendRawLookaround(4);
231
276
  break;
232
277
  case "!":
233
- features.add("negativeLookbehind");
234
- appendRawGroup(4);
278
+ featureMask |= FEATURE_BIT.negativeLookbehind;
279
+ appendRawLookaround(4);
235
280
  break;
236
281
  default:
237
- features.add("namedGroup");
238
- features.add("capturingGroup");
282
+ featureMask |= FEATURE_BIT.namedGroup;
283
+ featureMask |= FEATURE_BIT.capturingGroup;
284
+ if (withinLookaround)
285
+ featureMask |= FEATURE_BIT.lookaroundCapture;
239
286
  ++groupCount;
240
287
  appendRaw(source.indexOf(">", i) - i + 1);
241
- result.push(...process(), DISJUNCTION_TO_END_OF_INPUT);
288
+ result.push(...process(withinLookaround), DISJUNCTION_TO_END_OF_INPUT);
242
289
  break;
243
290
  }
244
291
  break;
245
292
  }
246
293
  }
247
294
  else {
248
- features.add("capturingGroup");
295
+ featureMask |= FEATURE_BIT.capturingGroup;
296
+ if (withinLookaround)
297
+ featureMask |= FEATURE_BIT.lookaroundCapture;
249
298
  ++groupCount;
250
299
  appendRaw(1);
251
- result.push(...process(), DISJUNCTION_TO_END_OF_INPUT);
300
+ result.push(...process(withinLookaround), DISJUNCTION_TO_END_OF_INPUT);
252
301
  }
253
302
  break;
254
303
  case ")":
255
304
  ++i;
256
305
  return result;
257
306
  default:
258
- features.add("patternCharacter");
307
+ featureMask |= FEATURE_BIT.patternCharacter;
259
308
  appendOptional(isUnicode && ((_a = source.codePointAt(i)) !== null && _a !== void 0 ? _a : 0) > 0xffff ? 2 : 1);
260
309
  break;
261
310
  }
262
311
  }
263
312
  return result;
264
313
  }
265
- return { parts: process(), groupCount, features };
314
+ return { parts: process(false), groupCount, featureMask };
266
315
  }
267
316
  function render(parts, renderBackref) {
268
- return parts
269
- .map((part) => (isBackreference(part) ? renderBackref(part) : part))
270
- .join("");
317
+ let rendered = "";
318
+ for (const part of parts) {
319
+ rendered += isBackreference(part) ? renderBackref(part) : part;
320
+ }
321
+ return rendered;
271
322
  }
272
323
  function backrefToken(backref) {
273
324
  return isNumericBackreference(backref)
@@ -283,7 +334,7 @@ function spliceOriginalSource(source, backrefs) {
283
334
  let result = "";
284
335
  let cursor = 0;
285
336
  for (const { start, end } of backrefs) {
286
- result += source.slice(cursor, start) + "(?:[\\s\\S]*?)";
337
+ result += source.slice(cursor, start) + ANY_CAPTURED_TEXT;
287
338
  cursor = end;
288
339
  }
289
340
  return result + source.slice(cursor);
@@ -293,14 +344,34 @@ function expandCaptured(value, isUnicode) {
293
344
  .map((atom) => "(?:" + escapeAtom(atom) + DISJUNCTION_TO_END_OF_INPUT)
294
345
  .join("");
295
346
  }
347
+ class Compiled {
348
+ constructor(_featureMask) {
349
+ this._featureMask = _featureMask;
350
+ }
351
+ get features() {
352
+ var _a;
353
+ return ((_a = this._features) !== null && _a !== void 0 ? _a : (this._features = featureSet(this._featureMask)));
354
+ }
355
+ }
356
+ class CompiledStatic extends Compiled {
357
+ constructor(regex, featureMask) {
358
+ super(featureMask);
359
+ this.regex = regex;
360
+ this.kind = "static";
361
+ }
362
+ }
363
+ class CompiledDynamic extends Compiled {
364
+ constructor(dynamic, featureMask) {
365
+ super(featureMask);
366
+ this.dynamic = dynamic;
367
+ this.kind = "dynamic";
368
+ }
369
+ }
296
370
  export const compilePartial = (regex) => {
297
- const { parts, groupCount, features } = walk(regex);
371
+ const { parts, groupCount, featureMask } = walk(regex);
372
+ const flags = regex.flags;
298
373
  if (!MAYBE_HAS_BACKREFERENCE_REGEX.test(regex.source)) {
299
- return {
300
- kind: "static",
301
- regex: new RegExp(render(parts, backrefToken), regex.flags),
302
- features
303
- };
374
+ return new CompiledStatic(new RegExp(render(parts, backrefToken), flags), featureMask);
304
375
  }
305
376
  const isUnicode = regex.unicode || regex.unicodeSets;
306
377
  const sanitisedParts = isUnicode
@@ -308,27 +379,19 @@ export const compilePartial = (regex) => {
308
379
  : reclassifyOctalEscapes(parts, regex.source, groupCount);
309
380
  const backreferences = sanitisedParts.filter(isBackreference);
310
381
  if (backreferences.length === 0) {
311
- return {
312
- kind: "static",
313
- regex: new RegExp(render(sanitisedParts, backrefToken), regex.flags),
314
- features
315
- };
382
+ return new CompiledStatic(new RegExp(render(sanitisedParts, backrefToken), flags), featureMask);
316
383
  }
317
- return {
318
- kind: "dynamic",
319
- features,
320
- dynamic: {
321
- originalCaptureScan: new RegExp(spliceOriginalSource(regex.source, backreferences), regex.flags),
322
- preScan: new RegExp(render(parts, () => "(?:[\\s\\S]*?)"), regex.flags),
323
- expand: (capture) => render(parts, (backref) => {
324
- var _a;
325
- const captured = isNumericBackreference(backref)
326
- ? capture[backref.ref]
327
- : (_a = capture.groups) === null || _a === void 0 ? void 0 : _a[backref.ref];
328
- return captured === undefined
329
- ? "(?:" + backrefToken(backref) + DISJUNCTION_TO_END_OF_INPUT
330
- : expandCaptured(captured, isUnicode);
331
- })
332
- }
333
- };
384
+ return new CompiledDynamic({
385
+ originalCaptureScan: new RegExp(spliceOriginalSource(regex.source, backreferences), flags),
386
+ preScan: new RegExp(render(parts, () => ANY_CAPTURED_TEXT), flags),
387
+ expand: (capture) => render(parts, (backref) => {
388
+ var _a;
389
+ const captured = isNumericBackreference(backref)
390
+ ? capture[backref.ref]
391
+ : (_a = capture.groups) === null || _a === void 0 ? void 0 : _a[backref.ref];
392
+ return captured === undefined
393
+ ? "(?:" + backrefToken(backref) + DISJUNCTION_TO_END_OF_INPUT
394
+ : expandCaptured(captured, isUnicode);
395
+ })
396
+ }, featureMask);
334
397
  };
@@ -1,5 +1,6 @@
1
1
  import { type RegexFeature } from "./compilePartial.ts";
2
2
  export type { RegexFeature };
3
+ declare const compiledPartial: unique symbol;
3
4
  /**
4
5
  * A `RegExp` subclass that supports partial (prefix) matching.
5
6
  *
@@ -27,9 +28,24 @@ export type { RegexFeature };
27
28
  * @see {@link https://github.com/TomStrepsil/regex-partial-match#readme | Documentation}
28
29
  */
29
30
  declare class PartialMatchRegExp extends RegExp {
30
- #private;
31
- readonly features: ReadonlySet<RegexFeature>;
31
+ private [compiledPartial];
32
32
  constructor(pattern: RegExp | string, flags?: string);
33
+ /**
34
+ * The syntactic constructs the original pattern uses, recorded as a side
35
+ * effect of the single walk that builds the partial-match regex.
36
+ *
37
+ * The set is built on first read and cached, so patterns that are only ever
38
+ * matched against never pay for it. It iterates in `RegexFeature` declaration
39
+ * order, not the order the constructs appear in the pattern.
40
+ *
41
+ * @returns The features the original pattern contains
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * new PartialMatchRegExp(/^[a-z]+/).features.has("startAnchor"); // true
46
+ * ```
47
+ */
48
+ get features(): ReadonlySet<RegexFeature>;
33
49
  exec(input: string): RegExpExecArray | null;
34
50
  private _execDynamic;
35
51
  }
@@ -1,16 +1,5 @@
1
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
- if (kind === "m") throw new TypeError("Private method is not writable");
3
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
- };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _PartialMatchRegExp_compiledPartial;
13
1
  import { compilePartial } from "./compilePartial.js";
2
+ const compiledPartial = Symbol("compiledPartial");
14
3
  /**
15
4
  * A `RegExp` subclass that supports partial (prefix) matching.
16
5
  *
@@ -40,12 +29,28 @@ import { compilePartial } from "./compilePartial.js";
40
29
  class PartialMatchRegExp extends RegExp {
41
30
  constructor(pattern, flags) {
42
31
  super(pattern, flags);
43
- _PartialMatchRegExp_compiledPartial.set(this, void 0);
44
- __classPrivateFieldSet(this, _PartialMatchRegExp_compiledPartial, compilePartial(this), "f");
45
- this.features = __classPrivateFieldGet(this, _PartialMatchRegExp_compiledPartial, "f").features;
32
+ this[compiledPartial] = compilePartial(this);
33
+ }
34
+ /**
35
+ * The syntactic constructs the original pattern uses, recorded as a side
36
+ * effect of the single walk that builds the partial-match regex.
37
+ *
38
+ * The set is built on first read and cached, so patterns that are only ever
39
+ * matched against never pay for it. It iterates in `RegexFeature` declaration
40
+ * order, not the order the constructs appear in the pattern.
41
+ *
42
+ * @returns The features the original pattern contains
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * new PartialMatchRegExp(/^[a-z]+/).features.has("startAnchor"); // true
47
+ * ```
48
+ */
49
+ get features() {
50
+ return this[compiledPartial].features;
46
51
  }
47
52
  exec(input) {
48
- const compiled = __classPrivateFieldGet(this, _PartialMatchRegExp_compiledPartial, "f");
53
+ const compiled = this[compiledPartial];
49
54
  if (compiled.kind === "dynamic")
50
55
  return this._execDynamic(compiled.dynamic, input);
51
56
  const { regex } = compiled;
@@ -81,7 +86,6 @@ class PartialMatchRegExp extends RegExp {
81
86
  return match;
82
87
  }
83
88
  }
84
- _PartialMatchRegExp_compiledPartial = new WeakMap();
85
89
  function execFrom(regex, input, start) {
86
90
  regex.lastIndex = start;
87
91
  return regex.exec(input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "regex-partial-match",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "A regular expression transform for partial matching",
5
5
  "devEngines": {
6
6
  "runtime": {
@@ -78,11 +78,11 @@
78
78
  "registry": "https://registry.npmjs.org"
79
79
  },
80
80
  "devDependencies": {
81
- "@typescript/lib-es2018": "file:types/lib-es2018",
82
- "@typescript/lib-es2022": "file:types/lib-es2022",
83
81
  "@eslint/js": "^9.17.0",
84
82
  "@eslint/markdown": "^7.5.1",
85
83
  "@types/node": "^25.9.3",
84
+ "@typescript/lib-es2018": "file:types/lib-es2018",
85
+ "@typescript/lib-es2022": "file:types/lib-es2022",
86
86
  "eslint": "^9.17.0",
87
87
  "jiti": "^2.6.1",
88
88
  "lint-staged": "^15.2.11",