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