jtlt 0.24.2 → 0.25.1

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.
@@ -75,10 +75,7 @@ class Plugins {
75
75
  * @param {PluginSetup} plugins.init The init function
76
76
  * @public
77
77
  */
78
- register() {
79
- for (var _len = arguments.length, plugins = new Array(_len), _key = 0; _key < _len; _key++) {
80
- plugins[_key] = arguments[_key];
81
- }
78
+ register(...plugins) {
82
79
  plugins.forEach(plugin => {
83
80
  if (typeof plugin !== 'object' || !plugin.name || !plugin.init) {
84
81
  throw new Error('Invalid JSEP plugin format');
@@ -1198,8 +1195,29 @@ const plugin = {
1198
1195
  }
1199
1196
  };
1200
1197
 
1198
+ /* eslint-disable unicorn/no-top-level-side-effects -- Temporary? */
1201
1199
  /* eslint-disable no-bitwise -- Convenient */
1202
1200
 
1201
+ /**
1202
+ * @import {EvaluatedResult, UnknownResult} from './jsonpath.js';
1203
+ */
1204
+
1205
+ /**
1206
+ * @typedef {any} AssignmentExpression
1207
+ */
1208
+
1209
+ /**
1210
+ * @typedef {any} Substitution
1211
+ */
1212
+
1213
+ /**
1214
+ * @typedef {any} AnyParameter
1215
+ */
1216
+
1217
+ /**
1218
+ * @typedef {Record<string, Substitution>} Substitutions
1219
+ */
1220
+
1203
1221
  // register plugins
1204
1222
  jsep.plugins.register(index, plugin);
1205
1223
  jsep.addUnaryOp('typeof');
@@ -1207,74 +1225,160 @@ jsep.addUnaryOp('void');
1207
1225
  jsep.addLiteral('null', null);
1208
1226
  jsep.addLiteral('undefined', undefined);
1209
1227
  const BLOCKED_PROTO_PROPERTIES = new Set(['constructor', '__proto__', '__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__']);
1228
+
1229
+ // Every function-constructor variant, along with the invocation helpers which
1230
+ // could otherwise reach them indirectly, e.g., `Function.call(0, 'code')()`,
1231
+ // plus whichever BLOCKED_PROTO_PROPERTIES names resolve to an own method on
1232
+ // Object.prototype (e.g. `__defineGetter__`) - an own-property access on a
1233
+ // shared prototype (`@.prototype.__defineGetter__`) bypasses the
1234
+ // inherited-only name check below, so those methods need blocking by value.
1235
+ /** @type {WeakSet<object>} */
1236
+ const BLOCKED_FUNCTIONS = new WeakSet([Function,
1237
+ // eslint-disable-next-line no-empty-function -- Only need the constructor
1238
+ function* () {}.constructor,
1239
+ // eslint-disable-next-line no-empty-function -- Only need the constructor
1240
+ async function () {}.constructor,
1241
+ // eslint-disable-next-line no-empty-function -- Only need the constructor
1242
+ async function* () {}.constructor, Function.prototype.call, Function.prototype.apply, Function.prototype.bind, Reflect.apply, Reflect.construct, ...[...BLOCKED_PROTO_PROPERTIES].map(name => Reflect.get(Object.prototype, name)).filter(value => typeof value === 'function')]);
1243
+
1244
+ /**
1245
+ * @param {UnknownResult} value
1246
+ * @returns {boolean}
1247
+ */
1248
+ const isBlockedFunction = value => {
1249
+ return typeof value === 'function' && BLOCKED_FUNCTIONS.has(value);
1250
+ };
1251
+
1252
+ /**
1253
+ * Guarded `obj[prop]`, applying the same restrictions as a MemberExpression.
1254
+ * @param {UnknownResult} obj
1255
+ * @param {string} prop
1256
+ * @returns {UnknownResult}
1257
+ */
1258
+ const getSafeProperty = (obj, prop) => {
1259
+ if (obj === undefined || obj === null) {
1260
+ throw new TypeError(`Cannot read properties of ${obj} (reading '${prop}')`);
1261
+ }
1262
+ if (!Object.hasOwn(obj, prop) && BLOCKED_PROTO_PROPERTIES.has(prop)) {
1263
+ throw new TypeError(`Cannot read properties of ${obj} (reading '${prop}')`);
1264
+ }
1265
+ const result = /** @type {Record<string, UnknownResult>} */obj[prop];
1266
+ if (isBlockedFunction(result)) {
1267
+ throw new TypeError('Function constructor is disabled');
1268
+ }
1269
+ return result;
1270
+ };
1271
+
1272
+ /**
1273
+ * @typedef {Record<
1274
+ * string,
1275
+ * (a: AnyParameter, b: AnyParameter) => UnknownResult
1276
+ * >} OperatorTable
1277
+ */
1278
+
1279
+ // eslint-disable-next-line @stylistic/max-len -- Long
1280
+ const BINOPS = Object.assign(Object.create(null), /** @type {OperatorTable} */{
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 -- API
1287
+ '==': (a, b) => a == b(),
1288
+ // eslint-disable-next-line eqeqeq -- API
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
+ });
1305
+
1306
+ /**
1307
+ * @typedef {{
1308
+ * [key: string]: (a: AnyParameter) => UnknownResult
1309
+ * }} UnaryOperatorTable
1310
+ */
1311
+
1312
+ // eslint-disable-next-line @stylistic/max-len -- Long
1313
+ const UNOPS = Object.assign(Object.create(null), /** @type {UnaryOperatorTable} */{
1314
+ '-': a => -(/** @type {EvaluatedResult} */a),
1315
+ '!': a => !a,
1316
+ '~': a => ~(/** @type {EvaluatedResult} */a),
1317
+ // eslint-disable-next-line no-implicit-coercion -- API
1318
+ '+': a => +(/** @type {EvaluatedResult} */a),
1319
+ typeof: a => typeof a,
1320
+ void: () => undefined
1321
+ });
1210
1322
  const SafeEval = {
1211
1323
  /**
1212
1324
  * @param {jsep.Expression} ast
1213
- * @param {Record<string, any>} subs
1325
+ * @param {Substitutions} subs
1326
+ * @returns {UnknownResult}
1214
1327
  */
1215
1328
  evalAst(ast, subs) {
1216
1329
  switch (ast.type) {
1217
1330
  case 'BinaryExpression':
1218
1331
  case 'LogicalExpression':
1219
- return SafeEval.evalBinaryExpression(ast, subs);
1332
+ return SafeEval.evalBinaryExpression(/** @type {jsep.BinaryExpression} */ast, subs);
1220
1333
  case 'Compound':
1221
- return SafeEval.evalCompound(ast, subs);
1334
+ return SafeEval.evalCompound(/** @type {jsep.Compound} */ast, subs);
1222
1335
  case 'ConditionalExpression':
1223
- return SafeEval.evalConditionalExpression(ast, subs);
1336
+ return SafeEval.evalConditionalExpression(/** @type {jsep.ConditionalExpression} */ast, subs);
1224
1337
  case 'Identifier':
1225
- return SafeEval.evalIdentifier(ast, subs);
1338
+ return SafeEval.evalIdentifier(/** @type {jsep.Identifier} */ast, subs);
1226
1339
  case 'Literal':
1227
- return SafeEval.evalLiteral(ast, subs);
1340
+ return SafeEval.evalLiteral(/** @type {jsep.Literal} */ast);
1228
1341
  case 'MemberExpression':
1229
- return SafeEval.evalMemberExpression(ast, subs);
1342
+ return SafeEval.evalMemberExpression(/** @type {jsep.MemberExpression} */ast, subs);
1230
1343
  case 'UnaryExpression':
1231
- return SafeEval.evalUnaryExpression(ast, subs);
1344
+ return SafeEval.evalUnaryExpression(/** @type {jsep.UnaryExpression} */ast, subs);
1232
1345
  case 'ArrayExpression':
1233
- return SafeEval.evalArrayExpression(ast, subs);
1346
+ return SafeEval.evalArrayExpression(/** @type {jsep.ArrayExpression} */ast, subs);
1234
1347
  case 'CallExpression':
1235
- return SafeEval.evalCallExpression(ast, subs);
1348
+ return SafeEval.evalCallExpression(/** @type {jsep.CallExpression} */ast, subs);
1236
1349
  case 'AssignmentExpression':
1237
- return SafeEval.evalAssignmentExpression(ast, subs);
1350
+ return SafeEval.evalAssignmentExpression(/** @type {AssignmentExpression} */ast, subs);
1238
1351
  default:
1239
- throw SyntaxError('Unexpected expression', ast);
1352
+ throw new SyntaxError('Unexpected expression', {
1353
+ cause: ast
1354
+ });
1240
1355
  }
1241
1356
  },
1357
+ /**
1358
+ * @param {jsep.BinaryExpression} ast
1359
+ * @param {Substitutions} subs
1360
+ * @returns {UnknownResult}
1361
+ */
1242
1362
  evalBinaryExpression(ast, subs) {
1243
- const result = {
1244
- '||': (a, b) => a || b(),
1245
- '&&': (a, b) => a && b(),
1246
- '|': (a, b) => a | b(),
1247
- '^': (a, b) => a ^ b(),
1248
- '&': (a, b) => a & b(),
1249
- // eslint-disable-next-line eqeqeq -- API
1250
- '==': (a, b) => a == b(),
1251
- // eslint-disable-next-line eqeqeq -- API
1252
- '!=': (a, b) => a != b(),
1253
- '===': (a, b) => a === b(),
1254
- '!==': (a, b) => a !== b(),
1255
- '<': (a, b) => a < b(),
1256
- '>': (a, b) => a > b(),
1257
- '<=': (a, b) => a <= b(),
1258
- '>=': (a, b) => a >= b(),
1259
- '<<': (a, b) => a << b(),
1260
- '>>': (a, b) => a >> b(),
1261
- '>>>': (a, b) => a >>> b(),
1262
- '+': (a, b) => a + b(),
1263
- '-': (a, b) => a - b(),
1264
- '*': (a, b) => a * b(),
1265
- '/': (a, b) => a / b(),
1266
- '%': (a, b) => a % b()
1267
- }[ast.operator](SafeEval.evalAst(ast.left, subs), () => SafeEval.evalAst(ast.right, subs));
1363
+ /* c8 ignore next 3 -- Defensive guard for malformed ASTs */
1364
+ if (!Object.hasOwn(BINOPS, ast.operator)) {
1365
+ throw new SyntaxError(`Unknown binary operator: ${ast.operator}`);
1366
+ }
1367
+ const result = BINOPS[ast.operator](SafeEval.evalAst(ast.left, subs), () => SafeEval.evalAst(ast.right, subs));
1268
1368
  return result;
1269
1369
  },
1370
+ /**
1371
+ * @param {jsep.Compound} ast
1372
+ * @param {Substitutions} subs
1373
+ * @returns {UnknownResult}
1374
+ */
1270
1375
  evalCompound(ast, subs) {
1271
1376
  let last;
1272
1377
  for (let i = 0; i < ast.body.length; i++) {
1273
- if (ast.body[i].type === 'Identifier' && ['var', 'let', 'const'].includes(ast.body[i].name) && ast.body[i + 1] && ast.body[i + 1].type === 'AssignmentExpression') {
1378
+ if (ast.body[i].type === 'Identifier' && ['var', 'let', 'const'].includes(/** @type {jsep.Identifier} */
1379
+ ast.body[i].name) && Object.hasOwn(ast.body, i + 1) && ast.body[i + 1].type === 'AssignmentExpression') {
1274
1380
  // var x=2; is detected as
1275
1381
  // [{Identifier var}, {AssignmentExpression x=2}]
1276
- // eslint-disable-next-line @stylistic/max-len -- Long
1277
- // eslint-disable-next-line sonarjs/updated-loop-counter -- Convenient
1278
1382
  i += 1;
1279
1383
  }
1280
1384
  const expr = ast.body[i];
@@ -1282,74 +1386,98 @@ const SafeEval = {
1282
1386
  }
1283
1387
  return last;
1284
1388
  },
1389
+ /**
1390
+ * @param {jsep.ConditionalExpression} ast
1391
+ * @param {Substitutions} subs
1392
+ * @returns {UnknownResult}
1393
+ */
1285
1394
  evalConditionalExpression(ast, subs) {
1286
- if (SafeEval.evalAst(ast.test, subs)) {
1287
- return SafeEval.evalAst(ast.consequent, subs);
1288
- }
1289
- return SafeEval.evalAst(ast.alternate, subs);
1395
+ return SafeEval.evalAst(ast.test, subs) ? SafeEval.evalAst(ast.consequent, subs) : SafeEval.evalAst(ast.alternate, subs);
1290
1396
  },
1397
+ /**
1398
+ * @param {jsep.Identifier} ast
1399
+ * @param {Substitutions} subs
1400
+ * @returns {UnknownResult}
1401
+ */
1291
1402
  evalIdentifier(ast, subs) {
1292
1403
  if (Object.hasOwn(subs, ast.name)) {
1293
1404
  return subs[ast.name];
1294
1405
  }
1295
- throw ReferenceError(`${ast.name} is not defined`);
1406
+ throw new ReferenceError(`${ast.name} is not defined`);
1296
1407
  },
1408
+ /**
1409
+ * @param {jsep.Literal} ast
1410
+ * @returns {UnknownResult}
1411
+ */
1297
1412
  evalLiteral(ast) {
1298
1413
  return ast.value;
1299
1414
  },
1415
+ /**
1416
+ * @param {jsep.MemberExpression} ast
1417
+ * @param {Substitutions} subs
1418
+ * @returns {UnknownResult}
1419
+ */
1300
1420
  evalMemberExpression(ast, subs) {
1301
1421
  const prop = String(
1302
1422
  // NOTE: `String(value)` throws error when
1303
1423
  // value has overwritten the toString method to return non-string
1304
1424
  // i.e. `value = {toString: () => []}`
1305
- ast.computed ? SafeEval.evalAst(ast.property) // `object[property]`
1425
+ ast.computed ? SafeEval.evalAst(ast.property, subs) // `object[property]`
1306
1426
  : ast.property.name // `object.property` property is Identifier
1307
1427
  );
1308
1428
  const obj = SafeEval.evalAst(ast.object, subs);
1309
- if (obj === undefined || obj === null) {
1310
- throw TypeError(`Cannot read properties of ${obj} (reading '${prop}')`);
1311
- }
1312
- if (!Object.hasOwn(obj, prop) && BLOCKED_PROTO_PROPERTIES.has(prop)) {
1313
- throw TypeError(`Cannot read properties of ${obj} (reading '${prop}')`);
1314
- }
1315
- const result = obj[prop];
1429
+ const result = getSafeProperty(obj, prop);
1316
1430
  if (typeof result === 'function') {
1317
1431
  return result.bind(obj); // arrow functions aren't affected by bind.
1318
1432
  }
1319
1433
  return result;
1320
1434
  },
1435
+ /**
1436
+ * @param {jsep.UnaryExpression} ast
1437
+ * @param {Substitutions} subs
1438
+ * @returns {UnknownResult}
1439
+ */
1321
1440
  evalUnaryExpression(ast, subs) {
1322
- const result = {
1323
- '-': a => -SafeEval.evalAst(a, subs),
1324
- '!': a => !SafeEval.evalAst(a, subs),
1325
- '~': a => ~SafeEval.evalAst(a, subs),
1326
- // eslint-disable-next-line no-implicit-coercion -- API
1327
- '+': a => +SafeEval.evalAst(a, subs),
1328
- typeof: a => typeof SafeEval.evalAst(a, subs),
1329
- // eslint-disable-next-line no-void, sonarjs/void-use -- feature
1330
- void: a => void SafeEval.evalAst(a, subs)
1331
- }[ast.operator](ast.argument);
1332
- return result;
1441
+ /* c8 ignore next 3 -- Defensive guard for malformed ASTs */
1442
+ if (!Object.hasOwn(UNOPS, ast.operator)) {
1443
+ throw new SyntaxError(`Unknown unary operator: ${ast.operator}`);
1444
+ }
1445
+ const operand = SafeEval.evalAst(ast.argument, subs);
1446
+ return UNOPS[ast.operator](operand);
1333
1447
  },
1448
+ /**
1449
+ * @param {jsep.ArrayExpression} ast
1450
+ * @param {Substitutions} subs
1451
+ * @returns {UnknownResult}
1452
+ */
1334
1453
  evalArrayExpression(ast, subs) {
1335
- return ast.elements.map(el => SafeEval.evalAst(el, subs));
1454
+ return ast.elements.map(el => SafeEval.evalAst(/** @type {jsep.Expression} */
1455
+ el, subs));
1336
1456
  },
1457
+ /**
1458
+ * @param {jsep.CallExpression} ast
1459
+ * @param {Substitutions} subs
1460
+ * @returns {UnknownResult}
1461
+ */
1337
1462
  evalCallExpression(ast, subs) {
1338
1463
  const args = ast.arguments.map(arg => SafeEval.evalAst(arg, subs));
1339
1464
  const func = SafeEval.evalAst(ast.callee, subs);
1340
- /* c8 ignore start */
1341
- if (func === Function) {
1342
- // unreachable since BLOCKED_PROTO_PROPERTIES includes 'constructor'
1465
+ if (isBlockedFunction(func) || args.some(arg => isBlockedFunction(arg))) {
1343
1466
  throw new Error('Function constructor is disabled');
1344
1467
  }
1345
- /* c8 ignore end */
1346
- return func(...args);
1468
+ return (/** @type {(...args: AnyParameter[]) => UnknownResult} */
1469
+ func)(...args);
1347
1470
  },
1471
+ /**
1472
+ * @param {AssignmentExpression} ast
1473
+ * @param {Substitutions} subs
1474
+ * @returns {UnknownResult}
1475
+ */
1348
1476
  evalAssignmentExpression(ast, subs) {
1349
1477
  if (ast.left.type !== 'Identifier') {
1350
- throw SyntaxError('Invalid left-hand side in assignment');
1478
+ throw new SyntaxError('Invalid left-hand side in assignment');
1351
1479
  }
1352
- const id = ast.left.name;
1480
+ const id = /** @type {jsep.Identifier} */ast.left.name;
1353
1481
  const value = SafeEval.evalAst(ast.right, subs);
1354
1482
  subs[id] = value;
1355
1483
  return subs[id];
@@ -1365,7 +1493,7 @@ class SafeScript {
1365
1493
  */
1366
1494
  constructor(expr) {
1367
1495
  this.code = expr;
1368
- this.ast = jsep(this.code);
1496
+ this.ast = /** @type {unknown} */jsep(this.code);
1369
1497
  }
1370
1498
 
1371
1499
  /**
@@ -1376,30 +1504,69 @@ class SafeScript {
1376
1504
  runInNewContext(context) {
1377
1505
  // `Object.create(null)` creates a prototypeless object
1378
1506
  const keyMap = Object.assign(Object.create(null), context);
1379
- return SafeEval.evalAst(this.ast, keyMap);
1507
+ return SafeEval.evalAst(/** @type {jsep.Expression} */this.ast, keyMap);
1380
1508
  }
1381
1509
  }
1382
1510
 
1511
+ /**
1512
+ * @param {unknown} val
1513
+ */
1514
+ const toStringTag = val => {
1515
+ return Object.prototype.toString.call(val).slice(8, -1);
1516
+ };
1517
+
1383
1518
  /* eslint-disable camelcase -- Convenient for escaping */
1519
+ /* eslint-disable class-methods-use-this -- Consistent monkey-patching */
1520
+ /* eslint-disable unicorn/prefer-private-class-fields -- Allow
1521
+ monkey-patching */
1522
+ const scriptCache = new Map();
1523
+ const pathCache = new Map();
1524
+
1525
+ /**
1526
+ * @typedef {any} AnyInput
1527
+ */
1528
+
1529
+ /**
1530
+ * @typedef {((...args: any[]) => any)} SandboxCallback
1531
+ */
1532
+
1533
+ /**
1534
+ * @typedef {any|SandboxCallback} SandboxPropertyValue
1535
+ */
1536
+
1537
+ /**
1538
+ * @typedef {(string|number)[]} ExpressionArray
1539
+ */
1540
+
1541
+ /**
1542
+ * @typedef {"scalar"|"boolean"|"string"|"undefined"
1543
+ * |"function"|"integer"|"number"|"nonFinite"|"object"
1544
+ * |"array"|"other"|"null"|"symbol"|"Promise"|"BigInt"
1545
+ * |"jsonReference"} ValueType
1546
+ */
1384
1547
 
1548
+ /**
1549
+ * @typedef {unknown} ParentValue
1550
+ */
1385
1551
 
1386
1552
  /**
1387
- * @typedef {null|boolean|number|string|object|GenericArray} JSONObject
1553
+ * @typedef {unknown} UnknownResult
1388
1554
  */
1389
1555
 
1390
1556
  /**
1391
- * @typedef {any} AnyItem
1557
+ * @typedef {string|number|null} ParentProperty
1392
1558
  */
1393
1559
 
1394
1560
  /**
1395
- * @typedef {any} AnyResult
1561
+ * @typedef {ReturnObject|string|number|boolean|null|unknown[]
1562
+ * |Record<string, unknown>} PreferredOutput
1396
1563
  */
1397
1564
 
1398
1565
  /**
1399
1566
  * Copies array and then pushes item into it.
1400
- * @param {GenericArray} arr Array to copy and into which to push
1401
- * @param {AnyItem} item Array item to add (to end)
1402
- * @returns {GenericArray} Copy of the original array
1567
+ * @param {ExpressionArray} arr Array to copy and into which to push
1568
+ * @param {string|number} item Array item to add (to end)
1569
+ * @returns {ExpressionArray} Copy of the original array
1403
1570
  */
1404
1571
  function push(arr, item) {
1405
1572
  arr = arr.slice();
@@ -1408,9 +1575,9 @@ function push(arr, item) {
1408
1575
  }
1409
1576
  /**
1410
1577
  * Copies array and then unshifts item into it.
1411
- * @param {AnyItem} item Array item to add (to beginning)
1412
- * @param {GenericArray} arr Array to copy and into which to unshift
1413
- * @returns {GenericArray} Copy of the original array
1578
+ * @param {string|number} item Array item to add (to beginning)
1579
+ * @param {ExpressionArray} arr Array to copy and into which to unshift
1580
+ * @returns {ExpressionArray} Copy of the original array
1414
1581
  */
1415
1582
  function unshift(item, arr) {
1416
1583
  arr = arr.slice();
@@ -1419,45 +1586,35 @@ function unshift(item, arr) {
1419
1586
  }
1420
1587
 
1421
1588
  /**
1422
- * Caught when JSONPath is used without `new` but rethrown if with `new`
1423
- * @extends Error
1589
+ * @typedef {object} ReturnObject
1590
+ * @property {ExpressionArray|string} path
1591
+ * @property {unknown} value
1592
+ * @property {ParentValue} parent
1593
+ * @property {ParentProperty} parentProperty
1594
+ * @property {boolean} [isParentSelector]
1595
+ * @property {boolean} [hasArrExpr]
1596
+ * @property {ExpressionArray} [expr]
1597
+ * @property {string} [pointer]
1424
1598
  */
1425
- class NewError extends Error {
1426
- /**
1427
- * @param {AnyResult} value The evaluated scalar value
1428
- */
1429
- constructor(value) {
1430
- super('JSONPath should not be called with "new" (it prevents return ' + 'of (unwrapped) scalar values)');
1431
- this.avoidNew = true;
1432
- this.value = value;
1433
- this.name = 'NewError';
1434
- }
1435
- }
1436
-
1437
- /**
1438
- * @typedef {object} ReturnObject
1439
- * @property {string} path
1440
- * @property {JSONObject} value
1441
- * @property {object|GenericArray} parent
1442
- * @property {string} parentProperty
1443
- */
1444
1599
 
1445
1600
  /**
1446
- * @callback JSONPathCallback
1447
- * @param {string|object} preferredOutput
1448
- * @param {"value"|"property"} type
1449
- * @param {ReturnObject} fullRetObj
1450
- * @returns {void}
1451
- */
1601
+ * @callback JSONPathCallback
1602
+ * @param {any} preferredOutput Using `any` type instead of `PreferredOutput` so
1603
+ * that user can supply flexible type
1604
+ * @param {"value"|"property"} type
1605
+ * @param {ReturnObject} fullRetObj
1606
+ * @returns {void}
1607
+ */
1452
1608
 
1453
1609
  /**
1454
- * @callback OtherTypeCallback
1455
- * @param {JSONObject} val
1456
- * @param {string} path
1457
- * @param {object|GenericArray} parent
1458
- * @param {string} parentPropName
1459
- * @returns {boolean}
1460
- */
1610
+ * @callback OtherTypeCallback
1611
+ * @param {unknown} val
1612
+ * @param {ExpressionArray} path
1613
+ * @param {ParentValue} parent
1614
+ * @param {string|number|null} parentPropName
1615
+ * @param {string} arg
1616
+ * @returns {boolean|null}
1617
+ */
1461
1618
 
1462
1619
  /**
1463
1620
  * @typedef {any} ContextItem
@@ -1468,431 +1625,676 @@ class NewError extends Error {
1468
1625
  */
1469
1626
 
1470
1627
  /**
1471
- * @callback EvalCallback
1472
- * @param {string} code
1473
- * @param {ContextItem} context
1474
- * @returns {EvaluatedResult}
1475
- */
1628
+ * @callback EvalCallback
1629
+ * @param {string} code
1630
+ * @param {ContextItem} context
1631
+ * @returns {EvaluatedResult}
1632
+ */
1633
+
1634
+ /**
1635
+ * @typedef {new (expr: string) => {
1636
+ * runInNewContext: (context: object) => EvaluatedResult
1637
+ * }} ScriptConstructor
1638
+ */
1639
+
1640
+ /**
1641
+ * @typedef {ScriptConstructor} EvalClass
1642
+ */
1643
+
1644
+ /**
1645
+ * @typedef {"value"|"path"|"pointer"|"parent"|"parentProperty"
1646
+ * |"all"} ResultType
1647
+ */
1648
+
1649
+ /**
1650
+ * @typedef {EvalCallback|EvalClass|'safe'|'native'|boolean} EvalValue
1651
+ */
1652
+
1653
+ /**
1654
+ * @typedef {string|string[]} PathType
1655
+ */
1656
+
1657
+ /**
1658
+ * @typedef {{Script: ScriptConstructor}} SafeScriptType
1659
+ */
1660
+
1661
+ /**
1662
+ * @typedef {{Script: ScriptConstructor}} ScriptType
1663
+ */
1476
1664
 
1477
1665
  /**
1478
- * @typedef {typeof SafeScript} EvalClass
1666
+ * @typedef {{
1667
+ * _$_path?: string,
1668
+ * _$_parentProperty?: ParentProperty,
1669
+ * _$_parent?: ParentValue,
1670
+ * _$_property?: string|number,
1671
+ * _$_root?: AnyInput,
1672
+ * _$_v?: unknown,
1673
+ * [key: string]: SandboxPropertyValue
1674
+ * }} SandboxType
1479
1675
  */
1480
1676
 
1481
1677
  /**
1482
1678
  * @typedef {object} JSONPathOptions
1483
- * @property {JSON} json
1484
- * @property {string|string[]} path
1485
- * @property {"value"|"path"|"pointer"|"parent"|"parentProperty"|
1486
- * "all"} [resultType="value"]
1679
+ * @property {AnyInput} [json]
1680
+ * @property {PathType} [path]
1681
+ * @property {ResultType} [resultType="value"]
1487
1682
  * @property {boolean} [flatten=false]
1488
1683
  * @property {boolean} [wrap=true]
1489
- * @property {object} [sandbox={}]
1490
- * @property {EvalCallback|EvalClass|'safe'|'native'|
1491
- * boolean} [eval = 'safe']
1492
- * @property {object|GenericArray|null} [parent=null]
1493
- * @property {string|null} [parentProperty=null]
1684
+ * @property {SandboxType} [sandbox={}]
1685
+ * @property {EvalValue} [eval='safe']
1686
+ * @property {any|null} [parent=null]
1687
+ * @property {ParentProperty} [parentProperty=null]
1494
1688
  * @property {JSONPathCallback} [callback]
1495
1689
  * @property {OtherTypeCallback} [otherTypeCallback] Defaults to
1496
1690
  * function which throws on encountering `@other`
1691
+ * @property {Record<string, OtherTypeCallback>} [customTypes] Map of
1692
+ * custom type operator names to their evaluation callbacks
1497
1693
  * @property {boolean} [autostart=true]
1694
+ * @property {boolean} [ignoreEvalErrors=false]
1498
1695
  */
1499
1696
 
1500
1697
  /**
1501
- * @param {string|JSONPathOptions} opts If a string, will be treated as `expr`
1502
- * @param {string} [expr] JSON path to evaluate
1503
- * @param {JSON} [obj] JSON object to evaluate against
1504
- * @param {JSONPathCallback} [callback] Passed 3 arguments: 1) desired payload
1505
- * per `resultType`, 2) `"value"|"property"`, 3) Full returned object with
1698
+ * @overload
1699
+ * @param {string} opts JSON path to evaluate
1700
+ * @param {AnyInput} [expr] JSON object to evaluate against
1701
+ * @param {JSONPathCallback} [obj] Passed 3 arguments: 1) desired
1702
+ * payload per `resultType`, 2) `"value"|"property"`, 3) Full returned
1703
+ * object with all payloads
1704
+ * @param {OtherTypeCallback} [callback] If `@other()` is at the
1705
+ * end of one's query, this will be invoked with the value of the item,
1706
+ * its path, its parent, and its parent's property name, and it should
1707
+ * return a boolean indicating whether the supplied value belongs to the
1708
+ * "other" type or not (or it may handle transformations and return
1709
+ * `false`).
1710
+ * @param {undefined} [otherTypeCallback]
1711
+ * @returns {unknown} The string form always has `autostart` implicitly
1712
+ * `true`, so the result is the evaluated value, not a `JSONPathClass`
1713
+ */
1714
+ /**
1715
+ * @overload
1716
+ * @param {JSONPathOptions & {autostart: false}} opts An options object
1717
+ * with `autostart` explicitly set to `false` defers evaluation and
1718
+ * returns the `JSONPathClass` instance instead
1719
+ * @returns {JSONPathClass}
1720
+ */
1721
+ /**
1722
+ * @overload
1723
+ * @param {JSONPathOptions} opts If a string, will be treated as
1724
+ * `expr`
1725
+ * @returns {unknown}
1726
+ */
1727
+ /**
1728
+ * @param {JSONPathOptions|string} opts If a string, will be treated as `expr`
1729
+ * @param {string|AnyInput} [expr] JSON path to evaluate
1730
+ * @param {AnyInput|JSONPathCallback} [obj] JSON object to evaluate against
1731
+ * @param {JSONPathCallback|OtherTypeCallback} [callback] Passed 3
1732
+ * arguments: 1) desired payload per `resultType`,
1733
+ * 2) `"value"|"property"`, 3) Full returned object with
1506
1734
  * all payloads
1507
1735
  * @param {OtherTypeCallback} [otherTypeCallback] If `@other()` is at the end
1508
1736
  * of one's query, this will be invoked with the value of the item, its
1509
1737
  * path, its parent, and its parent's property name, and it should return
1510
1738
  * a boolean indicating whether the supplied value belongs to the "other"
1511
1739
  * type or not (or it may handle transformations and return `false`).
1512
- * @returns {JSONPath}
1513
- * @class
1740
+ * @throws {Error}
1741
+ * @returns {unknown|JSONPathClass}
1514
1742
  */
1515
1743
  function JSONPath(opts, expr, obj, callback, otherTypeCallback) {
1516
- // eslint-disable-next-line no-restricted-syntax -- Allow for pseudo-class
1517
- if (!(this instanceof JSONPath)) {
1518
- try {
1519
- return new JSONPath(opts, expr, obj, callback, otherTypeCallback);
1520
- } catch (e) {
1521
- if (!e.avoidNew) {
1522
- throw e;
1523
- }
1524
- return e.value;
1525
- }
1526
- }
1527
- if (typeof opts === 'string') {
1528
- otherTypeCallback = callback;
1529
- callback = obj;
1530
- obj = expr;
1531
- expr = opts;
1532
- opts = null;
1533
- }
1534
- const optObj = opts && typeof opts === 'object';
1535
- opts = opts || {};
1536
- this.json = opts.json || obj;
1537
- this.path = opts.path || expr;
1538
- this.resultType = opts.resultType || 'value';
1539
- this.flatten = opts.flatten || false;
1540
- this.wrap = Object.hasOwn(opts, 'wrap') ? opts.wrap : true;
1541
- this.sandbox = opts.sandbox || {};
1542
- this.eval = opts.eval === undefined ? 'safe' : opts.eval;
1543
- this.ignoreEvalErrors = typeof opts.ignoreEvalErrors === 'undefined' ? false : opts.ignoreEvalErrors;
1544
- this.parent = opts.parent || null;
1545
- this.parentProperty = opts.parentProperty || null;
1546
- this.callback = opts.callback || callback || null;
1547
- this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function () {
1548
- throw new TypeError('You must supply an otherTypeCallback callback option ' + 'with the @other() operator.');
1549
- };
1550
- if (opts.autostart !== false) {
1551
- const args = {
1744
+ try {
1745
+ if (opts && typeof opts === 'object') {
1746
+ return new JSONPathClass(opts);
1747
+ }
1748
+ return new JSONPathClass(opts, expr, /** @type {JSONPathCallback|undefined} */obj, /** @type {OtherTypeCallback|undefined} */callback, /** @type {undefined} */otherTypeCallback);
1749
+ } catch (e) {
1750
+ if (new.target) {
1751
+ throw e;
1752
+ }
1753
+ if (e && typeof e === 'object' && 'value' in e) {
1754
+ return /** @type {{value: UnknownResult}} */e.value;
1755
+ }
1756
+ throw e;
1757
+ }
1758
+ }
1759
+
1760
+ /**
1761
+ *
1762
+ */
1763
+ class JSONPathClass {
1764
+ /**
1765
+ * @overload
1766
+ * @param {string} opts JSON path to evaluate
1767
+ * @param {AnyInput} [expr] JSON object to evaluate against
1768
+ * @param {JSONPathCallback} [obj] Passed 3 arguments: 1) desired
1769
+ * payload per `resultType`, 2) `"value"|"property"`, 3) Full returned
1770
+ * object with all payloads
1771
+ * @param {OtherTypeCallback} [callback] If `@other()` is at the
1772
+ * end of one's query, this will be invoked with the value of the item,
1773
+ * its path, its parent, and its parent's property name, and it should
1774
+ * return a boolean indicating whether the supplied value belongs to the
1775
+ * "other" type or not (or it may handle transformations and return
1776
+ * `false`).
1777
+ * @param {undefined} [otherTypeCallback]
1778
+ */
1779
+ /**
1780
+ * @overload
1781
+ * @param {JSONPathOptions} opts If a string, will be treated as
1782
+ * `expr`
1783
+ */
1784
+ /**
1785
+ * @param {null|string|JSONPathOptions} opts If a string, will be treated as
1786
+ * `expr`
1787
+ * @param {string|AnyInput} [expr] JSON path to evaluate
1788
+ * @param {AnyInput|JSONPathCallback} [obj] JSON object to evaluate against
1789
+ * @param {JSONPathCallback|OtherTypeCallback} [callback] Passed 3
1790
+ * arguments: 1) desired payload per `resultType`,
1791
+ * 2) `"value"|"property"`, 3) Full returned
1792
+ * object with all payloads
1793
+ * @param {OtherTypeCallback} [otherTypeCallback] If `@other()` is at the
1794
+ * end of one's query, this will be invoked with the value of the item,
1795
+ * its path, its parent, and its parent's property name, and it should
1796
+ * return a boolean indicating whether the supplied value belongs to the
1797
+ * "other" type or not (or it may handle transformations and return
1798
+ * `false`).
1799
+ */
1800
+ constructor(opts, expr, obj, callback, otherTypeCallback) {
1801
+ if (typeof opts === 'string') {
1802
+ otherTypeCallback = /** @type {OtherTypeCallback} */
1803
+ callback;
1804
+ callback = /** @type {JSONPathCallback} */
1805
+ obj;
1806
+ obj = expr;
1807
+ expr = opts;
1808
+ opts = null;
1809
+ }
1810
+ const optObj = opts && typeof opts === 'object';
1811
+ opts ||= /** @type {JSONPathOptions} */{};
1812
+ /** @type {ResultType|undefined} */
1813
+ this.currResultType = undefined;
1814
+
1815
+ /** @type {EvalValue|undefined} */
1816
+ this.currEval = undefined;
1817
+
1818
+ /** @type {OtherTypeCallback|undefined} */
1819
+ this.currOtherTypeCallback = undefined;
1820
+
1821
+ /** @type {Record<string, OtherTypeCallback>|undefined} */
1822
+ this.currCustomTypes = undefined;
1823
+
1824
+ /** @type {SandboxType|undefined} */
1825
+ this.currSandbox = undefined;
1826
+ this._hasParentSelector = false;
1827
+ this.json = opts.json || obj;
1828
+ this.path = opts.path || expr;
1829
+ this.resultType = opts.resultType || 'value';
1830
+ this.flatten = Object.hasOwn(opts, 'flatten') && opts.flatten;
1831
+ this.wrap = !Object.hasOwn(opts, 'wrap') || opts.wrap;
1832
+ this.sandbox = opts.sandbox || {};
1833
+ this.eval = opts.eval === undefined ? 'safe' : opts.eval;
1834
+ this.ignoreEvalErrors = typeof opts.ignoreEvalErrors !== 'undefined' && opts.ignoreEvalErrors;
1835
+ this.parent = Object.hasOwn(opts, 'parent') ? opts.parent : null;
1836
+ this.parentProperty = Object.hasOwn(opts, 'parentProperty') ? opts.parentProperty : null;
1837
+ this.callback = opts.callback || (/** @type {JSONPathCallback} */
1838
+ callback) || null;
1839
+ this.otherTypeCallback = opts.otherTypeCallback || otherTypeCallback || function () {
1840
+ throw new TypeError('You must supply an otherTypeCallback callback option ' + 'with the @other() operator.');
1841
+ };
1842
+ this.customTypes = opts.customTypes || {};
1843
+ if (opts.autostart === false) {
1844
+ return;
1845
+ }
1846
+ const args = /** @type {JSONPathOptions} */{
1552
1847
  path: optObj ? opts.path : expr
1553
1848
  };
1554
- if (!optObj) {
1849
+ if (!optObj && obj !== undefined) {
1555
1850
  args.json = obj;
1556
1851
  } else if ('json' in opts) {
1557
1852
  args.json = opts.json;
1558
1853
  }
1559
1854
  const ret = this.evaluate(args);
1560
1855
  if (!ret || typeof ret !== 'object') {
1561
- throw new NewError(ret);
1856
+ const err = /** @type {Error & {value: UnknownResult}} */
1857
+ new Error('JSONPath should not be called with "new" (it ' + 'prevents return of (unwrapped) scalar values)');
1858
+ err.value = ret;
1859
+ throw err;
1562
1860
  }
1861
+
1862
+ // @ts-expect-error - Constructor returns evaluate result for legacy API
1863
+ // eslint-disable-next-line @stylistic/max-len -- Long
1864
+ // eslint-disable-next-line consistent-return, no-constructor-return -- Legacy API
1563
1865
  return ret;
1564
1866
  }
1565
- }
1566
1867
 
1567
- // PUBLIC METHODS
1568
- JSONPath.prototype.evaluate = function (expr, json, callback, otherTypeCallback) {
1569
- let currParent = this.parent,
1570
- currParentProperty = this.parentProperty;
1571
- let {
1572
- flatten,
1573
- wrap
1574
- } = this;
1575
- this.currResultType = this.resultType;
1576
- this.currEval = this.eval;
1577
- this.currSandbox = this.sandbox;
1578
- callback = callback || this.callback;
1579
- this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;
1580
- json = json || this.json;
1581
- expr = expr || this.path;
1582
- if (expr && typeof expr === 'object' && !Array.isArray(expr)) {
1583
- if (!expr.path && expr.path !== '') {
1584
- throw new TypeError('You must supply a "path" property when providing an object ' + 'argument to JSONPath.evaluate().');
1585
- }
1586
- if (!Object.hasOwn(expr, 'json')) {
1587
- throw new TypeError('You must supply a "json" property when providing an object ' + 'argument to JSONPath.evaluate().');
1588
- }
1589
- ({
1590
- json
1591
- } = expr);
1592
- flatten = Object.hasOwn(expr, 'flatten') ? expr.flatten : flatten;
1593
- this.currResultType = Object.hasOwn(expr, 'resultType') ? expr.resultType : this.currResultType;
1594
- this.currSandbox = Object.hasOwn(expr, 'sandbox') ? expr.sandbox : this.currSandbox;
1595
- wrap = Object.hasOwn(expr, 'wrap') ? expr.wrap : wrap;
1596
- this.currEval = Object.hasOwn(expr, 'eval') ? expr.eval : this.currEval;
1597
- callback = Object.hasOwn(expr, 'callback') ? expr.callback : callback;
1598
- this.currOtherTypeCallback = Object.hasOwn(expr, 'otherTypeCallback') ? expr.otherTypeCallback : this.currOtherTypeCallback;
1599
- currParent = Object.hasOwn(expr, 'parent') ? expr.parent : currParent;
1600
- currParentProperty = Object.hasOwn(expr, 'parentProperty') ? expr.parentProperty : currParentProperty;
1601
- expr = expr.path;
1602
- }
1603
- currParent = currParent || null;
1604
- currParentProperty = currParentProperty || null;
1605
- if (Array.isArray(expr)) {
1606
- expr = JSONPath.toPathString(expr);
1607
- }
1608
- if (!expr && expr !== '' || !json) {
1609
- return undefined;
1610
- }
1611
- const exprList = JSONPath.toPathArray(expr);
1612
- if (exprList[0] === '$' && exprList.length > 1) {
1613
- exprList.shift();
1614
- }
1615
- this._hasParentSelector = null;
1616
- const result = this._trace(exprList, json, ['$'], currParent, currParentProperty, callback).filter(function (ea) {
1617
- return ea && !ea.isParentSelector;
1618
- });
1619
- if (!result.length) {
1620
- return wrap ? [] : undefined;
1621
- }
1622
- if (!wrap && result.length === 1 && !result[0].hasArrExpr) {
1623
- return this._getPreferredOutput(result[0]);
1624
- }
1625
- return result.reduce((rslt, ea) => {
1626
- const valOrPath = this._getPreferredOutput(ea);
1627
- if (flatten && Array.isArray(valOrPath)) {
1628
- rslt = rslt.concat(valOrPath);
1868
+ // PUBLIC METHODS
1869
+
1870
+ /**
1871
+ * @overload
1872
+ * @param {JSONPathOptions} [expr]
1873
+ * @returns {ReturnObject|ReturnObject[]|undefined|unknown}
1874
+ */
1875
+
1876
+ /**
1877
+ * @overload
1878
+ * @param {PathType|undefined} [expr]
1879
+ * @param {AnyInput} [json]
1880
+ * @param {JSONPathCallback|null} [callback]
1881
+ * @param {OtherTypeCallback} [otherTypeCallback]
1882
+ * @returns {ReturnObject|ReturnObject[]|undefined|unknown}
1883
+ */
1884
+
1885
+ /**
1886
+ * @param {PathType|JSONPathOptions|undefined} [expr]
1887
+ * @param {AnyInput} [json]
1888
+ * @param {JSONPathCallback|null} [callback]
1889
+ * @param {OtherTypeCallback} [otherTypeCallback]
1890
+ * @returns {ReturnObject|ReturnObject[]|undefined|unknown}
1891
+ */
1892
+ evaluate(expr, json, callback, otherTypeCallback) {
1893
+ let currParent = this.parent,
1894
+ currParentProperty = this.parentProperty;
1895
+ let {
1896
+ flatten,
1897
+ wrap
1898
+ } = this;
1899
+ this.currResultType = this.resultType;
1900
+ this.currEval = this.eval;
1901
+ this.currSandbox = this.sandbox;
1902
+ callback ||= this.callback;
1903
+ this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;
1904
+ this.currCustomTypes = this.customTypes;
1905
+ if (expr && typeof expr === 'object' && !Array.isArray(expr)) {
1906
+ const exprObj = expr;
1907
+ if (!exprObj.path && exprObj.path !== '') {
1908
+ throw new TypeError('You must supply a "path" property when providing an ' + 'object argument to JSONPath.evaluate().');
1909
+ }
1910
+ if (!Object.hasOwn(exprObj, 'json')) {
1911
+ throw new TypeError('You must supply a "json" property when providing an ' + 'object argument to JSONPath.evaluate().');
1912
+ }
1913
+ ({
1914
+ json
1915
+ } = exprObj);
1916
+ flatten = Object.hasOwn(exprObj, 'flatten') ? exprObj.flatten : flatten;
1917
+ this.currResultType = Object.hasOwn(exprObj, 'resultType') ? exprObj.resultType : this.currResultType;
1918
+ this.currSandbox = Object.hasOwn(exprObj, 'sandbox') ? exprObj.sandbox : this.currSandbox;
1919
+ wrap = Object.hasOwn(exprObj, 'wrap') ? exprObj.wrap : wrap;
1920
+ this.currEval = Object.hasOwn(exprObj, 'eval') ? exprObj.eval : this.currEval;
1921
+ callback = Object.hasOwn(exprObj, 'callback') ? exprObj.callback : callback;
1922
+ this.currOtherTypeCallback = Object.hasOwn(exprObj, 'otherTypeCallback') ? exprObj.otherTypeCallback : this.currOtherTypeCallback;
1923
+ this.currCustomTypes = Object.hasOwn(exprObj, 'customTypes') ? exprObj.customTypes : this.currCustomTypes;
1924
+ currParent = Object.hasOwn(exprObj, 'parent') ? exprObj.parent : currParent;
1925
+ currParentProperty = Object.hasOwn(exprObj, 'parentProperty') ? exprObj.parentProperty : currParentProperty;
1926
+ expr = exprObj.path;
1629
1927
  } else {
1630
- rslt.push(valOrPath);
1928
+ json ||= this.json;
1929
+ expr ||= this.path;
1631
1930
  }
1632
- return rslt;
1633
- }, []);
1634
- };
1931
+ currParent ||= null;
1932
+ currParentProperty ||= null;
1933
+ if (Array.isArray(expr)) {
1934
+ expr = JSONPath.toPathString(expr);
1935
+ }
1936
+ if (!json || !expr && expr !== '') {
1937
+ return undefined;
1938
+ }
1939
+ const exprList = JSONPath.toPathArray(/** @type {string} */
1940
+ expr);
1941
+ if (exprList[0] === '$' && exprList.length > 1) {
1942
+ exprList.shift();
1943
+ }
1944
+ this._hasParentSelector = false;
1945
+ const traceResult = this._trace(exprList, json, ['$'], currParent, currParentProperty, callback ?? undefined, undefined);
1635
1946
 
1636
- // PRIVATE METHODS
1637
-
1638
- JSONPath.prototype._getPreferredOutput = function (ea) {
1639
- const resultType = this.currResultType;
1640
- switch (resultType) {
1641
- case 'all':
1642
- {
1643
- const path = Array.isArray(ea.path) ? ea.path : JSONPath.toPathArray(ea.path);
1644
- ea.pointer = JSONPath.toPointer(path);
1645
- ea.path = typeof ea.path === 'string' ? ea.path : JSONPath.toPathString(ea.path);
1646
- return ea;
1947
+ // eslint-disable-next-line @stylistic/max-len -- Long
1948
+ /* c8 ignore next 2 -- Unreachable: _trace returns array when hasArrExpr set */
1949
+ const result = (Array.isArray(traceResult) ? traceResult : [traceResult]).filter(ea => {
1950
+ return ea && !ea.isParentSelector;
1951
+ });
1952
+ if (!result.length) {
1953
+ // eslint-disable-next-line @stylistic/max-len -- Long
1954
+ /* c8 ignore next -- Unreachable: valid queries always produce results */
1955
+ return wrap ? [] : undefined;
1956
+ }
1957
+ if (!wrap && result.length === 1 && !result[0].hasArrExpr) {
1958
+ const preferredOutput = this._getPreferredOutput(result[0]);
1959
+ return preferredOutput;
1960
+ }
1961
+ const reduced = result.reduce((rslt, ea) => {
1962
+ const valOrPath = this._getPreferredOutput(ea);
1963
+ if (flatten && Array.isArray(valOrPath)) {
1964
+ rslt = rslt.concat(valOrPath);
1965
+ } else {
1966
+ rslt.push(valOrPath);
1647
1967
  }
1648
- case 'value':
1649
- case 'parent':
1650
- case 'parentProperty':
1651
- return ea[resultType];
1652
- case 'path':
1653
- return JSONPath.toPathString(ea[resultType]);
1654
- case 'pointer':
1655
- return JSONPath.toPointer(ea.path);
1656
- default:
1657
- throw new TypeError('Unknown result type');
1658
- }
1659
- };
1660
- JSONPath.prototype._handleCallback = function (fullRetObj, callback, type) {
1661
- if (callback) {
1662
- const preferredOutput = this._getPreferredOutput(fullRetObj);
1663
- fullRetObj.path = typeof fullRetObj.path === 'string' ? fullRetObj.path : JSONPath.toPathString(fullRetObj.path);
1664
- // eslint-disable-next-line n/callback-return -- No need to return
1665
- callback(preferredOutput, type, fullRetObj);
1968
+ return rslt;
1969
+ }, /** @type {UnknownResult[]} */
1970
+ []);
1971
+ return reduced;
1666
1972
  }
1667
- };
1668
1973
 
1669
- /**
1670
- *
1671
- * @param {string} expr
1672
- * @param {JSONObject} val
1673
- * @param {string} path
1674
- * @param {object|GenericArray} parent
1675
- * @param {string} parentPropName
1676
- * @param {JSONPathCallback} callback
1677
- * @param {boolean} hasArrExpr
1678
- * @param {boolean} literalPriority
1679
- * @returns {ReturnObject|ReturnObject[]}
1680
- */
1681
- JSONPath.prototype._trace = function (expr, val, path, parent, parentPropName, callback, hasArrExpr, literalPriority) {
1682
- // No expr to follow? return path and value as the result of
1683
- // this trace branch
1684
- let retObj;
1685
- if (!expr.length) {
1686
- retObj = {
1687
- path,
1688
- value: val,
1689
- parent,
1690
- parentProperty: parentPropName,
1691
- hasArrExpr
1692
- };
1693
- this._handleCallback(retObj, callback, 'value');
1694
- return retObj;
1974
+ // PRIVATE METHODS
1975
+
1976
+ /**
1977
+ * @param {ReturnObject} ea
1978
+ * @returns {PreferredOutput}
1979
+ */
1980
+ _getPreferredOutput(ea) {
1981
+ const resultType = this.currResultType;
1982
+ switch (resultType) {
1983
+ case 'all':
1984
+ {
1985
+ const path = Array.isArray(ea.path) ? ea.path : JSONPath.toPathArray(ea.path);
1986
+ ea.pointer = JSONPath.toPointer(/** @type {string[]} */path);
1987
+ ea.path = typeof ea.path === 'string' ? ea.path : JSONPath.toPathString(/** @type {string[]} */ea.path);
1988
+ return ea;
1989
+ }
1990
+ case 'value':
1991
+ case 'parent':
1992
+ case 'parentProperty':
1993
+ return /** @type {PreferredOutput} */ea[resultType];
1994
+ case 'path':
1995
+ if (typeof ea.path === 'string') {
1996
+ return ea.path;
1997
+ }
1998
+ return JSONPath.toPathString(/** @type {string[]} */ea.path);
1999
+ case 'pointer':
2000
+ {
2001
+ const pathArray = Array.isArray(ea.path) ? ea.path : JSONPath.toPathArray(ea.path);
2002
+ return JSONPath.toPointer(/** @type {string[]} */pathArray);
2003
+ }
2004
+ default:
2005
+ throw new TypeError('Unknown result type');
2006
+ }
1695
2007
  }
1696
- const loc = expr[0],
1697
- x = expr.slice(1);
1698
2008
 
1699
- // We need to gather the return value of recursive trace calls in order to
1700
- // do the parent sel computation.
1701
- const ret = [];
1702
2009
  /**
1703
- *
1704
- * @param {ReturnObject|ReturnObject[]} elems
2010
+ * @param {ReturnObject} fullRetObj
2011
+ * @param {JSONPathCallback|undefined} callback
2012
+ * @param {"value"|"property"} type
1705
2013
  * @returns {void}
1706
2014
  */
1707
- function addRet(elems) {
1708
- if (Array.isArray(elems)) {
1709
- // This was causing excessive stack size in Node (with or
1710
- // without Babel) against our performance test:
1711
- // `ret.push(...elems);`
1712
- elems.forEach(t => {
1713
- ret.push(t);
1714
- });
1715
- } else {
1716
- ret.push(elems);
2015
+ _handleCallback(fullRetObj, callback, type) {
2016
+ // Early return if no callback provided (defensive
2017
+ // check for internal calls)
2018
+ if (!callback) {
2019
+ return;
1717
2020
  }
2021
+ const preferredOutput = this._getPreferredOutput(fullRetObj);
2022
+ if (Array.isArray(fullRetObj.path)) {
2023
+ fullRetObj.path = JSONPath.toPathString(/** @type {string[]} */fullRetObj.path);
2024
+ }
2025
+ callback(preferredOutput, type, fullRetObj);
1718
2026
  }
1719
- if ((typeof loc !== 'string' || literalPriority) && val && Object.hasOwn(val, loc)) {
1720
- // simple case--directly follow property
1721
- addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr));
1722
- // eslint-disable-next-line unicorn/prefer-switch -- Part of larger `if`
1723
- } else if (loc === '*') {
1724
- // all child properties
1725
- this._walk(val, m => {
1726
- addRet(this._trace(x, val[m], push(path, m), val, m, callback, true, true));
1727
- });
1728
- } else if (loc === '..') {
1729
- // all descendent parent properties
1730
- // Check remaining expression with val's immediate children
1731
- addRet(this._trace(x, val, path, parent, parentPropName, callback, hasArrExpr));
1732
- this._walk(val, m => {
1733
- // We don't join m and x here because we only want parents,
1734
- // not scalar values
1735
- if (typeof val[m] === 'object') {
1736
- // Keep going with recursive descent on val's
1737
- // object children
1738
- addRet(this._trace(expr.slice(), val[m], push(path, m), val, m, callback, true));
2027
+
2028
+ /**
2029
+ *
2030
+ * @param {ExpressionArray} expr
2031
+ * @param {unknown} val
2032
+ * @param {ExpressionArray} path
2033
+ * @param {ParentValue} parent
2034
+ * @param {ParentProperty} parentPropName
2035
+ * @param {JSONPathCallback|undefined} callback
2036
+ * @param {boolean|undefined} hasArrExpr
2037
+ * @param {boolean} [literalPriority]
2038
+ * @returns {ReturnObject|ReturnObject[]}
2039
+ */
2040
+ _trace(expr, val, path, parent, parentPropName, callback, hasArrExpr, literalPriority) {
2041
+ // No expr to follow? return path and value as the result of
2042
+ // this trace branch
2043
+ let retObj;
2044
+ if (!expr.length) {
2045
+ retObj = {
2046
+ path,
2047
+ value: val,
2048
+ parent,
2049
+ parentProperty: parentPropName,
2050
+ hasArrExpr
2051
+ };
2052
+ this._handleCallback(retObj, callback, 'value');
2053
+ return retObj;
2054
+ }
2055
+ const loc = /** @type {string} */expr[0],
2056
+ x = expr.slice(1);
2057
+
2058
+ // We need to gather the return value of recursive trace calls in order
2059
+ // to do the parent sel computation.
2060
+ /** @type {ReturnObject[]} */
2061
+ const ret = [];
2062
+ /**
2063
+ *
2064
+ * @param {ReturnObject|ReturnObject[]} elems
2065
+ * @returns {void}
2066
+ */
2067
+ function addRet(elems) {
2068
+ if (Array.isArray(elems)) {
2069
+ // This was causing excessive stack size in Node (with or
2070
+ // without Babel) against our performance test:
2071
+ // `ret.push(...elems);`
2072
+ elems.forEach(t => {
2073
+ ret.push(t);
2074
+ });
2075
+ } else {
2076
+ ret.push(elems);
1739
2077
  }
1740
- });
1741
- // The parent sel computation is handled in the frame above using the
1742
- // ancestor object of val
1743
- } else if (loc === '^') {
1744
- // This is not a final endpoint, so we do not invoke the callback here
1745
- this._hasParentSelector = true;
1746
- return {
1747
- path: path.slice(0, -1),
1748
- expr: x,
1749
- isParentSelector: true
1750
- };
1751
- } else if (loc === '~') {
1752
- // property name
1753
- retObj = {
1754
- path: push(path, loc),
1755
- value: parentPropName,
1756
- parent,
1757
- parentProperty: null
1758
- };
1759
- this._handleCallback(retObj, callback, 'property');
1760
- return retObj;
1761
- } else if (loc === '$') {
1762
- // root only
1763
- addRet(this._trace(x, val, path, null, null, callback, hasArrExpr));
1764
- } else if (/^(-?\d*):(-?\d*):?(\d*)$/u.test(loc)) {
1765
- // [start:end:step] Python slice syntax
1766
- addRet(this._slice(loc, x, val, path, parent, parentPropName, callback));
1767
- } else if (loc.indexOf('?(') === 0) {
1768
- // [?(expr)] (filtering)
1769
- if (this.currEval === false) {
1770
- throw new Error('Eval [?(expr)] prevented in JSONPath expression.');
1771
- }
1772
- const safeLoc = loc.replace(/^\?\((.*?)\)$/u, '$1');
1773
- // check for a nested filter expression
1774
- const nested = /@.?([^?]*)[['](\??\(.*?\))(?!.\)\])[\]']/gu.exec(safeLoc);
1775
- if (nested) {
1776
- // find if there are matches in the nested expression
1777
- // add them to the result set if there is at least one match
2078
+ }
2079
+ if (val && (typeof loc !== 'string' || literalPriority) && Object.hasOwn(val, /** @type {PropertyKey} */loc)) {
2080
+ // simple case--directly follow property
2081
+ const valObj = /** @type {Record<string, unknown>} */val;
2082
+ addRet(this._trace(x, valObj[(/** @type {string} */loc)], push(path, loc), val, /** @type {string|number} */loc, callback, hasArrExpr));
2083
+ // eslint-disable-next-line unicorn/prefer-switch -- Part of larger `if`
2084
+ } else if (loc === '*') {
2085
+ // all child properties
1778
2086
  this._walk(val, m => {
1779
- const npath = [nested[2]];
1780
- const nvalue = nested[1] ? val[m][nested[1]] : val[m];
1781
- const filterResults = this._trace(npath, nvalue, path, parent, parentPropName, callback, true);
1782
- if (filterResults.length > 0) {
1783
- addRet(this._trace(x, val[m], push(path, m), val, m, callback, true));
1784
- }
2087
+ const valObj = /** @type {Record<string, unknown>} */val;
2088
+ addRet(this._trace(x, valObj[m], push(path, m), val, m, callback, true, true));
1785
2089
  });
1786
- } else {
2090
+ } else if (loc === '..') {
2091
+ // all descendent parent properties
2092
+ // Check remaining expression with val's immediate children
2093
+ addRet(this._trace(x, val, path, parent, parentPropName, callback, hasArrExpr));
1787
2094
  this._walk(val, m => {
1788
- if (this._eval(safeLoc, val[m], m, path, parent, parentPropName)) {
1789
- addRet(this._trace(x, val[m], push(path, m), val, m, callback, true));
2095
+ // We don't join m and x here because we only want parents,
2096
+ // not scalar values
2097
+ const valObj = /** @type {Record<string, unknown>} */val;
2098
+ if (typeof valObj[m] === 'object') {
2099
+ // Keep going with recursive descent on val's
2100
+ // object children
2101
+ addRet(this._trace(expr.slice(), valObj[m], push(path, m), val, m, callback, true));
1790
2102
  }
1791
2103
  });
1792
- }
1793
- } else if (loc[0] === '(') {
1794
- // [(expr)] (dynamic property/index)
1795
- if (this.currEval === false) {
1796
- throw new Error('Eval [(expr)] prevented in JSONPath expression.');
1797
- }
1798
- // As this will resolve to a property name (but we don't know it
1799
- // yet), property and parent information is relative to the
1800
- // parent of the property to which this expression will resolve
1801
- addRet(this._trace(unshift(this._eval(loc, val, path.at(-1), path.slice(0, -1), parent, parentPropName), x), val, path, parent, parentPropName, callback, hasArrExpr));
1802
- } else if (loc[0] === '@') {
1803
- // value type: @boolean(), etc.
1804
- let addType = false;
1805
- const valueType = loc.slice(1, -2);
1806
- switch (valueType) {
1807
- case 'scalar':
1808
- if (!val || !['object', 'function'].includes(typeof val)) {
1809
- addType = true;
1810
- }
1811
- break;
1812
- case 'boolean':
1813
- case 'string':
1814
- case 'undefined':
1815
- case 'function':
1816
- if (typeof val === valueType) {
1817
- addType = true;
1818
- }
1819
- break;
1820
- case 'integer':
1821
- if (Number.isFinite(val) && !(val % 1)) {
1822
- addType = true;
1823
- }
1824
- break;
1825
- case 'number':
1826
- if (Number.isFinite(val)) {
1827
- addType = true;
1828
- }
1829
- break;
1830
- case 'nonFinite':
1831
- if (typeof val === 'number' && !Number.isFinite(val)) {
1832
- addType = true;
1833
- }
1834
- break;
1835
- case 'object':
1836
- if (val && typeof val === valueType) {
1837
- addType = true;
1838
- }
1839
- break;
1840
- case 'array':
1841
- if (Array.isArray(val)) {
1842
- addType = true;
1843
- }
1844
- break;
1845
- case 'other':
1846
- addType = this.currOtherTypeCallback(val, path, parent, parentPropName);
1847
- break;
1848
- case 'null':
1849
- if (val === null) {
1850
- addType = true;
1851
- }
1852
- break;
1853
- /* c8 ignore next 2 */
1854
- default:
1855
- throw new TypeError('Unknown value type ' + valueType);
1856
- }
1857
- if (addType) {
2104
+ // The parent sel computation is handled in the frame above using the
2105
+ // ancestor object of val
2106
+ } else if (loc === '^') {
2107
+ // This is not a final endpoint, so we do not invoke the
2108
+ // callback here
2109
+ this._hasParentSelector = true;
2110
+ return /** @type {ReturnObject} */{
2111
+ path: path.slice(0, -1),
2112
+ expr: x,
2113
+ isParentSelector: true,
2114
+ value: undefined,
2115
+ parent: undefined,
2116
+ parentProperty: null
2117
+ };
2118
+ } else if (loc === '~') {
2119
+ // property name
1858
2120
  retObj = {
1859
- path,
1860
- value: val,
2121
+ path: push(path, loc),
2122
+ value: parentPropName,
1861
2123
  parent,
1862
- parentProperty: parentPropName
2124
+ parentProperty: null
1863
2125
  };
1864
- this._handleCallback(retObj, callback, 'value');
2126
+ this._handleCallback(retObj, callback, 'property');
1865
2127
  return retObj;
2128
+ } else if (loc === '$') {
2129
+ // root only
2130
+ addRet(this._trace(x, val, path, null, null, callback, hasArrExpr));
2131
+ } else if (/^(-?\d*):(-?\d*):?(\d*)$/v.test(loc)) {
2132
+ // [start:end:step] Python slice syntax
2133
+ const sliceResult = this._slice(loc, x, val, path, parent, parentPropName, callback);
2134
+ if (sliceResult) {
2135
+ addRet(sliceResult);
2136
+ }
2137
+ } else if (loc.indexOf('?(') === 0) {
2138
+ // [?(expr)] (filtering)
2139
+ if (this.currEval === false) {
2140
+ throw new Error('Eval [?(expr)] prevented in JSONPath expression.');
2141
+ }
2142
+ const safeLoc = loc.replace(/^\?\((.*?)\)$/v, '$1');
2143
+ // check for a nested filter expression
2144
+
2145
+ const nested = /@.?([^?]*)[\['](\??\(.*?\))(?!.\)\])[\]']/gv.exec(safeLoc);
2146
+ if (nested) {
2147
+ // find if there are matches in the nested expression
2148
+ // add them to the result set if there is at least one match
2149
+ this._walk(val, m => {
2150
+ const npath = [nested[2]];
2151
+ const valObj2 = /** @type {Record<string, unknown>} */
2152
+ val;
2153
+ // guard against nested[1] resolving to `constructor`
2154
+ const nvalue = /** @type {ValueType} */nested[1] ? getSafeProperty(/** @type {Record<string, unknown>} */
2155
+ valObj2[m], nested[1]) : valObj2[m];
2156
+ const filterResults = this._trace(npath, nvalue, path, parent, parentPropName, callback, true);
2157
+ // eslint-disable-next-line @stylistic/max-len -- Long
2158
+ /* c8 ignore next 3 -- Unreachable: _trace always returns array for nested filters */
2159
+ const filterArray = Array.isArray(filterResults) ? filterResults : [filterResults];
2160
+ if (filterArray.length > 0) {
2161
+ addRet(this._trace(x, valObj2[m], push(path, m), val, m, callback, true));
2162
+ }
2163
+ });
2164
+ } else {
2165
+ const valObj3 = /** @type {Record<string, unknown>} */val;
2166
+ this._walk(val, m => {
2167
+ if (this._eval(safeLoc, valObj3[m], m, path, parent, parentPropName)) {
2168
+ addRet(this._trace(x, valObj3[m], push(path, m), val, m, callback, true));
2169
+ }
2170
+ });
2171
+ }
2172
+ } else if (loc[0] === '(') {
2173
+ // [(expr)] (dynamic property/index)
2174
+ if (this.currEval === false) {
2175
+ throw new Error('Eval [(expr)] prevented in JSONPath expression.');
2176
+ }
2177
+ // As this will resolve to a property name (but we don't know it
2178
+ // yet), property and parent information is relative to the
2179
+ const evalResult = this._eval(/** @type {string} */loc, val, /** @type {string|number} */path.at(-1), path.slice(0, -1), parent, parentPropName);
2180
+ const exprToUse = /** @type {string|number} */
2181
+ evalResult !== undefined ? evalResult : '';
2182
+ addRet(this._trace(unshift(exprToUse, x), val, path, parent, parentPropName, callback, hasArrExpr));
2183
+ } else if (loc[0] === '@') {
2184
+ // value type: @boolean(), etc.
2185
+ // eslint-disable-next-line no-useless-assignment -- ESLint bug
2186
+ let addType = false;
2187
+ const parenthIndex = loc.indexOf('(');
2188
+ const valueType = /** @type {ValueType|string} */loc.slice(1, parenthIndex);
2189
+ const lastParenth = loc.lastIndexOf(')');
2190
+ const argRaw = loc.slice(parenthIndex + 1, lastParenth);
2191
+ const arg = argRaw ? JSON.parse(argRaw) : undefined;
2192
+ switch (valueType) {
2193
+ case 'scalar':
2194
+ addType = !val || !['object', 'function'].includes(typeof val);
2195
+ break;
2196
+ case 'boolean':
2197
+ case 'string':
2198
+ case 'undefined':
2199
+ case 'function':
2200
+ addType = typeof val === valueType;
2201
+ break;
2202
+ case 'integer':
2203
+ addType = Number.isFinite(val) && !(/** @type {number} */val % 1);
2204
+ break;
2205
+ case 'number':
2206
+ addType = Number.isFinite(val);
2207
+ break;
2208
+ case 'nonFinite':
2209
+ addType = typeof val === 'number' && !Number.isFinite(val);
2210
+ break;
2211
+ case 'object':
2212
+ addType = Boolean(val) && typeof val === valueType;
2213
+ break;
2214
+ case 'array':
2215
+ addType = Array.isArray(val);
2216
+ break;
2217
+ case 'other':
2218
+ addType = /** @type {OtherTypeCallback} */this.currOtherTypeCallback(val, path, parent, parentPropName, arg) || false;
2219
+ break;
2220
+ case 'null':
2221
+ addType = val === null;
2222
+ break;
2223
+ case 'symbol':
2224
+ addType = typeof val === 'symbol';
2225
+ break;
2226
+ case 'BigInt':
2227
+ addType = typeof val === 'bigint';
2228
+ break;
2229
+ case 'Promise':
2230
+ addType = toStringTag(val) === 'Promise';
2231
+ break;
2232
+ case 'jsonReference':
2233
+ addType = Boolean(val) && typeof val === 'object' && Object.hasOwn(/** @type {object} */val, '$ref');
2234
+ break;
2235
+ default:
2236
+ if (this.currCustomTypes && Object.hasOwn(this.currCustomTypes, valueType)) {
2237
+ addType = this.currCustomTypes[valueType](val, path, parent, parentPropName, arg) || false;
2238
+ } else {
2239
+ throw new TypeError('Unknown value type ' + valueType);
2240
+ }
2241
+ }
2242
+ if (addType) {
2243
+ retObj = {
2244
+ path,
2245
+ value: val,
2246
+ parent,
2247
+ parentProperty: parentPropName
2248
+ };
2249
+ this._handleCallback(retObj, callback, 'value');
2250
+ return retObj;
2251
+ }
2252
+ // `-escaped property
2253
+ } else if (val && loc[0] === '`' && Object.hasOwn(val, loc.slice(1))) {
2254
+ const locProp = loc.slice(1);
2255
+ const valObj = /** @type {Record<string, unknown>} */val;
2256
+ addRet(this._trace(x, valObj[locProp], push(path, locProp), val, locProp, callback, hasArrExpr, true));
2257
+ } else if (loc[0] === "'" || loc[0] === '"') {
2258
+ // ['name1','name2',...]
2259
+ // Quoted members are literal property names
2260
+ for (const [, single, double] of loc.matchAll(/'((?:\\.|[^'\\])*)'|"((?:\\.|[^"\\])*)"/gv)) {
2261
+ const prop = (single ?? double).replaceAll(/\\(['"\\])/gv, '$1');
2262
+ if (!val || !Object.hasOwn(val, prop)) {
2263
+ continue;
2264
+ }
2265
+ const valObj = /** @type {Record<string, unknown>} */val;
2266
+ addRet(this._trace(x, valObj[prop], push(path, prop), val, prop, callback, true));
2267
+ }
2268
+ } else if (loc.includes(',')) {
2269
+ // [name1,name2,...]
2270
+ const parts = loc.split(',');
2271
+ for (const part of parts) {
2272
+ addRet(this._trace(unshift(part, x), val, path, parent, parentPropName, callback, true));
2273
+ }
2274
+ // simple case--directly follow property
2275
+ } else if (!literalPriority && val && Object.hasOwn(val, loc)) {
2276
+ const valObj = /** @type {Record<string, unknown>} */val;
2277
+ addRet(this._trace(x, valObj[loc], push(path, loc), val, loc, callback, hasArrExpr, true));
1866
2278
  }
1867
- // `-escaped property
1868
- } else if (loc[0] === '`' && val && Object.hasOwn(val, loc.slice(1))) {
1869
- const locProp = loc.slice(1);
1870
- addRet(this._trace(x, val[locProp], push(path, locProp), val, locProp, callback, hasArrExpr, true));
1871
- } else if (loc.includes(',')) {
1872
- // [name1,name2,...]
1873
- const parts = loc.split(',');
1874
- for (const part of parts) {
1875
- addRet(this._trace(unshift(part, x), val, path, parent, parentPropName, callback, true));
1876
- }
1877
- // simple case--directly follow property
1878
- } else if (!literalPriority && val && Object.hasOwn(val, loc)) {
1879
- addRet(this._trace(x, val[loc], push(path, loc), val, loc, callback, hasArrExpr, true));
1880
- }
1881
-
1882
- // We check the resulting values for parent selections. For parent
1883
- // selections we discard the value object and continue the trace with the
1884
- // current val object
1885
- if (this._hasParentSelector) {
1886
- for (let t = 0; t < ret.length; t++) {
1887
- const rett = ret[t];
1888
- if (rett && rett.isParentSelector) {
1889
- const tmp = this._trace(rett.expr, val, rett.path, parent, parentPropName, callback, hasArrExpr);
2279
+
2280
+ // We check the resulting values for parent selections. For parent
2281
+ // selections we discard the value object and continue the trace with
2282
+ // the current val object
2283
+ if (this._hasParentSelector) {
2284
+ for (let t = 0; t < ret.length; t++) {
2285
+ const rett = ret[t];
2286
+ if (!rett || !rett.isParentSelector) {
2287
+ continue;
2288
+ }
2289
+ const exprToUse = /** @type {ExpressionArray} */
2290
+ rett.expr;
2291
+ const pathToUse = /** @type {ExpressionArray} */
2292
+ rett.path;
2293
+ const tmp = this._trace(exprToUse, val, pathToUse, parent, parentPropName, callback, hasArrExpr);
1890
2294
  if (Array.isArray(tmp)) {
1891
2295
  ret[t] = tmp[0];
1892
2296
  const tl = tmp.length;
1893
2297
  for (let tt = 1; tt < tl; tt++) {
1894
- // eslint-disable-next-line @stylistic/max-len -- Long
1895
- // eslint-disable-next-line sonarjs/updated-loop-counter -- Convenient
1896
2298
  t++;
1897
2299
  ret.splice(t, 0, tmp[tt]);
1898
2300
  }
@@ -1901,91 +2303,184 @@ JSONPath.prototype._trace = function (expr, val, path, parent, parentPropName, c
1901
2303
  }
1902
2304
  }
1903
2305
  }
2306
+ return ret;
1904
2307
  }
1905
- return ret;
1906
- };
1907
- JSONPath.prototype._walk = function (val, f) {
1908
- if (Array.isArray(val)) {
1909
- const n = val.length;
1910
- for (let i = 0; i < n; i++) {
1911
- f(i);
1912
- }
1913
- } else if (val && typeof val === 'object') {
1914
- Object.keys(val).forEach(m => {
1915
- f(m);
1916
- });
2308
+
2309
+ /**
2310
+ * @param {unknown} val
2311
+ * @param {(prop: string|number) => void} f
2312
+ * @returns {void}
2313
+ */
2314
+ _walk(val, f) {
2315
+ if (Array.isArray(val)) {
2316
+ const n = val.length;
2317
+ for (let i = 0; i < n; i++) {
2318
+ f(i);
2319
+ }
2320
+ } else if (val && typeof val === 'object') {
2321
+ Object.keys(val).forEach(m => {
2322
+ f(m);
2323
+ });
2324
+ }
1917
2325
  }
1918
- };
1919
- JSONPath.prototype._slice = function (loc, expr, val, path, parent, parentPropName, callback) {
1920
- if (!Array.isArray(val)) {
1921
- return undefined;
1922
- }
1923
- const len = val.length,
1924
- parts = loc.split(':'),
1925
- step = parts[2] && Number.parseInt(parts[2]) || 1;
1926
- let start = parts[0] && Number.parseInt(parts[0]) || 0,
1927
- end = parts[1] && Number.parseInt(parts[1]) || len;
1928
- start = start < 0 ? Math.max(0, start + len) : Math.min(len, start);
1929
- end = end < 0 ? Math.max(0, end + len) : Math.min(len, end);
1930
- const ret = [];
1931
- for (let i = start; i < end; i += step) {
1932
- const tmp = this._trace(unshift(i, expr), val, path, parent, parentPropName, callback, true);
1933
- // Should only be possible to be an array here since first part of
1934
- // ``unshift(i, expr)` passed in above would not be empty, nor `~`,
1935
- // nor begin with `@` (as could return objects)
1936
- // This was causing excessive stack size in Node (with or
1937
- // without Babel) against our performance test: `ret.push(...tmp);`
1938
- tmp.forEach(t => {
1939
- ret.push(t);
1940
- });
2326
+
2327
+ /**
2328
+ * @param {string} loc
2329
+ * @param {ExpressionArray} expr
2330
+ * @param {unknown} val
2331
+ * @param {ExpressionArray} path
2332
+ * @param {ParentValue} parent
2333
+ * @param {ParentProperty} parentPropName
2334
+ * @param {JSONPathCallback|undefined} callback
2335
+ * @returns {ReturnObject[]|undefined}
2336
+ */
2337
+ _slice(loc, expr, val, path, parent, parentPropName, callback) {
2338
+ if (!Array.isArray(val)) {
2339
+ return undefined;
2340
+ }
2341
+ const len = val.length,
2342
+ parts = loc.split(':'),
2343
+ step = parts[2] && Number(parts[2]) || 1;
2344
+ let start = parts[0] && Number(parts[0]) || 0,
2345
+ end = parts[1] ? Number(parts[1]) : len;
2346
+ start = start < 0 ? Math.max(0, start + len) : Math.min(len, start);
2347
+ end = end < 0 ? Math.max(0, end + len) : Math.min(len, end);
2348
+ /** @type {ReturnObject[]} */
2349
+ const ret = [];
2350
+ for (let i = start; i < end; i += step) {
2351
+ const tmp = this._trace(unshift(i, expr), val, path, parent, parentPropName, callback, true);
2352
+ // Should only be possible to be an array here since first part of
2353
+ // ``unshift(i, expr)` passed in above would not be empty,
2354
+ // nor `~`, nor begin with `@` (as could return objects)
2355
+ // This was causing excessive stack size in Node (with or
2356
+ // without Babel) against our performance test: `ret.push(...tmp);`
2357
+ // eslint-disable-next-line @stylistic/max-len -- Long
2358
+ /* c8 ignore next -- Unreachable: _trace returns array when expr non-empty */
2359
+ const tmpArray = Array.isArray(tmp) ? tmp : [tmp];
2360
+ tmpArray.forEach(t => {
2361
+ ret.push(t);
2362
+ });
2363
+ }
2364
+ return ret;
1941
2365
  }
1942
- return ret;
1943
- };
1944
- JSONPath.prototype._eval = function (code, _v, _vname, path, parent, parentPropName) {
1945
- this.currSandbox._$_parentProperty = parentPropName;
1946
- this.currSandbox._$_parent = parent;
1947
- this.currSandbox._$_property = _vname;
1948
- this.currSandbox._$_root = this.json;
1949
- this.currSandbox._$_v = _v;
1950
- const containsPath = code.includes('@path');
1951
- if (containsPath) {
1952
- this.currSandbox._$_path = JSONPath.toPathString(path.concat([_vname]));
1953
- }
1954
- const scriptCacheKey = this.currEval + 'Script:' + code;
1955
- if (!JSONPath.cache[scriptCacheKey]) {
1956
- let script = code.replaceAll('@parentProperty', '_$_parentProperty').replaceAll('@parent', '_$_parent').replaceAll('@property', '_$_property').replaceAll('@root', '_$_root').replaceAll(/@([.\s)[])/gu, '_$_v$1');
2366
+
2367
+ /**
2368
+ * @param {string} code
2369
+ * @param {unknown} _v
2370
+ * @param {string|number} _vname
2371
+ * @param {ExpressionArray} path
2372
+ * @param {ParentValue} parent
2373
+ * @param {ParentProperty} parentPropName
2374
+ * @returns {UnknownResult}
2375
+ */
2376
+ _eval(code, _v, _vname, path, parent, parentPropName) {
2377
+ if (this.currSandbox) {
2378
+ this.currSandbox._$_parentProperty = parentPropName;
2379
+ this.currSandbox._$_parent = parent;
2380
+ this.currSandbox._$_property = _vname;
2381
+ this.currSandbox._$_root = this.json;
2382
+ this.currSandbox._$_v = _v;
2383
+ }
2384
+ const containsPath = code.includes('@path');
1957
2385
  if (containsPath) {
1958
- script = script.replaceAll('@path', '_$_path');
1959
- }
1960
- if (this.currEval === 'safe' || this.currEval === true || this.currEval === undefined) {
1961
- JSONPath.cache[scriptCacheKey] = new this.safeVm.Script(script);
1962
- } else if (this.currEval === 'native') {
1963
- JSONPath.cache[scriptCacheKey] = new this.vm.Script(script);
1964
- } else if (typeof this.currEval === 'function' && this.currEval.prototype && Object.hasOwn(this.currEval.prototype, 'runInNewContext')) {
1965
- const CurrEval = this.currEval;
1966
- JSONPath.cache[scriptCacheKey] = new CurrEval(script);
1967
- } else if (typeof this.currEval === 'function') {
1968
- JSONPath.cache[scriptCacheKey] = {
1969
- runInNewContext: context => this.currEval(script, context)
1970
- };
1971
- } else {
1972
- throw new TypeError(`Unknown "eval" property "${this.currEval}"`);
2386
+ // eslint-disable-next-line @stylistic/max-len -- Long
2387
+ /* c8 ignore next -- Unreachable: currSandbox set in evaluate() before _eval */
2388
+ const currSandbox = this.currSandbox ?? {};
2389
+ currSandbox._$_path = JSONPath.toPathString(/** @type {string[]} */path.concat([_vname]));
1973
2390
  }
1974
- }
1975
- try {
1976
- return JSONPath.cache[scriptCacheKey].runInNewContext(this.currSandbox);
1977
- } catch (e) {
1978
- if (this.ignoreEvalErrors) {
1979
- return false;
2391
+ const scriptCacheKey = this.currEval + 'Script:' + code;
2392
+ if (!scriptCache.has(scriptCacheKey)) {
2393
+ let script = code.replaceAll('@parentProperty', '_$_parentProperty').replaceAll('@parent', '_$_parent').replaceAll('@property', '_$_property').replaceAll('@root', '_$_root')
2394
+ // Replace a bare `@` (not followed by an identifier
2395
+ // character) while leaving quoted string literals and
2396
+ // regex literals (a `/` where an operand is expected,
2397
+ // as opposed to division) intact
2398
+ .replaceAll(/('(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"|(?<=(?:^|[\(,=:\[!\|?\{\};+\-*%<>~^]|&)\s*)\/(?:\\.|\[(?:\\.|[^\]\\])*\]|[^\/\\\[])+\/[dgimsuvy]*)|@(?![\w$])/gv, (_, literal) => literal ?? '_$_v');
2399
+ if (containsPath) {
2400
+ script = script.replaceAll('@path', '_$_path');
2401
+ }
2402
+ const evalType = /** @type {string|boolean|undefined} */
2403
+ this.currEval;
2404
+ if (['safe', true, undefined].includes(evalType)) {
2405
+ // eslint-disable-next-line @stylistic/max-len -- Long
2406
+ /* eslint-disable unicorn/no-undeclared-class-members -- Prototype members */
2407
+ scriptCache.set(scriptCacheKey, new (
2408
+ /**
2409
+ * @type {JSONPathClass & {
2410
+ * safeVm: SafeScriptType,
2411
+ * vm: ScriptType
2412
+ * }}
2413
+ */ /** @type {unknown} */
2414
+ this).safeVm.Script(script));
2415
+ // eslint-disable-next-line @stylistic/max-len -- Long
2416
+ /* eslint-enable unicorn/no-undeclared-class-members -- End prototype member scope */
2417
+ } else if (this.currEval === 'native') {
2418
+ // eslint-disable-next-line @stylistic/max-len -- Long
2419
+ /* eslint-disable unicorn/no-undeclared-class-members -- Prototype members */
2420
+ scriptCache.set(scriptCacheKey, new (
2421
+ /**
2422
+ * @type {JSONPathClass & {
2423
+ * safeVm: SafeScriptType,
2424
+ * vm: ScriptType
2425
+ * }}
2426
+ */ /** @type {unknown} */
2427
+ this).vm.Script(script));
2428
+ // eslint-disable-next-line @stylistic/max-len -- Long
2429
+ /* eslint-enable unicorn/no-undeclared-class-members -- End prototype member scope */
2430
+ } else if (typeof this.currEval === 'function' && this.currEval.prototype && Object.hasOwn(this.currEval.prototype, 'runInNewContext')) {
2431
+ const CurrEval = this.currEval;
2432
+ // eslint-disable-next-line @stylistic/max-len -- Long
2433
+ // @ts-expect-error - Type checked above to have proper constructor
2434
+ scriptCache.set(scriptCacheKey, new CurrEval(script));
2435
+ } else if (typeof this.currEval === 'function') {
2436
+ // Type narrowing: at this point currEval is a function
2437
+ // but not a constructor
2438
+ const evalFunc = /** @type {EvalCallback} */this.currEval;
2439
+ scriptCache.set(scriptCacheKey, {
2440
+ runInNewContext: (/** @type {ContextItem} */context) => evalFunc(script, context)
2441
+ });
2442
+ } else {
2443
+ throw new TypeError(`Unknown "eval" property "${this.currEval}"`);
2444
+ }
2445
+ }
2446
+ try {
2447
+ /**
2448
+ * @typedef {{
2449
+ * runInNewContext: (
2450
+ * ctx: SandboxType|undefined
2451
+ * ) => EvaluatedResult
2452
+ * }} RunInNewContext
2453
+ */
2454
+
2455
+ return /** @type {RunInNewContext} */scriptCache.get(scriptCacheKey).runInNewContext(this.currSandbox);
2456
+ } catch (e) {
2457
+ if (this.ignoreEvalErrors) {
2458
+ return false;
2459
+ }
2460
+ const error = /** @type {Error} */e;
2461
+ throw new Error('jsonPath: ' + error.message + ': ' + code, {
2462
+ cause: e
2463
+ });
1980
2464
  }
1981
- throw new Error('jsonPath: ' + e.message + ': ' + code);
1982
2465
  }
2466
+ }
2467
+
2468
+ /** @type {{safeVm: SafeScriptType}} */
2469
+ (/** @type {unknown} */JSONPathClass.prototype).safeVm = {
2470
+ Script: SafeScript
1983
2471
  };
2472
+ JSONPath.prototype = JSONPathClass.prototype;
1984
2473
 
1985
2474
  // PUBLIC CLASS PROPERTIES AND METHODS
1986
2475
 
1987
- // Could store the cache object itself
1988
- JSONPath.cache = {};
2476
+ /**
2477
+ * Clears cached parsed paths and compiled scripts.
2478
+ * @returns {void}
2479
+ */
2480
+ JSONPath.clearCache = function () {
2481
+ pathCache.clear();
2482
+ scriptCache.clear();
2483
+ };
1989
2484
 
1990
2485
  /**
1991
2486
  * @param {string[]} pathArr Array to convert
@@ -1996,15 +2491,15 @@ JSONPath.toPathString = function (pathArr) {
1996
2491
  n = x.length;
1997
2492
  let p = '$';
1998
2493
  for (let i = 1; i < n; i++) {
1999
- if (!/^(~|\^|@.*?\(\))$/u.test(x[i])) {
2000
- p += /^[0-9*]+$/u.test(x[i]) ? '[' + x[i] + ']' : "['" + x[i] + "']";
2494
+ if (!/^(~|\^|@.*?\(\))$/v.test(x[i])) {
2495
+ p += /^[0-9*]+$/v.test(x[i]) ? '[' + x[i] + ']' : "['" + String(x[i]).replaceAll(/['\\]/gv, String.raw`\$&`) + "']";
2001
2496
  }
2002
2497
  }
2003
2498
  return p;
2004
2499
  };
2005
2500
 
2006
2501
  /**
2007
- * @param {string} pointer JSON Path
2502
+ * @param {string[]} pointer JSON Path array
2008
2503
  * @returns {string} JSON Pointer
2009
2504
  */
2010
2505
  JSONPath.toPointer = function (pointer) {
@@ -2012,7 +2507,7 @@ JSONPath.toPointer = function (pointer) {
2012
2507
  n = x.length;
2013
2508
  let p = '';
2014
2509
  for (let i = 1; i < n; i++) {
2015
- if (!/^(~|\^|@.*?\(\))$/u.test(x[i])) {
2510
+ if (!/^(~|\^|@.*?\(\))$/v.test(x[i])) {
2016
2511
  p += '/' + x[i].toString().replaceAll('~', '~0').replaceAll('/', '~1');
2017
2512
  }
2018
2513
  }
@@ -2024,71 +2519,144 @@ JSONPath.toPointer = function (pointer) {
2024
2519
  * @returns {string[]}
2025
2520
  */
2026
2521
  JSONPath.toPathArray = function (expr) {
2027
- const {
2028
- cache
2029
- } = JSONPath;
2030
- if (cache[expr]) {
2031
- return cache[expr].concat();
2522
+ if (pathCache.has(expr)) {
2523
+ return /** @type {string[]} */pathCache.get(expr).concat();
2032
2524
  }
2525
+ /** @type {string[]} */
2033
2526
  const subx = [];
2034
2527
  const normalized = expr
2035
2528
  // Properties
2036
- .replaceAll(/@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\(\)/gu, ';$&;')
2529
+ .replaceAll(/@[\w$\-]+\([^\)]*\)/gv, ';$&;')
2037
2530
  // Parenthetical evaluations (filtering and otherwise), directly
2038
2531
  // within brackets or single quotes
2039
- .replaceAll(/[['](\??\(.*?\))[\]'](?!.\])/gu, function ($0, $1) {
2532
+ .replaceAll(/[\['](\??\(.*?\))[\]'](?!.\])/gv, function ($0, $1) {
2533
+ return '[#' + (
2534
+ // eslint-disable-next-line @stylistic/max-len -- Long
2535
+ // eslint-disable-next-line unicorn/no-return-array-push -- Optimization
2536
+ subx.push($1) - 1) + ']';
2537
+ })
2538
+ // Unions of quoted property names (e.g., `['x','y']`) and quoted
2539
+ // names with commas or backslash escapes, kept intact (with
2540
+ // quotes) so their members survive tokenization
2541
+ .replaceAll(/\[\s*((?:'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*")(?:\s*,\s*(?:'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"))*)\s*\]/gv, function ($0, $1) {
2542
+ if (!/[,\\]/v.test($1)) {
2543
+ return $0;
2544
+ }
2545
+ // eslint-disable-next-line @stylistic/max-len -- Long
2546
+ // eslint-disable-next-line unicorn/no-return-array-push -- Optimization
2040
2547
  return '[#' + (subx.push($1) - 1) + ']';
2041
2548
  })
2042
2549
  // Escape periods and tildes within properties
2043
- .replaceAll(/\[['"]([^'\]]*)['"]\]/gu, function ($0, prop) {
2550
+ .replaceAll(/\[['"]([^'\]]*)['"]\]/gv, function ($0, prop) {
2044
2551
  return "['" + prop.replaceAll('.', '%@%').replaceAll('~', '%%@@%%') + "']";
2045
2552
  })
2046
2553
  // Properties operator
2047
2554
  .replaceAll('~', ';~;')
2048
2555
  // Split by property boundaries
2049
- .replaceAll(/['"]?\.['"]?(?![^[]*\])|\[['"]?/gu, ';')
2556
+ .replaceAll(/['"]?\.['"]?(?![^\[]*\])|\[['"]?/gv, ';')
2050
2557
  // Reinsert periods within properties
2051
2558
  .replaceAll('%@%', '.')
2052
2559
  // Reinsert tildes within properties
2053
2560
  .replaceAll('%%@@%%', '~')
2054
2561
  // Parent
2055
- .replaceAll(/(?:;)?(\^+)(?:;)?/gu, function ($0, ups) {
2562
+ .replaceAll(/(?:;)?(\^+)(?:;)?/gv, function ($0, ups) {
2056
2563
  return ';' + ups.split('').join(';') + ';';
2057
2564
  })
2058
2565
  // Descendents
2059
- .replaceAll(/;;;|;;/gu, ';..;')
2566
+ .replaceAll(/;;;|;;/gv, ';..;')
2060
2567
  // Remove trailing
2061
- .replaceAll(/;$|'?\]|'$/gu, '');
2568
+ .replaceAll(/;$|'?\]|'$/gv, '');
2062
2569
  const exprList = normalized.split(';').map(function (exp) {
2063
- const match = exp.match(/#(\d+)/u);
2064
- return !match || !match[1] ? exp : subx[match[1]];
2570
+ const match = exp.match(/#(\d+)/v);
2571
+ return !match || !match[1] ? exp : subx[Number(match[1])];
2065
2572
  });
2066
- cache[expr] = exprList;
2067
- return cache[expr].concat();
2068
- };
2069
- JSONPath.prototype.safeVm = {
2070
- Script: SafeScript
2573
+ pathCache.set(expr, exprList);
2574
+ return /** @type {string[]} */pathCache.get(expr).concat();
2071
2575
  };
2072
2576
 
2073
2577
  /**
2074
- * @typedef {any} ContextItem
2578
+ * @typedef {import('./jsonpath.js').AnyInput} AnyInput
2075
2579
  */
2076
-
2077
2580
  /**
2078
- * @typedef {any} EvaluatedResult
2581
+ * @typedef {import('./jsonpath.js').SandboxCallback} SandboxCallback
2582
+ */
2583
+ /**
2584
+ * @typedef {import('./jsonpath.js').SandboxPropertyValue} SandboxPropertyValue
2585
+ */
2586
+ /**
2587
+ * @typedef {import('./jsonpath.js').ExpressionArray} ExpressionArray
2588
+ */
2589
+ /**
2590
+ * @typedef {import('./jsonpath.js').ValueType} ValueType
2591
+ */
2592
+ /**
2593
+ * @typedef {import('./jsonpath.js').ParentValue} ParentValue
2594
+ */
2595
+ /**
2596
+ * @typedef {import('./jsonpath.js').UnknownResult} UnknownResult
2597
+ */
2598
+ /**
2599
+ * @typedef {import('./jsonpath.js').ParentProperty} ParentProperty
2600
+ */
2601
+ /**
2602
+ * @typedef {import('./jsonpath.js').PreferredOutput} PreferredOutput
2603
+ */
2604
+ /**
2605
+ * @typedef {import('./jsonpath.js').ReturnObject} ReturnObject
2606
+ */
2607
+ /**
2608
+ * @typedef {import('./jsonpath.js').JSONPathCallback} JSONPathCallback
2609
+ */
2610
+ /**
2611
+ * @typedef {import('./jsonpath.js').OtherTypeCallback} OtherTypeCallback
2612
+ */
2613
+ /**
2614
+ * @typedef {import('./jsonpath.js').ContextItem} ContextItem
2615
+ */
2616
+ /**
2617
+ * @typedef {import('./jsonpath.js').EvaluatedResult} EvaluatedResult
2618
+ */
2619
+ /**
2620
+ * @typedef {import('./jsonpath.js').EvalCallback} EvalCallback
2621
+ */
2622
+ /**
2623
+ * @typedef {import('./jsonpath.js').EvalClass} EvalClass
2624
+ */
2625
+ /**
2626
+ * @typedef {import('./jsonpath.js').ResultType} ResultType
2627
+ */
2628
+ /**
2629
+ * @typedef {import('./jsonpath.js').EvalValue} EvalValue
2630
+ */
2631
+ /**
2632
+ * @typedef {import('./jsonpath.js').PathType} PathType
2633
+ */
2634
+ /**
2635
+ * @typedef {import('./jsonpath.js').SafeScriptType} SafeScriptType
2636
+ */
2637
+ /**
2638
+ * @typedef {import('./jsonpath.js').ScriptType} ScriptType
2639
+ */
2640
+ /**
2641
+ * @typedef {import('./jsonpath.js').SandboxType} SandboxType
2642
+ */
2643
+ /**
2644
+ * @typedef {import('./jsonpath.js').JSONPathOptions} JSONPathOptions
2079
2645
  */
2080
2646
 
2081
2647
  /**
2648
+ * @template T
2082
2649
  * @callback ConditionCallback
2083
- * @param {ContextItem} item
2650
+ * @param {T} item
2084
2651
  * @returns {boolean}
2085
2652
  */
2086
2653
 
2087
2654
  /**
2088
2655
  * Copy items out of one array into another.
2089
- * @param {GenericArray} source Array with items to copy
2090
- * @param {GenericArray} target Array to which to copy
2091
- * @param {ConditionCallback} conditionCb Callback passed the current item;
2656
+ * @template T
2657
+ * @param {T[]} source Array with items to copy
2658
+ * @param {T[]} target Array to which to copy
2659
+ * @param {ConditionCallback<T>} conditionCb Callback passed the current item;
2092
2660
  * will move item if evaluates to `true`
2093
2661
  * @returns {void}
2094
2662
  */
@@ -2097,8 +2665,6 @@ const moveToAnotherArray = function (source, target, conditionCb) {
2097
2665
  for (let i = 0; i < il; i++) {
2098
2666
  const item = source[i];
2099
2667
  if (conditionCb(item)) {
2100
- // eslint-disable-next-line @stylistic/max-len -- Long
2101
- // eslint-disable-next-line sonarjs/updated-loop-counter -- Convenient
2102
2668
  target.push(source.splice(i--, 1)[0]);
2103
2669
  }
2104
2670
  }
@@ -2116,14 +2682,14 @@ class Script {
2116
2682
  }
2117
2683
 
2118
2684
  /**
2119
- * @param {object} context Object whose items will be added
2685
+ * @param {SandboxType} context Object whose items will be added
2120
2686
  * to evaluation
2121
2687
  * @returns {EvaluatedResult} Result of evaluated code
2122
2688
  */
2123
2689
  runInNewContext(context) {
2124
2690
  let expr = this.code;
2125
2691
  const keys = Object.keys(context);
2126
- const funcs = [];
2692
+ const funcs = /** @type {string[]} */[];
2127
2693
  moveToAnotherArray(keys, funcs, key => {
2128
2694
  return typeof context[key] === 'function';
2129
2695
  });
@@ -2132,22 +2698,22 @@ class Script {
2132
2698
  });
2133
2699
  const funcString = funcs.reduce((s, func) => {
2134
2700
  let fString = context[func].toString();
2135
- if (!/function/u.test(fString)) {
2701
+ if (!/function/v.test(fString)) {
2136
2702
  fString = 'function ' + fString;
2137
2703
  }
2138
2704
  return 'var ' + func + '=' + fString + ';' + s;
2139
2705
  }, '');
2140
2706
  expr = funcString + expr;
2141
2707
 
2142
- // Mitigate http://perfectionkills.com/global-eval-what-are-the-options/#new_function
2143
- if (!/(['"])use strict\1/u.test(expr) && !keys.includes('arguments')) {
2708
+ // Mitigate https://perfectionkills.com/global-eval-what-are-the-options/#new_function
2709
+ if (!/(['"])use strict\1/v.test(expr) && !keys.includes('arguments')) {
2144
2710
  expr = 'var arguments = undefined;' + expr;
2145
2711
  }
2146
2712
 
2147
2713
  // Remove last semi so `return` will be inserted before
2148
2714
  // the previous one instead, allowing for the return
2149
2715
  // of a bare ending expression
2150
- expr = expr.replace(/;\s*$/u, '');
2716
+ expr = expr.replace(/;\s*$/v, '');
2151
2717
 
2152
2718
  // Insert `return`
2153
2719
  const lastStatementEnd = expr.lastIndexOf(';');
@@ -2157,8 +2723,10 @@ class Script {
2157
2723
  return new Function(...keys, code)(...values);
2158
2724
  }
2159
2725
  }
2160
- JSONPath.prototype.vm = {
2726
+
2727
+ /** @type {{vm: ScriptType}} */
2728
+ (/** @type {unknown} */JSONPathClass.prototype).vm = {
2161
2729
  Script
2162
2730
  };
2163
2731
 
2164
- export { JSONPath };
2732
+ export { JSONPath, JSONPathClass, Script };