Skip to content

Commit 52c4514

Browse files
authored
Merge pull request #19 from jg-rp/i-regexp
Map JS RegExp to I-Regexp
2 parents 8ea4cde + e32b90e commit 52c4514

File tree

6 files changed

+98
-7
lines changed

6 files changed

+98
-7
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# JSON P3 Change Log
22

3+
## Version 1.3.1 (unreleased)
4+
5+
**Fixes**
6+
7+
- Fixed RegExp to I-Regex pattern mapping with the `match` and `search` filter functions. We now correctly match the special `.` character to everything other than `\r` and `\n`.
8+
39
## Version 1.3.0
410

511
**Fixes**

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "json-p3",
3-
"version": "1.3.0",
3+
"version": "1.3.1",
44
"author": "James Prior",
55
"license": "MIT",
66
"description": "JSONPath, JSON Pointer and JSON Patch",

src/path/functions/match.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,56 @@ export class Match implements FilterFunction {
5858

5959
protected fullMatch(pattern: string): string {
6060
const parts: string[] = [];
61-
if (!pattern.startsWith("^")) parts.push("^");
62-
parts.push(pattern);
63-
if (!pattern.endsWith("$")) parts.push("$");
61+
let nonCaptureGroup = false;
62+
63+
if (!pattern.startsWith("^") && !pattern.startsWith("^(")) {
64+
nonCaptureGroup = true;
65+
parts.push("^(?:");
66+
}
67+
parts.push(this.mapRegexp(pattern));
68+
69+
if (nonCaptureGroup && !pattern.endsWith("$") && !pattern.endsWith(")$")) {
70+
parts.push(")$");
71+
}
72+
73+
return parts.join("");
74+
}
75+
76+
// See https://datatracker.ietf.org/doc/html/rfc9485#name-ecmascript-regexps
77+
protected mapRegexp(pattern: string): string {
78+
let escaped = false;
79+
let charClass = false;
80+
const parts: string[] = [];
81+
for (const ch of pattern) {
82+
switch (ch) {
83+
case ".":
84+
if (!escaped && !charClass) {
85+
parts.push("(?:(?![\r\n])\\P{Cs}|\\p{Cs}\\p{Cs})");
86+
} else {
87+
parts.push(ch);
88+
escaped = false;
89+
}
90+
break;
91+
case "\\":
92+
escaped = true;
93+
parts.push(ch);
94+
break;
95+
case "[":
96+
charClass = true;
97+
escaped = false;
98+
parts.push(ch);
99+
break;
100+
case "]":
101+
charClass = false;
102+
escaped = false;
103+
parts.push(ch);
104+
break;
105+
default:
106+
escaped = false;
107+
parts.push(ch);
108+
break;
109+
}
110+
}
64111
return parts.join("");
65112
}
66113
}

src/path/functions/search.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,50 @@ export class Search implements FilterFunction {
4848
}
4949

5050
try {
51-
const re = new RegExp(pattern, "u");
51+
const re = new RegExp(this.mapRegexp(pattern), "u");
5252
if (this.cacheSize > 0) this.#cache.set(pattern, re);
5353
return !!s.match(re);
5454
} catch (error) {
5555
if (this.throwErrors) throw error;
5656
return false;
5757
}
5858
}
59+
60+
// See https://datatracker.ietf.org/doc/html/rfc9485#name-ecmascript-regexps
61+
protected mapRegexp(pattern: string): string {
62+
let escaped = false;
63+
let charClass = false;
64+
const parts: string[] = [];
65+
for (const ch of pattern) {
66+
switch (ch) {
67+
case ".":
68+
if (!escaped && !charClass) {
69+
parts.push("(?:(?![\r\n])\\P{Cs}|\\p{Cs}\\p{Cs})");
70+
} else {
71+
parts.push(ch);
72+
escaped = false;
73+
}
74+
break;
75+
case "\\":
76+
escaped = true;
77+
parts.push(ch);
78+
break;
79+
case "[":
80+
charClass = true;
81+
escaped = false;
82+
parts.push(ch);
83+
break;
84+
case "]":
85+
charClass = false;
86+
escaped = false;
87+
parts.push(ch);
88+
break;
89+
default:
90+
escaped = false;
91+
parts.push(ch);
92+
break;
93+
}
94+
}
95+
return parts.join("");
96+
}
5997
}

src/path/lex.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ function lexInsideBracketedSelection(l: Lexer): StateFn | null {
367367
}
368368

369369
if (!l.environment.strict && l.acceptMatchRun(l.environment.keysPattern)) {
370+
// FIXME: fall back to legacy behavior if keysPattern is not the default
370371
switch (l.peek()) {
371372
case "'":
372373
l.ignore(); // ~
@@ -377,7 +378,6 @@ function lexInsideBracketedSelection(l: Lexer): StateFn | null {
377378
l.next();
378379
return lexDoubleQuoteKeyString(l);
379380
case "?":
380-
l.ignore(); // ~
381381
l.next();
382382
l.emit(TokenKind.KEYS_FILTER);
383383
l.filterLevel += 1;

0 commit comments

Comments
 (0)