dd-trace 4.47.1 → 4.48.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/LICENSE-3rdparty.csv +0 -1
  2. package/ext/types.d.ts +1 -0
  3. package/ext/types.js +1 -0
  4. package/index.d.ts +26 -0
  5. package/package.json +6 -7
  6. package/packages/datadog-code-origin/index.js +38 -0
  7. package/packages/datadog-core/index.js +2 -2
  8. package/packages/datadog-instrumentations/src/avsc.js +37 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
  10. package/packages/datadog-instrumentations/src/child_process.js +17 -8
  11. package/packages/datadog-instrumentations/src/express.js +37 -4
  12. package/packages/datadog-instrumentations/src/fastify.js +12 -1
  13. package/packages/datadog-instrumentations/src/fs.js +27 -7
  14. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  15. package/packages/datadog-instrumentations/src/jest.js +2 -1
  16. package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
  17. package/packages/datadog-instrumentations/src/mysql2.js +220 -1
  18. package/packages/datadog-instrumentations/src/protobufjs.js +127 -0
  19. package/packages/datadog-instrumentations/src/winston.js +22 -0
  20. package/packages/datadog-plugin-avsc/src/index.js +9 -0
  21. package/packages/datadog-plugin-avsc/src/schema_iterator.js +169 -0
  22. package/packages/datadog-plugin-azure-functions/src/index.js +77 -0
  23. package/packages/datadog-plugin-fastify/src/code_origin.js +31 -0
  24. package/packages/datadog-plugin-fastify/src/index.js +10 -12
  25. package/packages/datadog-plugin-fastify/src/tracing.js +19 -0
  26. package/packages/datadog-plugin-protobufjs/src/index.js +14 -0
  27. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +180 -0
  28. package/packages/dd-trace/src/appsec/addresses.js +6 -1
  29. package/packages/dd-trace/src/appsec/channels.js +5 -1
  30. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +13 -1
  31. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +8 -1
  32. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  33. package/packages/dd-trace/src/appsec/iast/index.js +3 -0
  34. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  35. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +15 -0
  36. package/packages/dd-trace/src/appsec/index.js +58 -43
  37. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +99 -0
  38. package/packages/dd-trace/src/appsec/rasp/index.js +24 -10
  39. package/packages/dd-trace/src/appsec/rasp/lfi.js +112 -0
  40. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +24 -4
  41. package/packages/dd-trace/src/appsec/rasp/utils.js +2 -1
  42. package/packages/dd-trace/src/appsec/recommended.json +2 -4
  43. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +5 -1
  44. package/packages/dd-trace/src/appsec/remote_config/index.js +8 -0
  45. package/packages/dd-trace/src/appsec/reporter.js +12 -5
  46. package/packages/dd-trace/src/appsec/sdk/track_event.js +5 -0
  47. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
  48. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -14
  49. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +53 -0
  50. package/packages/dd-trace/src/config.js +12 -1
  51. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +25 -17
  52. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  53. package/packages/dd-trace/src/debugger/devtools_client/index.js +56 -5
  54. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +4 -4
  55. package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -1
  56. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +153 -0
  57. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +30 -0
  58. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +241 -0
  59. package/packages/dd-trace/src/debugger/devtools_client/state.js +10 -4
  60. package/packages/dd-trace/src/exporters/common/request.js +8 -34
  61. package/packages/dd-trace/src/exporters/common/url-to-http-options-polyfill.js +31 -0
  62. package/packages/dd-trace/src/payload-tagging/index.js +1 -1
  63. package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +2094 -0
  64. package/packages/dd-trace/src/plugin_manager.js +4 -2
  65. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
  66. package/packages/dd-trace/src/plugins/index.js +3 -0
  67. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  68. package/packages/dd-trace/src/plugins/schema.js +35 -0
  69. package/packages/dd-trace/src/plugins/util/ci.js +23 -1
  70. package/packages/dd-trace/src/plugins/util/serverless.js +7 -0
  71. package/packages/dd-trace/src/plugins/util/stacktrace.js +94 -0
  72. package/packages/dd-trace/src/plugins/util/tags.js +7 -0
  73. package/packages/dd-trace/src/plugins/util/test.js +20 -22
  74. package/packages/dd-trace/src/plugins/util/web.js +6 -4
  75. package/packages/dd-trace/src/profiling/profiler.js +24 -14
  76. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  77. package/packages/dd-trace/src/profiling/profilers/wall.js +94 -66
  78. package/packages/dd-trace/src/proxy.js +12 -0
  79. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  80. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  81. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  82. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  83. package/packages/datadog-core/src/storage/async_resource.js +0 -108
  84. package/packages/datadog-core/src/storage/index.js +0 -5
