Skip to content

Commit 25841b6

Browse files
committed
Add the NTS as a submodule and minimize parentheses
1 parent bec01d1 commit 25841b6

File tree

7 files changed

+92
-10
lines changed

7 files changed

+92
-10
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "tests/path/cts"]
22
path = tests/path/cts
33
url = https://github.com/jsonpath-standard/jsonpath-compliance-test-suite.git
4+
[submodule "tests/path/nts"]
5+
path = tests/path/nts
6+
url = git@github.com:jg-rp/jsonpath-compliance-normalized-paths.git

src/path/expression.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { JSONPathQuery } from "./path";
66
import { Token } from "./token";
77
import { FilterContext, Nothing, SerializationOptions } from "./types";
88
import { isNumber, isString } from "../types";
9+
import { toCanonical } from "./serialize";
910

1011
/**
1112
* Base class for all filter expressions.
@@ -70,7 +71,7 @@ export class StringLiteral extends FilterExpressionLiteral {
7071
}
7172

7273
public toString(): string {
73-
return JSON.stringify(this.value);
74+
return toCanonical(this.value);
7475
}
7576
}
7677

@@ -117,6 +118,10 @@ export class PrefixExpression extends FilterExpression {
117118
}
118119
}
119120

121+
const PRECEDENCE_LOGICAL_OR = 4;
122+
const PRECEDENCE_LOGICAL_AND = 5;
123+
const PRECEDENCE_PREFIX = 7;
124+
120125
export class InfixExpression extends FilterExpression {
121126
readonly logical: boolean;
122127

@@ -159,6 +164,7 @@ export class InfixExpression extends FilterExpression {
159164
}
160165

161166
public toString(options?: SerializationOptions): string {
167+
// Note that `LogicalExpression.toString()` does not call this.
162168
if (this.logical) {
163169
return `(${this.left.toString(options)} ${
164170
this.operator
@@ -183,7 +189,43 @@ export class LogicalExpression extends FilterExpression {
183189
}
184190

185191
public toString(options?: SerializationOptions): string {
186-
return this.expression.toString(options);
192+
// Minimize parentheses in logical expressions.
193+
function _toString(
194+
expression: FilterExpression,
195+
parentPrecedence: number,
196+
): string {
197+
let precedence: number;
198+
let op: string;
199+
let left: string;
200+
let right: string;
201+
202+
if (expression instanceof InfixExpression) {
203+
if (expression.operator === "&&") {
204+
precedence = PRECEDENCE_LOGICAL_AND;
205+
op = "&&";
206+
left = _toString(expression.left, precedence);
207+
right = _toString(expression.right, precedence);
208+
} else if (expression.operator === "||") {
209+
precedence = PRECEDENCE_LOGICAL_OR;
210+
op = "||";
211+
left = _toString(expression.left, precedence);
212+
right = _toString(expression.right, precedence);
213+
} else {
214+
return expression.toString(options);
215+
}
216+
} else if (expression instanceof PrefixExpression) {
217+
const operand = _toString(expression.right, PRECEDENCE_PREFIX);
218+
const expr = `!${operand}`;
219+
return parentPrecedence > PRECEDENCE_PREFIX ? `(${expr})` : expr;
220+
} else {
221+
return expression.toString(options);
222+
}
223+
224+
const expr = `${left} ${op} ${right}`;
225+
return precedence < parentPrecedence ? `(${expr})` : expr;
226+
}
227+
228+
return _toString(this.expression, 0);
187229
}
188230
}
189231

tests/path/canonical_path.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ const testCases: Case[] = [
102102
{
103103
description: "filter, logical and",
104104
path: "$[?@.foo && @.bar]",
105-
want: "$[?(@['foo'] && @['bar'])]",
105+
want: "$[?@['foo'] && @['bar']]",
106106
},
107107
{
108108
description: "filter, logical or",
109109
path: "$[?@.foo || @.bar]",
110-
want: "$[?(@['foo'] || @['bar'])]",
110+
want: "$[?@['foo'] || @['bar']]",
111111
},
112112
{
113113
description: "filter, logical not",

tests/path/nts

Submodule nts added at e3539fa
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { readFileSync } from "fs";
2+
3+
import { JSONPathEnvironment } from "../../src/path/environment";
4+
5+
type Case = {
6+
name: string;
7+
query: string;
8+
canonical: string;
9+
};
10+
11+
const normalized_paths = JSON.parse(
12+
readFileSync(
13+
process.env.JSONP3_NTS_CANONICAL_PATH ||
14+
"tests/path/nts/canonical_paths.json",
15+
{
16+
encoding: "utf8",
17+
},
18+
),
19+
);
20+
21+
const env = new JSONPathEnvironment();
22+
23+
describe("serialize to canonical path", () => {
24+
test.each<Case>(normalized_paths.tests)(
25+
"$name",
26+
({ query, canonical }: Case) => {
27+
const q = env.compile(query).toString({ form: "canonical" });
28+
expect(q).toStrictEqual(canonical);
29+
},
30+
);
31+
});

tests/path/parse.test.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,27 @@ const TEST_CASES: TestCase[] = [
1515
{
1616
description: "not binds more tightly than and",
1717
path: "$[?!@.a && !@.b]",
18-
want: "$[?(!@.a && !@.b)]",
18+
want: "$[?!@.a && !@.b]",
1919
},
2020
{
2121
description: "not binds more tightly than or",
2222
path: "$[?!@.a || !@.b]",
23-
want: "$[?(!@.a || !@.b)]",
23+
want: "$[?!@.a || !@.b]",
2424
},
2525
{
26-
description: "control precedence with parens",
26+
description: "control precedence with parens, not",
2727
path: "$[?!(@.a && !@.b)]",
2828
want: "$[?!(@.a && !@.b)]",
2929
},
30+
{
31+
description: "control precedence with parens",
32+
path: "$[?(@.a || @.b) && @.c]",
33+
want: "$[?(@.a || @.b) && @.c]",
34+
},
3035
{
3136
description: "non-singular query in logical expression",
3237
path: "$[?@.* && @.b]",
33-
want: "$[?(@[*] && @.b)]",
38+
want: "$[?@[*] && @.b]",
3439
},
3540
];
3641

tests/path/pretty_path.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,12 @@ const testCases: Case[] = [
102102
{
103103
description: "filter, logical and",
104104
path: "$[?@.foo && @.bar]",
105-
want: "$[?(@.foo && @.bar)]",
105+
want: "$[?@.foo && @.bar]",
106106
},
107107
{
108108
description: "filter, logical or",
109109
path: "$[?@.foo || @.bar]",
110-
want: "$[?(@.foo || @.bar)]",
110+
want: "$[?@.foo || @.bar]",
111111
},
112112
{
113113
description: "filter, logical not",

0 commit comments

Comments
 (0)