@vitest/utils 3.2.0-beta.2 → 3.2.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.
@@ -29,6 +29,7 @@ function stringify(object, maxDepth = 10, { maxLength,...options } = {}) {
29
29
  ...options
30
30
  });
31
31
  }
32
+ // Prevents infinite loop https://github.com/vitest-dev/vitest/issues/7249
32
33
  return result.length >= MAX_LENGTH && maxDepth > 1 ? stringify(object, Math.floor(Math.min(maxDepth, Number.MAX_SAFE_INTEGER) / 2), {
33
34
  maxLength,
34
35
  ...options
package/dist/diff.d.ts CHANGED
@@ -61,8 +61,13 @@ declare class Diff {
61
61
  * LICENSE file in the root directory of this source tree.
62
62
  */
63
63
 
64
+ // Compare two arrays of strings line-by-line. Format as comparison lines.
64
65
  declare function diffLinesUnified(aLines: Array<string>, bLines: Array<string>, options?: DiffOptions): string;
66
+ // Given two pairs of arrays of strings:
67
+ // Compare the pair of comparison arrays line-by-line.
68
+ // Format the corresponding lines in the pair of displayable arrays.
65
69
  declare function diffLinesUnified2(aLinesDisplay: Array<string>, bLinesDisplay: Array<string>, aLinesCompare: Array<string>, bLinesCompare: Array<string>, options?: DiffOptions): string;
70
+ // Compare two arrays of strings line-by-line.
66
71
  declare function diffLinesRaw(aLines: Array<string>, bLines: Array<string>, options?: DiffOptions): [Array<Diff>, boolean];
67
72
 
68
73
  /**
@@ -72,9 +77,15 @@ declare function diffLinesRaw(aLines: Array<string>, bLines: Array<string>, opti
72
77
  * LICENSE file in the root directory of this source tree.
73
78
  */
74
79
 
80
+ // Compare two strings character-by-character.
81
+ // Format as comparison lines in which changed substrings have inverse colors.
75
82
  declare function diffStringsUnified(a: string, b: string, options?: DiffOptions): string;
83
+ // Compare two strings character-by-character.
84
+ // Optionally clean up small common substrings, also known as chaff.
76
85
  declare function diffStringsRaw(a: string, b: string, cleanup: boolean, options?: DiffOptions): [Array<Diff>, boolean];
77
86
 
87
+ // Generate a string that will highlight the difference between two values
88
+ // with green and red. (similar to how github does code diffing)
78
89
  /**
79
90
  * @param a Expected value
80
91
  * @param b Received value
package/dist/diff.js CHANGED
@@ -66,9 +66,12 @@ class Diff {
66
66
  * string.
67
67
  */
68
68
  function diff_commonPrefix(text1, text2) {
69
+ // Quick check for common null cases.
69
70
  if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) {
70
71
  return 0;
71
72
  }
73
+ // Binary search.
74
+ // Performance analysis: https://neil.fraser.name/news/2007/10/09/
72
75
  let pointermin = 0;
73
76
  let pointermax = Math.min(text1.length, text2.length);
74
77
  let pointermid = pointermax;
@@ -91,9 +94,12 @@ function diff_commonPrefix(text1, text2) {
91
94
  * @return {number} The number of characters common to the end of each string.
92
95
  */
93
96
  function diff_commonSuffix(text1, text2) {
97
+ // Quick check for common null cases.
94
98
  if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
95
99
  return 0;
96
100
  }
101
+ // Binary search.
102
+ // Performance analysis: https://neil.fraser.name/news/2007/10/09/
97
103
  let pointermin = 0;
98
104
  let pointermax = Math.min(text1.length, text2.length);
99
105
  let pointermid = pointermax;
@@ -118,20 +124,27 @@ function diff_commonSuffix(text1, text2) {
118
124
  * @private
119
125
  */
120
126
  function diff_commonOverlap_(text1, text2) {
127
+ // Cache the text lengths to prevent multiple calls.
121
128
  const text1_length = text1.length;
122
129
  const text2_length = text2.length;
130
+ // Eliminate the null case.
123
131
  if (text1_length === 0 || text2_length === 0) {
124
132
  return 0;
125
133
  }
134
+ // Truncate the longer string.
126
135
  if (text1_length > text2_length) {
127
136
  text1 = text1.substring(text1_length - text2_length);
128
137
  } else if (text1_length < text2_length) {
129
138
  text2 = text2.substring(0, text1_length);
130
139
  }
131
140
  const text_length = Math.min(text1_length, text2_length);
141
+ // Quick check for the worst case.
132
142
  if (text1 === text2) {
133
143
  return text_length;
134
144
  }
145
+ // Start by looking for a single character match
146
+ // and increase length until no match is found.
147
+ // Performance analysis: https://neil.fraser.name/news/2010/11/04/
135
148
  let best = 0;
136
149
  let length = 1;
137
150
  while (true) {
@@ -157,13 +170,17 @@ function diff_cleanupSemantic(diffs) {
157
170
  let equalitiesLength = 0;
158
171
  /** @type {?string} */
159
172
  let lastEquality = null;
173
+ // Always equal to diffs[equalities[equalitiesLength - 1]][1]
160
174
  let pointer = 0;
175
+ // Number of characters that changed prior to the equality.
161
176
  let length_insertions1 = 0;
162
177
  let length_deletions1 = 0;
178
+ // Number of characters that changed after the equality.
163
179
  let length_insertions2 = 0;
164
180
  let length_deletions2 = 0;
165
181
  while (pointer < diffs.length) {
166
182
  if (diffs[pointer][0] === DIFF_EQUAL) {
183
+ // Equality found.
167
184
  equalities[equalitiesLength++] = pointer;
168
185
  length_insertions1 = length_insertions2;
169
186
  length_deletions1 = length_deletions2;
@@ -171,15 +188,22 @@ function diff_cleanupSemantic(diffs) {
171
188
  length_deletions2 = 0;
172
189
  lastEquality = diffs[pointer][1];
173
190
  } else {
191
+ // An insertion or deletion.
174
192
  if (diffs[pointer][0] === DIFF_INSERT) {
175
193
  length_insertions2 += diffs[pointer][1].length;
176
194
  } else {
177
195
  length_deletions2 += diffs[pointer][1].length;
178
196
  }
197
+ // Eliminate an equality that is smaller or equal to the edits on both
198
+ // sides of it.
179
199
  if (lastEquality && lastEquality.length <= Math.max(length_insertions1, length_deletions1) && lastEquality.length <= Math.max(length_insertions2, length_deletions2)) {
200
+ // Duplicate record.
180
201
  diffs.splice(equalities[equalitiesLength - 1], 0, new Diff(DIFF_DELETE, lastEquality));
202
+ // Change second copy to insert.
181
203
  diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
204
+ // Throw away the equality we just deleted.
182
205
  equalitiesLength--;
206
+ // Throw away the previous equality (it needs to be reevaluated).
183
207
  equalitiesLength--;
184
208
  pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
185
209
  length_insertions1 = 0;
@@ -192,10 +216,17 @@ function diff_cleanupSemantic(diffs) {
192
216
  }
193
217
  pointer++;
194
218
  }
219
+ // Normalize the diff.
195
220
  if (changes) {
196
221
  diff_cleanupMerge(diffs);
197
222
  }
198
223
  diff_cleanupSemanticLossless(diffs);
224
+ // Find any overlaps between deletions and insertions.
225
+ // e.g: <del>abcxxx</del><ins>xxxdef</ins>
226
+ // -> <del>abc</del>xxx<ins>def</ins>
227
+ // e.g: <del>xxxabc</del><ins>defxxx</ins>
228
+ // -> <ins>def</ins>xxx<del>abc</del>
229
+ // Only extract an overlap if it is as big as the edit ahead or behind it.
199
230
  pointer = 1;
200
231
  while (pointer < diffs.length) {
201
232
  if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) {
@@ -205,6 +236,7 @@ function diff_cleanupSemantic(diffs) {
205
236
  const overlap_length2 = diff_commonOverlap_(insertion, deletion);
206
237
  if (overlap_length1 >= overlap_length2) {
207
238
  if (overlap_length1 >= deletion.length / 2 || overlap_length1 >= insertion.length / 2) {
239
+ // Overlap found. Insert an equality and trim the surrounding edits.
208
240
  diffs.splice(pointer, 0, new Diff(DIFF_EQUAL, insertion.substring(0, overlap_length1)));
209
241
  diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlap_length1);
210
242
  diffs[pointer + 1][1] = insertion.substring(overlap_length1);
@@ -212,6 +244,8 @@ function diff_cleanupSemantic(diffs) {
212
244
  }
213
245
  } else {
214
246
  if (overlap_length2 >= deletion.length / 2 || overlap_length2 >= insertion.length / 2) {
247
+ // Reverse overlap found.
248
+ // Insert an equality and swap and trim the surrounding edits.
215
249
  diffs.splice(pointer, 0, new Diff(DIFF_EQUAL, deletion.substring(0, overlap_length2)));
216
250
  diffs[pointer - 1][0] = DIFF_INSERT;
217
251
  diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlap_length2);
@@ -225,6 +259,7 @@ function diff_cleanupSemantic(diffs) {
225
259
  pointer++;
226
260
  }
227
261
  }
262
+ // Define some regex patterns for matching boundaries.
228
263
  const nonAlphaNumericRegex_ = /[^a-z0-9]/i;
229
264
  const whitespaceRegex_ = /\s/;
230
265
  const linebreakRegex_ = /[\r\n]/;
@@ -238,11 +273,14 @@ const blanklineStartRegex_ = /^\r?\n\r?\n/;
238
273
  */
239
274
  function diff_cleanupSemanticLossless(diffs) {
240
275
  let pointer = 1;
276
+ // Intentionally ignore the first and last element (don't need checking).
241
277
  while (pointer < diffs.length - 1) {
242
278
  if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
279
+ // This is a single edit surrounded by equalities.
243
280
  let equality1 = diffs[pointer - 1][1];
244
281
  let edit = diffs[pointer][1];
245
282
  let equality2 = diffs[pointer + 1][1];
283
+ // First, shift the edit as far left as possible.
246
284
  const commonOffset = diff_commonSuffix(equality1, edit);
247
285
  if (commonOffset) {
248
286
  const commonString = edit.substring(edit.length - commonOffset);
@@ -250,6 +288,7 @@ function diff_cleanupSemanticLossless(diffs) {
250
288
  edit = commonString + edit.substring(0, edit.length - commonOffset);
251
289
  equality2 = commonString + equality2;
252
290
  }
291
+ // Second, step character by character right, looking for the best fit.
253
292
  let bestEquality1 = equality1;
254
293
  let bestEdit = edit;
255
294
  let bestEquality2 = equality2;
@@ -259,6 +298,7 @@ function diff_cleanupSemanticLossless(diffs) {
259
298
  edit = edit.substring(1) + equality2.charAt(0);
260
299
  equality2 = equality2.substring(1);
261
300
  const score = diff_cleanupSemanticScore_(equality1, edit) + diff_cleanupSemanticScore_(edit, equality2);
301
+ // The >= encourages trailing rather than leading whitespace on edits.
262
302
  if (score >= bestScore) {
263
303
  bestScore = score;
264
304
  bestEquality1 = equality1;
@@ -267,6 +307,7 @@ function diff_cleanupSemanticLossless(diffs) {
267
307
  }
268
308
  }
269
309
  if (diffs[pointer - 1][1] !== bestEquality1) {
310
+ // We have an improvement, save it back to the diff.
270
311
  if (bestEquality1) {
271
312
  diffs[pointer - 1][1] = bestEquality1;
272
313
  } else {
@@ -291,6 +332,7 @@ function diff_cleanupSemanticLossless(diffs) {
291
332
  * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
292
333
  */
293
334
  function diff_cleanupMerge(diffs) {
335
+ // Add a dummy entry at the end.
294
336
  diffs.push(new Diff(DIFF_EQUAL, ""));
295
337
  let pointer = 0;
296
338
  let count_delete = 0;
@@ -311,8 +353,10 @@ function diff_cleanupMerge(diffs) {
311
353
  pointer++;
312
354
  break;
313
355
  case DIFF_EQUAL:
356
+ // Upon reaching an equality, check for prior redundancies.
314
357
  if (count_delete + count_insert > 1) {
315
358
  if (count_delete !== 0 && count_insert !== 0) {
359
+ // Factor out any common prefixes.
316
360
  commonlength = diff_commonPrefix(text_insert, text_delete);
317
361
  if (commonlength !== 0) {
318
362
  if (pointer - count_delete - count_insert > 0 && diffs[pointer - count_delete - count_insert - 1][0] === DIFF_EQUAL) {
@@ -324,6 +368,7 @@ function diff_cleanupMerge(diffs) {
324
368
  text_insert = text_insert.substring(commonlength);
325
369
  text_delete = text_delete.substring(commonlength);
326
370
  }
371
+ // Factor out any common suffixes.
327
372
  commonlength = diff_commonSuffix(text_insert, text_delete);
328
373
  if (commonlength !== 0) {
329
374
  diffs[pointer][1] = text_insert.substring(text_insert.length - commonlength) + diffs[pointer][1];
@@ -331,6 +376,7 @@ function diff_cleanupMerge(diffs) {
331
376
  text_delete = text_delete.substring(0, text_delete.length - commonlength);
332
377
  }
333
378
  }
379
+ // Delete the offending records and add the merged ones.
334
380
  pointer -= count_delete + count_insert;
335
381
  diffs.splice(pointer, count_delete + count_insert);
336
382
  if (text_delete.length) {
@@ -343,6 +389,7 @@ function diff_cleanupMerge(diffs) {
343
389
  }
344
390
  pointer++;
345
391
  } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
392
+ // Merge this equality with the previous one.
346
393
  diffs[pointer - 1][1] += diffs[pointer][1];
347
394
  diffs.splice(pointer, 1);
348
395
  } else {
@@ -358,16 +405,23 @@ function diff_cleanupMerge(diffs) {
358
405
  if (diffs[diffs.length - 1][1] === "") {
359
406
  diffs.pop();
360
407
  }
408
+ // Second pass: look for single edits surrounded on both sides by equalities
409
+ // which can be shifted sideways to eliminate an equality.
410
+ // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
361
411
  let changes = false;
362
412
  pointer = 1;
413
+ // Intentionally ignore the first and last element (don't need checking).
363
414
  while (pointer < diffs.length - 1) {
364
415
  if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) {
416
+ // This is a single edit surrounded by equalities.
365
417
  if (diffs[pointer][1].substring(diffs[pointer][1].length - diffs[pointer - 1][1].length) === diffs[pointer - 1][1]) {
418
+ // Shift the edit over the previous equality.
366
419
  diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length);
367
420
  diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
368
421
  diffs.splice(pointer - 1, 1);
369
422
  changes = true;
370
423
  } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) {
424
+ // Shift the edit over the next equality.
371
425
  diffs[pointer - 1][1] += diffs[pointer + 1][1];
372
426
  diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1];
373
427
  diffs.splice(pointer + 1, 1);
@@ -376,6 +430,7 @@ function diff_cleanupMerge(diffs) {
376
430
  }
377
431
  pointer++;
378
432
  }
433
+ // If shifts were made, the diff needs reordering and another shift sweep.
379
434
  if (changes) {
380
435
  diff_cleanupMerge(diffs);
381
436
  }
@@ -392,8 +447,14 @@ function diff_cleanupMerge(diffs) {
392
447
  */
393
448
  function diff_cleanupSemanticScore_(one, two) {
394
449
  if (!one || !two) {
450
+ // Edges are the best.
395
451
  return 6;
396
452
  }
453
+ // Each port of this function behaves slightly differently due to
454
+ // subtle differences in each language's definition of things like
455
+ // 'whitespace'. Since this function's purpose is largely cosmetic,
456
+ // the choice has been made to use each language's native features
457
+ // rather than force total conformity.
397
458
  const char1 = one.charAt(one.length - 1);
398
459
  const char2 = two.charAt(0);
399
460
  const nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_);
@@ -405,14 +466,19 @@ function diff_cleanupSemanticScore_(one, two) {
405
466
  const blankLine1 = lineBreak1 && one.match(blanklineEndRegex_);
406
467
  const blankLine2 = lineBreak2 && two.match(blanklineStartRegex_);
407
468
  if (blankLine1 || blankLine2) {
469
+ // Five points for blank lines.
408
470
  return 5;
409
471
  } else if (lineBreak1 || lineBreak2) {
472
+ // Four points for line breaks.
410
473
  return 4;
411
474
  } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
475
+ // Three points for end of sentences.
412
476
  return 3;
413
477
  } else if (whitespace1 || whitespace2) {
478
+ // Two points for whitespace.
414
479
  return 2;
415
480
  } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
481
+ // One point for non-alphanumeric.
416
482
  return 1;
417
483
  }
418
484
  return 0;
@@ -1252,13 +1318,19 @@ function printInsertLine(line, isFirstOrLast, { bColor, bIndicator, changeLineTr
1252
1318
  function printCommonLine(line, isFirstOrLast, { commonColor, commonIndicator, commonLineTrailingSpaceColor, emptyFirstOrLastLinePlaceholder }) {
1253
1319
  return printDiffLine(line, isFirstOrLast, commonColor, commonIndicator, commonLineTrailingSpaceColor, emptyFirstOrLastLinePlaceholder);
1254
1320
  }
1321
+ // In GNU diff format, indexes are one-based instead of zero-based.
1255
1322
  function createPatchMark(aStart, aEnd, bStart, bEnd, { patchColor }) {
1256
1323
  return patchColor(`@@ -${aStart + 1},${aEnd - aStart} +${bStart + 1},${bEnd - bStart} @@`);
1257
1324
  }
1325
+ // jest --no-expand
1326
+ //
1327
+ // Given array of aligned strings with inverse highlight formatting,
1328
+ // return joined lines with diff formatting (and patch marks, if needed).
1258
1329
  function joinAlignedDiffsNoExpand(diffs, options) {
1259
1330
  const iLength = diffs.length;
1260
1331
  const nContextLines = options.contextLines;
1261
1332
  const nContextLines2 = nContextLines + nContextLines;
1333
+ // First pass: count output lines and see if it has patches.
1262
1334
  let jLength = iLength;
1263
1335
  let hasExcessAtStartOrEnd = false;
1264
1336
  let nExcessesBetweenChanges = 0;
@@ -1270,17 +1342,20 @@ function joinAlignedDiffsNoExpand(diffs, options) {
1270
1342
  }
1271
1343
  if (iStart !== i) {
1272
1344
  if (iStart === 0) {
1345
+ // at start
1273
1346
  if (i > nContextLines) {
1274
1347
  jLength -= i - nContextLines;
1275
1348
  hasExcessAtStartOrEnd = true;
1276
1349
  }
1277
1350
  } else if (i === iLength) {
1351
+ // at end
1278
1352
  const n = i - iStart;
1279
1353
  if (n > nContextLines) {
1280
1354
  jLength -= n - nContextLines;
1281
1355
  hasExcessAtStartOrEnd = true;
1282
1356
  }
1283
1357
  } else {
1358
+ // between changes
1284
1359
  const n = i - iStart;
1285
1360
  if (n > nContextLines2) {
1286
1361
  jLength -= n - nContextLines2;
@@ -1304,6 +1379,7 @@ function joinAlignedDiffsNoExpand(diffs, options) {
1304
1379
  if (hasPatch) {
1305
1380
  lines.push("");
1306
1381
  }
1382
+ // Indexes of expected or received lines in current patch:
1307
1383
  let aStart = 0;
1308
1384
  let bStart = 0;
1309
1385
  let aEnd = 0;
@@ -1324,6 +1400,7 @@ function joinAlignedDiffsNoExpand(diffs, options) {
1324
1400
  lines.push(printInsertLine(line, j === 0 || j === jLast, options));
1325
1401
  bEnd += 1;
1326
1402
  };
1403
+ // Second pass: push lines with diff formatting (and patch marks, if needed).
1327
1404
  i = 0;
1328
1405
  while (i !== iLength) {
1329
1406
  let iStart = i;
@@ -1332,6 +1409,7 @@ function joinAlignedDiffsNoExpand(diffs, options) {
1332
1409
  }
1333
1410
  if (iStart !== i) {
1334
1411
  if (iStart === 0) {
1412
+ // at beginning
1335
1413
  if (i > nContextLines) {
1336
1414
  iStart = i - nContextLines;
1337
1415
  aStart = iStart;
@@ -1343,11 +1421,13 @@ function joinAlignedDiffsNoExpand(diffs, options) {
1343
1421
  pushCommonLine(diffs[iCommon][1]);
1344
1422
  }
1345
1423
  } else if (i === iLength) {
1424
+ // at end
1346
1425
  const iEnd = i - iStart > nContextLines ? iStart + nContextLines : i;
1347
1426
  for (let iCommon = iStart; iCommon !== iEnd; iCommon += 1) {
1348
1427
  pushCommonLine(diffs[iCommon][1]);
1349
1428
  }
1350
1429
  } else {
1430
+ // between changes
1351
1431
  const nCommon = i - iStart;
1352
1432
  if (nCommon > nContextLines2) {
1353
1433
  const iEnd = iStart + nContextLines;
@@ -1386,6 +1466,10 @@ function joinAlignedDiffsNoExpand(diffs, options) {
1386
1466
  }
1387
1467
  return lines.join("\n");
1388
1468
  }
1469
+ // jest --expand
1470
+ //
1471
+ // Given array of aligned strings with inverse highlight formatting,
1472
+ // return joined lines with diff formatting.
1389
1473
  function joinAlignedDiffsExpand(diffs, options) {
1390
1474
  return diffs.map((diff, i, diffs) => {
1391
1475
  const line = diff[1];
@@ -1433,6 +1517,7 @@ function getCompareKeys(compareKeys) {
1433
1517
  function getContextLines(contextLines) {
1434
1518
  return typeof contextLines === "number" && Number.isSafeInteger(contextLines) && contextLines >= 0 ? contextLines : DIFF_CONTEXT_DEFAULT;
1435
1519
  }
1520
+ // Pure function returns options with all properties.
1436
1521
  function normalizeDiffOptions(options = {}) {
1437
1522
  return {
1438
1523
  ...getDefaultOptions(),
@@ -1472,9 +1557,11 @@ function printAnnotation({ aAnnotation, aColor, aIndicator, bAnnotation, bColor,
1472
1557
  if (includeChangeCounts) {
1473
1558
  const aCount = String(changeCounts.a);
1474
1559
  const bCount = String(changeCounts.b);
1560
+ // Padding right aligns the ends of the annotations.
1475
1561
  const baAnnotationLengthDiff = bAnnotation.length - aAnnotation.length;
1476
1562
  const aAnnotationPadding = " ".repeat(Math.max(0, baAnnotationLengthDiff));
1477
1563
  const bAnnotationPadding = " ".repeat(Math.max(0, -baAnnotationLengthDiff));
1564
+ // Padding left aligns the ends of the counts.
1478
1565
  const baCountLengthDiff = bCount.length - aCount.length;
1479
1566
  const aCountPadding = " ".repeat(Math.max(0, baCountLengthDiff));
1480
1567
  const bCountPadding = " ".repeat(Math.max(0, -baCountLengthDiff));
@@ -1488,11 +1575,15 @@ function printAnnotation({ aAnnotation, aColor, aIndicator, bAnnotation, bColor,
1488
1575
  function printDiffLines(diffs, truncated, options) {
1489
1576
  return printAnnotation(options, countChanges(diffs)) + (options.expand ? joinAlignedDiffsExpand(diffs, options) : joinAlignedDiffsNoExpand(diffs, options)) + (truncated ? options.truncateAnnotationColor(`\n${options.truncateAnnotation}`) : "");
1490
1577
  }
1578
+ // Compare two arrays of strings line-by-line. Format as comparison lines.
1491
1579
  function diffLinesUnified(aLines, bLines, options) {
1492
1580
  const normalizedOptions = normalizeDiffOptions(options);
1493
1581
  const [diffs, truncated] = diffLinesRaw(isEmptyString(aLines) ? [] : aLines, isEmptyString(bLines) ? [] : bLines, normalizedOptions);
1494
1582
  return printDiffLines(diffs, truncated, normalizedOptions);
1495
1583
  }
1584
+ // Given two pairs of arrays of strings:
1585
+ // Compare the pair of comparison arrays line-by-line.
1586
+ // Format the corresponding lines in the pair of displayable arrays.
1496
1587
  function diffLinesUnified2(aLinesDisplay, bLinesDisplay, aLinesCompare, bLinesCompare, options) {
1497
1588
  if (isEmptyString(aLinesDisplay) && isEmptyString(aLinesCompare)) {
1498
1589
  aLinesDisplay = [];
@@ -1503,9 +1594,11 @@ function diffLinesUnified2(aLinesDisplay, bLinesDisplay, aLinesCompare, bLinesCo
1503
1594
  bLinesCompare = [];
1504
1595
  }
1505
1596
  if (aLinesDisplay.length !== aLinesCompare.length || bLinesDisplay.length !== bLinesCompare.length) {
1597
+ // Fall back to diff of display lines.
1506
1598
  return diffLinesUnified(aLinesDisplay, bLinesDisplay, options);
1507
1599
  }
1508
1600
  const [diffs, truncated] = diffLinesRaw(aLinesCompare, bLinesCompare, options);
1601
+ // Replace comparison lines with displayable lines.
1509
1602
  let aIndex = 0;
1510
1603
  let bIndex = 0;
1511
1604
  diffs.forEach((diff) => {
@@ -1526,6 +1619,7 @@ function diffLinesUnified2(aLinesDisplay, bLinesDisplay, aLinesCompare, bLinesCo
1526
1619
  });
1527
1620
  return printDiffLines(diffs, truncated, normalizeDiffOptions(options));
1528
1621
  }
1622
+ // Compare two arrays of strings line-by-line.
1529
1623
  function diffLinesRaw(aLines, bLines, options) {
1530
1624
  const truncate = (options === null || options === void 0 ? void 0 : options.truncateThreshold) ?? false;
1531
1625
  const truncateThreshold = Math.max(Math.floor((options === null || options === void 0 ? void 0 : options.truncateThreshold) ?? 0), 0);
@@ -1548,6 +1642,7 @@ function diffLinesRaw(aLines, bLines, options) {
1548
1642
  }
1549
1643
  };
1550
1644
  diffSequences(aLength, bLength, isCommon, foundSubsequence);
1645
+ // After the last common subsequence, push remaining change items.
1551
1646
  for (; aIndex !== aLength; aIndex += 1) {
1552
1647
  diffs.push(new Diff(DIFF_DELETE, aLines[aIndex]));
1553
1648
  }
@@ -1557,6 +1652,8 @@ function diffLinesRaw(aLines, bLines, options) {
1557
1652
  return [diffs, truncated];
1558
1653
  }
1559
1654
 
1655
+ // get the type of a value with handling the edge cases like `typeof []`
1656
+ // and `typeof null`
1560
1657
  function getType(value) {
1561
1658
  if (value === undefined) {
1562
1659
  return "undefined";
@@ -1593,6 +1690,7 @@ function getType(value) {
1593
1690
  throw new Error(`value of unknown type: ${value}`);
1594
1691
  }
1595
1692
 
1693
+ // platforms compatible
1596
1694
  function getNewLineSymbol(string) {
1597
1695
  return string.includes("\r\n") ? "\r\n" : "\n";
1598
1696
  }
@@ -1606,6 +1704,7 @@ function diffStrings(a, b, options) {
1606
1704
  const bMultipleLines = b.includes("\n");
1607
1705
  const aNewLineSymbol = getNewLineSymbol(a);
1608
1706
  const bNewLineSymbol = getNewLineSymbol(b);
1707
+ // multiple-lines string expects a newline to be appended at the end
1609
1708
  const _a = aMultipleLines ? `${a.split(aNewLineSymbol, truncateThreshold).join(aNewLineSymbol)}\n` : a;
1610
1709
  const _b = bMultipleLines ? `${b.split(bNewLineSymbol, truncateThreshold).join(bNewLineSymbol)}\n` : b;
1611
1710
  aLength = _a.length;
@@ -1628,6 +1727,7 @@ function diffStrings(a, b, options) {
1628
1727
  diffs.push(new Diff(DIFF_EQUAL, b.slice(bCommon, bIndex)));
1629
1728
  };
1630
1729
  diffSequences(aLength, bLength, isCommon, foundSubsequence);
1730
+ // After the last common subsequence, push remaining change items.
1631
1731
  if (aIndex !== aLength) {
1632
1732
  diffs.push(new Diff(DIFF_DELETE, a.slice(aIndex)));
1633
1733
  }
@@ -1637,9 +1737,14 @@ function diffStrings(a, b, options) {
1637
1737
  return [diffs, truncated];
1638
1738
  }
1639
1739
 
1740
+ // Given change op and array of diffs, return concatenated string:
1741
+ // * include common strings
1742
+ // * include change strings which have argument op with changeColor
1743
+ // * exclude change strings which have opposite op
1640
1744
  function concatenateRelevantDiffs(op, diffs, changeColor) {
1641
1745
  return diffs.reduce((reduced, diff) => reduced + (diff[0] === DIFF_EQUAL ? diff[1] : diff[0] === op && diff[1].length !== 0 ? changeColor(diff[1]) : ""), "");
1642
1746
  }
1747
+ // Encapsulate change lines until either a common newline or the end.
1643
1748
  class ChangeBuffer {
1644
1749
  op;
1645
1750
  line;
@@ -1655,15 +1760,22 @@ class ChangeBuffer {
1655
1760
  this.pushDiff(new Diff(this.op, substring));
1656
1761
  }
1657
1762
  pushLine() {
1763
+ // Assume call only if line has at least one diff,
1764
+ // therefore an empty line must have a diff which has an empty string.
1765
+ // If line has multiple diffs, then assume it has a common diff,
1766
+ // therefore change diffs have change color;
1767
+ // otherwise then it has line color only.
1658
1768
  this.lines.push(this.line.length !== 1 ? new Diff(this.op, concatenateRelevantDiffs(this.op, this.line, this.changeColor)) : this.line[0][0] === this.op ? this.line[0] : new Diff(this.op, this.line[0][1]));
1659
1769
  this.line.length = 0;
1660
1770
  }
1661
1771
  isLineEmpty() {
1662
1772
  return this.line.length === 0;
1663
1773
  }
1774
+ // Minor input to buffer.
1664
1775
  pushDiff(diff) {
1665
1776
  this.line.push(diff);
1666
1777
  }
1778
+ // Main input to buffer.
1667
1779
  align(diff) {
1668
1780
  const string = diff[1];
1669
1781
  if (string.includes("\n")) {
@@ -1671,16 +1783,23 @@ class ChangeBuffer {
1671
1783
  const iLast = substrings.length - 1;
1672
1784
  substrings.forEach((substring, i) => {
1673
1785
  if (i < iLast) {
1786
+ // The first substring completes the current change line.
1787
+ // A middle substring is a change line.
1674
1788
  this.pushSubstring(substring);
1675
1789
  this.pushLine();
1676
1790
  } else if (substring.length !== 0) {
1791
+ // The last substring starts a change line, if it is not empty.
1792
+ // Important: This non-empty condition also automatically omits
1793
+ // the newline appended to the end of expected and received strings.
1677
1794
  this.pushSubstring(substring);
1678
1795
  }
1679
1796
  });
1680
1797
  } else {
1798
+ // Append non-multiline string to current change line.
1681
1799
  this.pushDiff(diff);
1682
1800
  }
1683
1801
  }
1802
+ // Output from buffer.
1684
1803
  moveLinesTo(lines) {
1685
1804
  if (!this.isLineEmpty()) {
1686
1805
  this.pushLine();
@@ -1689,6 +1808,7 @@ class ChangeBuffer {
1689
1808
  this.lines.length = 0;
1690
1809
  }
1691
1810
  }
1811
+ // Encapsulate common and change lines.
1692
1812
  class CommonBuffer {
1693
1813
  deleteBuffer;
1694
1814
  insertBuffer;
@@ -1703,6 +1823,7 @@ class CommonBuffer {
1703
1823
  }
1704
1824
  pushDiffChangeLines(diff) {
1705
1825
  const isDiffEmpty = diff[1].length === 0;
1826
+ // An empty diff string is redundant, unless a change line is empty.
1706
1827
  if (!isDiffEmpty || this.deleteBuffer.isLineEmpty()) {
1707
1828
  this.deleteBuffer.pushDiff(diff);
1708
1829
  }
@@ -1714,6 +1835,7 @@ class CommonBuffer {
1714
1835
  this.deleteBuffer.moveLinesTo(this.lines);
1715
1836
  this.insertBuffer.moveLinesTo(this.lines);
1716
1837
  }
1838
+ // Input to buffer.
1717
1839
  align(diff) {
1718
1840
  const op = diff[0];
1719
1841
  const string = diff[1];
@@ -1724,27 +1846,49 @@ class CommonBuffer {
1724
1846
  if (i === 0) {
1725
1847
  const subdiff = new Diff(op, substring);
1726
1848
  if (this.deleteBuffer.isLineEmpty() && this.insertBuffer.isLineEmpty()) {
1849
+ // If both current change lines are empty,
1850
+ // then the first substring is a common line.
1727
1851
  this.flushChangeLines();
1728
1852
  this.pushDiffCommonLine(subdiff);
1729
1853
  } else {
1854
+ // If either current change line is non-empty,
1855
+ // then the first substring completes the change lines.
1730
1856
  this.pushDiffChangeLines(subdiff);
1731
1857
  this.flushChangeLines();
1732
1858
  }
1733
1859
  } else if (i < iLast) {
1860
+ // A middle substring is a common line.
1734
1861
  this.pushDiffCommonLine(new Diff(op, substring));
1735
1862
  } else if (substring.length !== 0) {
1863
+ // The last substring starts a change line, if it is not empty.
1864
+ // Important: This non-empty condition also automatically omits
1865
+ // the newline appended to the end of expected and received strings.
1736
1866
  this.pushDiffChangeLines(new Diff(op, substring));
1737
1867
  }
1738
1868
  });
1739
1869
  } else {
1870
+ // Append non-multiline string to current change lines.
1871
+ // Important: It cannot be at the end following empty change lines,
1872
+ // because newline appended to the end of expected and received strings.
1740
1873
  this.pushDiffChangeLines(diff);
1741
1874
  }
1742
1875
  }
1876
+ // Output from buffer.
1743
1877
  getLines() {
1744
1878
  this.flushChangeLines();
1745
1879
  return this.lines;
1746
1880
  }
1747
1881
  }
1882
+ // Given diffs from expected and received strings,
1883
+ // return new array of diffs split or joined into lines.
1884
+ //
1885
+ // To correctly align a change line at the end, the algorithm:
1886
+ // * assumes that a newline was appended to the strings
1887
+ // * omits the last newline from the output array
1888
+ //
1889
+ // Assume the function is not called:
1890
+ // * if either expected or received is empty string
1891
+ // * if neither expected nor received is multiline string
1748
1892
  function getAlignedDiffs(diffs, changeColor) {
1749
1893
  const deleteBuffer = new ChangeBuffer(DIFF_DELETE, changeColor);
1750
1894
  const insertBuffer = new ChangeBuffer(DIFF_INSERT, changeColor);
@@ -1765,14 +1909,18 @@ function getAlignedDiffs(diffs, changeColor) {
1765
1909
 
1766
1910
  function hasCommonDiff(diffs, isMultiline) {
1767
1911
  if (isMultiline) {
1912
+ // Important: Ignore common newline that was appended to multiline strings!
1768
1913
  const iLast = diffs.length - 1;
1769
1914
  return diffs.some((diff, i) => diff[0] === DIFF_EQUAL && (i !== iLast || diff[1] !== "\n"));
1770
1915
  }
1771
1916
  return diffs.some((diff) => diff[0] === DIFF_EQUAL);
1772
1917
  }
1918
+ // Compare two strings character-by-character.
1919
+ // Format as comparison lines in which changed substrings have inverse colors.
1773
1920
  function diffStringsUnified(a, b, options) {
1774
1921
  if (a !== b && a.length !== 0 && b.length !== 0) {
1775
1922
  const isMultiline = a.includes("\n") || b.includes("\n");
1923
+ // getAlignedDiffs assumes that a newline was appended to the strings.
1776
1924
  const [diffs, truncated] = diffStringsRaw(isMultiline ? `${a}\n` : a, isMultiline ? `${b}\n` : b, true, options);
1777
1925
  if (hasCommonDiff(diffs, isMultiline)) {
1778
1926
  const optionsNormalized = normalizeDiffOptions(options);
@@ -1780,8 +1928,11 @@ function diffStringsUnified(a, b, options) {
1780
1928
  return printDiffLines(lines, truncated, optionsNormalized);
1781
1929
  }
1782
1930
  }
1931
+ // Fall back to line-by-line diff.
1783
1932
  return diffLinesUnified(a.split("\n"), b.split("\n"), options);
1784
1933
  }
1934
+ // Compare two strings character-by-character.
1935
+ // Optionally clean up small common substrings, also known as chaff.
1785
1936
  function diffStringsRaw(a, b, cleanup, options) {
1786
1937
  const [diffs, truncated] = diffStrings(a, b, options);
1787
1938
  if (cleanup) {
@@ -1813,6 +1964,8 @@ const FALLBACK_FORMAT_OPTIONS = {
1813
1964
  maxDepth: 8,
1814
1965
  plugins: PLUGINS
1815
1966
  };
1967
+ // Generate a string that will highlight the difference between two values
1968
+ // with green and red. (similar to how github does code diffing)
1816
1969
  /**
1817
1970
  * @param a Expected value
1818
1971
  * @param b Received value
@@ -1828,12 +1981,16 @@ function diff(a, b, options) {
1828
1981
  let omitDifference = false;
1829
1982
  if (aType === "object" && typeof a.asymmetricMatch === "function") {
1830
1983
  if (a.$$typeof !== Symbol.for("jest.asymmetricMatcher")) {
1984
+ // Do not know expected type of user-defined asymmetric matcher.
1831
1985
  return undefined;
1832
1986
  }
1833
1987
  if (typeof a.getExpectedType !== "function") {
1988
+ // For example, expect.anything() matches either null or undefined
1834
1989
  return undefined;
1835
1990
  }
1836
1991
  expectedType = a.getExpectedType();
1992
+ // Primitive types boolean and number omit difference below.
1993
+ // For example, omit difference for expect.stringMatching(regexp)
1837
1994
  omitDifference = expectedType === "string";
1838
1995
  }
1839
1996
  if (expectedType !== getType(b)) {
@@ -1841,6 +1998,10 @@ function diff(a, b, options) {
1841
1998
  const formatOptions = getFormatOptions(FALLBACK_FORMAT_OPTIONS, options);
1842
1999
  let aDisplay = format(a, formatOptions);
1843
2000
  let bDisplay = format(b, formatOptions);
2001
+ // even if prettyFormat prints successfully big objects,
2002
+ // large string can choke later on (concatenation? RPC?),
2003
+ // so truncate it to a reasonable length here.
2004
+ // (For example, playwright's ElementHandle can become about 200_000_000 length string)
1844
2005
  const MAX_LENGTH = 1e5;
1845
2006
  function truncate(s) {
1846
2007
  return s.length <= MAX_LENGTH ? s : `${s.slice(0, MAX_LENGTH)}...`;
@@ -1884,6 +2045,8 @@ function compareObjects(a, b, options) {
1884
2045
  hasThrown = true;
1885
2046
  }
1886
2047
  const noDiffMessage = getCommonMessage(NO_DIFF_MESSAGE, options);
2048
+ // If the comparison yields no results, compare again but this time
2049
+ // without calling `toJSON`. It's also possible that toJSON might throw.
1887
2050
  if (difference === undefined || difference === noDiffMessage) {
1888
2051
  const formatOptions = getFormatOptions(FALLBACK_FORMAT_OPTIONS, options);
1889
2052
  difference = getObjectsDifference(a, b, formatOptions, options);
@@ -1940,13 +2103,24 @@ function printDiffOrStringify(received, expected, options) {
1940
2103
  const receivedLine = printLabel(bAnnotation) + printReceived(getCommonAndChangedSubstrings(diffs, DIFF_INSERT, hasCommonDiff));
1941
2104
  return `${expectedLine}\n${receivedLine}`;
1942
2105
  }
2106
+ // if (isLineDiffable(expected, received)) {
1943
2107
  const clonedExpected = deepClone(expected, { forceWritable: true });
1944
2108
  const clonedReceived = deepClone(received, { forceWritable: true });
1945
2109
  const { replacedExpected, replacedActual } = replaceAsymmetricMatcher(clonedReceived, clonedExpected);
1946
2110
  const difference = diff(replacedExpected, replacedActual, options);
1947
2111
  return difference;
2112
+ // }
2113
+ // const printLabel = getLabelPrinter(aAnnotation, bAnnotation)
2114
+ // const expectedLine = printLabel(aAnnotation) + printExpected(expected)
2115
+ // const receivedLine
2116
+ // = printLabel(bAnnotation)
2117
+ // + (stringify(expected) === stringify(received)
2118
+ // ? 'serializes to the same string'
2119
+ // : printReceived(received))
2120
+ // return `${expectedLine}\n${receivedLine}`
1948
2121
  }
1949
2122
  function replaceAsymmetricMatcher(actual, expected, actualReplaced = new WeakSet(), expectedReplaced = new WeakSet()) {
2123
+ // handle asymmetric Error.cause diff
1950
2124
  if (actual instanceof Error && expected instanceof Error && typeof actual.cause !== "undefined" && typeof expected.cause === "undefined") {
1951
2125
  delete actual.cause;
1952
2126
  return {
package/dist/error.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { D as DiffOptions } from './types.d-BCElaP-c.js';
2
2
  import '@vitest/pretty-format';
3
3
 
4
+ // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
4
5
  declare function serializeValue(val: any, seen?: WeakMap<WeakKey, any>): any;
5
6
 
6
7
  declare function processError(_err: any, diffOptions?: DiffOptions, seen?: WeakSet<WeakKey>): any;
package/dist/error.js CHANGED
@@ -20,10 +20,29 @@ function getUnserializableMessage(err) {
20
20
  }
21
21
  return "<unserializable>";
22
22
  }
23
+ // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
23
24
  function serializeValue(val, seen = new WeakMap()) {
24
25
  if (!val || typeof val === "string") {
25
26
  return val;
26
27
  }
28
+ if (val instanceof Error && "toJSON" in val && typeof val.toJSON === "function") {
29
+ const jsonValue = val.toJSON();
30
+ if (jsonValue && jsonValue !== val && typeof jsonValue === "object") {
31
+ if (typeof val.message === "string") {
32
+ safe(() => jsonValue.message ?? (jsonValue.message = val.message));
33
+ }
34
+ if (typeof val.stack === "string") {
35
+ safe(() => jsonValue.stack ?? (jsonValue.stack = val.stack));
36
+ }
37
+ if (typeof val.name === "string") {
38
+ safe(() => jsonValue.name ?? (jsonValue.name = val.name));
39
+ }
40
+ if (val.cause != null) {
41
+ safe(() => jsonValue.cause ?? (jsonValue.cause = serializeValue(val.cause, seen)));
42
+ }
43
+ }
44
+ return serializeValue(jsonValue, seen);
45
+ }
27
46
  if (typeof val === "function") {
28
47
  return `Function<${val.name || "anonymous"}>`;
29
48
  }
@@ -33,6 +52,7 @@ function serializeValue(val, seen = new WeakMap()) {
33
52
  if (typeof val !== "object") {
34
53
  return val;
35
54
  }
55
+ // cannot serialize immutables as immutables
36
56
  if (isImmutable(val)) {
37
57
  return serializeValue(val.toJSON(), seen);
38
58
  }
@@ -52,6 +72,7 @@ function serializeValue(val, seen = new WeakMap()) {
52
72
  return seen.get(val);
53
73
  }
54
74
  if (Array.isArray(val)) {
75
+ // eslint-disable-next-line unicorn/no-new-array -- we need to keep sparse arrays ([1,,3])
55
76
  const clone = new Array(val.length);
56
77
  seen.set(val, clone);
57
78
  val.forEach((e, i) => {
@@ -63,6 +84,8 @@ function serializeValue(val, seen = new WeakMap()) {
63
84
  });
64
85
  return clone;
65
86
  } else {
87
+ // Objects with `Error` constructors appear to cause problems during worker communication
88
+ // using `MessagePort`, so the serialized error object is being recreated as plain object.
66
89
  const clone = Object.create(null);
67
90
  seen.set(val, clone);
68
91
  let obj = val;
@@ -74,6 +97,7 @@ function serializeValue(val, seen = new WeakMap()) {
74
97
  try {
75
98
  clone[key] = serializeValue(val[key], seen);
76
99
  } catch (err) {
100
+ // delete in case it has a setter from prototype that might throw
77
101
  delete clone[key];
78
102
  clone[key] = getUnserializableMessage(err);
79
103
  }
@@ -83,6 +107,11 @@ function serializeValue(val, seen = new WeakMap()) {
83
107
  return clone;
84
108
  }
85
109
  }
110
+ function safe(fn) {
111
+ try {
112
+ return fn();
113
+ } catch {}
114
+ }
86
115
  function normalizeErrorMessage(message) {
87
116
  return message.replace(/__(vite_ssr_import|vi_import)_\d+__\./g, "");
88
117
  }
@@ -91,29 +120,26 @@ function processError(_err, diffOptions, seen = new WeakSet()) {
91
120
  return { message: String(_err) };
92
121
  }
93
122
  const err = _err;
94
- if (err.stack) {
95
- err.stackStr = String(err.stack);
96
- }
97
- if (err.name) {
98
- err.nameStr = String(err.name);
99
- }
100
123
  if (err.showDiff || err.showDiff === undefined && err.expected !== undefined && err.actual !== undefined) {
101
124
  err.diff = printDiffOrStringify(err.actual, err.expected, {
102
125
  ...diffOptions,
103
126
  ...err.diffOptions
104
127
  });
105
128
  }
106
- if (typeof err.expected !== "string") {
129
+ if ("expected" in err && typeof err.expected !== "string") {
107
130
  err.expected = stringify(err.expected, 10);
108
131
  }
109
- if (typeof err.actual !== "string") {
132
+ if ("actual" in err && typeof err.actual !== "string") {
110
133
  err.actual = stringify(err.actual, 10);
111
134
  }
135
+ // some Error implementations don't allow rewriting message
112
136
  try {
113
137
  if (typeof err.message === "string") {
114
138
  err.message = normalizeErrorMessage(err.message);
115
139
  }
116
140
  } catch {}
141
+ // some Error implementations may not allow rewriting cause
142
+ // in most cases, the assignment will lead to "err.cause = err.cause"
117
143
  try {
118
144
  if (!seen.has(err) && typeof err.cause === "object") {
119
145
  seen.add(err);
package/dist/helpers.d.ts CHANGED
@@ -17,6 +17,7 @@ declare function notNullish<T>(v: T | null | undefined): v is NonNullable<T>;
17
17
  declare function assertTypes(value: unknown, name: string, types: string[]): void;
18
18
  declare function isPrimitive(value: unknown): boolean;
19
19
  declare function slash(path: string): string;
20
+ // convert RegExp.toString to RegExp
20
21
  declare function parseRegexp(input: string): RegExp;
21
22
  declare function toArray<T>(array?: Nullable<Arrayable<T>>): Array<T>;
22
23
  declare function isObject(item: unknown): boolean;
package/dist/helpers.js CHANGED
@@ -31,14 +31,21 @@ function isPrimitive(value) {
31
31
  function slash(path) {
32
32
  return path.replace(/\\/g, "/");
33
33
  }
34
+ // convert RegExp.toString to RegExp
34
35
  function parseRegexp(input) {
36
+ // Parse input
37
+ // eslint-disable-next-line regexp/no-misleading-capturing-group
35
38
  const m = input.match(/(\/?)(.+)\1([a-z]*)/i);
39
+ // match nothing
36
40
  if (!m) {
37
41
  return /$^/;
38
42
  }
43
+ // Invalid flags
44
+ // eslint-disable-next-line regexp/optimal-quantifier-concatenation
39
45
  if (m[3] && !/^(?!.*?(.).*?\1)[gmixXsuUAJ]+$/.test(m[3])) {
40
46
  return new RegExp(input);
41
47
  }
48
+ // Create the regular expression
42
49
  return new RegExp(m[2], m[3]);
43
50
  }
44
51
  function toArray(array) {
@@ -93,6 +100,7 @@ function clone(val, seen, options = defaultCloneOptions) {
93
100
  if (Object.prototype.toString.call(val) === "[object Object]") {
94
101
  out = Object.create(Object.getPrototypeOf(val));
95
102
  seen.set(val, out);
103
+ // we don't need properties from prototype
96
104
  const props = getOwnProperties(val);
97
105
  for (const k of props) {
98
106
  const descriptor = Object.getOwnPropertyDescriptor(val, k);
@@ -127,6 +135,7 @@ function clone(val, seen, options = defaultCloneOptions) {
127
135
  }
128
136
  function noop() {}
129
137
  function objectAttr(source, path, defaultValue = undefined) {
138
+ // a[3].b -> a.3.b
130
139
  const paths = path.replace(/\[(\d+)\]/g, ".$1").split(".");
131
140
  let result = source;
132
141
  for (const p of paths) {
package/dist/index.js CHANGED
@@ -539,6 +539,8 @@ function highlight(code, options = { jsx: false }) {
539
539
  });
540
540
  }
541
541
 
542
+ // port from nanoid
543
+ // https://github.com/ai/nanoid
542
544
  const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
543
545
  function nanoid(size = 21) {
544
546
  let id = "";
@@ -128,6 +128,8 @@ interface StackTraceParserOptions {
128
128
  }
129
129
  declare function parseSingleFFOrSafariStack(raw: string): ParsedStack | null;
130
130
  declare function parseSingleStack(raw: string): ParsedStack | null;
131
+ // Based on https://github.com/stacktracejs/error-stack-parser
132
+ // Credit to stacktracejs
131
133
  declare function parseSingleV8Stack(raw: string): ParsedStack | null;
132
134
  declare function createStackString(stacks: ParsedStack[]): string;
133
135
  declare function parseStacktrace(stack: string, options?: StackTraceParserOptions): ParsedStack[];
@@ -806,6 +806,7 @@ const stackIgnorePatterns = [
806
806
  /\/deps\/vitest_/
807
807
  ];
808
808
  function extractLocation(urlLike) {
809
+ // Fail-fast but return locations like "(native)"
809
810
  if (!urlLike.includes(":")) {
810
811
  return [urlLike];
811
812
  }
@@ -845,6 +846,7 @@ function parseSingleFFOrSafariStack(raw) {
845
846
  if (!line.includes("@") && !line.includes(":")) {
846
847
  return null;
847
848
  }
849
+ // eslint-disable-next-line regexp/no-super-linear-backtracking, regexp/optimal-quantifier-concatenation
848
850
  const functionNameRegex = /((.*".+"[^@]*)?[^@]*)(@)/;
849
851
  const matches = line.match(functionNameRegex);
850
852
  const functionName = matches && matches[1] ? matches[1] : undefined;
@@ -866,6 +868,8 @@ function parseSingleStack(raw) {
866
868
  }
867
869
  return parseSingleV8Stack(line);
868
870
  }
871
+ // Based on https://github.com/stacktracejs/error-stack-parser
872
+ // Credit to stacktracejs
869
873
  function parseSingleV8Stack(raw) {
870
874
  let line = raw.trim();
871
875
  if (!CHROME_IE_STACK_REGEXP.test(line)) {
@@ -875,8 +879,13 @@ function parseSingleV8Stack(raw) {
875
879
  line = line.replace(/eval code/g, "eval").replace(/(\(eval at [^()]*)|(,.*$)/g, "");
876
880
  }
877
881
  let sanitizedLine = line.replace(/^\s+/, "").replace(/\(eval code/g, "(").replace(/^.*?\s+/, "");
882
+ // capture and preserve the parenthesized location "(/foo/my bar.js:12:87)" in
883
+ // case it has spaces in it, as the string is split on \s+ later on
878
884
  const location = sanitizedLine.match(/ (\(.+\)$)/);
885
+ // remove the parenthesized location from the line, if it was matched
879
886
  sanitizedLine = location ? sanitizedLine.replace(location[0], "") : sanitizedLine;
887
+ // if a location was matched, pass it to extractLocation() otherwise pass all sanitizedLine
888
+ // because this line doesn't have function name
880
889
  const [url, lineNumber, columnNumber] = extractLocation(location ? location[1] : sanitizedLine);
881
890
  let method = location && sanitizedLine || "";
882
891
  let file = url && ["eval", "<anonymous>"].includes(url) ? undefined : url;
@@ -889,6 +898,7 @@ function parseSingleV8Stack(raw) {
889
898
  if (file.startsWith("file://")) {
890
899
  file = file.slice(7);
891
900
  }
901
+ // normalize Windows path (\ -> /)
892
902
  file = file.startsWith("node:") || file.startsWith("internal:") ? file : resolve(file);
893
903
  if (method) {
894
904
  method = method.replace(/__vite_ssr_import_\d+__\./g, "");
@@ -928,6 +938,10 @@ function parseStacktrace(stack, options = {}) {
928
938
  const fileUrl = stack.file.startsWith("file://") ? stack.file : `file://${stack.file}`;
929
939
  const sourceRootUrl = map.sourceRoot ? new URL(map.sourceRoot, fileUrl) : fileUrl;
930
940
  file = new URL(source, sourceRootUrl).pathname;
941
+ // if the file path is on windows, we need to remove the leading slash
942
+ if (file.match(/\/\w:\//)) {
943
+ file = file.slice(1);
944
+ }
931
945
  }
932
946
  if (shouldFilter(ignoreStackEntries, file)) {
933
947
  return null;
@@ -959,8 +973,10 @@ function parseErrorStacktrace(e, options = {}) {
959
973
  if (e.stacks) {
960
974
  return e.stacks;
961
975
  }
962
- const stackStr = e.stack || e.stackStr || "";
963
- let stackFrames = parseStacktrace(stackStr, options);
976
+ const stackStr = e.stack || "";
977
+ // if "stack" property was overwritten at runtime to be something else,
978
+ // ignore the value because we don't know how to process it
979
+ let stackFrames = typeof stackStr === "string" ? parseStacktrace(stackStr, options) : [];
964
980
  if (!stackFrames.length) {
965
981
  const e_ = e;
966
982
  if (e_.fileName != null && e_.lineNumber != null && e_.columnNumber != null) {
package/dist/types.d.ts CHANGED
@@ -38,9 +38,7 @@ interface ErrorWithDiff {
38
38
  message: string;
39
39
  name?: string;
40
40
  cause?: unknown;
41
- nameStr?: string;
42
41
  stack?: string;
43
- stackStr?: string;
44
42
  stacks?: ParsedStack[];
45
43
  showDiff?: boolean;
46
44
  actual?: any;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vitest/utils",
3
3
  "type": "module",
4
- "version": "3.2.0-beta.2",
4
+ "version": "3.2.0",
5
5
  "description": "Shared Vitest utility functions",
6
6
  "license": "MIT",
7
7
  "funding": "https://opencollective.com/vitest",
@@ -62,7 +62,7 @@
62
62
  "dependencies": {
63
63
  "loupe": "^3.1.3",
64
64
  "tinyrainbow": "^2.0.0",
65
- "@vitest/pretty-format": "3.2.0-beta.2"
65
+ "@vitest/pretty-format": "3.2.0"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@jridgewell/trace-mapping": "^0.3.25",