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