From ebc13788f2940a97b8fe460dddfb90f74fdb4efb Mon Sep 17 00:00:00 2001 From: James Prior Date: Fri, 13 Oct 2023 15:45:53 +0100 Subject: [PATCH] Fix JSONPath filter operator precedence. --- CHANGELOG.md | 6 ++++++ package.json | 2 +- src/path/parse.ts | 6 +++--- tests/path/parse.test.ts | 40 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ccf02a..ce6cbaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # JSON P3 Change Log +# Version 0.2.1 + +**Fixes** + +- Fixed JSONPath filter operator precedence. Previously logical _and_ (`&&`) and _or_ (`||`) were binding more tightly than logical negation (`!`). + # Version 0.2.0 **Breaking Changes** diff --git a/package.json b/package.json index a303b2b..563ae27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "json-p3", - "version": "0.2.0", + "version": "0.2.1", "author": "James Prior", "license": "MIT", "description": "JSONPath, JSON Pointer and JSON Patch", diff --git a/src/path/parse.ts b/src/path/parse.ts index 819f2cf..15d4ca2 100644 --- a/src/path/parse.ts +++ b/src/path/parse.ts @@ -29,10 +29,10 @@ import { import { Token, TokenKind, TokenStream } from "./token"; const PRECEDENCE_LOWEST = 1; -const PRECEDENCE_LOGICALRIGHT = 3; const PRECEDENCE_LOGICAL_AND = 4; const PRECEDENCE_LOGICAL_OR = 5; const PRECEDENCE_COMPARISON = 6; +const PRECEDENCE_PREFIX = 7; const PRECEDENCES: Map = new Map([ [TokenKind.AND, PRECEDENCE_LOGICAL_AND], @@ -42,7 +42,7 @@ const PRECEDENCES: Map = new Map([ [TokenKind.LE, PRECEDENCE_COMPARISON], [TokenKind.LT, PRECEDENCE_COMPARISON], [TokenKind.NE, PRECEDENCE_COMPARISON], - [TokenKind.NOT, PRECEDENCE_LOGICALRIGHT], + [TokenKind.NOT, PRECEDENCE_PREFIX], [TokenKind.OR, PRECEDENCE_LOGICAL_OR], [TokenKind.RPAREN, PRECEDENCE_LOWEST], ]); @@ -312,7 +312,7 @@ export class Parser { return new PrefixExpression( stream.current, "!", - this.parseFilterExpression(stream, PRECEDENCE_LOGICALRIGHT), + this.parseFilterExpression(stream, PRECEDENCE_PREFIX), ); } diff --git a/tests/path/parse.test.ts b/tests/path/parse.test.ts index 39dec57..c9100ea 100644 --- a/tests/path/parse.test.ts +++ b/tests/path/parse.test.ts @@ -1,6 +1,44 @@ -import { JSONPathSyntaxError, compile, query } from "../../src/path"; +import { + JSONPathEnvironment, + JSONPathSyntaxError, + compile, + query, +} from "../../src/path"; + +type TestCase = { + description: string; + path: string; + want: string; +}; + +const TEST_CASES: TestCase[] = [ + { + description: "not binds more tightly than and", + path: "$[?!@.a && !@.b]", + want: "$[?(!@['a'] && !@['b'])]", + }, + { + description: "not binds more tightly than or", + path: "$[?!@.a || !@.b]", + want: "$[?(!@['a'] || !@['b'])]", + }, + { + description: "control precedence with parens", + path: "$[?!(@.a && !@.b)]", + want: "$[?!(@['a'] && !@['b'])]", + }, +]; describe("parse", () => { + const env = new JSONPathEnvironment(); + + test.each(TEST_CASES)( + "$description", + ({ path, want }: TestCase) => { + expect(env.compile(path).toString()).toBe(want); + }, + ); + test("well-typed nested functions", () => { const data = { regex: "a.*",