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.
- package/.github/workflows/test.yml +17 -0
- package/CHANGES.md +5 -1
- package/demo/codemirror.esm.js +1 -1
- package/demo/vendor/jsonpath-plus/dist/index-browser-esm.js +1124 -604
- package/package.json +9 -9
- package/pnpm-workspace.yaml +31 -29
|
@@ -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 {
|
|
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
|
|
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',
|
|
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
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
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(
|
|
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
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1346
|
-
|
|
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 {
|
|
1537
|
+
* @typedef {unknown} UnknownResult
|
|
1388
1538
|
*/
|
|
1389
1539
|
|
|
1390
1540
|
/**
|
|
1391
|
-
* @typedef {
|
|
1541
|
+
* @typedef {string|number|null} ParentProperty
|
|
1392
1542
|
*/
|
|
1393
1543
|
|
|
1394
1544
|
/**
|
|
1395
|
-
* @typedef {
|
|
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 {
|
|
1401
|
-
* @param {
|
|
1402
|
-
* @returns {
|
|
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 {
|
|
1412
|
-
* @param {
|
|
1413
|
-
* @returns {
|
|
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
|
-
*
|
|
1423
|
-
* @
|
|
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
|
-
* @
|
|
1439
|
-
* @
|
|
1440
|
-
*
|
|
1441
|
-
* @
|
|
1442
|
-
* @
|
|
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 {
|
|
1456
|
-
* @param {
|
|
1457
|
-
* @param {
|
|
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 {
|
|
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 {
|
|
1484
|
-
* @property {
|
|
1485
|
-
* @property {"value"
|
|
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 {
|
|
1490
|
-
* @property {
|
|
1491
|
-
*
|
|
1492
|
-
* @property {
|
|
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
|
-
* @
|
|
1502
|
-
* @param {string}
|
|
1503
|
-
* @param {
|
|
1504
|
-
* @param {JSONPathCallback} [
|
|
1505
|
-
* per `resultType`, 2) `"value"|"property"`, 3) Full returned
|
|
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
|
-
* @
|
|
1513
|
-
* @
|
|
1723
|
+
* @throws {Error}
|
|
1724
|
+
* @returns {unknown|JSONPathClass}
|
|
1514
1725
|
*/
|
|
1515
1726
|
function JSONPath(opts, expr, obj, callback, otherTypeCallback) {
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
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
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
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
|
-
|
|
1910
|
+
json ||= this.json;
|
|
1911
|
+
expr ||= this.path;
|
|
1631
1912
|
}
|
|
1632
|
-
|
|
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
|
-
//
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
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
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
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
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
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 {
|
|
1992
|
+
* @param {ReturnObject} fullRetObj
|
|
1993
|
+
* @param {JSONPathCallback|undefined} callback
|
|
1994
|
+
* @param {"value"|"property"} type
|
|
1705
1995
|
* @returns {void}
|
|
1706
1996
|
*/
|
|
1707
|
-
|
|
1708
|
-
if (
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
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
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
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
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
|
1780
|
-
|
|
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
|
-
|
|
1789
|
-
|
|
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
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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:
|
|
2103
|
+
path: push(path, loc),
|
|
2104
|
+
value: parentPropName,
|
|
1861
2105
|
parent,
|
|
1862
|
-
parentProperty:
|
|
2106
|
+
parentProperty: null
|
|
1863
2107
|
};
|
|
1864
|
-
this._handleCallback(retObj, callback, '
|
|
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
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
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
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
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
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
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
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
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
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
JSONPath.
|
|
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
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
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
|
-
|
|
1988
|
-
|
|
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 (!/^(~|\^|@.*?\(\))$/
|
|
2000
|
-
p += /^[0-9*]+$/
|
|
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 (!/^(~|\^|@.*?\(\))$/
|
|
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
|
-
|
|
2028
|
-
|
|
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(/@
|
|
2492
|
+
.replaceAll(/@[\w$\-]+\(\)/gv, ';$&;')
|
|
2037
2493
|
// Parenthetical evaluations (filtering and otherwise), directly
|
|
2038
2494
|
// within brackets or single quotes
|
|
2039
|
-
.replaceAll(/[['](\??\(.*?\))[\]'](?!.\])/
|
|
2040
|
-
return '[#' + (
|
|
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(/\[['"]([^'\]]*)['"]\]/
|
|
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(/['"]?\.['"]?(?![
|
|
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(/(?:;)?(\^+)(?:;)?/
|
|
2514
|
+
.replaceAll(/(?:;)?(\^+)(?:;)?/gv, function ($0, ups) {
|
|
2056
2515
|
return ';' + ups.split('').join(';') + ';';
|
|
2057
2516
|
})
|
|
2058
2517
|
// Descendents
|
|
2059
|
-
.replaceAll(/;;;|;;/
|
|
2518
|
+
.replaceAll(/;;;|;;/gv, ';..;')
|
|
2060
2519
|
// Remove trailing
|
|
2061
|
-
.replaceAll(/;$|'?\]|'$/
|
|
2520
|
+
.replaceAll(/;$|'?\]|'$/gv, '');
|
|
2062
2521
|
const exprList = normalized.split(';').map(function (exp) {
|
|
2063
|
-
const match = exp.match(/#(\d+)/
|
|
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
|
-
|
|
2067
|
-
return
|
|
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 {
|
|
2530
|
+
* @typedef {import('./jsonpath.js').AnyInput} AnyInput
|
|
2075
2531
|
*/
|
|
2076
|
-
|
|
2077
2532
|
/**
|
|
2078
|
-
* @typedef {
|
|
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 {
|
|
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
|
-
* @
|
|
2090
|
-
* @param {
|
|
2091
|
-
* @param {
|
|
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 {
|
|
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/
|
|
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
|
|
2143
|
-
if (!/(['"])use strict\1/
|
|
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*$/
|
|
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
|
-
|
|
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 };
|