@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,175 @@
1
+ /**
2
+ * Filter query evaluator.
3
+ *
4
+ * Evaluates FilterQuery expressions which contain either:
5
+ * - RelQuery (@.path) - relative to current node
6
+ * - JsonPathQuery ($.path) - absolute from root
7
+ *
8
+ * Returns a nodelist (array of matched values).
9
+ *
10
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2.1
11
+ */
12
+ import visitSelector from "../visitors/selector.mjs";
13
+ import visitBracketedSelection from "../visitors/bracketed-selection.mjs";
14
+ /**
15
+ * Apply a segment to a value and collect all results.
16
+ *
17
+ * @param {object} ctx - Evaluation context
18
+ * @param {unknown} value - Current value
19
+ * @param {object} segment - Segment AST node
20
+ * @returns {unknown[]} - Array of matched values
21
+ */
22
+ const applySegment = (ctx, value, segment) => {
23
+ const results = [];
24
+ const emit = selectedValue => {
25
+ results.push(selectedValue);
26
+ };
27
+ const {
28
+ selector
29
+ } = segment;
30
+ switch (selector.type) {
31
+ case 'BracketedSelection':
32
+ visitBracketedSelection(ctx, value, selector, emit);
33
+ break;
34
+ case 'NameSelector':
35
+ case 'WildcardSelector':
36
+ case 'IndexSelector':
37
+ case 'SliceSelector':
38
+ case 'FilterSelector':
39
+ visitSelector(ctx, value, selector, emit);
40
+ break;
41
+ default:
42
+ break;
43
+ }
44
+ return results;
45
+ };
46
+
47
+ /**
48
+ * Apply segments to get nodelist, supporting descendant segments.
49
+ *
50
+ * @param {object} ctx - Evaluation context
51
+ * @param {unknown} root - Root value ($)
52
+ * @param {unknown[]} values - Current nodelist
53
+ * @param {object[]} segments - Remaining segments
54
+ * @returns {unknown[]} - Result nodelist
55
+ */
56
+ const applySegments = (ctx, root, values, segments) => {
57
+ let current = values;
58
+ for (const segment of segments) {
59
+ const next = [];
60
+ if (segment.type === 'DescendantSegment') {
61
+ // Descendant segment: apply to current and all descendants
62
+ const collectDescendants = value => {
63
+ const {
64
+ realm
65
+ } = ctx;
66
+ // Apply selector at current level
67
+ const results = applySegment(ctx, value, segment);
68
+ next.push(...results);
69
+
70
+ // Recurse into children
71
+ for (const [, child] of realm.entries(value)) {
72
+ collectDescendants(child);
73
+ }
74
+ };
75
+ for (const value of current) {
76
+ collectDescendants(value);
77
+ }
78
+ } else {
79
+ // Child segment: apply to current values only
80
+ for (const value of current) {
81
+ const results = applySegment(ctx, value, segment);
82
+ next.push(...results);
83
+ }
84
+ }
85
+ current = next;
86
+ }
87
+ return current;
88
+ };
89
+
90
+ /**
91
+ * Evaluate a RelQuery (@.path).
92
+ *
93
+ * @param {object} ctx - Evaluation context
94
+ * @param {unknown} root - Root value ($)
95
+ * @param {unknown} current - Current value (@)
96
+ * @param {object} node - RelQuery AST node
97
+ * @returns {unknown[]} - Nodelist of matched values
98
+ */
99
+ const evaluateRelQuery = (ctx, root, current, node) => {
100
+ const {
101
+ segments
102
+ } = node;
103
+ if (segments.length === 0) {
104
+ // @ with no segments returns current as single-element nodelist
105
+ return [current];
106
+ }
107
+ return applySegments(ctx, root, [current], segments);
108
+ };
109
+
110
+ /**
111
+ * Evaluate a JsonPathQuery ($.path).
112
+ *
113
+ * @param {object} ctx - Evaluation context
114
+ * @param {unknown} root - Root value ($)
115
+ * @param {unknown} current - Current value (unused)
116
+ * @param {object} node - JsonPathQuery AST node
117
+ * @returns {unknown[]} - Nodelist of matched values
118
+ */
119
+ const evaluateJsonPathQuery = (ctx, root, current, node) => {
120
+ const {
121
+ segments
122
+ } = node;
123
+ if (segments.length === 0) {
124
+ // $ with no segments returns root as single-element nodelist
125
+ return [root];
126
+ }
127
+ return applySegments(ctx, root, [root], segments);
128
+ };
129
+
130
+ /**
131
+ * Mark an array as a nodelist.
132
+ * This helps functions distinguish nodelists from array values.
133
+ *
134
+ * @param {unknown[]} arr - Array to mark
135
+ * @returns {unknown[]} - Marked array
136
+ */
137
+ const markAsNodelist = arr => {
138
+ Object.defineProperty(arr, '_isNodelist', {
139
+ value: true,
140
+ enumerable: false,
141
+ writable: false
142
+ });
143
+ return arr;
144
+ };
145
+
146
+ /**
147
+ * Evaluate a FilterQuery.
148
+ *
149
+ * @param {object} ctx - Evaluation context
150
+ * @param {unknown} root - Root value ($)
151
+ * @param {unknown} current - Current value (@)
152
+ * @param {object} node - FilterQuery AST node
153
+ * @returns {unknown[]} - Nodelist of matched values
154
+ */
155
+ const evaluateFilterQuery = (ctx, root, current, node) => {
156
+ const {
157
+ query
158
+ } = node;
159
+ let result;
160
+ switch (query.type) {
161
+ case 'RelQuery':
162
+ result = evaluateRelQuery(ctx, root, current, query);
163
+ break;
164
+ case 'JsonPathQuery':
165
+ result = evaluateJsonPathQuery(ctx, root, current, query);
166
+ break;
167
+ default:
168
+ result = [];
169
+ }
170
+
171
+ // Mark result as a nodelist so functions can handle type coercion
172
+ return markAsNodelist(result);
173
+ };
174
+ export default evaluateFilterQuery;
175
+ export { evaluateRelQuery, evaluateJsonPathQuery };
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Function expression evaluator.
3
+ *
4
+ * Evaluates function calls like length(@.items), match(@.name, "pattern"), etc.
5
+ *
6
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4
7
+ */
8
+ import evaluateComparable from "./comparable.mjs";
9
+ import evaluateFilterQuery from "./filter-query.mjs";
10
+ /**
11
+ * Evaluate a function argument.
12
+ * Arguments can be:
13
+ * - Literals
14
+ * - Singular queries (@.path, $.path)
15
+ * - Filter queries (for NodesType parameters)
16
+ * - Function expressions (nested calls)
17
+ * - Logical expressions (for LogicalType parameters)
18
+ *
19
+ * Special case: When a TestExpr contains only a FilterQuery,
20
+ * we evaluate it as a nodelist (for functions like count() that expect NodesType).
21
+ *
22
+ * @param {object} ctx - Evaluation context
23
+ * @param {unknown} root - Root value ($)
24
+ * @param {unknown} current - Current value (@)
25
+ * @param {object} arg - Argument AST node
26
+ * @returns {unknown} - Evaluated argument value
27
+ */
28
+ const evaluateArgument = (ctx, root, current, arg) => {
29
+ switch (arg.type) {
30
+ case 'Literal':
31
+ case 'RelSingularQuery':
32
+ case 'AbsSingularQuery':
33
+ case 'FunctionExpr':
34
+ return evaluateComparable(ctx, root, current, arg);
35
+ case 'FilterQuery':
36
+ // FilterQuery produces a nodelist (array of values)
37
+ return evaluateFilterQuery(ctx, root, current, arg);
38
+ case 'TestExpr':
39
+ // TestExpr can contain FilterQuery (for NodesType/ValueType) or FunctionExpr
40
+ if (arg.expression.type === 'FilterQuery') {
41
+ // Always return the nodelist - functions handle type coercion internally
42
+ // Per RFC 9535 Section 2.4.1: if a function expects ValueType and gets NodesType,
43
+ // it auto-converts (single node -> value, otherwise -> Nothing)
44
+ return evaluateFilterQuery(ctx, root, current, arg.expression);
45
+ }
46
+ if (arg.expression.type === 'FunctionExpr') {
47
+ // FunctionExpr as argument - evaluate the nested function
48
+ return evaluateComparable(ctx, root, current, arg.expression);
49
+ }
50
+ // Otherwise evaluate as logical expression
51
+ // eslint-disable-next-line no-use-before-define
52
+ return evaluateLogicalExpr(ctx, root, current, arg);
53
+ case 'LogicalOrExpr':
54
+ case 'LogicalAndExpr':
55
+ case 'LogicalNotExpr':
56
+ case 'ComparisonExpr':
57
+ // Import dynamically to avoid circular dependency
58
+ // eslint-disable-next-line no-use-before-define
59
+ return evaluateLogicalExpr(ctx, root, current, arg);
60
+ default:
61
+ return undefined;
62
+ }
63
+ };
64
+
65
+ // Lazy import to avoid circular dependency
66
+ let evaluateLogicalExpr;
67
+ export const setLogicalExprEvaluator = fn => {
68
+ evaluateLogicalExpr = fn;
69
+ };
70
+
71
+ /**
72
+ * Evaluate a function expression.
73
+ *
74
+ * @param {object} ctx - Evaluation context
75
+ * @param {unknown} root - Root value ($)
76
+ * @param {unknown} current - Current value (@)
77
+ * @param {object} node - AST node
78
+ * @param {string} node.name - Function name
79
+ * @param {object[]} node.arguments - Array of argument AST nodes
80
+ * @returns {unknown} - Function result
81
+ */
82
+ const evaluateFunctionExpr = (ctx, root, current, node) => {
83
+ const {
84
+ name,
85
+ arguments: args
86
+ } = node;
87
+ const fn = ctx.functions[name];
88
+ if (typeof fn !== 'function') {
89
+ // Unknown function returns Nothing
90
+ return undefined;
91
+ }
92
+
93
+ // Evaluate all arguments
94
+ const evaluatedArgs = args.map(arg => evaluateArgument(ctx, root, current, arg));
95
+
96
+ // Call the function with realm and evaluated arguments
97
+ return fn(ctx.realm, ...evaluatedArgs);
98
+ };
99
+ export default evaluateFunctionExpr;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Literal evaluator.
3
+ *
4
+ * Evaluates a literal value (string, number, boolean, null).
5
+ * Literals appear in comparisons and function arguments.
6
+ */
7
+
8
+ /**
9
+ * Evaluate a literal AST node.
10
+ *
11
+ * @param {object} ctx - Evaluation context (unused for literals)
12
+ * @param {unknown} root - Root value (unused for literals)
13
+ * @param {unknown} current - Current value (unused for literals)
14
+ * @param {object} node - AST node
15
+ * @param {unknown} node.value - The literal value
16
+ * @returns {unknown} - The literal value
17
+ */
18
+ const evaluateLiteral = (ctx, root, current, node) => {
19
+ return node.value;
20
+ };
21
+ export default evaluateLiteral;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Logical expression evaluator.
3
+ *
4
+ * Evaluates logical expressions:
5
+ * - LogicalOrExpr (||)
6
+ * - LogicalAndExpr (&&)
7
+ * - LogicalNotExpr (!)
8
+ * - TestExpr (existence test or function result)
9
+ * - ComparisonExpr (routed to comparison evaluator)
10
+ *
11
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2
12
+ */
13
+ import evaluateComparisonExpr from "./comparison-expr.mjs";
14
+ import evaluateFunctionExpr, { setLogicalExprEvaluator } from "./function-expr.mjs";
15
+ import evaluateFilterQuery from "./filter-query.mjs";
16
+ import { isArray } from "../utils/guards.mjs";
17
+ /**
18
+ * Evaluate a logical expression.
19
+ *
20
+ * @param {object} ctx - Evaluation context
21
+ * @param {unknown} root - Root value ($)
22
+ * @param {unknown} current - Current value (@)
23
+ * @param {object} node - Logical expression AST node
24
+ * @returns {boolean} - Logical result
25
+ */
26
+ const evaluateLogicalExpr = (ctx, root, current, node) => {
27
+ switch (node.type) {
28
+ case 'LogicalOrExpr':
29
+ {
30
+ // Short-circuit OR
31
+ const left = evaluateLogicalExpr(ctx, root, current, node.left);
32
+ if (left) return true;
33
+ return evaluateLogicalExpr(ctx, root, current, node.right);
34
+ }
35
+ case 'LogicalAndExpr':
36
+ {
37
+ // Short-circuit AND
38
+ const left = evaluateLogicalExpr(ctx, root, current, node.left);
39
+ if (!left) return false;
40
+ return evaluateLogicalExpr(ctx, root, current, node.right);
41
+ }
42
+ case 'LogicalNotExpr':
43
+ {
44
+ return !evaluateLogicalExpr(ctx, root, current, node.expression);
45
+ }
46
+ case 'TestExpr':
47
+ {
48
+ // TestExpr wraps a FilterQuery or FunctionExpr
49
+ const {
50
+ expression
51
+ } = node;
52
+ if (expression.type === 'FilterQuery') {
53
+ // Existence test: true if nodelist is non-empty
54
+ const nodelist = evaluateFilterQuery(ctx, root, current, expression);
55
+ return nodelist.length > 0;
56
+ }
57
+ if (expression.type === 'FunctionExpr') {
58
+ // Function result converted to boolean
59
+ const result = evaluateFunctionExpr(ctx, root, current, expression);
60
+ // LogicalType functions return boolean directly
61
+ // ValueType functions: undefined (Nothing) is false, truthy values are true
62
+ if (typeof result === 'boolean') {
63
+ return result;
64
+ }
65
+ // Nothing is false
66
+ if (result === undefined) {
67
+ return false;
68
+ }
69
+ // NodesType (array): non-empty is true
70
+ if (isArray(result)) {
71
+ return result.length > 0;
72
+ }
73
+ // Other ValueType: truthy check
74
+ return Boolean(result);
75
+ }
76
+ return false;
77
+ }
78
+ case 'ComparisonExpr':
79
+ {
80
+ return evaluateComparisonExpr(ctx, root, current, node);
81
+ }
82
+ default:
83
+ return false;
84
+ }
85
+ };
86
+
87
+ // Register with function-expr to break circular dependency
88
+ setLogicalExprEvaluator(evaluateLogicalExpr);
89
+ export default evaluateLogicalExpr;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Singular query evaluator.
3
+ *
4
+ * Evaluates RelSingularQuery (@.path) and AbsSingularQuery ($.path) expressions.
5
+ * These appear in comparison expressions within filters.
6
+ *
7
+ * A singular query can only contain name selectors and index selectors,
8
+ * ensuring it produces at most one value.
9
+ *
10
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.3.5.2.2
11
+ */
12
+
13
+ /**
14
+ * Apply a singular query segment (name or index selector).
15
+ *
16
+ * @param {object} ctx - Evaluation context
17
+ * @param {unknown} value - Current value
18
+ * @param {object} segment - Segment AST node
19
+ * @returns {unknown} - Selected value or undefined (Nothing)
20
+ */
21
+ const applySingularSegment = (ctx, value, segment) => {
22
+ const {
23
+ realm
24
+ } = ctx;
25
+ const {
26
+ selector
27
+ } = segment;
28
+ switch (selector.type) {
29
+ case 'NameSelector':
30
+ {
31
+ const {
32
+ value: name
33
+ } = selector;
34
+ if (realm.isObject(value) && realm.hasProperty(value, name)) {
35
+ return realm.getProperty(value, name);
36
+ }
37
+ return undefined; // Nothing
38
+ }
39
+ case 'IndexSelector':
40
+ {
41
+ const {
42
+ value: index
43
+ } = selector;
44
+ if (!realm.isArray(value)) return undefined;
45
+ const length = realm.getLength(value);
46
+ const normalizedIndex = index >= 0 ? index : length + index;
47
+ if (normalizedIndex >= 0 && normalizedIndex < length) {
48
+ return realm.getElement(value, normalizedIndex);
49
+ }
50
+ return undefined; // Nothing
51
+ }
52
+ default:
53
+ return undefined;
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Evaluate a RelSingularQuery (@.path).
59
+ *
60
+ * @param {object} ctx - Evaluation context
61
+ * @param {unknown} root - Root value (unused)
62
+ * @param {unknown} current - Current value (@)
63
+ * @param {object} node - AST node
64
+ * @param {object[]} node.segments - Array of singular query segments
65
+ * @returns {unknown} - Result value or undefined (Nothing)
66
+ */
67
+ export const evaluateRelSingularQuery = (ctx, root, current, node) => {
68
+ let value = current;
69
+ for (const segment of node.segments) {
70
+ value = applySingularSegment(ctx, value, segment);
71
+ if (value === undefined) {
72
+ return undefined; // Nothing - short circuit
73
+ }
74
+ }
75
+ return value;
76
+ };
77
+
78
+ /**
79
+ * Evaluate an AbsSingularQuery ($.path).
80
+ *
81
+ * @param {object} ctx - Evaluation context
82
+ * @param {unknown} root - Root value ($)
83
+ * @param {unknown} current - Current value (unused)
84
+ * @param {object} node - AST node
85
+ * @param {object[]} node.segments - Array of singular query segments
86
+ * @returns {unknown} - Result value or undefined (Nothing)
87
+ */
88
+ export const evaluateAbsSingularQuery = (ctx, root, current, node) => {
89
+ let value = root;
90
+ for (const segment of node.segments) {
91
+ value = applySingularSegment(ctx, value, segment);
92
+ if (value === undefined) {
93
+ return undefined; // Nothing - short circuit
94
+ }
95
+ }
96
+ return value;
97
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * RFC 9535 count() function.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.4
5
+ *
6
+ * Parameters:
7
+ * NodesType (nodelist)
8
+ *
9
+ * Returns:
10
+ * ValueType (number)
11
+ *
12
+ * Result:
13
+ * The number of nodes in the nodelist.
14
+ */
15
+ import { isNodelist } from "../utils/guards.mjs";
16
+ /**
17
+ * Count the number of nodes in a nodelist.
18
+ *
19
+ * @param {object} realm - Evaluation realm (unused, for consistent signature)
20
+ * @param {unknown} nodelist - A nodelist (array of values)
21
+ * @returns {number} - Number of nodes
22
+ */
23
+ const count = (realm, nodelist) => {
24
+ if (isNodelist(nodelist)) {
25
+ return nodelist.length;
26
+ }
27
+ // Not a nodelist (NodesType): return Nothing per RFC 9535
28
+ return undefined;
29
+ };
30
+ export default count;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * RFC 9535 built-in functions.
3
+ *
4
+ * All functions defined in RFC 9535 Section 2.4.
5
+ * Can be extended or overridden via the `functions` option in evaluate().
6
+ *
7
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4
8
+ */
9
+ export { default as length } from "./length.mjs";
10
+ export { default as count } from "./count.mjs";
11
+ export { default as value } from "./value.mjs";
12
+ export { default as match } from "./match.mjs";
13
+ export { default as search } from "./search.mjs";
@@ -0,0 +1,37 @@
1
+ /**
2
+ * RFC 9535 length() function.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.5
5
+ *
6
+ * Parameters:
7
+ * ValueType (single value)
8
+ *
9
+ * Returns:
10
+ * ValueType (number or Nothing)
11
+ *
12
+ * Result:
13
+ * - String: number of Unicode scalar values (not UTF-16 code units)
14
+ * - Array: number of elements
15
+ * - Object: number of members (key-value pairs)
16
+ * - Other: Nothing (undefined)
17
+ */
18
+ import { coerceToValueType, isNothing } from "../utils/guards.mjs";
19
+ /**
20
+ * Get the length of a value.
21
+ *
22
+ * @param {object} realm - Evaluation realm
23
+ * @param {unknown} value - The value to measure (may be a nodelist)
24
+ * @returns {number | undefined} - Length or Nothing (undefined)
25
+ */
26
+ const length = (realm, value) => {
27
+ // Coerce nodelist to single value if needed
28
+ const coerced = coerceToValueType(value);
29
+
30
+ // Nothing returns Nothing
31
+ if (isNothing(coerced)) return undefined;
32
+
33
+ // Use realm to get length (handles strings, arrays, objects)
34
+ const len = realm.getLength(coerced);
35
+ return len > 0 || realm.isString(coerced) || realm.isArray(coerced) || realm.isObject(coerced) ? len : undefined;
36
+ };
37
+ export default length;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * RFC 9535 match() function.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.1
5
+ *
6
+ * Parameters:
7
+ * ValueType (string), ValueType (I-Regexp pattern string)
8
+ *
9
+ * Returns:
10
+ * LogicalType (boolean)
11
+ *
12
+ * Result:
13
+ * true if the entire string matches the I-Regexp pattern, false otherwise.
14
+ * The pattern is implicitly anchored (^(?:pattern)$).
15
+ */
16
+ import { coerceToValueType } from "../utils/guards.mjs";
17
+ import { constructRegex } from "../utils/i-regexp.mjs";
18
+ /**
19
+ * Test if entire string matches I-Regexp pattern.
20
+ *
21
+ * @param {object} realm - Evaluation realm
22
+ * @param {unknown} value - The string to test (may be a nodelist)
23
+ * @param {unknown} pattern - The I-Regexp pattern (may be a nodelist)
24
+ * @returns {boolean} - true if entire string matches
25
+ */
26
+ const match = (realm, value, pattern) => {
27
+ // Coerce nodelists to single values
28
+ const coercedValue = coerceToValueType(value);
29
+ const coercedPattern = coerceToValueType(pattern);
30
+
31
+ // Get raw string values from realm
32
+ const strValue = realm.getString(coercedValue);
33
+ const strPattern = realm.getString(coercedPattern);
34
+ if (strValue === undefined || strPattern === undefined) {
35
+ return false;
36
+ }
37
+ const regex = constructRegex(strPattern, true); // anchored
38
+ if (regex === null) {
39
+ // Invalid I-Regexp pattern
40
+ return false;
41
+ }
42
+ return regex.test(strValue);
43
+ };
44
+ export default match;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * RFC 9535 search() function.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.2
5
+ *
6
+ * Parameters:
7
+ * ValueType (string), ValueType (I-Regexp pattern string)
8
+ *
9
+ * Returns:
10
+ * LogicalType (boolean)
11
+ *
12
+ * Result:
13
+ * true if any substring matches the I-Regexp pattern, false otherwise.
14
+ * The pattern is not anchored (can match anywhere in string).
15
+ */
16
+ import { coerceToValueType } from "../utils/guards.mjs";
17
+ import { constructRegex } from "../utils/i-regexp.mjs";
18
+ /**
19
+ * Test if any substring matches I-Regexp pattern.
20
+ *
21
+ * @param {object} realm - Evaluation realm
22
+ * @param {unknown} value - The string to search (may be a nodelist)
23
+ * @param {unknown} pattern - The I-Regexp pattern (may be a nodelist)
24
+ * @returns {boolean} - true if any substring matches
25
+ */
26
+ const search = (realm, value, pattern) => {
27
+ // Coerce nodelists to single values
28
+ const coercedValue = coerceToValueType(value);
29
+ const coercedPattern = coerceToValueType(pattern);
30
+
31
+ // Get raw string values from realm
32
+ const strValue = realm.getString(coercedValue);
33
+ const strPattern = realm.getString(coercedPattern);
34
+ if (strValue === undefined || strPattern === undefined) {
35
+ return false;
36
+ }
37
+ const regex = constructRegex(strPattern, false); // not anchored
38
+ if (regex === null) {
39
+ // Invalid I-Regexp pattern
40
+ return false;
41
+ }
42
+ return regex.test(strValue);
43
+ };
44
+ export default search;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * RFC 9535 value() function.
3
+ *
4
+ * @see https://www.rfc-editor.org/rfc/rfc9535#section-2.4.7
5
+ *
6
+ * Parameters:
7
+ * NodesType (nodelist)
8
+ *
9
+ * Returns:
10
+ * ValueType (the single value or Nothing)
11
+ *
12
+ * Result:
13
+ * - If nodelist has exactly one node: that node's value
14
+ * - Otherwise: Nothing (undefined)
15
+ */
16
+ import { isNodelist } from "../utils/guards.mjs";
17
+ /**
18
+ * Extract the single value from a nodelist.
19
+ *
20
+ * @param {object} realm - Evaluation realm (unused, for consistent signature)
21
+ * @param {unknown} nodelist - A nodelist (array of values)
22
+ * @returns {unknown} - The single value or Nothing (undefined)
23
+ */
24
+ const value = (realm, nodelist) => {
25
+ if (isNodelist(nodelist) && nodelist.length === 1) {
26
+ return nodelist[0];
27
+ }
28
+ // Nothing for empty nodelist, multiple values, or non-nodelist
29
+ return undefined;
30
+ };
31
+ export default value;