From 52280683b0e0bac20755ff42001eefa24a526726 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 14 Oct 2025 11:24:16 -0400 Subject: [PATCH] Add missing `scanBlankSpace` parsing `&&`s Add `lex.scanBlankSpace()` to the loop in `Parser.parseLogicalAndExpr` so that multiple consecutive `&&` comparison expressions delimited by blank spaces will be properly parsed. Add tests to ensure that the multiple consecutive `||` comparison expressions are also properly parsed. Thanks @jarangutan for the report and @jg-rp for the analysis. Resolves #24. --- CHANGELOG.md | 11 ++++ parser/parse.go | 1 + parser/parse_test.go | 154 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94e0b48..b637de3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,17 @@ All notable changes to this project will be documented in this file. It uses the * Upgraded to `golangci-lint` v2.5.0. * Removed unused constant from `parser/lex.go` +### 🐞 Bug Fixes + +* Fixed bug that prevented multiple blank-space delimited ANDed comparison + operations from being parsed. For example, `$[@.x=="hi"&&@.y!=3&&@[1]==1]` + would parse but `$[@.x == "hi" && @.y !=3 && @[1]==1]` would not. Thanks + to @jarangutan for the bug report and @jg-rp for the analysis ([#24]). + + [v0.10.2]: https://github.com/theory/jsonpath/compare/v0.10.1...v0.10.2 + [#24]: https://github.com/theory/jsonpath/issues/24 + "theory/jsonpath#24 Filter with 2+ consecutive '&&' operators returns parsing error" + ## [v0.10.1] — 2025-09-16 ### 🐞 Bug Fixes diff --git a/parser/parse.go b/parser/parse.go index de9d762..613080e 100644 --- a/parser/parse.go +++ b/parser/parse.go @@ -342,6 +342,7 @@ func (p *parser) parseLogicalAndExpr() (spec.LogicalAnd, error) { return nil, err } ors = append(ors, expr) + lex.scanBlankSpace() } return spec.LogicalAnd(ors), nil diff --git a/parser/parse_test.go b/parser/parse_test.go index 03b6796..1153e4f 100644 --- a/parser/parse_test.go +++ b/parser/parse_test.go @@ -383,6 +383,43 @@ func TestParseFilter(t *testing.T) { )), )), }, + { + test: "multi_logical_and", + query: `(@["x", 1] && $["y"] && @["a"])`, + filter: spec.Filter(spec.And( + spec.Paren(spec.And( + spec.Existence(spec.Query( + false, + spec.Child(spec.Name("x"), spec.Index(1)), + )), + spec.Existence(spec.Query( + true, + spec.Child(spec.Name("y")), + )), + spec.Existence(spec.Query( + false, + spec.Child(spec.Name("a")), + )), + )), + )), + }, + { + test: "logical_and_short_name", + query: `@.x && @.y`, + filter: spec.Filter(spec.And( + spec.Existence(spec.Query(false, spec.Child(spec.Name("x")))), + spec.Existence(spec.Query(false, spec.Child(spec.Name("y")))), + )), + }, + { + test: "multi_logical_and_short_name", + query: `@.x && @.y && @.z`, + filter: spec.Filter(spec.And( + spec.Existence(spec.Query(false, spec.Child(spec.Name("x")))), + spec.Existence(spec.Query(false, spec.Child(spec.Name("y")))), + spec.Existence(spec.Query(false, spec.Child(spec.Name("z")))), + )), + }, { test: "paren_logical_or", query: `(@["x", 1] || $["y"])`, @@ -399,6 +436,23 @@ func TestParseFilter(t *testing.T) { )), ), }, + { + test: "logical_or_short_name", + query: `@.x && @.y`, + filter: spec.Filter(spec.And( + spec.Existence(spec.Query(false, spec.Child(spec.Name("x")))), + spec.Existence(spec.Query(false, spec.Child(spec.Name("y")))), + )), + }, + { + test: "multi_logical_and_short_name", + query: `@.x && @.y && @.z`, + filter: spec.Filter(spec.And( + spec.Existence(spec.Query(false, spec.Child(spec.Name("x")))), + spec.Existence(spec.Query(false, spec.Child(spec.Name("y")))), + spec.Existence(spec.Query(false, spec.Child(spec.Name("z")))), + )), + }, // NotParenExistExpr { test: "not_paren_current_exists", @@ -669,6 +723,106 @@ func TestParseFilter(t *testing.T) { ), )), }, + { + test: "and_compare", + query: `@.x == "hi" && @.y != 3`, + filter: spec.Filter(spec.And( + spec.Comparison( + spec.SingularQuery(false, spec.Name("x")), + spec.EqualTo, + spec.Literal("hi"), + ), + spec.Comparison( + spec.SingularQuery(false, spec.Name("y")), + spec.NotEqualTo, + spec.Literal(int64(3)), + ), + )), + }, + { + test: "multi_and_compare", + query: `@.x == "hi" && @.y != 3 && $[1] == true`, + filter: spec.Filter(spec.And( + spec.Comparison( + spec.SingularQuery(false, spec.Name("x")), + spec.EqualTo, + spec.Literal("hi"), + ), + spec.Comparison( + spec.SingularQuery(false, spec.Name("y")), + spec.NotEqualTo, + spec.Literal(int64(3)), + ), + spec.Comparison( + spec.SingularQuery(true, spec.Index(1)), + spec.EqualTo, + spec.Literal(true), + ), + )), + }, + { + test: "multi_and_compare_compact", + query: `@.x=="hi"&&@.y!=3&&$[1]==true`, + filter: spec.Filter(spec.And( + spec.Comparison( + spec.SingularQuery(false, spec.Name("x")), + spec.EqualTo, + spec.Literal("hi"), + ), + spec.Comparison( + spec.SingularQuery(false, spec.Name("y")), + spec.NotEqualTo, + spec.Literal(int64(3)), + ), + spec.Comparison( + spec.SingularQuery(true, spec.Index(1)), + spec.EqualTo, + spec.Literal(true), + ), + )), + }, + { + test: "multi_or_compare", + query: `@.x == "hi" || @.y != 3 || $[1] == true`, + filter: spec.Filter( + spec.And(spec.Comparison( + spec.SingularQuery(false, spec.Name("x")), + spec.EqualTo, + spec.Literal("hi"), + )), + spec.And(spec.Comparison( + spec.SingularQuery(false, spec.Name("y")), + spec.NotEqualTo, + spec.Literal(int64(3)), + )), + spec.And(spec.Comparison( + spec.SingularQuery(true, spec.Index(1)), + spec.EqualTo, + spec.Literal(true), + )), + ), + }, + { + test: "multi_or_compare_compact", + query: `@.x=="hi"||@.y!=3||$[1]==true`, + filter: spec.Filter( + spec.And(spec.Comparison( + spec.SingularQuery(false, spec.Name("x")), + spec.EqualTo, + spec.Literal("hi"), + )), + spec.And(spec.Comparison( + spec.SingularQuery(false, spec.Name("y")), + spec.NotEqualTo, + spec.Literal(int64(3)), + )), + spec.And(spec.Comparison( + spec.SingularQuery(true, spec.Index(1)), + spec.EqualTo, + spec.Literal(true), + )), + ), + }, { test: "invalid_logical_or", query: `(@["x", 1] || hi)`,