@@ -0,0 +1,2094 @@
1
+ 'use strict';
2
+
3
+ // NOTE(bengl): This file is taken directly from jsonpath-plus@10.0.0
4
+ //
5
+ // https://github.com/JSONPath-Plus/JSONPath/blob/a04dcbac760fed48760b09f387874a36f289c3f3/dist/index-node-cjs.cjs
6
+ //
7
+ // The only changes are:
8
+ // - Replace Object.hasOwn with polyfill
9
+ //
10
+ // This vendoring-and-editing was done to support usage on Node.js 16.0.0, so
11
+ // once support for that release line has ended, this can be replaced with a
12
+ // direct dependency on jsonpath-plus@^10. See the PR that introduced this file
13
+ // for details. More explicitly as a searchable to-do:
14
+ //
15
+ // TODO(bengl): Replace this with a direct dependency on jsonpath-plus@^10 when
16
+ // we drop support for Node 16.
17
+
18
+ // NOTE(bengl): Here is the license as distributed with jsonpath-plus@10:
19
+ /*
20
+ MIT License
21
+
22
+ Copyright (c) 2011-2019 Stefan Goessner, Subbu Allamaraju, Mike Brevoort,
23
+ Robert Krahn, Brett Zamir, Richard Schneider
24
+
25
+ Permission is hereby granted, free of charge, to any person obtaining a copy
26
+ of this software and associated documentation files (the "Software"), to deal
27
+ in the Software without restriction, including without limitation the rights
28
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
29
+ copies of the Software, and to permit persons to whom the Software is
30
+ furnished to do so, subject to the following conditions:
31
+
32
+ The above copyright notice and this permission notice shall be included in all
33
+ copies or substantial portions of the Software.
34
+
35
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
38
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
39
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
40
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
41
+ SOFTWARE.
42
+ */
43
+
44
+ const hasOwn = Object.hasOwn || ((obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop));
45
+
46
+ var vm = require('vm');
47
+
48
+ /**
49
+ * @implements {IHooks}
50
+ */
51
+ class Hooks {
52
+ /**
53
+ * @callback HookCallback
54
+ * @this {*|Jsep} this
55
+ * @param {Jsep} env
56
+ * @returns: void
57
+ */
58
+ /**
59
+ * Adds the given callback to the list of callbacks for the given hook.
60
+ *
61
+ * The callback will be invoked when the hook it is registered for is run.
62
+ *
63
+ * One callback function can be registered to multiple hooks and the same hook multiple times.
64
+ *
65
+ * @param {string|object} name The name of the hook, or an object of callbacks keyed by name
66
+ * @param {HookCallback|boolean} callback The callback function which is given environment variables.
67
+ * @param {?boolean} [first=false] Will add the hook to the top of the list (defaults to the bottom)
68
+ * @public
69
+ */
70
+ add(name, callback, first) {
71
+ if (typeof arguments[0] != 'string') {
72
+ // Multiple hook callbacks, keyed by name
73
+ for (let name in arguments[0]) {
74
+ this.add(name, arguments[0][name], arguments[1]);
75
+ }
76
+ } else {
77
+ (Array.isArray(name) ? name : [name]).forEach(function (name) {
78
+ this[name] = this[name] || [];
79
+ if (callback) {
80
+ this[name][first ? 'unshift' : 'push'](callback);
81
+ }
82
+ }, this);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Runs a hook invoking all registered callbacks with the given environment variables.
88
+ *
89
+ * Callbacks will be invoked synchronously and in the order in which they were registered.
90
+ *
91
+ * @param {string} name The name of the hook.
92
+ * @param {Object<string, any>} env The environment variables of the hook passed to all callbacks registered.
93
+ * @public
94
+ */
95
+ run(name, env) {
96
+ this[name] = this[name] || [];
97
+ this[name].forEach(function (callback) {
98
+ callback.call(env && env.context ? env.context : env, env);
99
+ });
100
+ }
101
+ }
102
+
103
+ /**
104
+ * @implements {IPlugins}
105
+ */
106
+ class Plugins {
107
+ constructor(jsep) {
108
+ this.jsep = jsep;
109
+ this.registered = {};
110
+ }
111
+
112
+ /**
113
+ * @callback PluginSetup
114
+ * @this {Jsep} jsep
115
+ * @returns: void
116
+ */
117
+ /**
118
+ * Adds the given plugin(s) to the registry
119
+ *
120
+ * @param {object} plugins
121
+ * @param {string} plugins.name The name of the plugin
122
+ * @param {PluginSetup} plugins.init The init function
123
+ * @public
124
+ */
125
+ register(...plugins) {
126
+ plugins.forEach(plugin => {
127
+ if (typeof plugin !== 'object' || !plugin.name || !plugin.init) {
128
+ throw new Error('Invalid JSEP plugin format');
129
+ }
130
+ if (this.registered[plugin.name]) {
131
+ // already registered. Ignore.
132
+ return;
133
+ }
134
+ plugin.init(this.jsep);
135
+ this.registered[plugin.name] = plugin;
136
+ });
137
+ }
138
+ }
139
+
140
+ // JavaScript Expression Parser (JSEP) 1.3.9
141
+
142
+ class Jsep {
143
+ /**
144
+ * @returns {string}
145
+ */
146
+ static get version() {
147
+ // To be filled in by the template
148
+ return '1.3.9';
149
+ }
150
+
151
+ /**
152
+ * @returns {string}
153
+ */
154
+ static toString() {
155
+ return 'JavaScript Expression Parser (JSEP) v' + Jsep.version;
156
+ }
157
+ // ==================== CONFIG ================================
158
+ /**
159
+ * @method addUnaryOp
160
+ * @param {string} op_name The name of the unary op to add
161
+ * @returns {Jsep}
162
+ */
163
+ static addUnaryOp(op_name) {
164
+ Jsep.max_unop_len = Math.max(op_name.length, Jsep.max_unop_len);
165
+ Jsep.unary_ops[op_name] = 1;
166
+ return Jsep;
167
+ }
168
+
169
+ /**
170
+ * @method jsep.addBinaryOp
171
+ * @param {string} op_name The name of the binary op to add
172
+ * @param {number} precedence The precedence of the binary op (can be a float). Higher number = higher precedence
173
+ * @param {boolean} [isRightAssociative=false] whether operator is right-associative
174
+ * @returns {Jsep}
175
+ */
176
+ static addBinaryOp(op_name, precedence, isRightAssociative) {
177
+ Jsep.max_binop_len = Math.max(op_name.length, Jsep.max_binop_len);
178
+ Jsep.binary_ops[op_name] = precedence;
179
+ if (isRightAssociative) {
180
+ Jsep.right_associative.add(op_name);
181
+ } else {
182
+ Jsep.right_associative.delete(op_name);
183
+ }
184
+ return Jsep;
185
+ }
186
+
187
+ /**
188
+ * @method addIdentifierChar
189
+ * @param {string} char The additional character to treat as a valid part of an identifier
190
+ * @returns {Jsep}
191
+ */
192
+ static addIdentifierChar(char) {
193
+ Jsep.additional_identifier_chars.add(char);
194
+ return Jsep;
195
+ }
196
+
197
+ /**
198
+ * @method addLiteral
199
+ * @param {string} literal_name The name of the literal to add
200
+ * @param {*} literal_value The value of the literal
201
+ * @returns {Jsep}
202
+ */
203
+ static addLiteral(literal_name, literal_value) {
204
+ Jsep.literals[literal_name] = literal_value;
205
+ return Jsep;
206
+ }
207
+
208
+ /**
209
+ * @method removeUnaryOp
210
+ * @param {string} op_name The name of the unary op to remove
211
+ * @returns {Jsep}
212
+ */
213
+ static removeUnaryOp(op_name) {
214
+ delete Jsep.unary_ops[op_name];
215
+ if (op_name.length === Jsep.max_unop_len) {
216
+ Jsep.max_unop_len = Jsep.getMaxKeyLen(Jsep.unary_ops);
217
+ }
218
+ return Jsep;
219
+ }
220
+
221
+ /**
222
+ * @method removeAllUnaryOps
223
+ * @returns {Jsep}
224
+ */
225
+ static removeAllUnaryOps() {
226
+ Jsep.unary_ops = {};
227
+ Jsep.max_unop_len = 0;
228
+ return Jsep;
229
+ }
230
+
231
+ /**
232
+ * @method removeIdentifierChar
233
+ * @param {string} char The additional character to stop treating as a valid part of an identifier
234
+ * @returns {Jsep}
235
+ */
236
+ static removeIdentifierChar(char) {
237
+ Jsep.additional_identifier_chars.delete(char);
238
+ return Jsep;
239
+ }
240
+
241
+ /**
242
+ * @method removeBinaryOp
243
+ * @param {string} op_name The name of the binary op to remove
244
+ * @returns {Jsep}
245
+ */
246
+ static removeBinaryOp(op_name) {
247
+ delete Jsep.binary_ops[op_name];
248
+ if (op_name.length === Jsep.max_binop_len) {
249
+ Jsep.max_binop_len = Jsep.getMaxKeyLen(Jsep.binary_ops);
250
+ }
251
+ Jsep.right_associative.delete(op_name);
252
+ return Jsep;
253
+ }
254
+
255
+ /**
256
+ * @method removeAllBinaryOps
257
+ * @returns {Jsep}
258
+ */
259
+ static removeAllBinaryOps() {
260
+ Jsep.binary_ops = {};
261
+ Jsep.max_binop_len = 0;
262
+ return Jsep;
263
+ }
264
+
265
+ /**
266
+ * @method removeLiteral
267
+ * @param {string} literal_name The name of the literal to remove
268
+ * @returns {Jsep}
269
+ */
270
+ static removeLiteral(literal_name) {
271
+ delete Jsep.literals[literal_name];
272
+ return Jsep;
273
+ }
274
+
275
+ /**
276
+ * @method removeAllLiterals
277
+ * @returns {Jsep}
278
+ */
279
+ static removeAllLiterals() {
280
+ Jsep.literals = {};
281
+ return Jsep;
282
+ }
283
+ // ==================== END CONFIG ============================
284
+
285
+ /**
286
+ * @returns {string}
287
+ */
288
+ get char() {
289
+ return this.expr.charAt(this.index);
290
+ }
291
+
292
+ /**
293
+ * @returns {number}
294
+ */
295
+ get code() {
296
+ return this.expr.charCodeAt(this.index);
297
+ }
298
+ /**
299
+ * @param {string} expr a string with the passed in express
300
+ * @returns Jsep
301
+ */
302
+ constructor(expr) {
303
+ // `index` stores the character number we are currently at
304
+ // All of the gobbles below will modify `index` as we move along
305
+ this.expr = expr;
306
+ this.index = 0;
307
+ }
308
+
309
+ /**
310
+ * static top-level parser
311
+ * @returns {jsep.Expression}
312
+ */
313
+ static parse(expr) {
314
+ return new Jsep(expr).parse();
315
+ }
316
+
317
+ /**
318
+ * Get the longest key length of any object
319
+ * @param {object} obj
320
+ * @returns {number}
321
+ */
322
+ static getMaxKeyLen(obj) {
323
+ return Math.max(0, ...Object.keys(obj).map(k => k.length));
324
+ }
325
+
326
+ /**
327
+ * `ch` is a character code in the next three functions
328
+ * @param {number} ch
329
+ * @returns {boolean}
330
+ */
331
+ static isDecimalDigit(ch) {
332
+ return ch >= 48 && ch <= 57; // 0...9
333
+ }
334
+
335
+ /**
336
+ * Returns the precedence of a binary operator or `0` if it isn't a binary operator. Can be float.
337
+ * @param {string} op_val
338
+ * @returns {number}
339
+ */
340
+ static binaryPrecedence(op_val) {
341
+ return Jsep.binary_ops[op_val] || 0;
342
+ }
343
+
344
+ /**
345
+ * Looks for start of identifier
346
+ * @param {number} ch
347
+ * @returns {boolean}
348
+ */
349
+ static isIdentifierStart(ch) {
350
+ return ch >= 65 && ch <= 90 ||
351
+ // A...Z
352
+ ch >= 97 && ch <= 122 ||
353
+ // a...z
354
+ ch >= 128 && !Jsep.binary_ops[String.fromCharCode(ch)] ||
355
+ // any non-ASCII that is not an operator
356
+ Jsep.additional_identifier_chars.has(String.fromCharCode(ch)); // additional characters
357
+ }
358
+
359
+ /**
360
+ * @param {number} ch
361
+ * @returns {boolean}
362
+ */
363
+ static isIdentifierPart(ch) {
364
+ return Jsep.isIdentifierStart(ch) || Jsep.isDecimalDigit(ch);
365
+ }
366
+
367
+ /**
368
+ * throw error at index of the expression
369
+ * @param {string} message
370
+ * @throws
371
+ */
372
+ throwError(message) {
373
+ const error = new Error(message + ' at character ' + this.index);
374
+ error.index = this.index;
375
+ error.description = message;
376
+ throw error;
377
+ }
378
+
379
+ /**
380
+ * Run a given hook
381
+ * @param {string} name
382
+ * @param {jsep.Expression|false} [node]
383
+ * @returns {?jsep.Expression}
384
+ */
385
+ runHook(name, node) {
386
+ if (Jsep.hooks[name]) {
387
+ const env = {
388
+ context: this,
389
+ node
390
+ };
391
+ Jsep.hooks.run(name, env);
392
+ return env.node;
393
+ }
394
+ return node;
395
+ }
396
+
397
+ /**
398
+ * Runs a given hook until one returns a node
399
+ * @param {string} name
400
+ * @returns {?jsep.Expression}
401
+ */
402
+ searchHook(name) {
403
+ if (Jsep.hooks[name]) {
404
+ const env = {
405
+ context: this
406
+ };
407
+ Jsep.hooks[name].find(function (callback) {
408
+ callback.call(env.context, env);
409
+ return env.node;
410
+ });
411
+ return env.node;
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Push `index` up to the next non-space character
417
+ */
418
+ gobbleSpaces() {
419
+ let ch = this.code;
420
+ // Whitespace
421
+ while (ch === Jsep.SPACE_CODE || ch === Jsep.TAB_CODE || ch === Jsep.LF_CODE || ch === Jsep.CR_CODE) {
422
+ ch = this.expr.charCodeAt(++this.index);
423
+ }
424
+ this.runHook('gobble-spaces');
425
+ }
426
+
427
+ /**
428
+ * Top-level method to parse all expressions and returns compound or single node
429
+ * @returns {jsep.Expression}
430
+ */
431
+ parse() {
432
+ this.runHook('before-all');
433
+ const nodes = this.gobbleExpressions();
434
+
435
+ // If there's only one expression just try returning the expression
436
+ const node = nodes.length === 1 ? nodes[0] : {
437
+ type: Jsep.COMPOUND,
438
+ body: nodes
439
+ };
440
+ return this.runHook('after-all', node);
441
+ }
442
+
443
+ /**
444
+ * top-level parser (but can be reused within as well)
445
+ * @param {number} [untilICode]
446
+ * @returns {jsep.Expression[]}
447
+ */
448
+ gobbleExpressions(untilICode) {
449
+ let nodes = [],
450
+ ch_i,
451
+ node;
452
+ while (this.index < this.expr.length) {
453
+ ch_i = this.code;
454
+
455
+ // Expressions can be separated by semicolons, commas, or just inferred without any
456
+ // separators
457
+ if (ch_i === Jsep.SEMCOL_CODE || ch_i === Jsep.COMMA_CODE) {
458
+ this.index++; // ignore separators
459
+ } else {
460
+ // Try to gobble each expression individually
461
+ if (node = this.gobbleExpression()) {
462
+ nodes.push(node);
463
+ // If we weren't able to find a binary expression and are out of room, then
464
+ // the expression passed in probably has too much
465
+ } else if (this.index < this.expr.length) {
466
+ if (ch_i === untilICode) {
467
+ break;
468
+ }
469
+ this.throwError('Unexpected "' + this.char + '"');
470
+ }
471
+ }
472
+ }
473
+ return nodes;
474
+ }
475
+
476
+ /**
477
+ * The main parsing function.
478
+ * @returns {?jsep.Expression}
479
+ */
480
+ gobbleExpression() {
481
+ const node = this.searchHook('gobble-expression') || this.gobbleBinaryExpression();
482
+ this.gobbleSpaces();
483
+ return this.runHook('after-expression', node);
484
+ }
485
+
486
+ /**
487
+ * Search for the operation portion of the string (e.g. `+`, `===`)
488
+ * Start by taking the longest possible binary operations (3 characters: `===`, `!==`, `>>>`)
489
+ * and move down from 3 to 2 to 1 character until a matching binary operation is found
490
+ * then, return that binary operation
491
+ * @returns {string|boolean}
492
+ */
493
+ gobbleBinaryOp() {
494
+ this.gobbleSpaces();
495
+ let to_check = this.expr.substr(this.index, Jsep.max_binop_len);
496
+ let tc_len = to_check.length;
497
+ while (tc_len > 0) {
498
+ // Don't accept a binary op when it is an identifier.
499
+ // Binary ops that start with a identifier-valid character must be followed
500
+ // by a non identifier-part valid character
501
+ if (Jsep.binary_ops.hasOwnProperty(to_check) && (!Jsep.isIdentifierStart(this.code) || this.index + to_check.length < this.expr.length && !Jsep.isIdentifierPart(this.expr.charCodeAt(this.index + to_check.length)))) {
502
+ this.index += tc_len;
503
+ return to_check;
504
+ }
505
+ to_check = to_check.substr(0, --tc_len);
506
+ }
507
+ return false;
508
+ }
509
+
510
+ /**
511
+ * This function is responsible for gobbling an individual expression,
512
+ * e.g. `1`, `1+2`, `a+(b*2)-Math.sqrt(2)`
513
+ * @returns {?jsep.BinaryExpression}
514
+ */
515
+ gobbleBinaryExpression() {
516
+ let node, biop, prec, stack, biop_info, left, right, i, cur_biop;
517
+
518
+ // First, try to get the leftmost thing
519
+ // Then, check to see if there's a binary operator operating on that leftmost thing
520
+ // Don't gobbleBinaryOp without a left-hand-side
521
+ left = this.gobbleToken();
522
+ if (!left) {
523
+ return left;
524
+ }
525
+ biop = this.gobbleBinaryOp();
526
+
527
+ // If there wasn't a binary operator, just return the leftmost node
528
+ if (!biop) {
529
+ return left;
530
+ }
531
+
532
+ // Otherwise, we need to start a stack to properly place the binary operations in their
533
+ // precedence structure
534
+ biop_info = {
535
+ value: biop,
536
+ prec: Jsep.binaryPrecedence(biop),
537
+ right_a: Jsep.right_associative.has(biop)
538
+ };
539
+ right = this.gobbleToken();
540
+ if (!right) {
541
+ this.throwError("Expected expression after " + biop);
542
+ }
543
+ stack = [left, biop_info, right];
544
+
545
+ // Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
546
+ while (biop = this.gobbleBinaryOp()) {
547
+ prec = Jsep.binaryPrecedence(biop);
548
+ if (prec === 0) {
549
+ this.index -= biop.length;
550
+ break;
551
+ }
552
+ biop_info = {
553
+ value: biop,
554
+ prec,
555
+ right_a: Jsep.right_associative.has(biop)
556
+ };
557
+ cur_biop = biop;
558
+
559
+ // Reduce: make a binary expression from the three topmost entries.
560
+ const comparePrev = prev => biop_info.right_a && prev.right_a ? prec > prev.prec : prec <= prev.prec;
561
+ while (stack.length > 2 && comparePrev(stack[stack.length - 2])) {
562
+ right = stack.pop();
563
+ biop = stack.pop().value;
564
+ left = stack.pop();
565
+ node = {
566
+ type: Jsep.BINARY_EXP,
567
+ operator: biop,
568
+ left,
569
+ right
570
+ };
571
+ stack.push(node);
572
+ }
573
+ node = this.gobbleToken();
574
+ if (!node) {
575
+ this.throwError("Expected expression after " + cur_biop);
576
+ }
577
+ stack.push(biop_info, node);
578
+ }
579
+ i = stack.length - 1;
580
+ node = stack[i];
581
+ while (i > 1) {
582
+ node = {
583
+ type: Jsep.BINARY_EXP,
584
+ operator: stack[i - 1].value,
585
+ left: stack[i - 2],
586
+ right: node
587
+ };
588
+ i -= 2;
589
+ }
590
+ return node;
591
+ }
592
+
593
+ /**
594
+ * An individual part of a binary expression:
595
+ * e.g. `foo.bar(baz)`, `1`, `"abc"`, `(a % 2)` (because it's in parenthesis)
596
+ * @returns {boolean|jsep.Expression}
597
+ */
598
+ gobbleToken() {
599
+ let ch, to_check, tc_len, node;
600
+ this.gobbleSpaces();
601
+ node = this.searchHook('gobble-token');
602
+ if (node) {
603
+ return this.runHook('after-token', node);
604
+ }
605
+ ch = this.code;
606
+ if (Jsep.isDecimalDigit(ch) || ch === Jsep.PERIOD_CODE) {
607
+ // Char code 46 is a dot `.` which can start off a numeric literal
608
+ return this.gobbleNumericLiteral();
609
+ }
610
+ if (ch === Jsep.SQUOTE_CODE || ch === Jsep.DQUOTE_CODE) {
611
+ // Single or double quotes
612
+ node = this.gobbleStringLiteral();
613
+ } else if (ch === Jsep.OBRACK_CODE) {
614
+ node = this.gobbleArray();
615
+ } else {
616
+ to_check = this.expr.substr(this.index, Jsep.max_unop_len);
617
+ tc_len = to_check.length;
618
+ while (tc_len > 0) {
619
+ // Don't accept an unary op when it is an identifier.
620
+ // Unary ops that start with a identifier-valid character must be followed
621
+ // by a non identifier-part valid character
622
+ if (Jsep.unary_ops.hasOwnProperty(to_check) && (!Jsep.isIdentifierStart(this.code) || this.index + to_check.length < this.expr.length && !Jsep.isIdentifierPart(this.expr.charCodeAt(this.index + to_check.length)))) {
623
+ this.index += tc_len;
624
+ const argument = this.gobbleToken();
625
+ if (!argument) {
626
+ this.throwError('missing unaryOp argument');
627
+ }
628
+ return this.runHook('after-token', {
629
+ type: Jsep.UNARY_EXP,
630
+ operator: to_check,
631
+ argument,
632
+ prefix: true
633
+ });
634
+ }
635
+ to_check = to_check.substr(0, --tc_len);
636
+ }
637
+ if (Jsep.isIdentifierStart(ch)) {
638
+ node = this.gobbleIdentifier();
639
+ if (Jsep.literals.hasOwnProperty(node.name)) {
640
+ node = {
641
+ type: Jsep.LITERAL,
642
+ value: Jsep.literals[node.name],
643
+ raw: node.name
644
+ };
645
+ } else if (node.name === Jsep.this_str) {
646
+ node = {
647
+ type: Jsep.THIS_EXP
648
+ };
649
+ }
650
+ } else if (ch === Jsep.OPAREN_CODE) {
651
+ // open parenthesis
652
+ node = this.gobbleGroup();
653
+ }
654
+ }
655
+ if (!node) {
656
+ return this.runHook('after-token', false);
657
+ }
658
+ node = this.gobbleTokenProperty(node);
659
+ return this.runHook('after-token', node);
660
+ }
661
+
662
+ /**
663
+ * Gobble properties of of identifiers/strings/arrays/groups.
664
+ * e.g. `foo`, `bar.baz`, `foo['bar'].baz`
665
+ * It also gobbles function calls:
666
+ * e.g. `Math.acos(obj.angle)`
667
+ * @param {jsep.Expression} node
668
+ * @returns {jsep.Expression}
669
+ */
670
+ gobbleTokenProperty(node) {
671
+ this.gobbleSpaces();
672
+ let ch = this.code;
673
+ while (ch === Jsep.PERIOD_CODE || ch === Jsep.OBRACK_CODE || ch === Jsep.OPAREN_CODE || ch === Jsep.QUMARK_CODE) {
674
+ let optional;
675
+ if (ch === Jsep.QUMARK_CODE) {
676
+ if (this.expr.charCodeAt(this.index + 1) !== Jsep.PERIOD_CODE) {
677
+ break;
678
+ }
679
+ optional = true;
680
+ this.index += 2;
681
+ this.gobbleSpaces();
682
+ ch = this.code;
683
+ }
684
+ this.index++;
685
+ if (ch === Jsep.OBRACK_CODE) {
686
+ node = {
687
+ type: Jsep.MEMBER_EXP,
688
+ computed: true,
689
+ object: node,
690
+ property: this.gobbleExpression()
691
+ };
692
+ if (!node.property) {
693
+ this.throwError('Unexpected "' + this.char + '"');
694
+ }
695
+ this.gobbleSpaces();
696
+ ch = this.code;
697
+ if (ch !== Jsep.CBRACK_CODE) {
698
+ this.throwError('Unclosed [');
699
+ }
700
+ this.index++;
701
+ } else if (ch === Jsep.OPAREN_CODE) {
702
+ // A function call is being made; gobble all the arguments
703
+ node = {
704
+ type: Jsep.CALL_EXP,
705
+ 'arguments': this.gobbleArguments(Jsep.CPAREN_CODE),
706
+ callee: node
707
+ };
708
+ } else if (ch === Jsep.PERIOD_CODE || optional) {
709
+ if (optional) {
710
+ this.index--;
711
+ }
712
+ this.gobbleSpaces();
713
+ node = {
714
+ type: Jsep.MEMBER_EXP,
715
+ computed: false,
716
+ object: node,
717
+ property: this.gobbleIdentifier()
718
+ };
719
+ }
720
+ if (optional) {
721
+ node.optional = true;
722
+ } // else leave undefined for compatibility with esprima
723
+
724
+ this.gobbleSpaces();
725
+ ch = this.code;
726
+ }
727
+ return node;
728
+ }
729
+
730
+ /**
731
+ * Parse simple numeric literals: `12`, `3.4`, `.5`. Do this by using a string to
732
+ * keep track of everything in the numeric literal and then calling `parseFloat` on that string
733
+ * @returns {jsep.Literal}
734
+ */
735
+ gobbleNumericLiteral() {
736
+ let number = '',
737
+ ch,
738
+ chCode;
739
+ while (Jsep.isDecimalDigit(this.code)) {
740
+ number += this.expr.charAt(this.index++);
741
+ }
742
+ if (this.code === Jsep.PERIOD_CODE) {
743
+ // can start with a decimal marker
744
+ number += this.expr.charAt(this.index++);
745
+ while (Jsep.isDecimalDigit(this.code)) {
746
+ number += this.expr.charAt(this.index++);
747
+ }
748
+ }
749
+ ch = this.char;
750
+ if (ch === 'e' || ch === 'E') {
751
+ // exponent marker
752
+ number += this.expr.charAt(this.index++);
753
+ ch = this.char;
754
+ if (ch === '+' || ch === '-') {
755
+ // exponent sign
756
+ number += this.expr.charAt(this.index++);
757
+ }
758
+ while (Jsep.isDecimalDigit(this.code)) {
759
+ // exponent itself
760
+ number += this.expr.charAt(this.index++);
761
+ }
762
+ if (!Jsep.isDecimalDigit(this.expr.charCodeAt(this.index - 1))) {
763
+ this.throwError('Expected exponent (' + number + this.char + ')');
764
+ }
765
+ }
766
+ chCode = this.code;
767
+
768
+ // Check to make sure this isn't a variable name that start with a number (123abc)
769
+ if (Jsep.isIdentifierStart(chCode)) {
770
+ this.throwError('Variable names cannot start with a number (' + number + this.char + ')');
771
+ } else if (chCode === Jsep.PERIOD_CODE || number.length === 1 && number.charCodeAt(0) === Jsep.PERIOD_CODE) {
772
+ this.throwError('Unexpected period');
773
+ }
774
+ return {
775
+ type: Jsep.LITERAL,
776
+ value: parseFloat(number),
777
+ raw: number
778
+ };
779
+ }
780
+
781
+ /**
782
+ * Parses a string literal, staring with single or double quotes with basic support for escape codes
783
+ * e.g. `"hello world"`, `'this is\nJSEP'`
784
+ * @returns {jsep.Literal}
785
+ */
786
+ gobbleStringLiteral() {
787
+ let str = '';
788
+ const startIndex = this.index;
789
+ const quote = this.expr.charAt(this.index++);
790
+ let closed = false;
791
+ while (this.index < this.expr.length) {
792
+ let ch = this.expr.charAt(this.index++);
793
+ if (ch === quote) {
794
+ closed = true;
795
+ break;
796
+ } else if (ch === '\\') {
797
+ // Check for all of the common escape codes
798
+ ch = this.expr.charAt(this.index++);
799
+ switch (ch) {
800
+ case 'n':
801
+ str += '\n';
802
+ break;
803
+ case 'r':
804
+ str += '\r';
805
+ break;
806
+ case 't':
807
+ str += '\t';
808
+ break;
809
+ case 'b':
810
+ str += '\b';
811
+ break;
812
+ case 'f':
813
+ str += '\f';
814
+ break;
815
+ case 'v':
816
+ str += '\x0B';
817
+ break;
818
+ default:
819
+ str += ch;
820
+ }
821
+ } else {
822
+ str += ch;
823
+ }
824
+ }
825
+ if (!closed) {
826
+ this.throwError('Unclosed quote after "' + str + '"');
827
+ }
828
+ return {
829
+ type: Jsep.LITERAL,
830
+ value: str,
831
+ raw: this.expr.substring(startIndex, this.index)
832
+ };
833
+ }
834
+
835
+ /**
836
+ * Gobbles only identifiers
837
+ * e.g.: `foo`, `_value`, `$x1`
838
+ * Also, this function checks if that identifier is a literal:
839
+ * (e.g. `true`, `false`, `null`) or `this`
840
+ * @returns {jsep.Identifier}
841
+ */
842
+ gobbleIdentifier() {
843
+ let ch = this.code,
844
+ start = this.index;
845
+ if (Jsep.isIdentifierStart(ch)) {
846
+ this.index++;
847
+ } else {
848
+ this.throwError('Unexpected ' + this.char);
849
+ }
850
+ while (this.index < this.expr.length) {
851
+ ch = this.code;
852
+ if (Jsep.isIdentifierPart(ch)) {
853
+ this.index++;
854
+ } else {
855
+ break;
856
+ }
857
+ }
858
+ return {
859
+ type: Jsep.IDENTIFIER,
860
+ name: this.expr.slice(start, this.index)
861
+ };
862
+ }
863
+
864
+ /**
865
+ * Gobbles a list of arguments within the context of a function call
866
+ * or array literal. This function also assumes that the opening character
867
+ * `(` or `[` has already been gobbled, and gobbles expressions and commas
868
+ * until the terminator character `)` or `]` is encountered.
869
+ * e.g. `foo(bar, baz)`, `my_func()`, or `[bar, baz]`
870
+ * @param {number} termination
871
+ * @returns {jsep.Expression[]}
872
+ */
873
+ gobbleArguments(termination) {
874
+ const args = [];
875
+ let closed = false;
876
+ let separator_count = 0;
877
+ while (this.index < this.expr.length) {
878
+ this.gobbleSpaces();
879
+ let ch_i = this.code;
880
+ if (ch_i === termination) {
881
+ // done parsing
882
+ closed = true;
883
+ this.index++;
884
+ if (termination === Jsep.CPAREN_CODE && separator_count && separator_count >= args.length) {
885
+ this.throwError('Unexpected token ' + String.fromCharCode(termination));
886
+ }
887
+ break;
888
+ } else if (ch_i === Jsep.COMMA_CODE) {
889
+ // between expressions
890
+ this.index++;
891
+ separator_count++;
892
+ if (separator_count !== args.length) {
893
+ // missing argument
894
+ if (termination === Jsep.CPAREN_CODE) {
895
+ this.throwError('Unexpected token ,');
896
+ } else if (termination === Jsep.CBRACK_CODE) {
897
+ for (let arg = args.length; arg < separator_count; arg++) {
898
+ args.push(null);
899
+ }
900
+ }
901
+ }
902
+ } else if (args.length !== separator_count && separator_count !== 0) {
903
+ // NOTE: `&& separator_count !== 0` allows for either all commas, or all spaces as arguments
904
+ this.throwError('Expected comma');
905
+ } else {
906
+ const node = this.gobbleExpression();
907
+ if (!node || node.type === Jsep.COMPOUND) {
908
+ this.throwError('Expected comma');
909
+ }
910
+ args.push(node);
911
+ }
912
+ }
913
+ if (!closed) {
914
+ this.throwError('Expected ' + String.fromCharCode(termination));
915
+ }
916
+ return args;
917
+ }
918
+
919
+ /**
920
+ * Responsible for parsing a group of things within parentheses `()`
921
+ * that have no identifier in front (so not a function call)
922
+ * This function assumes that it needs to gobble the opening parenthesis
923
+ * and then tries to gobble everything within that parenthesis, assuming
924
+ * that the next thing it should see is the close parenthesis. If not,
925
+ * then the expression probably doesn't have a `)`
926
+ * @returns {boolean|jsep.Expression}
927
+ */
928
+ gobbleGroup() {
929
+ this.index++;
930
+ let nodes = this.gobbleExpressions(Jsep.CPAREN_CODE);
931
+ if (this.code === Jsep.CPAREN_CODE) {
932
+ this.index++;
933
+ if (nodes.length === 1) {
934
+ return nodes[0];
935
+ } else if (!nodes.length) {
936
+ return false;
937
+ } else {
938
+ return {
939
+ type: Jsep.SEQUENCE_EXP,
940
+ expressions: nodes
941
+ };
942
+ }
943
+ } else {
944
+ this.throwError('Unclosed (');
945
+ }
946
+ }
947
+
948
+ /**
949
+ * Responsible for parsing Array literals `[1, 2, 3]`
950
+ * This function assumes that it needs to gobble the opening bracket
951
+ * and then tries to gobble the expressions as arguments.
952
+ * @returns {jsep.ArrayExpression}
953
+ */
954
+ gobbleArray() {
955
+ this.index++;
956
+ return {
957
+ type: Jsep.ARRAY_EXP,
958
+ elements: this.gobbleArguments(Jsep.CBRACK_CODE)
959
+ };
960
+ }
961
+ }
962
+
963
+ // Static fields:
964
+ const hooks = new Hooks();
965
+ Object.assign(Jsep, {
966
+ hooks,
967
+ plugins: new Plugins(Jsep),
968
+ // Node Types
969
+ // ----------
970
+ // This is the full set of types that any JSEP node can be.
971
+ // Store them here to save space when minified
972
+ COMPOUND: 'Compound',
973
+ SEQUENCE_EXP: 'SequenceExpression',
974
+ IDENTIFIER: 'Identifier',
975
+ MEMBER_EXP: 'MemberExpression',
976
+ LITERAL: 'Literal',
977
+ THIS_EXP: 'ThisExpression',
978
+ CALL_EXP: 'CallExpression',
979
+ UNARY_EXP: 'UnaryExpression',
980
+ BINARY_EXP: 'BinaryExpression',
981
+ ARRAY_EXP: 'ArrayExpression',
982
+ TAB_CODE: 9,
983
+ LF_CODE: 10,
984
+ CR_CODE: 13,
985
+ SPACE_CODE: 32,
986
+ PERIOD_CODE: 46,
987
+ // '.'
988
+ COMMA_CODE: 44,
989
+ // ','
990
+ SQUOTE_CODE: 39,
991
+ // single quote
992
+ DQUOTE_CODE: 34,
993
+ // double quotes
994
+ OPAREN_CODE: 40,
995
+ // (
996
+ CPAREN_CODE: 41,
997
+ // )
998
+ OBRACK_CODE: 91,
999
+ // [
1000
+ CBRACK_CODE: 93,
1001
+ // ]
1002
+ QUMARK_CODE: 63,
1003
+ // ?
1004
+ SEMCOL_CODE: 59,
1005
+ // ;
1006
+ COLON_CODE: 58,
1007
+ // :
1008
+
1009
+ // Operations
1010
+ // ----------
1011
+ // Use a quickly-accessible map to store all of the unary operators
1012
+ // Values are set to `1` (it really doesn't matter)
1013
+ unary_ops: {
1014
+ '-': 1,
1015
+ '!': 1,
1016
+ '~': 1,
1017
+ '+': 1
1018
+ },
1019
+ // Also use a map for the binary operations but set their values to their
1020
+ // binary precedence for quick reference (higher number = higher precedence)
1021
+ // see [Order of operations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence)
1022
+ binary_ops: {
1023
+ '||': 1,
1024
+ '&&': 2,
1025
+ '|': 3,
1026
+ '^': 4,
1027
+ '&': 5,
1028
+ '==': 6,
1029
+ '!=': 6,
1030
+ '===': 6,
1031
+ '!==': 6,
1032
+ '<': 7,
1033
+ '>': 7,
1034
+ '<=': 7,
1035
+ '>=': 7,
1036
+ '<<': 8,
1037
+ '>>': 8,
1038
+ '>>>': 8,
1039
+ '+': 9,
1040
+ '-': 9,
1041
+ '*': 10,
1042
+ '/': 10,
1043
+ '%': 10
1044
+ },
1045
+ // sets specific binary_ops as right-associative
1046
+ right_associative: new Set(),
1047
+ // Additional valid identifier chars, apart from a-z, A-Z and 0-9 (except on the starting char)
1048
+ additional_identifier_chars: new Set(['$', '_']),
1049
+ // Literals
1050
+ // ----------
1051
+ // Store the values to return for the various literals we may encounter
1052
+ literals: {
1053
+ 'true': true,
1054
+ 'false': false,
1055
+ 'null': null
1056
+ },
1057
+ // Except for `this`, which is special. This could be changed to something like `'self'` as well
1058
+ this_str: 'this'
1059
+ });
1060
+ Jsep.max_unop_len = Jsep.getMaxKeyLen(Jsep.unary_ops);
1061
+ Jsep.max_binop_len = Jsep.getMaxKeyLen(Jsep.binary_ops);
1062
+
1063
+ // Backward Compatibility:
1064
+ const jsep = expr => new Jsep(expr).parse();
1065
+ const stdClassProps = Object.getOwnPropertyNames(class Test {});
1066
+ Object.getOwnPropertyNames(Jsep).filter(prop => !stdClassProps.includes(prop) && jsep[prop] === undefined).forEach(m => {
1067
+ jsep[m] = Jsep[m];
1068
+ });
1069
+ jsep.Jsep = Jsep; // allows for const { Jsep } = require('jsep');
1070
+
1071
+ const CONDITIONAL_EXP = 'ConditionalExpression';
1072
+ var ternary = {
1073
+ name: 'ternary',
1074
+ init(jsep) {
1075
+ // Ternary expression: test ? consequent : alternate
1076
+ jsep.hooks.add('after-expression', function gobbleTernary(env) {
1077
+ if (env.node && this.code === jsep.QUMARK_CODE) {
1078
+ this.index++;
1079
+ const test = env.node;
1080
+ const consequent = this.gobbleExpression();
1081
+ if (!consequent) {
1082
+ this.throwError('Expected expression');
1083
+ }
1084
+ this.gobbleSpaces();
1085
+ if (this.code === jsep.COLON_CODE) {
1086
+ this.index++;
1087
+ const alternate = this.gobbleExpression();
1088
+ if (!alternate) {
1089
+ this.throwError('Expected expression');
1090
+ }
1091
+ env.node = {
1092
+ type: CONDITIONAL_EXP,
1093
+ test,
1094
+ consequent,
1095
+ alternate
1096
+ };
1097
+
1098
+ // check for operators of higher priority than ternary (i.e. assignment)
1099
+ // jsep sets || at 1, and assignment at 0.9, and conditional should be between them
1100
+ if (test.operator && jsep.binary_ops[test.operator] <= 0.9) {
1101
+ let newTest = test;
1102
+ while (newTest.right.operator && jsep.binary_ops[newTest.right.operator] <= 0.9) {
1103
+ newTest = newTest.right;
1104
+ }
1105
+ env.node.test = newTest.right;
1106
+ newTest.right = env.node;
1107
+ env.node = test;
1108
+ }
1109
+ } else {
1110
+ this.throwError('Expected :');
1111
+ }
1112
+ }
1113
+ });
1114
+ }
1115
+ };
1116
+
1117
+ // Add default plugins:
1118
+
1119
+ jsep.plugins.register(ternary);
1120
+
1121
+ const FSLASH_CODE = 47; // '/'
1122
+ const BSLASH_CODE = 92; // '\\'
1123
+
1124
+ var index = {
1125
+ name: 'regex',
1126
+ init(jsep) {
1127
+ // Regex literal: /abc123/ig
1128
+ jsep.hooks.add('gobble-token', function gobbleRegexLiteral(env) {
1129
+ if (this.code === FSLASH_CODE) {
1130
+ const patternIndex = ++this.index;
1131
+ let inCharSet = false;
1132
+ while (this.index < this.expr.length) {
1133
+ if (this.code === FSLASH_CODE && !inCharSet) {
1134
+ const pattern = this.expr.slice(patternIndex, this.index);
1135
+ let flags = '';
1136
+ while (++this.index < this.expr.length) {
1137
+ const code = this.code;
1138
+ if (code >= 97 && code <= 122 // a...z
1139
+ || code >= 65 && code <= 90 // A...Z
1140
+ || code >= 48 && code <= 57) {
1141
+ // 0-9
1142
+ flags += this.char;
1143
+ } else {
1144
+ break;
1145
+ }
1146
+ }
1147
+ let value;
1148
+ try {
1149
+ value = new RegExp(pattern, flags);
1150
+ } catch (e) {
1151
+ this.throwError(e.message);
1152
+ }
1153
+ env.node = {
1154
+ type: jsep.LITERAL,
1155
+ value,
1156
+ raw: this.expr.slice(patternIndex - 1, this.index)
1157
+ };
1158
+
1159
+ // allow . [] and () after regex: /regex/.test(a)
1160
+ env.node = this.gobbleTokenProperty(env.node);
1161
+ return env.node;
1162
+ }
1163
+ if (this.code === jsep.OBRACK_CODE) {
1164
+ inCharSet = true;
1165
+ } else if (inCharSet && this.code === jsep.CBRACK_CODE) {
1166
+ inCharSet = false;
1167
+ }
1168
+ this.index += this.code === BSLASH_CODE ? 2 : 1;
1169
+ }
1170
+ this.throwError('Unclosed Regex');
1171
+ }
1172
+ });
1173
+ }
1174
+ };
1175
+
1176
+ const PLUS_CODE = 43; // +
1177
+ const MINUS_CODE = 45; // -
1178
+
1179
+ const plugin = {
1180
+ name: 'assignment',
1181
+ assignmentOperators: new Set(['=', '*=', '**=', '/=', '%=', '+=', '-=', '<<=', '>>=', '>>>=', '&=', '^=', '|=']),
1182
+ updateOperators: [PLUS_CODE, MINUS_CODE],
1183
+ assignmentPrecedence: 0.9,
1184
+ init(jsep) {
1185
+ const updateNodeTypes = [jsep.IDENTIFIER, jsep.MEMBER_EXP];
1186
+ plugin.assignmentOperators.forEach(op => jsep.addBinaryOp(op, plugin.assignmentPrecedence, true));
1187
+ jsep.hooks.add('gobble-token', function gobbleUpdatePrefix(env) {
1188
+ const code = this.code;
1189
+ if (plugin.updateOperators.some(c => c === code && c === this.expr.charCodeAt(this.index + 1))) {
1190
+ this.index += 2;
1191
+ env.node = {
1192
+ type: 'UpdateExpression',
1193
+ operator: code === PLUS_CODE ? '++' : '--',
1194
+ argument: this.gobbleTokenProperty(this.gobbleIdentifier()),
1195
+ prefix: true
1196
+ };
1197
+ if (!env.node.argument || !updateNodeTypes.includes(env.node.argument.type)) {
1198
+ this.throwError(`Unexpected ${env.node.operator}`);
1199
+ }
1200
+ }
1201
+ });
1202
+ jsep.hooks.add('after-token', function gobbleUpdatePostfix(env) {
1203
+ if (env.node) {
1204
+ const code = this.code;
1205
+ if (plugin.updateOperators.some(c => c === code && c === this.expr.charCodeAt(this.index + 1))) {
1206
+ if (!updateNodeTypes.includes(env.node.type)) {
1207
+ this.throwError(`Unexpected ${env.node.operator}`);
1208
+ }
1209
+ this.index += 2;
1210
+ env.node = {
1211
+ type: 'UpdateExpression',
1212
+ operator: code === PLUS_CODE ? '++' : '--',
1213
+ argument: env.node,
1214
+ prefix: false
1215
+ };
1216
+ }
1217
+ }
1218
+ });
1219
+ jsep.hooks.add('after-expression', function gobbleAssignment(env) {
1220
+ if (env.node) {
1221
+ // Note: Binaries can be chained in a single expression to respect
1222
+ // operator precedence (i.e. a = b = 1 + 2 + 3)
1223
+ // Update all binary assignment nodes in the tree
1224
+ updateBinariesToAssignments(env.node);
1225
+ }
1226
+ });
1227
+ function updateBinariesToAssignments(node) {
1228
+ if (plugin.assignmentOperators.has(node.operator)) {
1229
+ node.type = 'AssignmentExpression';
1230
+ updateBinariesToAssignments(node.left);
1231
+ updateBinariesToAssignments(node.right);
1232
+ } else if (!node.operator) {
1233
+ Object.values(node).forEach(val => {
1234
+ if (val && typeof val === 'object') {
1235
+ updateBinariesToAssignments(val);
1236
+ }
1237
+ });
1238
+ }
1239
+ }
1240
+ }
1241
+ };
1242
+
1243
+ /* eslint-disable no-bitwise */
1244
+
1245
+ // register plugins
1246
+ jsep.plugins.register(index, plugin);
1247
+ const SafeEval = {
1248
+ /**
1249
+ * @param {jsep.Expression} ast
1250
+ * @param {Record<string, any>} subs
1251
+ */
1252
+ evalAst(ast, subs) {
1253
+ switch (ast.type) {
1254
+ case 'BinaryExpression':
1255
+ case 'LogicalExpression':
1256
+ return SafeEval.evalBinaryExpression(ast, subs);
1257
+ case 'Compound':
1258
+ return SafeEval.evalCompound(ast, subs);
1259
+ case 'ConditionalExpression':
1260
+ return SafeEval.evalConditionalExpression(ast, subs);
1261
+ case 'Identifier':
1262
+ return SafeEval.evalIdentifier(ast, subs);
1263
+ case 'Literal':
1264
+ return SafeEval.evalLiteral(ast, subs);
1265
+ case 'MemberExpression':
1266
+ return SafeEval.evalMemberExpression(ast, subs);
1267
+ case 'UnaryExpression':
1268
+ return SafeEval.evalUnaryExpression(ast, subs);
1269
+ case 'ArrayExpression':
1270
+ return SafeEval.evalArrayExpression(ast, subs);
1271
+ case 'CallExpression':
1272
+ return SafeEval.evalCallExpression(ast, subs);
1273
+ case 'AssignmentExpression':
1274
+ return SafeEval.evalAssignmentExpression(ast, subs);
1275
+ default:
1276
+ throw SyntaxError('Unexpected expression', ast);
1277
+ }
1278
+ },
1279
+ evalBinaryExpression(ast, subs) {
1280
+ const result = {
1281
+ '||': (a, b) => a || b(),
1282
+ '&&': (a, b) => a && b(),
1283
+ '|': (a, b) => a | b(),
1284
+ '^': (a, b) => a ^ b(),
1285
+ '&': (a, b) => a & b(),
1286
+ // eslint-disable-next-line eqeqeq
1287
+ '==': (a, b) => a == b(),
1288
+ // eslint-disable-next-line eqeqeq
1289
+ '!=': (a, b) => a != b(),
1290
+ '===': (a, b) => a === b(),
1291
+ '!==': (a, b) => a !== b(),
1292
+ '<': (a, b) => a < b(),
1293
+ '>': (a, b) => a > b(),
1294
+ '<=': (a, b) => a <= b(),
1295
+ '>=': (a, b) => a >= b(),
1296
+ '<<': (a, b) => a << b(),
1297
+ '>>': (a, b) => a >> b(),
1298
+ '>>>': (a, b) => a >>> b(),
1299
+ '+': (a, b) => a + b(),
1300
+ '-': (a, b) => a - b(),
1301
+ '*': (a, b) => a * b(),
1302
+ '/': (a, b) => a / b(),
1303
+ '%': (a, b) => a % b()
1304
+ }[ast.operator](SafeEval.evalAst(ast.left, subs), () => SafeEval.evalAst(ast.right, subs));
1305
+ return result;
1306
+ },
1307
+ evalCompound(ast, subs) {
1308
+ let last;
1309
+ for (let i = 0; i < ast.body.length; i++) {
1310
+ if (ast.body[i].type === 'Identifier' && ['var', 'let', 'const'].includes(ast.body[i].name) && ast.body[i + 1] && ast.body[i + 1].type === 'AssignmentExpression') {
1311
+ // var x=2; is detected as
1312
+ // [{Identifier var}, {AssignmentExpression x=2}]
1313
+ // eslint-disable-next-line @stylistic/max-len -- Long
1314
+ // eslint-disable-next-line sonarjs/updated-loop-counter -- Convenient
1315
+ i += 1;
1316
+ }
1317
+ const expr = ast.body[i];
1318
+ last = SafeEval.evalAst(expr, subs);
1319
+ }
1320
+ return last;
1321
+ },
1322
+ evalConditionalExpression(ast, subs) {
1323
+ if (SafeEval.evalAst(ast.test, subs)) {
1324
+ return SafeEval.evalAst(ast.consequent, subs);
1325
+ }
1326
+ return SafeEval.evalAst(ast.alternate, subs);
1327
+ },
1328
+ evalIdentifier(ast, subs) {
1329
+ if (ast.name in subs) {
1330
+ return subs[ast.name];
1331
+ }
1332
+ throw ReferenceError(`${ast.name} is not defined`);
1333
+ },
1334
+ evalLiteral(ast) {
1335
+ return ast.value;
1336
+ },
1337
+ evalMemberExpression(ast, subs) {
1338
+ const prop = ast.computed ? SafeEval.evalAst(ast.property) // `object[property]`
1339
+ : ast.property.name; // `object.property` property is Identifier
1340
+ const obj = SafeEval.evalAst(ast.object, subs);
1341
+ const result = obj[prop];
1342
+ if (typeof result === 'function') {
1343
+ return result.bind(obj); // arrow functions aren't affected by bind.
1344
+ }
1345
+ return result;
1346
+ },
1347
+ evalUnaryExpression(ast, subs) {
1348
+ const result = {
1349
+ '-': a => -SafeEval.evalAst(a, subs),
1350
+ '!': a => !SafeEval.evalAst(a, subs),
1351
+ '~': a => ~SafeEval.evalAst(a, subs),
1352
+ // eslint-disable-next-line no-implicit-coercion
1353
+ '+': a => +SafeEval.evalAst(a, subs)
1354
+ }[ast.operator](ast.argument);
1355
+ return result;
1356
+ },
1357
+ evalArrayExpression(ast, subs) {
1358
+ return ast.elements.map(el => SafeEval.evalAst(el, subs));
1359
+ },
1360
+ evalCallExpression(ast, subs) {
1361
+ const args = ast.arguments.map(arg => SafeEval.evalAst(arg, subs));
1362
+ const func = SafeEval.evalAst(ast.callee, subs);
1363
+ return func(...args);
1364
+ },
1365
+ evalAssignmentExpression(ast, subs) {
1366
+ if (ast.left.type !== 'Identifier') {
1367
+ throw SyntaxError('Invalid left-hand side in assignment');
1368
+ }
1369
+ const id = ast.left.name;
1370
+ const value = SafeEval.evalAst(ast.right, subs);
1371
+ subs[id] = value;
1372
+ return subs[id];
1373
+ }
1374
+ };
1375
+
1376
+ /**
1377
+ * A replacement for NodeJS' VM.Script which is also {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP | Content Security Policy} friendly.
1378
+ */
1379
+ class SafeScript {
1380
+ /**
1381
+ * @param {string} expr Expression to evaluate
1382
+ */
1383
+ constructor(expr) {
1384
+ this.code = expr;
1385
+ this.ast = jsep(this.code);
1386
+ }
1387
+
1388
+ /**
1389
+ * @param {object} context Object whose items will be added
1390
+ * to evaluation
1391
+ * @returns {EvaluatedResult} Result of evaluated code
1392
+ */
1393
+ runInNewContext(context) {
1394
+ const keyMap = {
1395
+ ...context
1396
+ };
1397
+ return SafeEval.evalAst(this.ast, keyMap);
1398
+ }
1399
+ }
1400
+
1401
+ /* eslint-disable camelcase, unicorn/prefer-string-replace-all,
1402
+ unicorn/prefer-at */
1403
+
1404
+
1405
+ /**
1406
+ * @typedef {null|boolean|number|string|object|GenericArray} JSONObject
1407
+ */
1408
+
1409
+ /**
1410
+ * @typedef {any} AnyItem
1411
+ */
1412
+
1413
+ /**
1414
+ * @typedef {any} AnyResult
1415
+ */
1416
+
1417
+ /**
1418
+ * Copies array and then pushes item into it.
1419
+ * @param {GenericArray} arr Array to copy and into which to push
1420
+ * @param {AnyItem} item Array item to add (to end)
1421
+ * @returns {GenericArray} Copy of the original array
1422
+ */
1423
+ function push(arr, item) {
1424
+ arr = arr.slice();
1425
+ arr.push(item);
1426
+ return arr;
1427
+ }
1428
+ /**
1429
+ * Copies array and then unshifts item into it.
1430
+ * @param {AnyItem} item Array item to add (to beginning)
1431
+ * @param {GenericArray} arr Array to copy and into which to unshift
1432
+ * @returns {GenericArray} Copy of the original array
1433
+ */
1434
+ function unshift(item, arr) {
1435
+ arr = arr.slice();
1436
+ arr.unshift(item);
1437
+ return arr;
1438
+ }
1439
+
1440
+ /**
1441
+ * Caught when JSONPath is used without `new` but rethrown if with `new`
1442
+ * @extends Error
1443
+ */
1444
+ class NewError extends Error {
1445
+ /**
1446
+ * @param {AnyResult} value The evaluated scalar value
1447
+ */
1448
+ constructor(value) {
1449
+ super('JSONPath should not be called with "new" (it prevents return ' + 'of (unwrapped) scalar values)');
1450
+ this.avoidNew = true;
1451
+ this.value = value;
1452
+ this.name = 'NewError';
1453
+ }
1454
+ }
1455
+
1456
+ /**
1457
+ * @typedef {object} ReturnObject
1458
+ * @property {string} path
1459
+ * @property {JSONObject} value
1460
+ * @property {object|GenericArray} parent
1461
+ * @property {string} parentProperty
1462
+ */
1463
+
1464
+ /**
1465
+ * @callback JSONPathCallback
1466
+ * @param {string|object} preferredOutput
1467
+ * @param {"value"|"property"} type
1468
+ * @param {ReturnObject} fullRetObj
1469
+ * @returns {void}
1470
+ */
1471
+
1472
+ /**
1473
+ * @callback OtherTypeCallback
1474
+ * @param {JSONObject} val
1475
+ * @param {string} path
1476
+ * @param {object|GenericArray} parent
1477
+ * @param {string} parentPropName
1478
+ * @returns {boolean}
1479
+ */
1480
+
1481
+ /**
1482
+ * @typedef {any} ContextItem
1483
+ */
1484
+
1485
+ /**
1486
+ * @typedef {any} EvaluatedResult
1487
+ */
1488
+
1489
+ /**
1490
+ * @callback EvalCallback
1491
+ * @param {string} code
1492
+ * @param {ContextItem} context
1493
+ * @returns {EvaluatedResult}
1494
+ */
1495
+
1496
+ /**
1497
+ * @typedef {typeof SafeScript} EvalClass
1498
+ */
1499
+
1500
+ /**
1501
+ * @typedef {object} JSONPathOptions
1502
+ * @property {JSON} json
1503
+ * @property {string|string[]} path
1504
+ * @property {"value"|"path"|"pointer"|"parent"|"parentProperty"|
1505
+ * "all"} [resultType="value"]
1506
+ * @property {boolean} [flatten=false]
1507
+ * @property {boolean} [wrap=true]
1508
+ * @property {object} [sandbox={}]
1509
+ * @property {EvalCallback|EvalClass|'safe'|'native'|
1510
+ * boolean} [eval = 'safe']
1511
+ * @property {object|GenericArray|null} [parent=null]
1512
+ * @property {string|null} [parentProperty=null]
1513
+ * @property {JSONPathCallback} [callback]
1514
+ * @property {OtherTypeCallback} [otherTypeCallback] Defaults to
1515
+ * function which throws on encountering `@other`
1516
+ * @property {boolean} [autostart=true]
1517
+ */
1518
+
1519
+ /**
1520
+ * @param {string|JSONPathOptions} opts If a string, will be treated as `expr`
1521
+ * @param {string} [expr] JSON path to evaluate
1522
+ * @param {JSON} [obj] JSON object to evaluate against
1523
+ * @param {JSONPathCallback} [callback] Passed 3 arguments: 1) desired payload
1524
+ * per `resultType`, 2) `"value"|"property"`, 3) Full returned object with
1525
+ * all payloads
1526
+ * @param {OtherTypeCallback} [otherTypeCallback] If `@other()` is at the end
1527
+ * of one's query, this will be invoked with the value of the item, its
1528
+ * path, its parent, and its parent's property name, and it should return
1529
+ * a boolean indicating whether the supplied value belongs to the "other"
1530
+ * type or not (or it may handle transformations and return `false`).
1531
+ * @returns {JSONPath}
1532
+ * @class
1533
+ */
1534
+ function JSONPath(opts, expr, obj, callback, otherTypeCallback) {
1535
+ // eslint-disable-next-line no-restricted-syntax
1536
+ if (!(this instanceof JSONPath)) {
1537
+ try {
1538
+ return new JSONPath(opts, expr, obj, callback, otherTypeCallback);
1539
+ } catch (e) {
1540
+ if (!e.avoidNew) {
1541
+ throw e;
1542
+ }
1543
+ return e.value;
1544
+ }
1545
+ }
1546
+ if (typeof opts === 'string') {
1547
+ otherTypeCallback = callback;
1548
+ callback = obj;
1549
+ obj = expr;
1550
+ expr = opts;
1551
+ opts = null;
1552
+ }
1553
+ const optObj = opts && typeof opts === 'object';
1554
+ opts = opts || {};
1555
+ this.json = opts.json || obj;
1556
+ this.path = opts.path || expr;
1557
+ this.resultType = opts.resultType || 'value';
1558
+ this.flatten = opts.flatten || false;
1559
+ this.wrap = hasOwn(opts, 'wrap') ? opts.wrap : true;
1560
+ this.sandbox = opts.sandbox || {};
1561
+ this.eval = opts.eval === undefined ? 'safe' : opts.eval;
1562
+ this.ignoreEvalErrors = typeof opts.ignoreEvalErrors === 'undefined' ? false : opts.ignoreEvalErrors;
1563
+ this.parent = opts.parent || null;
1564
+ this.parentProperty = opts.parentProperty || null;
1565
+ this.callback = opts.callback || callback || null;
1566
+ this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function () {
1567
+ throw new TypeError('You must supply an otherTypeCallback callback option ' + 'with the @other() operator.');
1568
+ };
1569
+ if (opts.autostart !== false) {
1570
+ const args = {
1571
+ path: optObj ? opts.path : expr
1572
+ };
1573
+ if (!optObj) {
1574
+ args.json = obj;
1575
+ } else if ('json' in opts) {
1576
+ args.json = opts.json;
1577
+ }
1578
+ const ret = this.evaluate(args);
1579
+ if (!ret || typeof ret !== 'object') {
1580
+ throw new NewError(ret);
1581
+ }
1582
+ return ret;
1583
+ }
1584
+ }
1585
+
1586
+ // PUBLIC METHODS
1587
+ JSONPath.prototype.evaluate = function (expr, json, callback, otherTypeCallback) {
1588
+ let currParent = this.parent,
1589
+ currParentProperty = this.parentProperty;
1590
+ let {
1591
+ flatten,
1592
+ wrap
1593
+ } = this;
1594
+ this.currResultType = this.resultType;
1595
+ this.currEval = this.eval;
1596
+ this.currSandbox = this.sandbox;
1597
+ callback = callback || this.callback;
1598
+ this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;
1599
+ json = json || this.json;
1600
+ expr = expr || this.path;
1601
+ if (expr && typeof expr === 'object' && !Array.isArray(expr)) {
1602
+ if (!expr.path && expr.path !== '') {
1603
+ throw new TypeError('You must supply a "path" property when providing an object ' + 'argument to JSONPath.evaluate().');
1604
+ }
1605
+ if (!hasOwn(expr, 'json')) {
1606
+ throw new TypeError('You must supply a "json" property when providing an object ' + 'argument to JSONPath.evaluate().');
1607
+ }
1608
+ ({
1609
+ json
1610
+ } = expr);
1611
+ flatten = hasOwn(expr, 'flatten') ? expr.flatten : flatten;
1612
+ this.currResultType = hasOwn(expr, 'resultType') ? expr.resultType : this.currResultType;
1613
+ this.currSandbox = hasOwn(expr, 'sandbox') ? expr.sandbox : this.currSandbox;
1614
+ wrap = hasOwn(expr, 'wrap') ? expr.wrap : wrap;
1615
+ this.currEval = hasOwn(expr, 'eval') ? expr.eval : this.currEval;
1616
+ callback = hasOwn(expr, 'callback') ? expr.callback : callback;
1617
+ this.currOtherTypeCallback = hasOwn(expr, 'otherTypeCallback') ? expr.otherTypeCallback : this.currOtherTypeCallback;
1618
+ currParent = hasOwn(expr, 'parent') ? expr.parent : currParent;
1619
+ currParentProperty = hasOwn(expr, 'parentProperty') ? expr.parentProperty : currParentProperty;
1620
+ expr = expr.path;
1621
+ }
1622
+ currParent = currParent || null;
1623
+ currParentProperty = currParentProperty || null;
1624
+ if (Array.isArray(expr)) {
1625
+ expr = JSONPath.toPathString(expr);
1626
+ }
1627
+ if (!expr && expr !== '' || !json) {
1628
+ return undefined;
1629
+ }
1630
+ const exprList = JSONPath.toPathArray(expr);
1631
+ if (exprList[0] === '$' && exprList.length > 1) {
1632
+ exprList.shift();
1633
+ }
1634
+ this._hasParentSelector = null;
1635
+ const result = this._trace(exprList, json, ['$'], currParent, currParentProperty, callback).filter(function (ea) {
1636
+ return ea && !ea.isParentSelector;
1637
+ });
1638
+ if (!result.length) {
1639
+ return wrap ? [] : undefined;
1640
+ }
1641
+ if (!wrap && result.length === 1 && !result[0].hasArrExpr) {
1642
+ return this._getPreferredOutput(result[0]);
1643
+ }
1644
+ return result.reduce((rslt, ea) => {
1645
+ const valOrPath = this._getPreferredOutput(ea);
1646
+ if (flatten && Array.isArray(valOrPath)) {
1647
+ rslt = rslt.concat(valOrPath);
1648
+ } else {
1649
+ rslt.push(valOrPath);
1650
+ }
1651
+ return rslt;
1652
+ }, []);
1653
+ };
1654
+
1655
+ // PRIVATE METHODS
1656
+
1657
+ JSONPath.prototype._getPreferredOutput = function (ea) {
1658
+ const resultType = this.currResultType;
1659
+ switch (resultType) {
1660
+ case 'all':
1661
+ {
1662
+ const path = Array.isArray(ea.path) ? ea.path : JSONPath.toPathArray(ea.path);
1663
+ ea.pointer = JSONPath.toPointer(path);
1664
+ ea.path = typeof ea.path === 'string' ? ea.path : JSONPath.toPathString(ea.path);
1665
+ return ea;
1666
+ }
1667
+ case 'value':
1668
+ case 'parent':
1669
+ case 'parentProperty':
1670
+ return ea[resultType];
1671
+ case 'path':
1672
+ return JSONPath.toPathString(ea[resultType]);
1673
+ case 'pointer':
1674
+ return JSONPath.toPointer(ea.path);
1675
+ default:
1676
+ throw new TypeError('Unknown result type');
1677
+ }
1678
+ };
1679
+ JSONPath.prototype._handleCallback = function (fullRetObj, callback, type) {
1680
+ if (callback) {
1681
+ const preferredOutput = this._getPreferredOutput(fullRetObj);
1682
+ fullRetObj.path = typeof fullRetObj.path === 'string' ? fullRetObj.path : JSONPath.toPathString(fullRetObj.path);
1683
+ // eslint-disable-next-line n/callback-return
1684
+ callback(preferredOutput, type, fullRetObj);
1685
+ }
1686
+ };
1687
+
1688
+ /**
1689
+ *
1690
+ * @param {string} expr
1691
+ * @param {JSONObject} val
1692
+ * @param {string} path
1693
+ * @param {object|GenericArray} parent
1694
+ * @param {string} parentPropName
1695
+ * @param {JSONPathCallback} callback
1696
+ * @param {boolean} hasArrExpr
1697
+ * @param {boolean} literalPriority
1698
+ * @returns {ReturnObject|ReturnObject[]}
1699
+ */
1700
+ JSONPath.prototype._trace = function (expr, val, path, parent, parentPropName, callback, hasArrExpr, literalPriority) {
1701
+ // No expr to follow? return path and value as the result of
1702
+ // this trace branch
1703
+ let retObj;
1704
+ if (!expr.length) {
1705
+ retObj = {
1706
+ path,
1707
+ value: val,
1708
+ parent,
1709
+ parentProperty: parentPropName,
1710
+ hasArrExpr
1711
+ };
1712
+ this._handleCallback(retObj, callback, 'value');
1713
+ return retObj;
1714
+ }
1715
+ const loc = expr[0],
1716
+ x = expr.slice(1);
1717
+
1718
+ // We need to gather the return value of recursive trace calls in order to
1719
+ // do the parent sel computation.
1720
+ const ret = [];
1721
+ /**
1722
+ *
1723
+ * @param {ReturnObject|ReturnObject[]} elems
1724
+ * @returns {void}
1725
+ */
1726
+ function addRet(elems) {
1727
+ if (Array.isArray(elems)) {
1728
+ // This was causing excessive stack size in Node (with or
1729
+ // without Babel) against our performance test:
1730
+ // `ret.push(...elems);`
1731
+ elems.forEach(t => {
1732
+ ret.push(t);
1733
+ });
1734
+ } else {
1735
+ ret.push(elems);
1736
+ }
1737
+ }
1738
+ if ((typeof loc !== 'string' || literalPriority) && val && hasOwn(val, loc)) {
1739
+ // simple case--directly follow property
1740
+ addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr));
1741
+ // eslint-disable-next-line unicorn/prefer-switch -- Part of larger `if`
1742
+ } else if (loc === '*') {
1743
+ // all child properties
1744
+ this._walk(val, m => {
1745
+ addRet(this._trace(x, val[m], push(path, m), val, m, callback, true, true));
1746
+ });
1747
+ } else if (loc === '..') {
1748
+ // all descendent parent properties
1749
+ // Check remaining expression with val's immediate children
1750
+ addRet(this._trace(x, val, path, parent, parentPropName, callback, hasArrExpr));
1751
+ this._walk(val, m => {
1752
+ // We don't join m and x here because we only want parents,
1753
+ // not scalar values
1754
+ if (typeof val[m] === 'object') {
1755
+ // Keep going with recursive descent on val's
1756
+ // object children
1757
+ addRet(this._trace(expr.slice(), val[m], push(path, m), val, m, callback, true));
1758
+ }
1759
+ });
1760
+ // The parent sel computation is handled in the frame above using the
1761
+ // ancestor object of val
1762
+ } else if (loc === '^') {
1763
+ // This is not a final endpoint, so we do not invoke the callback here
1764
+ this._hasParentSelector = true;
1765
+ return {
1766
+ path: path.slice(0, -1),
1767
+ expr: x,
1768
+ isParentSelector: true
1769
+ };
1770
+ } else if (loc === '~') {
1771
+ // property name
1772
+ retObj = {
1773
+ path: push(path, loc),
1774
+ value: parentPropName,
1775
+ parent,
1776
+ parentProperty: null
1777
+ };
1778
+ this._handleCallback(retObj, callback, 'property');
1779
+ return retObj;
1780
+ } else if (loc === '$') {
1781
+ // root only
1782
+ addRet(this._trace(x, val, path, null, null, callback, hasArrExpr));
1783
+ } else if (/^(-?\d*):(-?\d*):?(\d*)$/u.test(loc)) {
1784
+ // [start:end:step] Python slice syntax
1785
+ addRet(this._slice(loc, x, val, path, parent, parentPropName, callback));
1786
+ } else if (loc.indexOf('?(') === 0) {
1787
+ // [?(expr)] (filtering)
1788
+ if (this.currEval === false) {
1789
+ throw new Error('Eval [?(expr)] prevented in JSONPath expression.');
1790
+ }
1791
+ const safeLoc = loc.replace(/^\?\((.*?)\)$/u, '$1');
1792
+ // check for a nested filter expression
1793
+ const nested = /@.?([^?]*)[['](\??\(.*?\))(?!.\)\])[\]']/gu.exec(safeLoc);
1794
+ if (nested) {
1795
+ // find if there are matches in the nested expression
1796
+ // add them to the result set if there is at least one match
1797
+ this._walk(val, m => {
1798
+ const npath = [nested[2]];
1799
+ const nvalue = nested[1] ? val[m][nested[1]] : val[m];
1800
+ const filterResults = this._trace(npath, nvalue, path, parent, parentPropName, callback, true);
1801
+ if (filterResults.length > 0) {
1802
+ addRet(this._trace(x, val[m], push(path, m), val, m, callback, true));
1803
+ }
1804
+ });
1805
+ } else {
1806
+ this._walk(val, m => {
1807
+ if (this._eval(safeLoc, val[m], m, path, parent, parentPropName)) {
1808
+ addRet(this._trace(x, val[m], push(path, m), val, m, callback, true));
1809
+ }
1810
+ });
1811
+ }
1812
+ } else if (loc[0] === '(') {
1813
+ // [(expr)] (dynamic property/index)
1814
+ if (this.currEval === false) {
1815
+ throw new Error('Eval [(expr)] prevented in JSONPath expression.');
1816
+ }
1817
+ // As this will resolve to a property name (but we don't know it
1818
+ // yet), property and parent information is relative to the
1819
+ // parent of the property to which this expression will resolve
1820
+ addRet(this._trace(unshift(this._eval(loc, val, path[path.length - 1], path.slice(0, -1), parent, parentPropName), x), val, path, parent, parentPropName, callback, hasArrExpr));
1821
+ } else if (loc[0] === '@') {
1822
+ // value type: @boolean(), etc.
1823
+ let addType = false;
1824
+ const valueType = loc.slice(1, -2);
1825
+ switch (valueType) {
1826
+ case 'scalar':
1827
+ if (!val || !['object', 'function'].includes(typeof val)) {
1828
+ addType = true;
1829
+ }
1830
+ break;
1831
+ case 'boolean':
1832
+ case 'string':
1833
+ case 'undefined':
1834
+ case 'function':
1835
+ if (typeof val === valueType) {
1836
+ addType = true;
1837
+ }
1838
+ break;
1839
+ case 'integer':
1840
+ if (Number.isFinite(val) && !(val % 1)) {
1841
+ addType = true;
1842
+ }
1843
+ break;
1844
+ case 'number':
1845
+ if (Number.isFinite(val)) {
1846
+ addType = true;
1847
+ }
1848
+ break;
1849
+ case 'nonFinite':
1850
+ if (typeof val === 'number' && !Number.isFinite(val)) {
1851
+ addType = true;
1852
+ }
1853
+ break;
1854
+ case 'object':
1855
+ if (val && typeof val === valueType) {
1856
+ addType = true;
1857
+ }
1858
+ break;
1859
+ case 'array':
1860
+ if (Array.isArray(val)) {
1861
+ addType = true;
1862
+ }
1863
+ break;
1864
+ case 'other':
1865
+ addType = this.currOtherTypeCallback(val, path, parent, parentPropName);
1866
+ break;
1867
+ case 'null':
1868
+ if (val === null) {
1869
+ addType = true;
1870
+ }
1871
+ break;
1872
+ /* c8 ignore next 2 */
1873
+ default:
1874
+ throw new TypeError('Unknown value type ' + valueType);
1875
+ }
1876
+ if (addType) {
1877
+ retObj = {
1878
+ path,
1879
+ value: val,
1880
+ parent,
1881
+ parentProperty: parentPropName
1882
+ };
1883
+ this._handleCallback(retObj, callback, 'value');
1884
+ return retObj;
1885
+ }
1886
+ // `-escaped property
1887
+ } else if (loc[0] === '`' && val && hasOwn(val, loc.slice(1))) {
1888
+ const locProp = loc.slice(1);
1889
+ addRet(this._trace(x, val[locProp], push(path, locProp), val, locProp, callback, hasArrExpr, true));
1890
+ } else if (loc.includes(',')) {
1891
+ // [name1,name2,...]
1892
+ const parts = loc.split(',');
1893
+ for (const part of parts) {
1894
+ addRet(this._trace(unshift(part, x), val, path, parent, parentPropName, callback, true));
1895
+ }
1896
+ // simple case--directly follow property
1897
+ } else if (!literalPriority && val && hasOwn(val, loc)) {
1898
+ addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr, true));
1899
+ }
1900
+
1901
+ // We check the resulting values for parent selections. For parent
1902
+ // selections we discard the value object and continue the trace with the
1903
+ // current val object
1904
+ if (this._hasParentSelector) {
1905
+ for (let t = 0; t < ret.length; t++) {
1906
+ const rett = ret[t];
1907
+ if (rett && rett.isParentSelector) {
1908
+ const tmp = this._trace(rett.expr, val, rett.path, parent, parentPropName, callback, hasArrExpr);
1909
+ if (Array.isArray(tmp)) {
1910
+ ret[t] = tmp[0];
1911
+ const tl = tmp.length;
1912
+ for (let tt = 1; tt < tl; tt++) {
1913
+ // eslint-disable-next-line @stylistic/max-len -- Long
1914
+ // eslint-disable-next-line sonarjs/updated-loop-counter -- Convenient
1915
+ t++;
1916
+ ret.splice(t, 0, tmp[tt]);
1917
+ }
1918
+ } else {
1919
+ ret[t] = tmp;
1920
+ }
1921
+ }
1922
+ }
1923
+ }
1924
+ return ret;
1925
+ };
1926
+ JSONPath.prototype._walk = function (val, f) {
1927
+ if (Array.isArray(val)) {
1928
+ const n = val.length;
1929
+ for (let i = 0; i < n; i++) {
1930
+ f(i);
1931
+ }
1932
+ } else if (val && typeof val === 'object') {
1933
+ Object.keys(val).forEach(m => {
1934
+ f(m);
1935
+ });
1936
+ }
1937
+ };
1938
+ JSONPath.prototype._slice = function (loc, expr, val, path, parent, parentPropName, callback) {
1939
+ if (!Array.isArray(val)) {
1940
+ return undefined;
1941
+ }
1942
+ const len = val.length,
1943
+ parts = loc.split(':'),
1944
+ step = parts[2] && Number.parseInt(parts[2]) || 1;
1945
+ let start = parts[0] && Number.parseInt(parts[0]) || 0,
1946
+ end = parts[1] && Number.parseInt(parts[1]) || len;
1947
+ start = start < 0 ? Math.max(0, start + len) : Math.min(len, start);
1948
+ end = end < 0 ? Math.max(0, end + len) : Math.min(len, end);
1949
+ const ret = [];
1950
+ for (let i = start; i < end; i += step) {
1951
+ const tmp = this._trace(unshift(i, expr), val, path, parent, parentPropName, callback, true);
1952
+ // Should only be possible to be an array here since first part of
1953
+ // ``unshift(i, expr)` passed in above would not be empty, nor `~`,
1954
+ // nor begin with `@` (as could return objects)
1955
+ // This was causing excessive stack size in Node (with or
1956
+ // without Babel) against our performance test: `ret.push(...tmp);`
1957
+ tmp.forEach(t => {
1958
+ ret.push(t);
1959
+ });
1960
+ }
1961
+ return ret;
1962
+ };
1963
+ JSONPath.prototype._eval = function (code, _v, _vname, path, parent, parentPropName) {
1964
+ this.currSandbox._$_parentProperty = parentPropName;
1965
+ this.currSandbox._$_parent = parent;
1966
+ this.currSandbox._$_property = _vname;
1967
+ this.currSandbox._$_root = this.json;
1968
+ this.currSandbox._$_v = _v;
1969
+ const containsPath = code.includes('@path');
1970
+ if (containsPath) {
1971
+ this.currSandbox._$_path = JSONPath.toPathString(path.concat([_vname]));
1972
+ }
1973
+ const scriptCacheKey = this.currEval + 'Script:' + code;
1974
+ if (!JSONPath.cache[scriptCacheKey]) {
1975
+ let script = code.replace(/@parentProperty/gu, '_$_parentProperty').replace(/@parent/gu, '_$_parent').replace(/@property/gu, '_$_property').replace(/@root/gu, '_$_root').replace(/@([.\s)[])/gu, '_$_v$1');
1976
+ if (containsPath) {
1977
+ script = script.replace(/@path/gu, '_$_path');
1978
+ }
1979
+ if (this.currEval === 'safe' || this.currEval === true || this.currEval === undefined) {
1980
+ JSONPath.cache[scriptCacheKey] = new this.safeVm.Script(script);
1981
+ } else if (this.currEval === 'native') {
1982
+ JSONPath.cache[scriptCacheKey] = new this.vm.Script(script);
1983
+ } else if (typeof this.currEval === 'function' && this.currEval.prototype && hasOwn(this.currEval.prototype, 'runInNewContext')) {
1984
+ const CurrEval = this.currEval;
1985
+ JSONPath.cache[scriptCacheKey] = new CurrEval(script);
1986
+ } else if (typeof this.currEval === 'function') {
1987
+ JSONPath.cache[scriptCacheKey] = {
1988
+ runInNewContext: context => this.currEval(script, context)
1989
+ };
1990
+ } else {
1991
+ throw new TypeError(`Unknown "eval" property "${this.currEval}"`);
1992
+ }
1993
+ }
1994
+ try {
1995
+ return JSONPath.cache[scriptCacheKey].runInNewContext(this.currSandbox);
1996
+ } catch (e) {
1997
+ if (this.ignoreEvalErrors) {
1998
+ return false;
1999
+ }
2000
+ throw new Error('jsonPath: ' + e.message + ': ' + code);
2001
+ }
2002
+ };
2003
+
2004
+ // PUBLIC CLASS PROPERTIES AND METHODS
2005
+
2006
+ // Could store the cache object itself
2007
+ JSONPath.cache = {};
2008
+
2009
+ /**
2010
+ * @param {string[]} pathArr Array to convert
2011
+ * @returns {string} The path string
2012
+ */
2013
+ JSONPath.toPathString = function (pathArr) {
2014
+ const x = pathArr,
2015
+ n = x.length;
2016
+ let p = '$';
2017
+ for (let i = 1; i < n; i++) {
2018
+ if (!/^(~|\^|@.*?\(\))$/u.test(x[i])) {
2019
+ p += /^[0-9*]+$/u.test(x[i]) ? '[' + x[i] + ']' : "['" + x[i] + "']";
2020
+ }
2021
+ }
2022
+ return p;
2023
+ };
2024
+
2025
+ /**
2026
+ * @param {string} pointer JSON Path
2027
+ * @returns {string} JSON Pointer
2028
+ */
2029
+ JSONPath.toPointer = function (pointer) {
2030
+ const x = pointer,
2031
+ n = x.length;
2032
+ let p = '';
2033
+ for (let i = 1; i < n; i++) {
2034
+ if (!/^(~|\^|@.*?\(\))$/u.test(x[i])) {
2035
+ p += '/' + x[i].toString().replace(/~/gu, '~0').replace(/\//gu, '~1');
2036
+ }
2037
+ }
2038
+ return p;
2039
+ };
2040
+
2041
+ /**
2042
+ * @param {string} expr Expression to convert
2043
+ * @returns {string[]}
2044
+ */
2045
+ JSONPath.toPathArray = function (expr) {
2046
+ const {
2047
+ cache
2048
+ } = JSONPath;
2049
+ if (cache[expr]) {
2050
+ return cache[expr].concat();
2051
+ }
2052
+ const subx = [];
2053
+ const normalized = expr
2054
+ // Properties
2055
+ .replace(/@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\(\)/gu, ';$&;')
2056
+ // Parenthetical evaluations (filtering and otherwise), directly
2057
+ // within brackets or single quotes
2058
+ .replace(/[['](\??\(.*?\))[\]'](?!.\])/gu, function ($0, $1) {
2059
+ return '[#' + (subx.push($1) - 1) + ']';
2060
+ })
2061
+ // Escape periods and tildes within properties
2062
+ .replace(/\[['"]([^'\]]*)['"]\]/gu, function ($0, prop) {
2063
+ return "['" + prop.replace(/\./gu, '%@%').replace(/~/gu, '%%@@%%') + "']";
2064
+ })
2065
+ // Properties operator
2066
+ .replace(/~/gu, ';~;')
2067
+ // Split by property boundaries
2068
+ .replace(/['"]?\.['"]?(?![^[]*\])|\[['"]?/gu, ';')
2069
+ // Reinsert periods within properties
2070
+ .replace(/%@%/gu, '.')
2071
+ // Reinsert tildes within properties
2072
+ .replace(/%%@@%%/gu, '~')
2073
+ // Parent
2074
+ .replace(/(?:;)?(\^+)(?:;)?/gu, function ($0, ups) {
2075
+ return ';' + ups.split('').join(';') + ';';
2076
+ })
2077
+ // Descendents
2078
+ .replace(/;;;|;;/gu, ';..;')
2079
+ // Remove trailing
2080
+ .replace(/;$|'?\]|'$/gu, '');
2081
+ const exprList = normalized.split(';').map(function (exp) {
2082
+ const match = exp.match(/#(\d+)/u);
2083
+ return !match || !match[1] ? exp : subx[match[1]];
2084
+ });
2085
+ cache[expr] = exprList;
2086
+ return cache[expr].concat();
2087
+ };
2088
+ JSONPath.prototype.safeVm = {
2089
+ Script: SafeScript
2090
+ };
2091
+
2092
+ JSONPath.prototype.vm = vm;
2093
+
2094
+ exports.JSONPath = JSONPath;