Skip to content

Commit 2522ede

Browse files
committed
Improve error messages
1 parent a72c46f commit 2522ede

File tree

4 files changed

+87
-9
lines changed

4 files changed

+87
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
**Fixes**
66

7-
- Fixed decoding of JSONPath escape sequences (those found in name selectors and string literals). Previously we were relying on `JSON.parse()` to unescape strings containing escape sequences, now we have our own `unescapeString()` function that rejects invalid surrogate pairs. See [jsonpath-compliance-test-suite #87](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite/pull/87).
7+
- Fixed decoding of JSONPath escape sequences (those found in name selectors and string literals). Previously we were relying on `JSON.parse()` to unescape strings, now we have our own `unescapeString()` function that rejects invalid codepoints and surrogate pairs. See [jsonpath-compliance-test-suite #87](https://github.com/jsonpath-standard/jsonpath-compliance-test-suite/pull/87).
88

99
## Version 1.3.3
1010

src/path/parse.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ export class Parser {
548548
const ch = value[index];
549549
if (ch === "\\") {
550550
// Handle escape sequences
551-
index += 1;
551+
index += 1; // Move past '/'
552552

553553
switch (value[index]) {
554554
case '"':
@@ -580,7 +580,11 @@ export class Parser {
580580
rv.push(this.stringFromCodePoint(codepoint, token));
581581
break;
582582
default:
583-
throw new JSONPathSyntaxError("invalid escape sequence", token);
583+
// TODO: This is unreachable. The lexer will catch unknown escape sequences.
584+
throw new JSONPathSyntaxError(
585+
`unknown escape sequence at index ${token.index + index - 1}`,
586+
token,
587+
);
584588
}
585589
} else {
586590
this.stringFromCodePoint(ch.codePointAt(0), token);
@@ -609,14 +613,20 @@ export class Parser {
609613
const length = value.length;
610614

611615
if (index + 4 >= length) {
612-
throw new JSONPathSyntaxError("invalid escape sequence", token);
616+
throw new JSONPathSyntaxError(
617+
`incomplete escape sequence at index ${token.index + index - 1}`,
618+
token,
619+
);
613620
}
614621

615622
index += 1; // Move past 'u'
616623
let codepoint = this.parseInt16(value.slice(index, index + 4), token);
617624

618625
if (isLowSurrogate(codepoint)) {
619-
throw new JSONPathSyntaxError("invalid escape sequence", token);
626+
throw new JSONPathSyntaxError(
627+
`unexpected low surrogate codepoint at index ${token.index + index - 2}`,
628+
token,
629+
);
620630
}
621631

622632
if (isHighSurrogate(codepoint)) {
@@ -628,7 +638,10 @@ export class Parser {
628638
value[index + 5] === "u"
629639
)
630640
) {
631-
throw new JSONPathSyntaxError("invalid escape sequence", token);
641+
throw new JSONPathSyntaxError(
642+
`incomplete escape sequence at index ${token.index + index - 2}`,
643+
token,
644+
);
632645
}
633646

634647
const lowSurrogate = this.parseInt16(
@@ -637,7 +650,10 @@ export class Parser {
637650
);
638651

639652
if (!isLowSurrogate(lowSurrogate)) {
640-
throw new JSONPathSyntaxError("invalid escape sequence", token);
653+
throw new JSONPathSyntaxError(
654+
`unexpected codepoint at index ${token.index + index + 4}`,
655+
token,
656+
);
641657
}
642658

643659
codepoint =
@@ -695,7 +711,7 @@ export class Parser {
695711
break;
696712
default:
697713
throw new JSONPathSyntaxError(
698-
`invalid \\uXXXX escape sequence (${digit})`,
714+
"invalid \\uXXXX escape sequence",
699715
token,
700716
);
701717
}
@@ -715,6 +731,7 @@ export class Parser {
715731
try {
716732
return String.fromCodePoint(codepoint);
717733
} catch {
734+
// This should not be reachable.
718735
throw new JSONPathSyntaxError("invalid escape sequence", token);
719736
}
720737
}

tests/path/cts

tests/path/errors.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,64 @@ describe("filter expression EOF", () => {
123123
);
124124
});
125125
});
126+
127+
describe("escape sequence decode errors", () => {
128+
const env = new JSONPathEnvironment();
129+
130+
test("unknown escape sequence", () => {
131+
const query = String.raw`$['ab\xc']`;
132+
// From the lexer
133+
expect(() => env.query(query, {})).toThrow(JSONPathSyntaxError);
134+
expect(() => env.query(query, {})).toThrow(
135+
"invalid escape ('['ab\\xc']':6)",
136+
);
137+
});
138+
139+
test("incomplete \\u escape sequence at end of string", () => {
140+
const query = String.raw`$['abc\u263']`;
141+
expect(() => env.query(query, {})).toThrow(JSONPathSyntaxError);
142+
expect(() => env.query(query, {})).toThrow(
143+
"incomplete escape sequence at index 6 ('$['abc\\u2':3)",
144+
);
145+
});
146+
147+
test("incomplete surrogate pair at end of string", () => {
148+
const query = String.raw`$['abc\uD83D\uDE0']`;
149+
expect(() => env.query(query, {})).toThrow(JSONPathSyntaxError);
150+
expect(() => env.query(query, {})).toThrow(
151+
"incomplete escape sequence at index 6 ('$['abc\\uD':3)",
152+
);
153+
});
154+
155+
test("high high surrogate pair", () => {
156+
const query = String.raw`$['ab\uD800\uD800c']`;
157+
expect(() => env.query(query, {})).toThrow(JSONPathSyntaxError);
158+
expect(() => env.query(query, {})).toThrow(
159+
"unexpected codepoint at index 11 ('$['ab\\uD8':3)",
160+
);
161+
});
162+
163+
test("high surrogate followed by non-surrogate", () => {
164+
const query = String.raw`$['ab\uD800\u263Ac']`;
165+
expect(() => env.query(query, {})).toThrow(JSONPathSyntaxError);
166+
expect(() => env.query(query, {})).toThrow(
167+
"unexpected codepoint at index 11 ('$['ab\\uD8':3)",
168+
);
169+
});
170+
171+
test("just a low surrogate", () => {
172+
const query = String.raw`$['ab\uDC00c']`;
173+
expect(() => env.query(query, {})).toThrow(JSONPathSyntaxError);
174+
expect(() => env.query(query, {})).toThrow(
175+
"unexpected low surrogate codepoint at index 5 ('$['ab\\uDC':3)",
176+
);
177+
});
178+
179+
test("non-hex digits", () => {
180+
const query = String.raw`$['ab\u263Xc']`;
181+
expect(() => env.query(query, {})).toThrow(JSONPathSyntaxError);
182+
expect(() => env.query(query, {})).toThrow(
183+
"invalid \\uXXXX escape sequence ('$['ab\\u26':3)",
184+
);
185+
});
186+
});

0 commit comments

Comments
 (0)