@jsenv/test 1.6.9 → 1.7.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.
@@ -2,795 +2,142 @@ import { writeFileSync } from "node:fs";
2
2
  import { Session } from "node:inspector";
3
3
  import { PerformanceObserver, performance } from "node:perf_hooks";
4
4
 
5
- // https://developer.mozilla.org/en-US/docs/Glossary/Primitive
6
-
7
- const isComposite = (value) => {
8
- if (value === null) {
9
- return false
10
- }
11
-
12
- const type = typeof value;
13
- if (type === "object") {
14
- return true
15
- }
16
-
17
- if (type === "function") {
18
- return true
19
- }
20
-
21
- return false
22
- };
23
-
24
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
25
- const compositeWellKnownMap = new WeakMap();
26
- const primitiveWellKnownMap = new Map();
27
-
28
- const getCompositeGlobalPath = (value) => compositeWellKnownMap.get(value);
29
-
30
- const getPrimitiveGlobalPath = (value) => primitiveWellKnownMap.get(value);
31
-
32
- const visitGlobalObject = (value) => {
33
- const visitValue = (value, path) => {
34
- if (isComposite(value)) {
35
- // prevent infinite recursion
36
- if (compositeWellKnownMap.has(value)) {
37
- return
38
- }
39
- compositeWellKnownMap.set(value, path);
40
-
41
- const visitProperty = (property) => {
42
- let descriptor;
43
- try {
44
- descriptor = Object.getOwnPropertyDescriptor(value, property);
45
- } catch (e) {
46
- if (e.name === "SecurityError") {
47
- return
48
- }
49
- throw e
50
- }
51
-
52
- if (!descriptor) {
53
- // it's apparently possible to have getOwnPropertyNames returning
54
- // a property that later returns a null descriptor
55
- // for instance window.showModalDialog in webkit 13.0
56
- return
57
- }
58
-
59
- // do not trigger getter/setter
60
- if ("value" in descriptor) {
61
- const propertyValue = descriptor.value;
62
- visitValue(propertyValue, [...path, property]);
63
- }
64
- };
65
-
66
- Object.getOwnPropertyNames(value).forEach((name) => visitProperty(name));
67
- Object.getOwnPropertySymbols(value).forEach((symbol) => visitProperty(symbol));
68
- }
69
-
70
- primitiveWellKnownMap.set(value, path);
71
- return
72
- };
73
-
74
- visitValue(value, []);
75
- };
76
-
77
- if (typeof window === "object") visitGlobalObject(window);
78
-
79
- if (typeof global === "object") visitGlobalObject(global);
80
-
81
- /**
82
- * transforms a javascript value into an object describing it.
5
+ /*
6
+ * Exception are objects used to wrap a value that is thrown
7
+ * Usually they wrap error but the value can be anything as
8
+ * throw "toto" can be used in JS
83
9
  *
10
+ * - provide a common API to interact with value that can be thrown
11
+ * - enrich usual errors with a bit more information (line, column)
12
+ * - normalize error properties
13
+ * - exception.stackTrace: only the stack trace as string (no error.name or error.message)
14
+ * - error.stack: error.name + error.message + error.stackTrace
15
+ *
16
+ * It is used mostly internally by jsenv but can also be found
17
+ * value returned by "executeTestPlan" public export (for failed executions)
18
+ * and value returned by "execute" public export (when execution fails)
19
+ *
20
+ * This file is responsible to wrap error hapenning in Node.js runtime
21
+ * The browser part can be found in "supervisor.js"
84
22
  */
85
23
 
