regex-partial-match 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,7 +71,7 @@ Such combinations have not been tested.
71
71
 
72
72
  ### Patterns with backreferences
73
73
 
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, as a short-circuit — if the input already satisfies the whole pattern, there's nothing further to do. 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.
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.
75
75
 
76
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.
77
77
 
@@ -418,7 +418,7 @@ partial.features.has("backreference"); // true
418
418
 
419
419
  Two things worth knowing about how these tags line up with the grammar:
420
420
 
421
- - **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 recognized.
421
+ - **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
422
  - **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.
423
423
 
424
424
  ## 📜 License
@@ -1,8 +1,8 @@
1
1
  import escapeAtom from "./escapeAtom.js";
2
2
  const OCCURRENCES_REGEX = /\{\d+,?\d*\}/y;
3
3
  const NOT_NUMBERS_REGEX = /\D/g;
4
+ const MAYBE_HAS_BACKREFERENCE_REGEX = /\\[1-9]|\\k</;
4
5
  const DISJUNCTION_TO_END_OF_INPUT = "|$(?![\\s\\S]))";
5
- const MAYBE_HAS_BACKREFERENCE = /\\[1-9]|\\k</;
6
6
  const isBackreference = (part) => typeof part !== "string";
7
7
  const isNumericBackreference = (part) => isBackreference(part) && typeof part.ref === "number";
