diff --git a/src/parse.ts b/src/parse.ts index e179ac5..2f61f5c 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -47,6 +47,13 @@ export function parse(query: string, options?: JSONQueryParseOptions): JSONQuery while (true) { skipWhitespace() + if (query[i] === '.' && 'pipe' in currentOperators) { + // an implicitly piped property like "fn().prop" + const right = parseProperty() + left = left[0] === 'pipe' ? [...left, right] : ['pipe', left, right] + continue + } + const start = i const name = parseOperatorName(currentOperators) if (!name) { @@ -115,6 +122,8 @@ export function parse(query: string, options?: JSONQueryParseOptions): JSONQuery parseInteger() ?? throwSyntaxError('Property expected') ) + + skipWhitespace() } return ['get', ...props] diff --git a/test-suite/parse.test.json b/test-suite/parse.test.json index a8d4fc8..b61bca2 100644 --- a/test-suite/parse.test.json +++ b/test-suite/parse.test.json @@ -46,6 +46,42 @@ { "input": ".array.2", "output": ["get", "array", 2] } ] }, + { + "category": "property", + "description": "should parse an implicitly piped a property", + "tests": [ + { + "input": "groupBy(.city).myCity", + "output": ["pipe", ["groupBy", ["get", "city"]], ["get", "myCity"]] + }, + { + "input": "groupBy(.city).myCity | size()", + "output": ["pipe", ["groupBy", ["get", "city"]], ["get", "myCity"], ["size"]] + }, + { + "input": "groupBy(.city).myCity.location | size()", + "output": ["pipe", ["groupBy", ["get", "city"]], ["get", "myCity", "location"], ["size"]] + }, + { + "input": ".data | groupBy(.city).myCity", + "output": ["pipe", ["get", "data"], ["groupBy", ["get", "city"]], ["get", "myCity"]] + }, + { + "input": "\"hello\".length", + "output": ["pipe", "hello", ["get", "length"]] + } + ] + }, + { + "category": "property", + "description": "should allow whitespace between multiple properties", + "tests": [ + { "input": ".\"address\" .\"city\"", "output": ["get", "address", "city"] }, + { "input": ".address .city", "output": ["get", "address", "city"] }, + { "input": ".address\t.city", "output": ["get", "address", "city"] }, + { "input": ".address\n.city", "output": ["get", "address", "city"] } + ] + }, { "category": "property", "description": "should throw an error when a property misses an end quote", @@ -54,11 +90,7 @@ { "category": "property", "description": "should throw an error when there is whitespace between the dot and the property name", - "tests": [ - { "input": ". \"name\"", "throws": "Property expected (pos: 1)" }, - { "input": ".\"address\" .\"city\"", "throws": "Unexpected part '.\"city\"' (pos: 11)" }, - { "input": ".address .city", "throws": "Unexpected part '.city' (pos: 9)" } - ] + "tests": [{ "input": ". \"name\"", "throws": "Property expected (pos: 1)" }] }, { "category": "function", @@ -453,11 +485,10 @@ "description": "should throw an error in case of an invalid number", "tests": [ { "input": "-", "throws": "Value expected (pos: 0)" }, - { "input": "2.", "throws": "Unexpected part '.' (pos: 1)" }, + { "input": "2.", "throws": "Property expected (pos: 2)" }, { "input": "2.3e", "throws": "Unexpected part 'e' (pos: 3)" }, { "input": "2.3e+", "throws": "Unexpected part 'e+' (pos: 3)" }, - { "input": "2.3e-", "throws": "Unexpected part 'e-' (pos: 3)" }, - { "input": "2.", "throws": "Unexpected part '.' (pos: 1)" } + { "input": "2.3e-", "throws": "Unexpected part 'e-' (pos: 3)" } ] }, {