86
-
87
- const decompose = (mainValue, { functionAllowed, prototypeStrict, ignoreSymbols }) => {
88
- const valueMap = {};
89
- const recipeArray = [];
90
-
91
- const valueToIdentifier = (value, path = []) => {
92
- if (!isComposite(value)) {
93
- const existingIdentifier = identifierForPrimitive(value);
94
- if (existingIdentifier !== undefined) {
95
- return existingIdentifier
96
- }
97
- const identifier = identifierForNewValue(value);
98
- recipeArray[identifier] = primitiveToRecipe(value);
99
- return identifier
100
- }
101
-
102
- if (typeof Promise === "function" && value instanceof Promise) {
103
- throw new Error(createPromiseAreNotSupportedMessage({ path }))
104
- }
105
- if (typeof WeakSet === "function" && value instanceof WeakSet) {
106
- throw new Error(createWeakSetAreNotSupportedMessage({ path }))
107
- }
108
- if (typeof WeakMap === "function" && value instanceof WeakMap) {
109
- throw new Error(createWeakMapAreNotSupportedMessage({ path }))
110
- }
111
- if (typeof value === "function" && !functionAllowed) {
112
- throw new Error(createForbiddenFunctionMessage({ path }))
113
- }
114
-
115
- const existingIdentifier = identifierForComposite(value);
116
- if (existingIdentifier !== undefined) {
117
- return existingIdentifier
118
- }
119
- const identifier = identifierForNewValue(value);
120
-
121
- const compositeGlobalPath = getCompositeGlobalPath(value);
122
- if (compositeGlobalPath) {
123
- recipeArray[identifier] = createGlobalReferenceRecipe(compositeGlobalPath);
124
- return identifier
125
- }
126
-
127
- const propertyDescriptionArray = [];
128
- Object.getOwnPropertyNames(value).forEach((propertyName) => {
129
- const propertyDescriptor = Object.getOwnPropertyDescriptor(value, propertyName);
130
- const propertyNameIdentifier = valueToIdentifier(propertyName, [...path, propertyName]);
131
- const propertyDescription = computePropertyDescription(propertyDescriptor, propertyName, path);
132
- propertyDescriptionArray.push({ propertyNameIdentifier, propertyDescription });
133
- });
134
-
135
- const symbolDescriptionArray = [];
136
- if (!ignoreSymbols) {
137
- Object.getOwnPropertySymbols(value).forEach((symbol) => {
138
- const propertyDescriptor = Object.getOwnPropertyDescriptor(value, symbol);
139
- const symbolIdentifier = valueToIdentifier(symbol, [...path, `[${symbol.toString()}]`]);
140
- const propertyDescription = computePropertyDescription(propertyDescriptor, symbol, path);
141
- symbolDescriptionArray.push({ symbolIdentifier, propertyDescription });
142
- });
143
- }
144
-
145
- const methodDescriptionArray = computeMethodDescriptionArray(value, path);
146
-
147
- const extensible = Object.isExtensible(value);
148
-
149
- recipeArray[identifier] = createCompositeRecipe({
150
- propertyDescriptionArray,
151
- symbolDescriptionArray,
152
- methodDescriptionArray,
153
- extensible,
154
- });
155
- return identifier
156
- };
157
-
158
- const computePropertyDescription = (propertyDescriptor, propertyNameOrSymbol, path) => {
159
- if (propertyDescriptor.set && !functionAllowed) {
160
- throw new Error(createForbiddenPropertySetterMessage({ path, propertyNameOrSymbol }))
161
- }
162
- if (propertyDescriptor.get && !functionAllowed) {
163
- throw new Error(createForbiddenPropertyGetterMessage({ path, propertyNameOrSymbol }))
164
- }
165
-
166
- return {
167
- configurable: propertyDescriptor.configurable,
168
- writable: propertyDescriptor.writable,
169
- enumerable: propertyDescriptor.enumerable,
170
- getIdentifier:
171
- "get" in propertyDescriptor
172
- ? valueToIdentifier(propertyDescriptor.get, [
173
- ...path,
174
- String(propertyNameOrSymbol),
175
- "[[descriptor:get]]",
176
- ])
177
- : undefined,
178
- setIdentifier:
179
- "set" in propertyDescriptor
180
- ? valueToIdentifier(propertyDescriptor.set, [
181
- ...path,
182
- String(propertyNameOrSymbol),
183
- "[[descriptor:set]]",
184
- ])
185
- : undefined,
186
- valueIdentifier:
187
- "value" in propertyDescriptor
188
- ? valueToIdentifier(propertyDescriptor.value, [
189
- ...path,
190
- String(propertyNameOrSymbol),
191
- "[[descriptor:value]]",
192
- ])
193
- : undefined,
194
- }
24
+ const createException = (reason) => {
25
+ const exception = {
26
+ isException: true,
27
+ isError: false,
28
+ name: "",
29
+ message: "",
30
+ stack: "",
31
+ stackTrace: "",
32
+ site: null,
195
33
  };
196
34
 
197
- const computeMethodDescriptionArray = (value, path) => {
198
- const methodDescriptionArray = [];
199
-
200
- if (typeof Set === "function" && value instanceof Set) {
201
- const callArray = [];
202
- value.forEach((entryValue, index) => {
203
- const entryValueIdentifier = valueToIdentifier(entryValue, [
204
- ...path,
205
- `[[SetEntryValue]]`,
206
- index,
207
- ]);
208
- callArray.push([entryValueIdentifier]);
209
- });
210
- methodDescriptionArray.push({ methodNameIdentifier: valueToIdentifier("add"), callArray });
211
- }
212
-
213
- if (typeof Map === "function" && value instanceof Map) {
214
- const callArray = [];
215
- value.forEach((entryValue, entryKey) => {
216
- const entryKeyIdentifier = valueToIdentifier(entryKey, [
217
- ...path,
218
- "[[MapEntryKey]]",
219
- entryKey,
220
- ]);
221
- const entryValueIdentifier = valueToIdentifier(entryValue, [
222
- ...path,
223
- "[[MapEntryValue]]",
224
- entryValue,
225
- ]);
226
- callArray.push([entryKeyIdentifier, entryValueIdentifier]);
227
- });
228
- methodDescriptionArray.push({ methodNameIdentifier: valueToIdentifier("set"), callArray });
229
- }
230
-
231
- return methodDescriptionArray
232
- };
233
-
234
- const identifierForPrimitive = (value) => {
235
- return Object.keys(valueMap).find((existingIdentifier) => {
236
- const existingValue = valueMap[existingIdentifier];
237
- if (Object.is(value, existingValue)) return true
238
- return value === existingValue
239
- })
240
- };
241
-
242
- const identifierForComposite = (value) => {
243
- return Object.keys(valueMap).find((existingIdentifier) => {
244
- const existingValue = valueMap[existingIdentifier];
245
- return value === existingValue
246
- })
247
- };
248
-
249
- const identifierForNewValue = (value) => {
250
- const identifier = nextIdentifier();
251
- valueMap[identifier] = value;
252
- return identifier
253
- };
254
-
255
- let currentIdentifier = -1;
256
- const nextIdentifier = () => {
257
- const identifier = String(parseInt(currentIdentifier) + 1);
258
- currentIdentifier = identifier;
259
- return identifier
260
- };
261
-
262
- const mainIdentifier = valueToIdentifier(mainValue);
263
-
264
- // prototype, important to keep after the whole structure was visited
265
- // so that we discover if any prototype is part of the value
266
- const prototypeValueToIdentifier = (prototypeValue) => {
267
- // prototype is null
268
- if (prototypeValue === null) {
269
- return valueToIdentifier(prototypeValue)
270
- }
271
-
272
- // prototype found somewhere already
273
- const prototypeExistingIdentifier = identifierForComposite(prototypeValue);
274
- if (prototypeExistingIdentifier !== undefined) {
275
- return prototypeExistingIdentifier
276
- }
277
-
278
- // mark prototype as visited
279
- const prototypeIdentifier = identifierForNewValue(prototypeValue);
280
-
281
- // prototype is a global reference ?
282
- const prototypeGlobalPath = getCompositeGlobalPath(prototypeValue);
283
- if (prototypeGlobalPath) {
284
- recipeArray[prototypeIdentifier] = createGlobalReferenceRecipe(prototypeGlobalPath);
285
- return prototypeIdentifier
286
- }
287
-
288
- // otherwise prototype is unknown
289
- if (prototypeStrict) {
290
- throw new Error(createUnknownPrototypeMessage({ prototypeValue }))
291
- }
292
-
293
- return prototypeValueToIdentifier(Object.getPrototypeOf(prototypeValue))
294
- };
295
- const identifierForValueOf = (value, path = []) => {
296
- if (value instanceof Array) {
297
- return valueToIdentifier(value.length, [...path, "length"])
298
- }
299
-
300
- if ("valueOf" in value === false) {
301
- return undefined
302
- }
303
-
304
- if (typeof value.valueOf !== "function") {
305
- return undefined
306
- }
307
-
308
- const valueOfReturnValue = value.valueOf();
309
- if (!isComposite(valueOfReturnValue)) {
310
- return valueToIdentifier(valueOfReturnValue, [...path, "valueOf()"])
311
- }
312
-
313
- if (valueOfReturnValue === value) {
314
- return undefined
315
- }
316
-
317
- throw new Error(createUnexpectedValueOfReturnValueMessage())
318
- };
319
-
320
- recipeArray.slice().forEach((recipe, index) => {
321
- if (recipe.type === "composite") {
322
- const value = valueMap[index];
323
-
324
- if (typeof value === "function") {
325
- const valueOfIdentifier = nextIdentifier();
326
- recipeArray[valueOfIdentifier] = {
327
- type: "primitive",
328
- value,
329
- };
330
- recipe.valueOfIdentifier = valueOfIdentifier;
331
- return
332
- }
333
-
334
- if (value instanceof RegExp) {
335
- const valueOfIdentifier = nextIdentifier();
336
- recipeArray[valueOfIdentifier] = {
337
- type: "primitive",
338
- value,
339
- };
340
- recipe.valueOfIdentifier = valueOfIdentifier;
341
- return
342
- }
343
-
344
- // valueOf, mandatory to uneval new Date(10) for instance.
345
- recipe.valueOfIdentifier = identifierForValueOf(value);
346
- const prototypeValue = Object.getPrototypeOf(value);
347
- recipe.prototypeIdentifier = prototypeValueToIdentifier(prototypeValue);
348
- }
349
- });
350
-
351
- return {
352
- recipeArray,
353
- mainIdentifier,
354
- valueMap,
355
- }
356
- };
357
-
358
- const primitiveToRecipe = (value) => {
359
- if (typeof value === "symbol") {
360
- return symbolToRecipe(value)
361
- }
362
-
363
- return createPimitiveRecipe(value)
364
- };
365
-
366
- const symbolToRecipe = (symbol) => {
367
- const globalSymbolKey = Symbol.keyFor(symbol);
368
- if (globalSymbolKey !== undefined) {
369
- return createGlobalSymbolRecipe(globalSymbolKey)
35
+ if (reason === undefined) {
36
+ exception.message = "undefined";
37
+ return exception;
370
38
  }
371
-
372
- const symbolGlobalPath = getPrimitiveGlobalPath(symbol);
373
- if (!symbolGlobalPath) {
374
- throw new Error(createUnknownSymbolMessage({ symbol }))
39
+ if (reason === undefined) {
40
+ exception.message = "undefined";
41
+ return exception;
375
42
  }
376
-
377
- return createGlobalReferenceRecipe(symbolGlobalPath)
378
- };
379
-
380
- const createPimitiveRecipe = (value) => {
381
- return {
382
- type: "primitive",
383
- value,
43
+ if (typeof reason === "string") {
44
+ exception.message = reason;
45
+ return exception;
384
46
  }
385
- };
386
-
387
- const createGlobalReferenceRecipe = (path) => {
388
- const recipe = {
389
- type: "global-reference",
390
- path,
391
- };
392
- return recipe
393
- };
394
-
395
- const createGlobalSymbolRecipe = (key) => {
396
- return {
397
- type: "global-symbol",
398
- key,
47
+ if (reason instanceof Error) {
48
+ exception.isError = true;
49
+ exception.name = reason.name;
50
+ exception.message = reason.message;
51
+ const stackInfo = getStackInfo(reason);
52
+ if (stackInfo) {
53
+ const { stackTrace, stackObject } = stackInfo;
54
+ exception.stackTrace = stackTrace;
55
+ const [firstCallSite] = stackObject;
56
+ writePropertiesFromCallSite(exception, firstCallSite);
57
+ }
58
+ exception.stack = reason.stack;
59
+ return exception;
399
60
  }
400
- };
401
-
402
- const createCompositeRecipe = ({
403
- prototypeIdentifier,
404
- valueOfIdentifier,
405
- propertyDescriptionArray,
406
- symbolDescriptionArray,
407
- methodDescriptionArray,
408
- extensible,
409
- }) => {
410
- return {
411
- type: "composite",
412
- prototypeIdentifier,
413
- valueOfIdentifier,
414
- propertyDescriptionArray,
415
- symbolDescriptionArray,
416
- methodDescriptionArray,
417
- extensible,
61
+ if (typeof reason === "object") {
62
+ exception.code = reason.code;
63
+ exception.message = reason.message;
64
+ exception.stack = reason.stack;
65
+ return exception;
418
66
  }
67
+ exception.message = JSON.stringify(reason);
68
+ return exception;
419
69
  };
420
70
 
421
- const createPromiseAreNotSupportedMessage = ({ path }) => {
422
- if (path.length === 0) {
423
- return `promise are not supported.`
71
+ const getStackInfo = (reason) => {
72
+ if (reason instanceof Error) {
73
+ const { prepareStackTrace } = Error;
74
+ let stackTrace;
75
+ let stackObject;
76
+ Error.prepareStackTrace = (e, secondArg) => {
77
+ Error.prepareStackTrace = prepareStackTrace;
78
+ stackObject = secondArg;
79
+ stackTrace = secondArg.map((callSite) => ` at ${callSite}`).join("\n");
80
+ const name = e.name || "Error";
81
+ const message = e.message || "";
82
+ let stack = `${name}: ${message}`;
83
+ if (stackTrace) {
84
+ stack += `\n${stackTrace}`;
85
+ }
86
+ return stack;
87
+ };
88
+ // eslint-disable-next-line no-unused-expressions
89
+ reason.stack;
90
+ if (stackTrace === undefined) {
91
+ return null;
92
+ }
93
+ return {
94
+ stackTrace,
95
+ stackObject,
96
+ };
424
97
  }
425
-
426
- return `promise are not supported.
427
- promise found at: ${path.join("")}`
98
+ return null;
428
99
  };
429
-
430
- const createWeakSetAreNotSupportedMessage = ({ path }) => {
431
- if (path.length === 0) {
432
- return `weakSet are not supported.`
100
+ const writePropertiesFromCallSite = (exception, callSite) => {
101
+ const source = callSite.getFileName() || callSite.getScriptNameOrSourceURL();
102
+ if (source) {
103
+ const line = callSite.getLineNumber();
104
+ const column = callSite.getColumnNumber() - 1;
105
+ exception.site = {
106
+ url: source,
107
+ line,
108
+ column,
109
+ };
110
+ return;
433
111
  }
434
-
435
- return `weakSet are not supported.
436
- weakSet found at: ${path.join("")}`
437
- };
438
-
439
- const createWeakMapAreNotSupportedMessage = ({ path }) => {
440
- if (path.length === 0) {
441
- return `weakMap are not supported.`
112
+ // Code called using eval() needs special handling
113
+ if (callSite.isEval()) {
114
+ const origin = callSite.getEvalOrigin();
115
+ if (origin) {
116
+ writePropertiesFromEvalOrigin(exception, origin);
117
+ }
442
118
  }
443
-
444
- return `weakMap are not supported.
445
- weakMap found at: ${path.join("")}`
446
119
  };
447
-
448
- const createForbiddenFunctionMessage = ({ path }) => {
449
- if (path.length === 0) {
450
- return `function are not allowed.`
120
+ const writePropertiesFromEvalOrigin = (exception, origin) => {
121
+ // Most eval() calls are in this format
122
+ const topLevelEvalMatch = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(
123
+ origin,
124
+ );
125
+ if (topLevelEvalMatch) {
126
+ const source = topLevelEvalMatch[2];
127
+ const line = Number(topLevelEvalMatch[3]);
128
+ const column = topLevelEvalMatch[4] - 1;
129
+ exception.site = {
130
+ url: source,
131
+ line,
132
+ column,
133
+ };
134
+ return;
451
135
  }
452
-
453
- return `function are not allowed.
454
- function found at: ${path.join("")}`
455
- };
456
-
457
- const createForbiddenPropertyGetterMessage = ({
458
- path,
459
- propertyNameOrSymbol,
460
- }) => `property getter are not allowed.
461
- getter found on property: ${String(propertyNameOrSymbol)}
462
- at: ${path.join("")}`;
463
-
464
- const createForbiddenPropertySetterMessage = ({
465
- path,
466
- propertyNameOrSymbol,
467
- }) => `property setter are not allowed.
468
- setter found on property: ${String(propertyNameOrSymbol)}
469
- at: ${path.join("")}`;
470
-
471
- const createUnexpectedValueOfReturnValueMessage = () =>
472
- `valueOf() must return a primitive of the object itself.`;
473
-
474
- const createUnknownSymbolMessage = ({
475
- symbol,
476
- }) => `symbol must be global, like Symbol.iterator, or created using Symbol.for().
477
- symbol: ${symbol.toString()}`;
478
-
479
- const createUnknownPrototypeMessage = ({ prototypeValue }) =>
480
- `prototype must be global, like Object.prototype, or somewhere in the value.
481
- prototype constructor name: ${prototypeValue.constructor.name}`;
482
-
483
- // be carefull because this function is mutating recipe objects inside the recipeArray.
484
- // this is not an issue because each recipe object is not accessible from the outside
485
- // when used internally by uneval
486
- const sortRecipe = (recipeArray) => {
487
- const findInRecipePrototypeChain = (recipe, callback) => {
488
- let currentRecipe = recipe;
489
- // eslint-disable-next-line no-constant-condition
490
- while (true) {
491
- if (currentRecipe.type !== "composite") {
492
- break
493
- }
494
-
495
- const prototypeIdentifier = currentRecipe.prototypeIdentifier;
496
- if (prototypeIdentifier === undefined) {
497
- break
498
- }
499
-
500
- currentRecipe = recipeArray[prototypeIdentifier];
501
-
502
- if (callback(currentRecipe, prototypeIdentifier)) {
503
- return prototypeIdentifier
504
- }
505
- }
506
- return undefined
507
- };
508
-
509
- const recipeArrayOrdered = recipeArray.slice();
510
- recipeArrayOrdered.sort((leftRecipe, rightRecipe) => {
511
- const leftType = leftRecipe.type;
512
- const rightType = rightRecipe.type;
513
-
514
- if (leftType === "composite" && rightType === "composite") {
515
- const rightRecipeIsInLeftRecipePrototypeChain = findInRecipePrototypeChain(
516
- leftRecipe,
517
- (recipeCandidate) => recipeCandidate === rightRecipe,
518
- );
519
- // if left recipe requires right recipe, left must be after right
520
- if (rightRecipeIsInLeftRecipePrototypeChain) {
521
- return 1
522
- }
523
-
524
- const leftRecipeIsInRightRecipePrototypeChain = findInRecipePrototypeChain(
525
- rightRecipe,
526
- (recipeCandidate) => recipeCandidate === leftRecipe,
527
- );
528
- // if right recipe requires left recipe, right must be after left
529
- if (leftRecipeIsInRightRecipePrototypeChain) {
530
- return -1
531
- }
532
- }
533
-
534
- if (leftType !== rightType) {
535
- // if left is a composite, left must be after right
536
- if (leftType === "composite") {
537
- return 1
538
- }
539
-
540
- // if right is a composite, right must be after left
541
- if (rightType === "composite") {
542
- return -1
543
- }
544
- }
545
-
546
- const leftIndex = recipeArray.indexOf(leftRecipe);
547
- const rightIndex = recipeArray.indexOf(rightRecipe);
548
- // left was before right, don't change that
549
- if (leftIndex < rightIndex) {
550
- return -1
551
- }
552
-
553
- // right was after left, don't change that
554
- return 1
555
- });
556
-
557
- return recipeArrayOrdered
558
- };
559
-
560
- // https://github.com/joliss/js-string-escape/blob/master/index.js
561
- // http://javascript.crockford.com/remedial.html
562
- const escapeString = (value) => {
563
- const string = String(value);
564
- let i = 0;
565
- const j = string.length;
566
- var escapedString = "";
567
- while (i < j) {
568
- const char = string[i];
569
- let escapedChar;
570
- if (char === '"' || char === "'" || char === "\\") {
571
- escapedChar = `\\${char}`;
572
- } else if (char === "\n") {
573
- escapedChar = "\\n";
574
- } else if (char === "\r") {
575
- escapedChar = "\\r";
576
- } else if (char === "\u2028") {
577
- escapedChar = "\\u2028";
578
- } else if (char === "\u2029") {
579
- escapedChar = "\\u2029";
580
- } else {
581
- escapedChar = char;
582
- }
583
- escapedString += escapedChar;
584
- i++;
136
+ // Parse nested eval() calls using recursion
137
+ const nestedEvalMatch = /^eval at ([^(]+) \((.+)\)$/.exec(origin);
138
+ if (nestedEvalMatch) {
139
+ writePropertiesFromEvalOrigin(exception, nestedEvalMatch[2]);
585
140
  }
586
- return escapedString
587
- };
588
-
589
- const uneval = (
590
- value,
591
- { functionAllowed = false, prototypeStrict = false, ignoreSymbols = false } = {},
592
- ) => {
593
- const { recipeArray, mainIdentifier, valueMap } = decompose(value, {
594
- functionAllowed,
595
- prototypeStrict,
596
- ignoreSymbols,
597
- });
598
- const recipeArraySorted = sortRecipe(recipeArray);
599
-
600
- let source = `(function () {
601
- var globalObject
602
- try {
603
- globalObject = Function('return this')() || (42, eval)('this');
604
- } catch(e) {
605
- globalObject = window;
606
- }
607
-
608
- function safeDefineProperty(object, propertyNameOrSymbol, descriptor) {
609
- var currentDescriptor = Object.getOwnPropertyDescriptor(object, propertyNameOrSymbol);
610
- if (currentDescriptor && !currentDescriptor.configurable) return
611
- Object.defineProperty(object, propertyNameOrSymbol, descriptor)
612
- };
613
- `;
614
-
615
- const variableNameMap = {};
616
- recipeArray.forEach((recipe, index) => {
617
- const indexSorted = recipeArraySorted.indexOf(recipe);
618
- variableNameMap[index] = `_${indexSorted}`;
619
- });
620
-
621
- const identifierToVariableName = (identifier) => variableNameMap[identifier];
622
-
623
- const recipeToSetupSource = (recipe) => {
624
- if (recipe.type === "primitive") return primitiveRecipeToSetupSource(recipe)
625
- if (recipe.type === "global-symbol") return globalSymbolRecipeToSetupSource(recipe)
626
- if (recipe.type === "global-reference") return globalReferenceRecipeToSetupSource(recipe)
627
- return compositeRecipeToSetupSource(recipe)
628
- };
629
-
630
- const primitiveRecipeToSetupSource = ({ value }) => {
631
- const type = typeof value;
632
-
633
- if (type === "string") {
634
- return `"${escapeString(value)}";`
635
- }
636
-
637
- if (type === "bigint") {
638
- return `${value.toString()}n`
639
- }
640
-
641
- if (Object.is(value, -0)) {
642
- return "-0;"
643
- }
644
-
645
- return `${String(value)};`
646
- };
647
-
648
- const globalSymbolRecipeToSetupSource = (recipe) => {
649
- return `Symbol.for("${escapeString(recipe.key)}");`
650
- };
651
-
652
- const globalReferenceRecipeToSetupSource = (recipe) => {
653
- const pathSource = recipe.path.map((part) => `["${escapeString(part)}"]`).join("");
654
- return `globalObject${pathSource};`
655
- };
656
-
657
- const compositeRecipeToSetupSource = ({ prototypeIdentifier, valueOfIdentifier }) => {
658
- if (prototypeIdentifier === undefined) {
659
- return identifierToVariableName(valueOfIdentifier)
660
- }
661
-
662
- const prototypeValue = valueMap[prototypeIdentifier];
663
- if (prototypeValue === null) {
664
- return `Object.create(null);`
665
- }
666
-
667
- const prototypeConstructor = prototypeValue.constructor;
668
- if (prototypeConstructor === Object) {
669
- return `Object.create(${identifierToVariableName(prototypeIdentifier)});`
670
- }
671
-
672
- if (valueOfIdentifier === undefined) {
673
- return `new ${prototypeConstructor.name}();`
674
- }
675
-
676
- if (prototypeConstructor.name === "BigInt") {
677
- return `Object(${identifierToVariableName(valueOfIdentifier)})`
678
- }
679
-
680
- return `new ${prototypeConstructor.name}(${identifierToVariableName(valueOfIdentifier)});`
681
- };
682
-
683
- recipeArraySorted.forEach((recipe) => {
684
- const recipeVariableName = identifierToVariableName(recipeArray.indexOf(recipe));
685
- source += `var ${recipeVariableName} = ${recipeToSetupSource(recipe)}
686
- `;
687
- });
688
-
689
- const recipeToMutateSource = (recipe, recipeVariableName) => {
690
- if (recipe.type === "composite") {
691
- return compositeRecipeToMutateSource(recipe, recipeVariableName)
692
- }
693
- return ``
694
- };
695
-
696
- const compositeRecipeToMutateSource = (
697
- { propertyDescriptionArray, symbolDescriptionArray, methodDescriptionArray, extensible },
698
- recipeVariableName,
699
- ) => {
700
- let mutateSource = ``;
701
-
702
- propertyDescriptionArray.forEach(({ propertyNameIdentifier, propertyDescription }) => {
703
- mutateSource += generateDefinePropertySource(
704
- recipeVariableName,
705
- propertyNameIdentifier,
706
- propertyDescription,
707
- );
708
- });
709
-
710
- symbolDescriptionArray.forEach(({ symbolIdentifier, propertyDescription }) => {
711
- mutateSource += generateDefinePropertySource(
712
- recipeVariableName,
713
- symbolIdentifier,
714
- propertyDescription,
715
- );
716
- });
717
-
718
- methodDescriptionArray.forEach(({ methodNameIdentifier, callArray }) => {
719
- mutateSource += generateMethodCallSource(recipeVariableName, methodNameIdentifier, callArray);
720
- });
721
-
722
- if (!extensible) {
723
- mutateSource += generatePreventExtensionSource(recipeVariableName);
724
- }
725
-
726
- return mutateSource
727
- };
728
-
729
- const generateDefinePropertySource = (
730
- recipeVariableName,
731
- propertyNameOrSymbolIdentifier,
732
- propertyDescription,
733
- ) => {
734
- const propertyOrSymbolVariableName = identifierToVariableName(propertyNameOrSymbolIdentifier);
735
-
736
- const propertyDescriptorSource = generatePropertyDescriptorSource(propertyDescription);
737
- return `safeDefineProperty(${recipeVariableName}, ${propertyOrSymbolVariableName}, ${propertyDescriptorSource});`
738
- };
739
-
740
- const generatePropertyDescriptorSource = ({
741
- configurable,
742
- writable,
743
- enumerable,
744
- getIdentifier,
745
- setIdentifier,
746
- valueIdentifier,
747
- }) => {
748
- if (valueIdentifier === undefined) {
749
- return `{
750
- configurable: ${configurable},
751
- enumerable: ${enumerable},
752
- get: ${getIdentifier === undefined ? undefined : identifierToVariableName(getIdentifier)},
753
- set: ${setIdentifier === undefined ? undefined : identifierToVariableName(setIdentifier)},
754
- }`
755
- }
756
-
757
- return `{
758
- configurable: ${configurable},
759
- writable: ${writable},
760
- enumerable: ${enumerable},
761
- value: ${valueIdentifier === undefined ? undefined : identifierToVariableName(valueIdentifier)}
762
- }`
763
- };
764
-
765
- const generateMethodCallSource = (recipeVariableName, methodNameIdentifier, callArray) => {
766
- let methodCallSource = ``;
767
-
768
- const methodVariableName = identifierToVariableName(methodNameIdentifier);
769
- callArray.forEach((argumentIdentifiers) => {
770
- const argumentVariableNames = argumentIdentifiers.map((argumentIdentifier) =>
771
- identifierToVariableName(argumentIdentifier),
772
- );
773
-
774
- methodCallSource += `${recipeVariableName}[${methodVariableName}](${argumentVariableNames.join(
775
- ",",
776
- )});`;
777
- });
778
-
779
- return methodCallSource
780
- };
781
-
782
- const generatePreventExtensionSource = (recipeVariableName) => {
783
- return `Object.preventExtensions(${recipeVariableName});`
784
- };
785
-
786
- recipeArraySorted.forEach((recipe) => {
787
- const recipeVariableName = identifierToVariableName(recipeArray.indexOf(recipe));
788
- source += `${recipeToMutateSource(recipe, recipeVariableName)}`;
789
- });
790
-
791
- source += `return ${identifierToVariableName(mainIdentifier)}; })()`;
792
-
793
- return source
794
141
  };
795
142
 
796
143
  /*
@@ -964,4 +311,4 @@ const executeUsingDynamicImport = async ({
964
311
  return result;
965
312
  };
966
313
 
967
- export { executeUsingDynamicImport, uneval };
314
+ export { createException, executeUsingDynamicImport };