@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.
- package/dist/chunk-_commonjsHelpers.js +1 -0
- package/dist/diff.d.ts +11 -0
- package/dist/diff.js +174 -0
- package/dist/error.d.ts +1 -0
- package/dist/error.js +34 -8
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +9 -0
- package/dist/index.js +2 -0
- package/dist/source-map.d.ts +2 -0
- package/dist/source-map.js +18 -2
- package/dist/types.d.ts +0 -2
- package/package.json +2 -2
@@ -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 = "";
|
package/dist/source-map.d.ts
CHANGED
@@ -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[];
|
package/dist/source-map.js
CHANGED
@@ -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 ||
|
963
|
-
|
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
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@vitest/utils",
|
3
3
|
"type": "module",
|
4
|
-
"version": "3.2.0
|
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
|
65
|
+
"@vitest/pretty-format": "3.2.0"
|
66
66
|
},
|
67
67
|
"devDependencies": {
|
68
68
|
"@jridgewell/trace-mapping": "^0.3.25",
|