8
8
  function walk(regex) {
@@ -295,7 +295,7 @@ function expandCaptured(value, isUnicode) {
295
295
  }
296
296
  export const compilePartial = (regex) => {
297
297
  const { parts, groupCount, features } = walk(regex);
298
- if (!MAYBE_HAS_BACKREFERENCE.test(regex.source)) {
298
+ if (!MAYBE_HAS_BACKREFERENCE_REGEX.test(regex.source)) {
299
299
  return {
300
300
  kind: "static",
301
301
  regex: new RegExp(render(parts, backrefToken), regex.flags),
@@ -303,14 +303,14 @@ export const compilePartial = (regex) => {
303
303
  };
304
304
  }
305
305
  const isUnicode = regex.unicode || regex.unicodeSets;
306
- const sanitizedParts = isUnicode
306
+ const sanitisedParts = isUnicode
307
307
  ? parts
308
308
  : reclassifyOctalEscapes(parts, regex.source, groupCount);
309
- const backreferences = sanitizedParts.filter(isBackreference);
309
+ const backreferences = sanitisedParts.filter(isBackreference);
310
310
  if (backreferences.length === 0) {
311
311
  return {
312
312
  kind: "static",
313
- regex: new RegExp(render(sanitizedParts, backrefToken), regex.flags),
313
+ regex: new RegExp(render(sanitisedParts, backrefToken), regex.flags),
314
314
  features
315
315
  };
316
316
  }
@@ -9,7 +9,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
9
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
10
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
11
  };
12
- var _PartialMatchRegExp_static, _PartialMatchRegExp_dynamic;
12
+ var _PartialMatchRegExp_instances, _PartialMatchRegExp_compiledPartial, _PartialMatchRegExp_execDynamic;
13
13
  import { compilePartial } from "./compilePartial.js";
14
14
  /**
15
15
  * A `RegExp` subclass that supports partial (prefix) matching.
@@ -39,48 +39,56 @@ import { compilePartial } from "./compilePartial.js";
39
39
  */
40
40
  class PartialMatchRegExp extends RegExp {
41
41
  constructor(pattern, flags) {
42
- var _a, _b;
43
42
  super(pattern, flags);
44
- _PartialMatchRegExp_static.set(this, void 0);
45
- _PartialMatchRegExp_dynamic.set(this, void 0);
46
- const compiled = compilePartial(this);
47
- _a = this, _b = this, [({ set value(_c) { __classPrivateFieldSet(_a, _PartialMatchRegExp_dynamic, _c, "f"); } }).value, ({ set value(_c) { __classPrivateFieldSet(_b, _PartialMatchRegExp_static, _c, "f"); } }).value] = compiled.kind === "dynamic"
48
- ? [compiled.dynamic, null]
49
- : [null, compiled.regex];
50
- this.features = compiled.features;
43
+ _PartialMatchRegExp_instances.add(this);
44
+ _PartialMatchRegExp_compiledPartial.set(this, void 0);
45
+ __classPrivateFieldSet(this, _PartialMatchRegExp_compiledPartial, compilePartial(this), "f");
46
+ this.features = __classPrivateFieldGet(this, _PartialMatchRegExp_compiledPartial, "f").features;
51
47
  }
52
48
  exec(input) {
53
- if (!__classPrivateFieldGet(this, _PartialMatchRegExp_dynamic, "f")) {
54
- const partial = __classPrivateFieldGet(this, _PartialMatchRegExp_static, "f"); // eslint-disable-line @typescript-eslint/no-non-null-assertion -- #static is set whenever #dynamic is null
55
- partial.lastIndex = this.lastIndex;
56
- const match = partial.exec(input);
57
- this.lastIndex = partial.lastIndex;
58
- return match;
59
- }
60
- const start = this.lastIndex;
61
- const originalMatch = super.exec(input);
62
- if (originalMatch)
63
- return originalMatch;
64
- const { originalCaptureScan, preScan, expand } = __classPrivateFieldGet(this, _PartialMatchRegExp_dynamic, "f");
65
- originalCaptureScan.lastIndex = start;
66
- let capture = originalCaptureScan.exec(input);
67
- if (capture === null) {
68
- preScan.lastIndex = start;
69
- capture = preScan.exec(input);
70
- }
71
- if (capture === null)
72
- return null;
73
- const expanded = new RegExp(expand(capture), this.flags);
74
- expanded.lastIndex = start;
75
- const match = expanded.exec(input);
76
- if (match === null)
77
- return null;
78
- preferLongerCaptures(match, capture);
79
- this.lastIndex = expanded.lastIndex;
49
+ const compiled = __classPrivateFieldGet(this, _PartialMatchRegExp_compiledPartial, "f");
50
+ if (compiled.kind === "dynamic")
51
+ return __classPrivateFieldGet(this, _PartialMatchRegExp_instances, "m", _PartialMatchRegExp_execDynamic).call(this, compiled.dynamic, input);
52
+ const { regex } = compiled;
53
+ const match = execFrom(regex, input, this.lastIndex);
54
+ this.lastIndex = regex.lastIndex;
80
55
  return match;
81
56
  }
82
57
  }
83
- _PartialMatchRegExp_static = new WeakMap(), _PartialMatchRegExp_dynamic = new WeakMap();
58
+ _PartialMatchRegExp_compiledPartial = new WeakMap(), _PartialMatchRegExp_instances = new WeakSet(), _PartialMatchRegExp_execDynamic = function _PartialMatchRegExp_execDynamic(dynamic, input) {
59
+ var _a, _b;
60
+ const { originalCaptureScan, preScan, expand } = dynamic;
61
+ const honoursLastIndex = this.global || this.sticky;
62
+ const start = honoursLastIndex ? this.lastIndex : 0;
63
+ const originalMatch = super.exec(input);
64
+ if (isAtOrBefore(originalMatch, start))
65
+ return originalMatch;
66
+ let preScanMatch = null;
67
+ if (originalMatch) {
68
+ preScanMatch = execFrom(preScan, input, start);
69
+ const noEarlierPartialPossible = preScanMatch === null || preScanMatch.index >= originalMatch.index;
70
+ if (noEarlierPartialPossible)
71
+ return originalMatch;
72
+ }
73
+ const capture = (_b = (_a = execFrom(originalCaptureScan, input, start)) !== null && _a !== void 0 ? _a : preScanMatch) !== null && _b !== void 0 ? _b : execFrom(preScan, input, start);
74
+ if (capture === null)
75
+ return originalMatch;
76
+ const expanded = new RegExp(expand(capture), this.flags);
77
+ const match = execFrom(expanded, input, start);
78
+ if (match === null || isAtOrBefore(originalMatch, match.index))
79
+ return originalMatch;
80
+ preferLongerCaptures(match, capture);
81
+ if (honoursLastIndex)
82
+ this.lastIndex = expanded.lastIndex;
83
+ return match;
84
+ };
85
+ function execFrom(regex, input, start) {
86
+ regex.lastIndex = start;
87
+ return regex.exec(input);
88
+ }
89
+ function isAtOrBefore(match, index) {
90
+ return match !== null && match.index <= index;
91
+ }
84
92
  function pickLonger(scanned, matched) {
85
93
  return scanned !== undefined &&
86
94
  matched !== undefined &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "regex-partial-match",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "A regular expression transform for partial matching",
5
5
  "devEngines": {
6
6
  "runtime": {