regex-partial-match 1.2.0 → 1.3.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 +146 -8
- package/lib/{extend.d.ts → extend/index.d.ts} +1 -1
- package/lib/{extend.js → extend/index.js} +1 -1
- package/lib/partialMatchRegExp/atomSyntax.d.ts +4 -0
- package/lib/partialMatchRegExp/atomSyntax.js +4 -0
- package/lib/partialMatchRegExp/backreferenceExpansion.d.ts +10 -0
- package/lib/partialMatchRegExp/backreferenceExpansion.js +1 -0
- package/lib/partialMatchRegExp/compilePartial/asNativeAtom.d.ts +2 -0
- package/lib/partialMatchRegExp/compilePartial/asNativeAtom.js +10 -0
- package/lib/partialMatchRegExp/compilePartial/asOptionalAtom.d.ts +1 -0
- package/lib/partialMatchRegExp/compilePartial/asOptionalAtom.js +4 -0
- package/lib/partialMatchRegExp/compilePartial/asPreScanPart.d.ts +2 -0
- package/lib/partialMatchRegExp/compilePartial/asPreScanPart.js +8 -0
- package/lib/partialMatchRegExp/compilePartial/caseFoldFlags.d.ts +1 -0
- package/lib/partialMatchRegExp/compilePartial/caseFoldFlags.js +3 -0
- package/lib/partialMatchRegExp/compilePartial/compiled.d.ts +29 -0
- package/lib/partialMatchRegExp/compilePartial/compiled.js +27 -0
- package/lib/partialMatchRegExp/compilePartial/flagsAtBackreference.d.ts +2 -0
- package/lib/partialMatchRegExp/compilePartial/flagsAtBackreference.js +4 -0
- package/lib/partialMatchRegExp/compilePartial/index.d.ts +2 -0
- package/lib/partialMatchRegExp/compilePartial/index.js +79 -0
- package/lib/partialMatchRegExp/compilePartial/longestBakedPrefixEndingInput.d.ts +1 -0
- package/lib/partialMatchRegExp/compilePartial/longestBakedPrefixEndingInput.js +50 -0
- package/lib/partialMatchRegExp/compilePartial/reclassifyLegacyEscapes.d.ts +2 -0
- package/lib/partialMatchRegExp/compilePartial/reclassifyLegacyEscapes.js +17 -0
- package/lib/partialMatchRegExp/compilePartial/renderParts.d.ts +2 -0
- package/lib/partialMatchRegExp/compilePartial/renderParts.js +9 -0
- package/lib/partialMatchRegExp/compilePartial/resolvedFromScan.d.ts +2 -0
- package/lib/partialMatchRegExp/compilePartial/resolvedFromScan.js +10 -0
- package/lib/partialMatchRegExp/compilePartial/spliceOriginalSource.d.ts +2 -0
- package/lib/partialMatchRegExp/compilePartial/spliceOriginalSource.js +12 -0
- package/lib/partialMatchRegExp/compilePartial/startsWithUnderFlags.d.ts +1 -0
- package/lib/partialMatchRegExp/compilePartial/startsWithUnderFlags.js +7 -0
- package/lib/partialMatchRegExp/compilePartial/toStatic.d.ts +3 -0
- package/lib/partialMatchRegExp/compilePartial/toStatic.js +4 -0
- package/lib/partialMatchRegExp/groupName.d.ts +2 -0
- package/lib/partialMatchRegExp/groupName.js +8 -0
- package/lib/partialMatchRegExp/index.d.ts +3 -0
- package/lib/partialMatchRegExp/index.js +3 -0
- package/lib/partialMatchRegExp/isComplete/index.d.ts +38 -0
- package/lib/partialMatchRegExp/isComplete/index.js +41 -0
- package/lib/partialMatchRegExp/isComplete/isMatchComplete.d.ts +3 -0
- package/lib/partialMatchRegExp/isComplete/isMatchComplete.js +14 -0
- package/lib/partialMatchRegExp/isComplete/partRole.d.ts +3 -0
- package/lib/partialMatchRegExp/isComplete/partRole.js +28 -0
- package/lib/partialMatchRegExp/isComplete/truncationProbe.d.ts +8 -0
- package/lib/partialMatchRegExp/isComplete/truncationProbe.js +105 -0
- package/lib/partialMatchRegExp/isComplete/truncationProbeCache.d.ts +4 -0
- package/lib/partialMatchRegExp/isComplete/truncationProbeCache.js +1 -0
- package/lib/partialMatchRegExp/legacyEscape.d.ts +2 -0
- package/lib/partialMatchRegExp/legacyEscape.js +18 -0
- package/lib/partialMatchRegExp/part.d.ts +24 -0
- package/lib/partialMatchRegExp/part.js +2 -0
- package/lib/partialMatchRegExp/partialMatchInternals.d.ts +2 -0
- package/lib/partialMatchRegExp/partialMatchInternals.js +2 -0
- package/lib/{partialMatchRegExp.d.ts → partialMatchRegExp/partialMatchRegExp.d.ts} +6 -3
- package/lib/{partialMatchRegExp.js → partialMatchRegExp/partialMatchRegExp.js} +23 -35
- package/lib/partialMatchRegExp/preferLongerCaptures.d.ts +1 -0
- package/lib/partialMatchRegExp/preferLongerCaptures.js +30 -0
- package/lib/partialMatchRegExp/regexFeatures.d.ts +6 -0
- package/lib/partialMatchRegExp/regexFeatures.js +47 -0
- package/lib/partialMatchRegExp/walk.d.ts +8 -0
- package/lib/{compilePartial.js → partialMatchRegExp/walk.js} +131 -162
- package/package.json +23 -13
- package/lib/compilePartial.d.ts +0 -26
- /package/lib/{escapeAtom.d.ts → partialMatchRegExp/escapeAtom.d.ts} +0 -0
- /package/lib/{escapeAtom.js → partialMatchRegExp/escapeAtom.js} +0 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NAMED_GROUP_OPENING } from "./atomSyntax.js";
|
|
2
|
+
const UNICODE_ESCAPE_IN_NAME_REGEX = /\\u\{([0-9a-fA-F]+)\}|\\u([0-9a-fA-F]{4})/g;
|
|
3
|
+
export const groupNameOf = (namedGroupOpening) => namedGroupOpening.slice(NAMED_GROUP_OPENING.length, -1);
|
|
4
|
+
export function decodeGroupName(rawName) {
|
|
5
|
+
return rawName.replace(UNICODE_ESCAPE_IN_NAME_REGEX, (_whole, braced, plain) =>
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- one of braced or plain is guaranteed to be defined by the regex
|
|
7
|
+
String.fromCodePoint(parseInt((braced !== null && braced !== void 0 ? braced : plain), 16)));
|
|
8
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type PartialMatchRegExp from "../partialMatchRegExp.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Whether `match` — a match `partial` produced — is a match of the original
|
|
4
|
+
* pattern, or merely a prefix of it.
|
|
5
|
+
*
|
|
6
|
+
* `true` means every atom matched literally, so the path the match took is
|
|
7
|
+
* one the original pattern could have taken itself. `false` means the match
|
|
8
|
+
* depended on the input running out: it took a `|$(?![\s\S])` truncation
|
|
9
|
+
* branch, so more input is needed and its captures are provisional.
|
|
10
|
+
*
|
|
11
|
+
* Complete is not the same as final: `/hello \w+/` matches `"hello world"`
|
|
12
|
+
* completely, and would match more of `"hello worldly"`.
|
|
13
|
+
*
|
|
14
|
+
* The implication runs one way only. The original pattern may also match at
|
|
15
|
+
* this index by some *other* path — yielding the same text, and even the
|
|
16
|
+
* same captures — so re-testing the original answers "does anything match
|
|
17
|
+
* here?", not "did this result depend on truncation?", and cannot recover
|
|
18
|
+
* this answer.
|
|
19
|
+
*
|
|
20
|
+
* @param partial - The `PartialMatchRegExp` instance that produced `match`
|
|
21
|
+
* @param match - A match returned by `partial.exec()`
|
|
22
|
+
* @returns `true` when the match is complete, `false` when it is a prefix
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* Requires ES2018+ regardless of the pattern — the probe this builds uses
|
|
26
|
+
* named capturing groups internally, unlike `exec()` and `test()`.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import PartialMatchRegExp, { isComplete } from "regex-partial-match";
|
|
31
|
+
*
|
|
32
|
+
* const partial = new PartialMatchRegExp(/^\d{4}-\d{2}-\d{2}/);
|
|
33
|
+
* const match = partial.exec('2024-06');
|
|
34
|
+
*
|
|
35
|
+
* match !== null && isComplete(partial, match); // false - keep typing
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export default function isComplete(partial: PartialMatchRegExp, match: RegExpExecArray): boolean;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { compiledPartial, truncationProbeCache } from "../partialMatchInternals.js";
|
|
2
|
+
import isMatchComplete from "./isMatchComplete.js";
|
|
3
|
+
/**
|
|
4
|
+
* Whether `match` — a match `partial` produced — is a match of the original
|
|
5
|
+
* pattern, or merely a prefix of it.
|
|
6
|
+
*
|
|
7
|
+
* `true` means every atom matched literally, so the path the match took is
|
|
8
|
+
* one the original pattern could have taken itself. `false` means the match
|
|
9
|
+
* depended on the input running out: it took a `|$(?![\s\S])` truncation
|
|
10
|
+
* branch, so more input is needed and its captures are provisional.
|
|
11
|
+
*
|
|
12
|
+
* Complete is not the same as final: `/hello \w+/` matches `"hello world"`
|
|
13
|
+
* completely, and would match more of `"hello worldly"`.
|
|
14
|
+
*
|
|
15
|
+
* The implication runs one way only. The original pattern may also match at
|
|
16
|
+
* this index by some *other* path — yielding the same text, and even the
|
|
17
|
+
* same captures — so re-testing the original answers "does anything match
|
|
18
|
+
* here?", not "did this result depend on truncation?", and cannot recover
|
|
19
|
+
* this answer.
|
|
20
|
+
*
|
|
21
|
+
* @param partial - The `PartialMatchRegExp` instance that produced `match`
|
|
22
|
+
* @param match - A match returned by `partial.exec()`
|
|
23
|
+
* @returns `true` when the match is complete, `false` when it is a prefix
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* Requires ES2018+ regardless of the pattern — the probe this builds uses
|
|
27
|
+
* named capturing groups internally, unlike `exec()` and `test()`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import PartialMatchRegExp, { isComplete } from "regex-partial-match";
|
|
32
|
+
*
|
|
33
|
+
* const partial = new PartialMatchRegExp(/^\d{4}-\d{2}-\d{2}/);
|
|
34
|
+
* const match = partial.exec('2024-06');
|
|
35
|
+
*
|
|
36
|
+
* match !== null && isComplete(partial, match); // false - keep typing
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export default function isComplete(partial, match) {
|
|
40
|
+
return isMatchComplete(partial[compiledPartial], match, partial.flags, partial[truncationProbeCache]);
|
|
41
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CompiledPartial } from "../compilePartial/compiled.ts";
|
|
2
|
+
import type { TruncationProbeCache } from "./truncationProbeCache.ts";
|
|
3
|
+
export default function isMatchComplete(compiled: CompiledPartial, match: RegExpExecArray, flags: string, cache: TruncationProbeCache): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { buildTruncationProbe, tookTruncationBranch } from "./truncationProbe.js";
|
|
2
|
+
import { backreferenceExpansion } from "../backreferenceExpansion.js";
|
|
3
|
+
export default function isMatchComplete(compiled, match, flags, cache) {
|
|
4
|
+
var _a, _b;
|
|
5
|
+
if (compiled.kind === "dynamic") {
|
|
6
|
+
const expansion = match[backreferenceExpansion];
|
|
7
|
+
if (expansion === undefined)
|
|
8
|
+
return true;
|
|
9
|
+
(_a = expansion.probe) !== null && _a !== void 0 ? _a : (expansion.probe = buildTruncationProbe(expansion.parts, compiled.rawLookarounds, compiled.namedGroupOpenings, flags));
|
|
10
|
+
return !tookTruncationBranch(expansion.probe, match.input, match.index);
|
|
11
|
+
}
|
|
12
|
+
(_b = cache.probe) !== null && _b !== void 0 ? _b : (cache.probe = buildTruncationProbe(compiled.parts, compiled.rawLookarounds, compiled.namedGroupOpenings, flags));
|
|
13
|
+
return !tookTruncationBranch(cache.probe, match.input, match.index);
|
|
14
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DISJUNCTION_TO_END_OF_INPUT, OPTIONAL_ATOM_OPENING, NAMED_GROUP_OPENING } from "../atomSyntax.js";
|
|
2
|
+
import { isBackreference } from "../part.js";
|
|
3
|
+
function isGroupOpen(part) {
|
|
4
|
+
return (part === "(" ||
|
|
5
|
+
(part.startsWith(NAMED_GROUP_OPENING) &&
|
|
6
|
+
!"=!".includes(part[NAMED_GROUP_OPENING.length])));
|
|
7
|
+
}
|
|
8
|
+
function isRawLookaroundOpen(part) {
|
|
9
|
+
return (part.startsWith("(?!") ||
|
|
10
|
+
part.startsWith(NAMED_GROUP_OPENING + "=") ||
|
|
11
|
+
part.startsWith(NAMED_GROUP_OPENING + "!"));
|
|
12
|
+
}
|
|
13
|
+
function endsAtTruncationBranch(part) {
|
|
14
|
+
return (part === DISJUNCTION_TO_END_OF_INPUT ||
|
|
15
|
+
(part.startsWith(OPTIONAL_ATOM_OPENING) &&
|
|
16
|
+
part.endsWith(DISJUNCTION_TO_END_OF_INPUT)));
|
|
17
|
+
}
|
|
18
|
+
export function roleOf(part) {
|
|
19
|
+
if (isBackreference(part))
|
|
20
|
+
return "backreference";
|
|
21
|
+
if (isRawLookaroundOpen(part))
|
|
22
|
+
return "rawLookaround";
|
|
23
|
+
if (isGroupOpen(part))
|
|
24
|
+
return "groupOpen";
|
|
25
|
+
if (endsAtTruncationBranch(part))
|
|
26
|
+
return "truncationEnd";
|
|
27
|
+
return "plain";
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Part, type RawLookaroundInfo } from "../part.ts";
|
|
2
|
+
export interface TruncationProbe {
|
|
3
|
+
regex: RegExp;
|
|
4
|
+
markerName: string;
|
|
5
|
+
markerCount: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const buildTruncationProbe: (parts: readonly Part[], rawLookarounds: readonly RawLookaroundInfo[], namedGroupOpenings: readonly string[], flags: string) => TruncationProbe;
|
|
8
|
+
export declare const tookTruncationBranch: (probe: TruncationProbe, input: string, index: number) => boolean;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { legacyEscapeAsLiteral } from "../legacyEscape.js";
|
|
2
|
+
import { DISJUNCTION_TO_END_OF_INPUT, OPTIONAL_ATOM_OPENING } from "../atomSyntax.js";
|
|
3
|
+
import { groupNameOf, decodeGroupName } from "../groupName.js";
|
|
4
|
+
import { isBackreference, isNumericBackreference } from "../part.js";
|
|
5
|
+
import { roleOf } from "./partRole.js";
|
|
6
|
+
const TRUNCATION_MARKER_NAME = "truncation";
|
|
7
|
+
const FLAGS_INCOMPATIBLE_WITH_PROBING = /[dgy]/g;
|
|
8
|
+
function groupShiftTable(rawLookarounds, roles) {
|
|
9
|
+
const shiftForGroup = [0];
|
|
10
|
+
let markerCount = 0;
|
|
11
|
+
let rawLookaroundIndex = 0;
|
|
12
|
+
for (const role of roles) {
|
|
13
|
+
switch (role) {
|
|
14
|
+
case "backreference":
|
|
15
|
+
case "truncationEnd":
|
|
16
|
+
markerCount++;
|
|
17
|
+
break;
|
|
18
|
+
case "groupOpen":
|
|
19
|
+
shiftForGroup.push(markerCount);
|
|
20
|
+
break;
|
|
21
|
+
case "rawLookaround": {
|
|
22
|
+
const { capturingGroupsOpened } = rawLookarounds[rawLookaroundIndex++];
|
|
23
|
+
for (let i = 0; i < capturingGroupsOpened; i++) {
|
|
24
|
+
shiftForGroup.push(markerCount);
|
|
25
|
+
}
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case "plain":
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return shiftForGroup;
|
|
33
|
+
}
|
|
34
|
+
function renumberedToken(backreference, shiftForGroup) {
|
|
35
|
+
return isNumericBackreference(backreference)
|
|
36
|
+
? "\\" + String(backreference.ref + shiftForGroup[backreference.ref])
|
|
37
|
+
: "\\k<" + backreference.ref + ">";
|
|
38
|
+
}
|
|
39
|
+
function renumberRawBackreferences(part, info, shiftForGroup, declaresNamedGroup) {
|
|
40
|
+
let renumbered = "";
|
|
41
|
+
let cursor = 0;
|
|
42
|
+
for (const backreference of info.backreferences) {
|
|
43
|
+
const relativeStart = backreference.start - info.sourceStart;
|
|
44
|
+
const relativeEnd = backreference.end - info.sourceStart;
|
|
45
|
+
renumbered +=
|
|
46
|
+
part.slice(cursor, relativeStart) +
|
|
47
|
+
rawReferenceReplacement(part.slice(relativeStart, relativeEnd), backreference, shiftForGroup, declaresNamedGroup);
|
|
48
|
+
cursor = relativeEnd;
|
|
49
|
+
}
|
|
50
|
+
return renumbered + part.slice(cursor);
|
|
51
|
+
}
|
|
52
|
+
function rawReferenceReplacement(spelling, backreference, shiftForGroup, declaresNamedGroup) {
|
|
53
|
+
if (isNumericBackreference(backreference)) {
|
|
54
|
+
return backreference.ref >= 1 && backreference.ref < shiftForGroup.length
|
|
55
|
+
? "\\" + String(backreference.ref + shiftForGroup[backreference.ref])
|
|
56
|
+
: legacyEscapeAsLiteral(spelling.slice(1));
|
|
57
|
+
}
|
|
58
|
+
return declaresNamedGroup ? spelling : "k" + spelling.slice(2);
|
|
59
|
+
}
|
|
60
|
+
export const buildTruncationProbe = (parts, rawLookarounds, namedGroupOpenings, flags) => {
|
|
61
|
+
const declaresNamedGroup = namedGroupOpenings.length > 0;
|
|
62
|
+
const declaredNames = namedGroupOpenings.map((opening) => decodeGroupName(groupNameOf(opening)));
|
|
63
|
+
let markerName = TRUNCATION_MARKER_NAME;
|
|
64
|
+
while (declaredNames.some((name) => name.startsWith(markerName))) {
|
|
65
|
+
markerName += "_";
|
|
66
|
+
}
|
|
67
|
+
const roles = parts.map(roleOf);
|
|
68
|
+
const shiftForGroup = groupShiftTable(rawLookarounds, roles);
|
|
69
|
+
let markerCount = 0;
|
|
70
|
+
let rawLookaroundIndex = 0;
|
|
71
|
+
const probed = parts.map((part, index) => {
|
|
72
|
+
if (isBackreference(part)) {
|
|
73
|
+
const marker = "|(?<" + markerName + String(markerCount++) + ">)";
|
|
74
|
+
return (OPTIONAL_ATOM_OPENING +
|
|
75
|
+
renumberedToken(part, shiftForGroup) +
|
|
76
|
+
marker +
|
|
77
|
+
DISJUNCTION_TO_END_OF_INPUT.slice(1));
|
|
78
|
+
}
|
|
79
|
+
if (roles[index] === "rawLookaround") {
|
|
80
|
+
return renumberRawBackreferences(part, rawLookarounds[rawLookaroundIndex++], shiftForGroup, declaresNamedGroup);
|
|
81
|
+
}
|
|
82
|
+
if (roles[index] !== "truncationEnd")
|
|
83
|
+
return part;
|
|
84
|
+
const marker = "|(?<" + markerName + String(markerCount++) + ">)";
|
|
85
|
+
return (part.slice(0, -DISJUNCTION_TO_END_OF_INPUT.length) +
|
|
86
|
+
marker +
|
|
87
|
+
DISJUNCTION_TO_END_OF_INPUT.slice(1));
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
regex: new RegExp(probed.join(""), flags.replace(FLAGS_INCOMPATIBLE_WITH_PROBING, "") + "y"),
|
|
91
|
+
markerName,
|
|
92
|
+
markerCount
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
export const tookTruncationBranch = (probe, input, index) => {
|
|
96
|
+
var _a, _b;
|
|
97
|
+
const { regex, markerName, markerCount } = probe;
|
|
98
|
+
regex.lastIndex = index;
|
|
99
|
+
const markers = (_b = (_a = regex.exec(input)) === null || _a === void 0 ? void 0 : _a.groups) !== null && _b !== void 0 ? _b : {};
|
|
100
|
+
for (let marker = 0; marker < markerCount; marker++) {
|
|
101
|
+
if (markers[markerName + String(marker)] !== undefined)
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const LEGACY_OCTAL_ESCAPE_REGEX = /^(?:[0-3][0-7]{0,2}|[4-7][0-7]?)/;
|
|
2
|
+
const hexEscape = (codeUnit) => "\\x" + (codeUnit < 0x10 ? "0" : "") + codeUnit.toString(16);
|
|
3
|
+
export function legacyEscapeAsLiteral(digits) {
|
|
4
|
+
return legacyEscapeAtoms(digits).join("");
|
|
5
|
+
}
|
|
6
|
+
export function legacyEscapeAtoms(digits) {
|
|
7
|
+
var _a;
|
|
8
|
+
const octal = (_a = LEGACY_OCTAL_ESCAPE_REGEX.exec(digits)) === null || _a === void 0 ? void 0 : _a[0];
|
|
9
|
+
const atoms = [];
|
|
10
|
+
let index = 0;
|
|
11
|
+
if (octal !== undefined) {
|
|
12
|
+
atoms.push(hexEscape(parseInt(octal, 8)));
|
|
13
|
+
index = octal.length;
|
|
14
|
+
}
|
|
15
|
+
for (; index < digits.length; index++)
|
|
16
|
+
atoms.push(digits[index]);
|
|
17
|
+
return atoms;
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
interface NumericBackreference {
|
|
2
|
+
ref: number;
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
forward?: boolean;
|
|
6
|
+
caseInsensitive: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface NamedBackreference {
|
|
9
|
+
ref: string;
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
forward?: boolean;
|
|
13
|
+
caseInsensitive: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type Backreference = NumericBackreference | NamedBackreference;
|
|
16
|
+
export type Part = string | Backreference;
|
|
17
|
+
export interface RawLookaroundInfo {
|
|
18
|
+
sourceStart: number;
|
|
19
|
+
capturingGroupsOpened: number;
|
|
20
|
+
backreferences: Backreference[];
|
|
21
|
+
}
|
|
22
|
+
export declare const isBackreference: (part: Part) => part is Backreference;
|
|
23
|
+
export declare const isNumericBackreference: (part: Part) => part is NumericBackreference;
|
|
24
|
+
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CompiledPartial } from "./compilePartial/compiled.ts";
|
|
2
|
+
import { compiledPartial, truncationProbeCache } from "./partialMatchInternals.ts";
|
|
3
|
+
import type { TruncationProbeCache } from "./isComplete/truncationProbeCache.ts";
|
|
4
|
+
import type { RegexFeature } from "./regexFeatures.ts";
|
|
2
5
|
export type { RegexFeature };
|
|
3
|
-
declare const compiledPartial: unique symbol;
|
|
4
6
|
/**
|
|
5
7
|
* A `RegExp` subclass that supports partial (prefix) matching.
|
|
6
8
|
*
|
|
@@ -28,7 +30,8 @@ declare const compiledPartial: unique symbol;
|
|
|
28
30
|
* @see {@link https://github.com/TomStrepsil/regex-partial-match#readme | Documentation}
|
|
29
31
|
*/
|
|
30
32
|
declare class PartialMatchRegExp extends RegExp {
|
|
31
|
-
|
|
33
|
+
[compiledPartial]: CompiledPartial;
|
|
34
|
+
[truncationProbeCache]: TruncationProbeCache;
|
|
32
35
|
constructor(pattern: RegExp | string, flags?: string);
|
|
33
36
|
/**
|
|
34
37
|
* The syntactic constructs the original pattern uses, recorded as a side
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import compilePartial from "./compilePartial/index.js";
|
|
2
|
+
import renderParts from "./compilePartial/renderParts.js";
|
|
3
|
+
import { backreferenceExpansion } from "./backreferenceExpansion.js";
|
|
4
|
+
import { compiledPartial, truncationProbeCache } from "./partialMatchInternals.js";
|
|
5
|
+
import { preferLongerCaptures } from "./preferLongerCaptures.js";
|
|
3
6
|
/**
|
|
4
7
|
* A `RegExp` subclass that supports partial (prefix) matching.
|
|
5
8
|
*
|
|
@@ -30,6 +33,7 @@ class PartialMatchRegExp extends RegExp {
|
|
|
30
33
|
constructor(pattern, flags) {
|
|
31
34
|
super(pattern, flags);
|
|
32
35
|
this[compiledPartial] = compilePartial(this);
|
|
36
|
+
this[truncationProbeCache] = { probe: undefined };
|
|
33
37
|
}
|
|
34
38
|
/**
|
|
35
39
|
* The syntactic constructs the original pattern uses, recorded as a side
|
|
@@ -60,7 +64,7 @@ class PartialMatchRegExp extends RegExp {
|
|
|
60
64
|
}
|
|
61
65
|
_execDynamic(dynamic, input) {
|
|
62
66
|
var _a, _b;
|
|
63
|
-
const { originalCaptureScan, preScan, expand } = dynamic;
|
|
67
|
+
const { originalCaptureScan, preScan, expand, expansionFitsCaptures } = dynamic;
|
|
64
68
|
const honoursLastIndex = this.global || this.sticky;
|
|
65
69
|
const start = honoursLastIndex ? this.lastIndex : 0;
|
|
66
70
|
const originalMatch = super.exec(input);
|
|
@@ -76,13 +80,27 @@ class PartialMatchRegExp extends RegExp {
|
|
|
76
80
|
const capture = (_b = (_a = execFrom(originalCaptureScan, input, start)) !== null && _a !== void 0 ? _a : preScanMatch) !== null && _b !== void 0 ? _b : execFrom(preScan, input, start);
|
|
77
81
|
if (capture === null)
|
|
78
82
|
return originalMatch;
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
let expandedFrom = capture;
|
|
84
|
+
let expandedParts = expand(expandedFrom);
|
|
85
|
+
let expanded = new RegExp(renderParts(expandedParts), this.flags);
|
|
86
|
+
let match = execFrom(expanded, input, start);
|
|
87
|
+
if (match !== null && !expansionFitsCaptures(expandedFrom, match, input)) {
|
|
88
|
+
expandedFrom = match;
|
|
89
|
+
expandedParts = expand(expandedFrom);
|
|
90
|
+
expanded = new RegExp(renderParts(expandedParts), this.flags);
|
|
91
|
+
match = execFrom(expanded, input, start);
|
|
92
|
+
if (match !== null && !expansionFitsCaptures(expandedFrom, match, input))
|
|
93
|
+
match = null;
|
|
94
|
+
}
|
|
81
95
|
if (match === null || isAtOrBefore(originalMatch, match.index))
|
|
82
96
|
return originalMatch;
|
|
83
97
|
preferLongerCaptures(match, capture);
|
|
84
98
|
if (honoursLastIndex)
|
|
85
99
|
this.lastIndex = expanded.lastIndex;
|
|
100
|
+
match[backreferenceExpansion] = {
|
|
101
|
+
parts: expandedParts,
|
|
102
|
+
probe: undefined
|
|
103
|
+
};
|
|
86
104
|
return match;
|
|
87
105
|
}
|
|
88
106
|
}
|
|
@@ -93,34 +111,4 @@ function execFrom(regex, input, start) {
|
|
|
93
111
|
function isAtOrBefore(match, index) {
|
|
94
112
|
return match !== null && match.index <= index;
|
|
95
113
|
}
|
|
96
|
-
function pickLonger(scanned, matched) {
|
|
97
|
-
return scanned !== undefined &&
|
|
98
|
-
matched !== undefined &&
|
|
99
|
-
scanned.length > matched.length
|
|
100
|
-
? scanned
|
|
101
|
-
: undefined;
|
|
102
|
-
}
|
|
103
|
-
function preferLongerCaptures(match, scanned) {
|
|
104
|
-
var _a, _b, _c, _d;
|
|
105
|
-
for (let index = 1; index < match.length; index++) {
|
|
106
|
-
const longer = pickLonger(scanned[index], match[index]);
|
|
107
|
-
if (longer === undefined)
|
|
108
|
-
continue;
|
|
109
|
-
match[index] = longer;
|
|
110
|
-
if (match.indices && ((_a = scanned.indices) === null || _a === void 0 ? void 0 : _a[index])) {
|
|
111
|
-
match.indices[index] = scanned.indices[index];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (match.groups && scanned.groups) {
|
|
115
|
-
for (const name of Object.keys(match.groups)) {
|
|
116
|
-
const longer = pickLonger(scanned.groups[name], match.groups[name]);
|
|
117
|
-
if (longer === undefined)
|
|
118
|
-
continue;
|
|
119
|
-
match.groups[name] = longer;
|
|
120
|
-
if ((_b = match.indices) === null || _b === void 0 ? void 0 : _b.groups) {
|
|
121
|
-
match.indices.groups[name] = (_d = (_c = scanned.indices) === null || _c === void 0 ? void 0 : _c.groups) === null || _d === void 0 ? void 0 : _d[name];
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
114
|
export default PartialMatchRegExp;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function preferLongerCaptures(match: RegExpExecArray, scanned: RegExpExecArray): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function pickLonger(scanned, matched) {
|
|
2
|
+
return scanned !== undefined &&
|
|
3
|
+
matched !== undefined &&
|
|
4
|
+
scanned.length > matched.length
|
|
5
|
+
? scanned
|
|
6
|
+
: undefined;
|
|
7
|
+
}
|
|
8
|
+
export function preferLongerCaptures(match, scanned) {
|
|
9
|
+
var _a, _b, _c, _d;
|
|
10
|
+
for (let index = 1; index < match.length; index++) {
|
|
11
|
+
const longer = pickLonger(scanned[index], match[index]);
|
|
12
|
+
if (longer === undefined)
|
|
13
|
+
continue;
|
|
14
|
+
match[index] = longer;
|
|
15
|
+
if (match.indices && ((_a = scanned.indices) === null || _a === void 0 ? void 0 : _a[index])) {
|
|
16
|
+
match.indices[index] = scanned.indices[index];
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (match.groups && scanned.groups) {
|
|
20
|
+
for (const name of Object.keys(match.groups)) {
|
|
21
|
+
const longer = pickLonger(scanned.groups[name], match.groups[name]);
|
|
22
|
+
if (longer === undefined)
|
|
23
|
+
continue;
|
|
24
|
+
match.groups[name] = longer;
|
|
25
|
+
if ((_b = match.indices) === null || _b === void 0 ? void 0 : _b.groups) {
|
|
26
|
+
match.indices.groups[name] = (_d = (_c = scanned.indices) === null || _c === void 0 ? void 0 : _c.groups) === null || _d === void 0 ? void 0 : _d[name];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
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];
|
|
3
|
+
export declare const FEATURE_BIT: Record<RegexFeature, number>;
|
|
4
|
+
export declare function hasFeature(mask: number, feature: RegexFeature): boolean;
|
|
5
|
+
export declare function featureSet(mask: number): Set<RegexFeature>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const REGEX_FEATURES = [
|
|
2
|
+
"patternCharacter",
|
|
3
|
+
"startAnchor",
|
|
4
|
+
"endAnchor",
|
|
5
|
+
"wordBoundary",
|
|
6
|
+
"nonWordBoundary",
|
|
7
|
+
"lookahead",
|
|
8
|
+
"negativeLookahead",
|
|
9
|
+
"lookbehind",
|
|
10
|
+
"negativeLookbehind",
|
|
11
|
+
"backreference",
|
|
12
|
+
"namedBackreference",
|
|
13
|
+
"namedGroup",
|
|
14
|
+
"capturingGroup",
|
|
15
|
+
"lookaroundCapture",
|
|
16
|
+
"nonCapturingGroup",
|
|
17
|
+
"modifierGroup",
|
|
18
|
+
"modifierGroupWithRemoval",
|
|
19
|
+
"characterClass",
|
|
20
|
+
"nestedCharacterClass",
|
|
21
|
+
"classIntersection",
|
|
22
|
+
"classSubtraction",
|
|
23
|
+
"disjunction",
|
|
24
|
+
"quantifier",
|
|
25
|
+
"unicodePropertyEscape",
|
|
26
|
+
"characterClassEscape",
|
|
27
|
+
"controlEscape",
|
|
28
|
+
"controlLetterEscape",
|
|
29
|
+
"hexEscapeSequence",
|
|
30
|
+
"unicodeEscapeSequence",
|
|
31
|
+
"otherEscape"
|
|
32
|
+
];
|
|
33
|
+
export const FEATURE_BIT = {};
|
|
34
|
+
for (let index = 0; index < REGEX_FEATURES.length; index++) {
|
|
35
|
+
FEATURE_BIT[REGEX_FEATURES[index]] = 1 << index;
|
|
36
|
+
}
|
|
37
|
+
export function hasFeature(mask, feature) {
|
|
38
|
+
return (mask & FEATURE_BIT[feature]) !== 0;
|
|
39
|
+
}
|
|
40
|
+
export function featureSet(mask) {
|
|
41
|
+
const features = new Set();
|
|
42
|
+
for (let index = 0; index < REGEX_FEATURES.length; index++) {
|
|
43
|
+
if (mask & (1 << index))
|
|
44
|
+
features.add(REGEX_FEATURES[index]);
|
|
45
|
+
}
|
|
46
|
+
return features;
|
|
47
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Part, RawLookaroundInfo } from "./part.ts";
|
|
2
|
+
export declare function walk(regex: RegExp, declaresNamedGroup: boolean): {
|
|
3
|
+
parts: Part[];
|
|
4
|
+
groupCount: number;
|
|
5
|
+
featureMask: number;
|
|
6
|
+
rawLookarounds: readonly RawLookaroundInfo[];
|
|
7
|
+
namedGroupOpenings: readonly string[];
|
|
8
|
+
};
|