diff --git a/CHANGELOG.md b/CHANGELOG.md index 484150d..8ed1bbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # JSON P3 Change Log -## Version 1.3.4 +## Version 1.3.4 (unreleased) **Fixes** - 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). +- Fixed default minimum integer boundary for JSONPath indexes and slice steps. We were off by one. +- Fixed parsing of JSONPath integer literals with an exponent and an upper case 'e'. We now allow 'e' to be upper case. +- Fixed handling of trailing commas in JSONPath bracketed segments. We now raise a syntax error. +- Fixed handling of invalid JSONPath integer and float literals with extra minus signs, leading zeros or too many zeros. We now raise a syntax error in such cases. ## Version 1.3.3 diff --git a/src/path/environment.ts b/src/path/environment.ts index fcf40b3..747baa6 100644 --- a/src/path/environment.ts +++ b/src/path/environment.ts @@ -129,7 +129,7 @@ export class JSONPathEnvironment { constructor(options: JSONPathEnvironmentOptions = {}) { this.strict = options.strict ?? true; this.maxIntIndex = options.maxIntIndex ?? Math.pow(2, 53) - 1; - this.minIntIndex = options.maxIntIndex ?? -Math.pow(2, 53) - 1; + this.minIntIndex = options.maxIntIndex ?? -Math.pow(2, 53) + 1; this.maxRecursionDepth = options.maxRecursionDepth ?? 50; this.nondeterministic = options.nondeterministic ?? false; this.keysPattern = options.keysPattern ?? /~/y; diff --git a/src/path/lex.ts b/src/path/lex.ts index a76bd22..1a2f433 100644 --- a/src/path/lex.ts +++ b/src/path/lex.ts @@ -5,7 +5,7 @@ import { Token, TokenKind } from "./token"; // These regular expressions are to be used with Lexer.acceptMatchRun(), // which expects the sticky flag to be set. -const exponentPattern = /e[+-]?\d+/y; +const exponentPattern = /[eE][+-]?\d+/y; const functionNamePattern = /[a-z][a-z_0-9]*/y; const indexPattern = /-?\d+/y; const intPattern = /-?[0-9]+/y; diff --git a/src/path/parse.ts b/src/path/parse.ts index c100b84..0ccd019 100644 --- a/src/path/parse.ts +++ b/src/path/parse.ts @@ -304,6 +304,7 @@ export class Parser { if (stream.peek.kind !== TokenKind.RBRACKET) { stream.expectPeek(TokenKind.COMMA); stream.next(); + stream.expectPeekNot(TokenKind.RBRACKET, "unexpected trailing comma"); } stream.next(); @@ -362,7 +363,23 @@ export class Parser { } protected parseNumber(stream: TokenStream): NumberLiteral { - return new NumberLiteral(stream.current, Number(stream.current.value)); + const value = stream.current.value; + if (value.startsWith("0") && value.length > 1) { + throw new JSONPathSyntaxError( + `invalid number literal '${value}'`, + stream.current, + ); + } + + const num = Number(stream.current.value); + + if (isNaN(num)) { + throw new JSONPathSyntaxError( + `invalid number literal '${value}'`, + stream.current, + ); + } + return new NumberLiteral(stream.current, num); } protected parsePrefixExpression(stream: TokenStream): PrefixExpression { diff --git a/src/path/token.ts b/src/path/token.ts index 4dc4de0..4e5c8a1 100644 --- a/src/path/token.ts +++ b/src/path/token.ts @@ -105,4 +105,11 @@ export class TokenStream { ); } } + + public expectPeekNot(kind: TokenKind, message: string): void { + const peeked = this.peek; + if (peeked.kind === kind) { + throw new JSONPathSyntaxError(message, peeked); + } + } } diff --git a/tests/path/cts b/tests/path/cts index b11c029..733fe52 160000 --- a/tests/path/cts +++ b/tests/path/cts @@ -1 +1 @@ -Subproject commit b11c0290808bd7021bd0b018cbb42fa1cc60bc7c +Subproject commit 733fe526ceccb183ed0e3397340f2ee9feec0d67