@swaggerexpert/jsonpath 3.2.5 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +225 -18
  2. package/cjs/errors/JSONNormalizedPathError.cjs +8 -0
  3. package/cjs/errors/JSONPathError.cjs +1 -1
  4. package/cjs/errors/{JSONPathCompileError.cjs → JSONPathEvaluateError.cjs} +2 -2
  5. package/cjs/evaluate/evaluators/comparable.cjs +44 -0
  6. package/cjs/evaluate/evaluators/comparison-expr.cjs +37 -0
  7. package/cjs/evaluate/evaluators/filter-query.cjs +182 -0
  8. package/cjs/evaluate/evaluators/function-expr.cjs +106 -0
  9. package/cjs/evaluate/evaluators/literal.cjs +25 -0
  10. package/cjs/evaluate/evaluators/logical-expr.cjs +96 -0
  11. package/cjs/evaluate/evaluators/singular-query.cjs +103 -0
  12. package/cjs/evaluate/functions/count.cjs +35 -0
  13. package/cjs/evaluate/functions/index.cjs +15 -0
  14. package/cjs/evaluate/functions/length.cjs +42 -0
  15. package/cjs/evaluate/functions/match.cjs +49 -0
  16. package/cjs/evaluate/functions/search.cjs +49 -0
  17. package/cjs/evaluate/functions/value.cjs +36 -0
  18. package/cjs/evaluate/index.cjs +182 -0
  19. package/cjs/evaluate/realms/EvaluationRealm.cjs +154 -0
  20. package/cjs/evaluate/realms/json/index.cjs +246 -0
  21. package/cjs/evaluate/utils/guards.cjs +129 -0
  22. package/cjs/evaluate/utils/i-regexp.cjs +118 -0
  23. package/cjs/evaluate/visitors/bracketed-selection.cjs +35 -0
  24. package/cjs/evaluate/visitors/filter-selector.cjs +43 -0
  25. package/cjs/evaluate/visitors/index-selector.cjs +55 -0
  26. package/cjs/evaluate/visitors/name-selector.cjs +38 -0
  27. package/cjs/evaluate/visitors/segment.cjs +99 -0
  28. package/cjs/evaluate/visitors/selector.cjs +47 -0
  29. package/cjs/evaluate/visitors/slice-selector.cjs +115 -0
  30. package/cjs/evaluate/visitors/wildcard-selector.cjs +32 -0
  31. package/cjs/index.cjs +16 -7
  32. package/cjs/normalized-path.cjs +145 -0
  33. package/cjs/parse/callbacks/cst.cjs +2 -4
  34. package/cjs/parse/index.cjs +3 -1
  35. package/cjs/parse/translators/ASTTranslator/index.cjs +1 -1
  36. package/cjs/parse/translators/ASTTranslator/transformers.cjs +246 -5
  37. package/cjs/parse/translators/CSTOptimizedTranslator.cjs +1 -3
  38. package/cjs/parse/translators/CSTTranslator.cjs +1 -2
  39. package/cjs/test/index.cjs +4 -2
  40. package/es/errors/JSONNormalizedPathError.mjs +3 -0
  41. package/es/errors/JSONPathError.mjs +1 -1
  42. package/es/errors/JSONPathEvaluateError.mjs +3 -0
  43. package/es/evaluate/evaluators/comparable.mjs +38 -0
  44. package/es/evaluate/evaluators/comparison-expr.mjs +31 -0
  45. package/es/evaluate/evaluators/filter-query.mjs +175 -0
  46. package/es/evaluate/evaluators/function-expr.mjs +99 -0
  47. package/es/evaluate/evaluators/literal.mjs +21 -0
  48. package/es/evaluate/evaluators/logical-expr.mjs +89 -0
  49. package/es/evaluate/evaluators/singular-query.mjs +97 -0
  50. package/es/evaluate/functions/count.mjs +30 -0
  51. package/es/evaluate/functions/index.mjs +13 -0
  52. package/es/evaluate/functions/length.mjs +37 -0
  53. package/es/evaluate/functions/match.mjs +44 -0
  54. package/es/evaluate/functions/search.mjs +44 -0
  55. package/es/evaluate/functions/value.mjs +31 -0
  56. package/es/evaluate/index.mjs +174 -0
  57. package/es/evaluate/realms/EvaluationRealm.mjs +148 -0
  58. package/es/evaluate/realms/json/index.mjs +240 -0
  59. package/es/evaluate/utils/guards.mjs +114 -0
  60. package/es/evaluate/utils/i-regexp.mjs +113 -0
  61. package/es/evaluate/visitors/bracketed-selection.mjs +29 -0
  62. package/es/evaluate/visitors/filter-selector.mjs +37 -0
  63. package/es/evaluate/visitors/index-selector.mjs +51 -0
  64. package/es/evaluate/visitors/name-selector.mjs +34 -0
  65. package/es/evaluate/visitors/segment.mjs +91 -0
  66. package/es/evaluate/visitors/selector.mjs +41 -0
  67. package/es/evaluate/visitors/slice-selector.mjs +111 -0
  68. package/es/evaluate/visitors/wildcard-selector.mjs +28 -0
  69. package/es/index.mjs +7 -3
  70. package/es/normalized-path.mjs +136 -0
  71. package/es/parse/callbacks/cst.mjs +2 -4
  72. package/es/parse/index.mjs +3 -1
  73. package/es/parse/translators/ASTTranslator/index.mjs +1 -1
  74. package/es/parse/translators/ASTTranslator/transformers.mjs +246 -5
  75. package/es/parse/translators/CSTOptimizedTranslator.mjs +1 -3
  76. package/es/parse/translators/CSTTranslator.mjs +1 -2
  77. package/es/test/index.mjs +4 -2
  78. package/package.json +4 -2
  79. package/types/index.d.ts +114 -11
  80. package/cjs/compile.cjs +0 -50
  81. package/cjs/escape.cjs +0 -59
  82. package/es/compile.mjs +0 -45
  83. package/es/errors/JSONPathCompileError.mjs +0 -3
  84. package/es/escape.mjs +0 -55
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.to = exports.test = exports.from = exports.escape = void 0;
5
+ var _index = _interopRequireDefault(require("./parse/index.cjs"));
6
+ var _index2 = _interopRequireDefault(require("./test/index.cjs"));
7
+ var _JSONNormalizedPathError = _interopRequireDefault(require("./errors/JSONNormalizedPathError.cjs"));
8
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
9
+ /**
10
+ * Tests if a string is a valid normalized JSONPath.
11
+ *
12
+ * @param {string} normalizedPath - The string to test
13
+ * @returns {boolean} True if valid normalized path, false otherwise
14
+ */
15
+ const test = normalizedPath => (0, _index2.default)(normalizedPath, {
16
+ normalized: true
17
+ });
18
+
19
+ /**
20
+ * Escapes a string for use in a normalized JSONPath name selector.
21
+ * Follows RFC 9535 Section 2.7 escaping rules for single-quoted strings.
22
+ *
23
+ * @param {string} selector - The string to escape
24
+ * @returns {string} The escaped string (without surrounding quotes)
25
+ */
26
+ exports.test = test;
27
+ const escape = selector => {
28
+ if (typeof selector !== 'string') {
29
+ throw new TypeError('Selector must be a string');
30
+ }
31
+ let escaped = '';
32
+ for (const char of selector) {
33
+ const codePoint = char.codePointAt(0);
34
+ switch (codePoint) {
35
+ case 0x08:
36
+ // backspace
37
+ escaped += '\\b';
38
+ break;
39
+ case 0x09:
40
+ // horizontal tab
41
+ escaped += '\\t';
42
+ break;
43
+ case 0x0a:
44
+ // line feed
45
+ escaped += '\\n';
46
+ break;
47
+ case 0x0c:
48
+ // form feed
49
+ escaped += '\\f';
50
+ break;
51
+ case 0x0d:
52
+ // carriage return
53
+ escaped += '\\r';
54
+ break;
55
+ case 0x27:
56
+ // apostrophe '
57
+ escaped += "\\'";
58
+ break;
59
+ case 0x5c:
60
+ // backslash \
61
+ escaped += '\\\\';
62
+ break;
63
+ default:
64
+ // Other control characters (U+0000-U+001F except those handled above)
65
+ if (codePoint <= 0x1f) {
66
+ escaped += `\\u${codePoint.toString(16).padStart(4, '0')}`;
67
+ } else {
68
+ escaped += char;
69
+ }
70
+ }
71
+ }
72
+ return escaped;
73
+ };
74
+
75
+ /**
76
+ * Creates a normalized path string from a list of selectors.
77
+ * Name selectors are automatically escaped.
78
+ *
79
+ * @param {Array<string|number>} selectors - Array of name selectors (strings) or index selectors (numbers)
80
+ * @returns {string} A normalized JSONPath string
81
+ * @throws {JSONNormalizedPathError} If selectors is not an array or contains invalid selector types
82
+ */
83
+ exports.escape = escape;
84
+ const from = selectors => {
85
+ if (!Array.isArray(selectors)) {
86
+ throw new _JSONNormalizedPathError.default(`Selectors must be an array, got: ${typeof selectors}`, {
87
+ selectors
88
+ });
89
+ }
90
+ try {
91
+ const segments = selectors.map(selector => {
92
+ if (typeof selector === 'string') {
93
+ // Name selector: escape and wrap in single quotes
94
+ return `['${escape(selector)}']`;
95
+ }
96
+ if (typeof selector === 'number') {
97
+ // Index selector: must be a non-negative safe integer (RFC 9535 Section 2.1)
98
+ if (!Number.isSafeInteger(selector) || selector < 0) {
99
+ throw new TypeError(`Index selector must be a non-negative safe integer, got: ${selector}`);
100
+ }
101
+ return `[${selector}]`;
102
+ }
103
+ throw new TypeError(`Selector must be a string or non-negative integer, got: ${typeof selector}`);
104
+ });
105
+ return `$${segments.join('')}`;
106
+ } catch (error) {
107
+ throw new _JSONNormalizedPathError.default('Failed to compile normalized JSONPath', {
108
+ cause: error,
109
+ selectors
110
+ });
111
+ }
112
+ };
113
+
114
+ /**
115
+ * Parses a normalized path string and returns a list of selectors.
116
+ *
117
+ * @param {string} normalizedPath - A normalized JSONPath string
118
+ * @returns {Array<string|number>} Array of name selectors (strings) or index selectors (numbers)
119
+ * @throws {JSONNormalizedPathError} If the normalized path is invalid
120
+ */
121
+ exports.from = from;
122
+ const to = normalizedPath => {
123
+ if (typeof normalizedPath !== 'string') {
124
+ throw new _JSONNormalizedPathError.default(`Normalized path must be a string, got: ${typeof normalizedPath}`, {
125
+ normalizedPath
126
+ });
127
+ }
128
+ const parseResult = (0, _index.default)(normalizedPath, {
129
+ normalized: true
130
+ });
131
+ if (!parseResult.result.success) {
132
+ throw new _JSONNormalizedPathError.default('Invalid normalized path', {
133
+ normalizedPath
134
+ });
135
+ }
136
+ const {
137
+ tree
138
+ } = parseResult;
139
+
140
+ // Extract selectors from AST segments
141
+ // Normalized path grammar only allows NameSelector and IndexSelector
142
+ // For normalized paths, segment.selector is directly the selector (not BracketedSelection)
143
+ return tree.segments.map(segment => segment.selector.value);
144
+ };
145
+ exports.to = to;
@@ -7,13 +7,12 @@ var _JSONPathParseError = _interopRequireDefault(require("../../errors/JSONPathP
7
7
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
8
  const cst = nodeType => {
9
9
  return (state, chars, phraseIndex, phraseLength, data) => {
10
- var _data$options, _data$options2;
11
10
  if (!(typeof data === 'object' && data !== null && !Array.isArray(data))) {
12
11
  throw new _JSONPathParseError.default("parser's user data must be an object");
13
12
  }
14
13
 
15
14
  // drop the empty nodes
16
- if ((_data$options = data.options) != null && _data$options.optimize && phraseLength === 0 && (_data$options2 = data.options) != null && (_data$options2 = _data$options2.droppableTypes) != null && _data$options2.includes(nodeType)) {
15
+ if (data.options?.optimize && phraseLength === 0 && data.options?.droppableTypes?.includes(nodeType)) {
17
16
  return;
18
17
  }
19
18
  if (state === _apgLite.identifiers.SEM_PRE) {
@@ -25,11 +24,10 @@ const cst = nodeType => {
25
24
  children: []
26
25
  };
27
26
  if (data.stack.length > 0) {
28
- var _data$options3, _data$options4;
29
27
  const parent = data.stack[data.stack.length - 1];
30
28
  const prevSibling = parent.children[parent.children.length - 1];
31
29
  const isTextNodeWithinTextNode = parent.type === 'text' && node.type === 'text';
32
- const shouldCollapse = ((_data$options3 = data.options) == null ? void 0 : _data$options3.optimize) && ((_data$options4 = data.options) == null || (_data$options4 = _data$options4.collapsibleTypes) == null ? void 0 : _data$options4.includes(node.type)) && (prevSibling == null ? void 0 : prevSibling.type) === node.type;
30
+ const shouldCollapse = data.options?.optimize && data.options?.collapsibleTypes?.includes(node.type) && prevSibling?.type === node.type;
33
31
  if (shouldCollapse) {
34
32
  prevSibling.text += node.text;
35
33
  prevSibling.length += node.length;
@@ -32,7 +32,9 @@ const parse = (jsonPath, {
32
32
  trace: parser.trace
33
33
  };
34
34
  } catch (error) {
35
- throw new _JSONPathParseError.default('Unexpected error during JSONPath parsing', {
35
+ // Provide specific error message for semantic validation errors
36
+ const message = error instanceof RangeError ? `Invalid JSONPath expression: ${error.message}` : 'Unexpected error during JSONPath parsing';
37
+ throw new _JSONPathParseError.default(message, {
36
38
  cause: error,
37
39
  jsonPath
38
40
  });
@@ -9,7 +9,7 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
9
9
  class ASTTranslator extends _CSTOptimizedTranslator.default {
10
10
  getTree() {
11
11
  const cst = super.getTree();
12
- return (0, _transformers.transformCSTtoAST)(cst.root, _transformers.default);
12
+ return (0, _transformers.transformCSTtoAST)(cst, _transformers.default);
13
13
  }
14
14
  }
15
15
  var _default = exports.default = ASTTranslator;
@@ -5,6 +5,204 @@ exports.transformCSTtoAST = exports.default = void 0;
5
5
  var _JSONPathParseError = _interopRequireDefault(require("../../../errors/JSONPathParseError.cjs"));
6
6
  var _decoders = require("./decoders.cjs");
7
7
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
8
+ /**
9
+ * RFC 9535 function type signatures.
10
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4
11
+ */
12
+ const FUNCTION_SIGNATURES = {
13
+ // count(NodesType) -> ValueType
14
+ count: {
15
+ params: [{
16
+ type: 'NodesType'
17
+ }],
18
+ returns: 'ValueType'
19
+ },
20
+ // length(ValueType) -> ValueType
21
+ length: {
22
+ params: [{
23
+ type: 'ValueType'
24
+ }],
25
+ returns: 'ValueType'
26
+ },
27
+ // value(NodesType) -> ValueType
28
+ value: {
29
+ params: [{
30
+ type: 'NodesType'
31
+ }],
32
+ returns: 'ValueType'
33
+ },
34
+ // match(ValueType, ValueType) -> LogicalType
35
+ match: {
36
+ params: [{
37
+ type: 'ValueType'
38
+ }, {
39
+ type: 'ValueType'
40
+ }],
41
+ returns: 'LogicalType'
42
+ },
43
+ // search(ValueType, ValueType) -> LogicalType
44
+ search: {
45
+ params: [{
46
+ type: 'ValueType'
47
+ }, {
48
+ type: 'ValueType'
49
+ }],
50
+ returns: 'LogicalType'
51
+ }
52
+ };
53
+
54
+ /**
55
+ * Check if an AST node represents a singular query.
56
+ * Singular queries use only name selectors and index selectors.
57
+ */
58
+ const isSingularQueryAST = node => {
59
+ if (node.type !== 'FilterQuery') return false;
60
+ const {
61
+ query
62
+ } = node;
63
+ if (!query || !query.segments) return false;
64
+ for (const segment of query.segments) {
65
+ if (segment.type !== 'ChildSegment') return false;
66
+ const {
67
+ selector
68
+ } = segment;
69
+ if (selector.type !== 'NameSelector' && selector.type !== 'IndexSelector') {
70
+ // Check for BracketedSelection with single NameSelector or IndexSelector
71
+ if (selector.type === 'BracketedSelection') {
72
+ if (selector.selectors.length !== 1) return false;
73
+ const inner = selector.selectors[0];
74
+ if (inner.type !== 'NameSelector' && inner.type !== 'IndexSelector') {
75
+ return false;
76
+ }
77
+ } else {
78
+ return false;
79
+ }
80
+ }
81
+ }
82
+ return true;
83
+ };
84
+
85
+ /**
86
+ * Check if argument type matches expected parameter type.
87
+ */
88
+ const checkArgumentType = (argAST, expectedType, funcName) => {
89
+ if (expectedType === 'NodesType') {
90
+ // NodesType requires a filter-query (non-singular)
91
+ // Literals and singular queries are NOT NodesType
92
+ if (argAST.type === 'Literal') {
93
+ throw new RangeError(`Function ${funcName}() requires NodesType argument, got literal`);
94
+ }
95
+ if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FilterQuery') {
96
+ // This is a filter query wrapped in TestExpr - valid NodesType
97
+ return;
98
+ }
99
+ if (argAST.type === 'FilterQuery') {
100
+ // Direct filter query - valid NodesType
101
+ return;
102
+ }
103
+ // Other types are not valid NodesType
104
+ throw new RangeError(`Function ${funcName}() requires NodesType argument`);
105
+ }
106
+ if (expectedType === 'ValueType') {
107
+ // ValueType accepts: literals, singular queries, function expressions
108
+ if (argAST.type === 'Literal') return;
109
+ if (argAST.type === 'FunctionExpr') return;
110
+
111
+ // TestExpr containing FunctionExpr - valid if function returns ValueType
112
+ if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FunctionExpr') {
113
+ // Function expressions that return ValueType are valid
114
+ // Unknown functions are allowed (they return Nothing at runtime)
115
+ return;
116
+ }
117
+
118
+ // TestExpr containing FilterQuery - check if singular
119
+ if (argAST.type === 'TestExpr' && argAST.expression?.type === 'FilterQuery') {
120
+ if (!isSingularQueryAST(argAST.expression)) {
121
+ throw new RangeError(`Function ${funcName}() requires ValueType argument, got non-singular query`);
122
+ }
123
+ return;
124
+ }
125
+
126
+ // FilterQuery - check if singular
127
+ if (argAST.type === 'FilterQuery') {
128
+ if (!isSingularQueryAST(argAST)) {
129
+ throw new RangeError(`Function ${funcName}() requires ValueType argument, got non-singular query`);
130
+ }
131
+ return;
132
+ }
133
+
134
+ // LogicalExpr types are not ValueType
135
+ throw new RangeError(`Function ${funcName}() requires ValueType argument`);
136
+ }
137
+ };
138
+
139
+ /**
140
+ * Get the return type of an expression AST node.
141
+ *
142
+ * @param {object} node - AST node
143
+ * @returns {string | null} - 'LogicalType', 'ValueType', 'NodesType', or null for unknown
144
+ */
145
+ const getExpressionReturnType = node => {
146
+ if (!node) return null;
147
+
148
+ // Function expressions return based on signature
149
+ if (node.type === 'FunctionExpr') {
150
+ const signature = FUNCTION_SIGNATURES[node.name];
151
+ return signature ? signature.returns : null;
152
+ }
153
+
154
+ // Literals are ValueType
155
+ if (node.type === 'Literal') {
156
+ return 'ValueType';
157
+ }
158
+
159
+ // Singular queries return ValueType
160
+ if (node.type === 'RelSingularQuery' || node.type === 'AbsSingularQuery') {
161
+ return 'ValueType';
162
+ }
163
+
164
+ // Filter queries return NodesType
165
+ if (node.type === 'FilterQuery') {
166
+ return 'NodesType';
167
+ }
168
+
169
+ // Logical expressions return LogicalType
170
+ if (node.type === 'LogicalOrExpr' || node.type === 'LogicalAndExpr' || node.type === 'LogicalNotExpr' || node.type === 'ComparisonExpr') {
171
+ return 'LogicalType';
172
+ }
173
+
174
+ // TestExpr: depends on what it wraps
175
+ if (node.type === 'TestExpr') {
176
+ return getExpressionReturnType(node.expression);
177
+ }
178
+ return null;
179
+ };
180
+
181
+ /**
182
+ * Validate function call against its type signature.
183
+ */
184
+ const validateFunctionCall = (funcName, argASTs) => {
185
+ const signature = FUNCTION_SIGNATURES[funcName];
186
+ if (!signature) {
187
+ // Unknown function - no validation (will return Nothing at runtime)
188
+ return;
189
+ }
190
+
191
+ // Check argument count
192
+ const expectedCount = signature.params.length;
193
+ const actualCount = argASTs.length;
194
+ if (actualCount < expectedCount) {
195
+ throw new RangeError(`Function ${funcName}() requires ${expectedCount} argument(s), got ${actualCount}`);
196
+ }
197
+ if (actualCount > expectedCount) {
198
+ throw new RangeError(`Function ${funcName}() requires ${expectedCount} argument(s), got ${actualCount}`);
199
+ }
200
+
201
+ // Check argument types
202
+ for (let i = 0; i < expectedCount; i++) {
203
+ checkArgumentType(argASTs[i], signature.params[i].type, funcName);
204
+ }
205
+ };
8
206
  const transformCSTtoAST = (node, transformerMap, ctx = {
9
207
  parent: null,
10
208
  path: []
@@ -124,9 +322,31 @@ const transformers = {
124
322
  const child = node.children.find(({
125
323
  type
126
324
  }) => type === 'logical-expr');
325
+ const expressionAST = transformCSTtoAST(child, transformers, ctx);
326
+
327
+ // Validate: ValueType functions cannot be used as existence tests
328
+ // Per RFC 9535 Section 2.4.9: "Type error: ValueType not TestExpr"
329
+ // This only applies when the top-level expression is a TestExpr
330
+ // containing a function that returns ValueType
331
+ if (expressionAST.type === 'TestExpr') {
332
+ const innerType = getExpressionReturnType(expressionAST.expression);
333
+ if (innerType === 'ValueType') {
334
+ const funcName = expressionAST.expression.type === 'FunctionExpr' ? expressionAST.expression.name : 'expression';
335
+ throw new RangeError(`Function ${funcName}() returns ValueType which cannot be used as existence test; result must be compared`);
336
+ }
337
+ }
338
+ // Also check for LogicalNotExpr wrapping TestExpr (for negated existence tests)
339
+ if (expressionAST.type === 'LogicalNotExpr' && expressionAST.expression?.type === 'TestExpr') {
340
+ const innerExpr = expressionAST.expression.expression;
341
+ const innerType = getExpressionReturnType(innerExpr);
342
+ if (innerType === 'ValueType') {
343
+ const funcName = innerExpr.type === 'FunctionExpr' ? innerExpr.name : 'expression';
344
+ throw new RangeError(`Function ${funcName}() returns ValueType which cannot be used as existence test; result must be compared`);
345
+ }
346
+ }
127
347
  return {
128
348
  type: 'FilterSelector',
129
- expression: transformCSTtoAST(child, transformers, ctx)
349
+ expression: expressionAST
130
350
  };
131
351
  },
132
352
  ['logical-expr'](node, ctx) {
@@ -153,6 +373,7 @@ const transformers = {
153
373
  right
154
374
  };
155
375
  }
376
+ return left;
156
377
  },
157
378
  ['logical-and-expr'](node, ctx) {
158
379
  const basicExprs = node.children.filter(({
@@ -197,9 +418,10 @@ const transformers = {
197
418
  const expression = node.children.find(({
198
419
  type
199
420
  }) => ['filter-query', 'function-expr'].includes(type));
421
+ const expressionAST = transformCSTtoAST(expression, transformers, ctx);
200
422
  const testExpr = {
201
423
  type: 'TestExpr',
202
- expression: transformCSTtoAST(expression, transformers, ctx)
424
+ expression: expressionAST
203
425
  };
204
426
  return isNegated ? {
205
427
  type: 'LogicalNotExpr',
@@ -227,11 +449,26 @@ const transformers = {
227
449
  type
228
450
  }) => ['comparable', 'comparison-op'].includes(type));
229
451
  const [left, op, right] = children;
452
+ const leftAST = transformCSTtoAST(left, transformers, ctx);
453
+ const rightAST = transformCSTtoAST(right, transformers, ctx);
454
+
455
+ // Validate: LogicalType functions cannot be used in comparisons
456
+ // Per RFC 9535 Section 2.4.9: "Type error: no compare to LogicalType"
457
+ const leftType = getExpressionReturnType(leftAST);
458
+ const rightType = getExpressionReturnType(rightAST);
459
+ if (leftType === 'LogicalType') {
460
+ const funcName = leftAST.type === 'FunctionExpr' ? leftAST.name : 'expression';
461
+ throw new RangeError(`Function ${funcName}() returns LogicalType which cannot be compared`);
462
+ }
463
+ if (rightType === 'LogicalType') {
464
+ const funcName = rightAST.type === 'FunctionExpr' ? rightAST.name : 'expression';
465
+ throw new RangeError(`Function ${funcName}() returns LogicalType which cannot be compared`);
466
+ }
230
467
  return {
231
468
  type: 'ComparisonExpr',
232
- left: transformCSTtoAST(left, transformers, ctx),
469
+ left: leftAST,
233
470
  op: op.text,
234
- right: transformCSTtoAST(right, transformers, ctx)
471
+ right: rightAST
235
472
  };
236
473
  },
237
474
  ['literal'](node, ctx) {
@@ -314,10 +551,14 @@ const transformers = {
314
551
  const args = node.children.filter(({
315
552
  type
316
553
  }) => type === 'function-argument');
554
+ const argASTs = args.map(arg => transformCSTtoAST(arg, transformers, ctx));
555
+
556
+ // Validate function call against type signature
557
+ validateFunctionCall(name.text, argASTs);
317
558
  return {
318
559
  type: 'FunctionExpr',
319
560
  name: name.text,
320
- arguments: args.map(arg => transformCSTtoAST(arg, transformers, ctx))
561
+ arguments: argASTs
321
562
  };
322
563
  },
323
564
  ['function-argument'](node, ctx) {
@@ -31,9 +31,7 @@ class CSTOptimizedTranslator extends _CSTTranslator.default {
31
31
  options
32
32
  };
33
33
  this.translate(data);
34
- delete data.stack;
35
- delete data.options;
36
- return data;
34
+ return data.root;
37
35
  }
38
36
  }
39
37
  var _default = exports.default = CSTOptimizedTranslator;
@@ -111,8 +111,7 @@ class CSTTranslator extends _apgLite.Ast {
111
111
  root: null
112
112
  };
113
113
  this.translate(data);
114
- delete data.stack;
115
- return data;
114
+ return data.root;
116
115
  }
117
116
  }
118
117
  var _default = exports.default = CSTTranslator;
@@ -3,9 +3,11 @@
3
3
  exports.__esModule = true;
4
4
  exports.default = void 0;
5
5
  var _index = _interopRequireDefault(require("../parse/index.cjs"));
6
+ var _index2 = _interopRequireDefault(require("../parse/translators/ASTTranslator/index.cjs"));
6
7
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
8
  const test = (jsonPath, {
8
- normalized = false
9
+ normalized = false,
10
+ wellTyped = true
9
11
  } = {}) => {
10
12
  if (typeof jsonPath !== 'string') return false;
11
13
  try {
@@ -15,7 +17,7 @@ const test = (jsonPath, {
15
17
  normalized,
16
18
  stats: false,
17
19
  trace: false,
18
- translator: null
20
+ translator: wellTyped ? new _index2.default() : null
19
21
  });
20
22
  return result.success;
21
23
  } catch {
@@ -0,0 +1,3 @@
1
+ import JSONPathError from "./JSONPathError.mjs";
2
+ class JSONNormalizedPathError extends JSONPathError {}
3
+ export default JSONNormalizedPathError;
@@ -15,7 +15,7 @@ class JSONPathError extends Error {
15
15
  * This needs to stay here until our minimum supported version of Node.js is >= 16.9.0.
16
16
  * Node.js is >= 16.9.0 supports error causes natively.
17
17
  */
18
- if (options != null && typeof options === 'object' && Object.prototype.hasOwnProperty.call(options, 'cause') && !('cause' in this)) {
18
+ if (options != null && typeof options === 'object' && Object.hasOwn(options, 'cause') && !('cause' in this)) {
19
19
  const {
20
20
  cause
21
21
  } = options;
@@ -0,0 +1,3 @@
1
+ import JSONPathError from "./JSONPathError.mjs";
2
+ class JSONPathEvaluateError extends JSONPathError {}
3
+ export default JSONPathEvaluateError;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Comparable evaluator.
3
+ *
4
+ * Evaluates comparables which can be:
5
+ * - Literal (string, number, boolean, null)
6
+ * - RelSingularQuery (@.path)
7
+ * - AbsSingularQuery ($.path)
8
+ * - FunctionExpr (function call)
9
+ *
10
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2.2
11
+ */
12
+ import evaluateLiteral from "./literal.mjs";
13
+ import { evaluateRelSingularQuery, evaluateAbsSingularQuery } from "./singular-query.mjs";
14
+ import evaluateFunctionExpr from "./function-expr.mjs";
15
+ /**
16
+ * Evaluate a comparable expression.
17
+ *
18
+ * @param {object} ctx - Evaluation context
19
+ * @param {unknown} root - Root value ($)
20
+ * @param {unknown} current - Current value (@)
21
+ * @param {object} node - Comparable AST node
22
+ * @returns {unknown} - Evaluated value
23
+ */
24
+ const evaluateComparable = (ctx, root, current, node) => {
25
+ switch (node.type) {
26
+ case 'Literal':
27
+ return evaluateLiteral(ctx, root, current, node);
28
+ case 'RelSingularQuery':
29
+ return evaluateRelSingularQuery(ctx, root, current, node);
30
+ case 'AbsSingularQuery':
31
+ return evaluateAbsSingularQuery(ctx, root, current, node);
32
+ case 'FunctionExpr':
33
+ return evaluateFunctionExpr(ctx, root, current, node);
34
+ default:
35
+ return undefined;
36
+ }
37
+ };
38
+ export default evaluateComparable;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Comparison expression evaluator.
3
+ *
4
+ * Evaluates comparison expressions like @.price < 10, @.name == "foo", etc.
5
+ *
6
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2.3
7
+ */
8
+ import evaluateComparable from "./comparable.mjs";
9
+ /**
10
+ * Evaluate a comparison expression.
11
+ *
12
+ * @param {object} ctx - Evaluation context
13
+ * @param {unknown} root - Root value ($)
14
+ * @param {unknown} current - Current value (@)
15
+ * @param {object} node - ComparisonExpr AST node
16
+ * @param {object} node.left - Left comparable
17
+ * @param {string} node.op - Comparison operator (==, !=, <, <=, >, >=)
18
+ * @param {object} node.right - Right comparable
19
+ * @returns {boolean} - Comparison result
20
+ */
21
+ const evaluateComparisonExpr = (ctx, root, current, node) => {
22
+ const {
23
+ left,
24
+ op,
25
+ right
26
+ } = node;
27
+ const leftValue = evaluateComparable(ctx, root, current, left);
28
+ const rightValue = evaluateComparable(ctx, root, current, right);
29
+ return ctx.realm.compare(leftValue, op, rightValue);
30
+ };
31
+ export default evaluateComparisonExpr;