| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent ba5b659 commit 7864ad1
2 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1315,13 +1315,122 @@ function identicalSequenceRange(a, b) { | |||
| 1315 | 1315 | len++; | |
| 1316 | 1316 | } | |
| 1317 | 1317 | if (len > 3) { | |
| 1318 | - return { len, offset: i }; | ||
| 1318 | + return [len, i]; | ||
| 1319 | 1319 | } | |
| 1320 | 1320 | } | |
| 1321 | 1321 | } | |
| 1322 | 1322 | } | |
| 1323 | 1323 | ||
| 1324 | - return { len: 0, offset: 0 }; | ||
| 1324 | + return [0, 0]; | ||
| 1325 | + } | ||
| 1326 | + | ||
| 1327 | + function getDuplicateErrorFrameRanges(frames) { | ||
| 1328 | + // Build a map: frame line -> sorted list of indices where it occurs | ||
| 1329 | + const result = []; | ||
| 1330 | + const lineToPositions = new SafeMap(); | ||
| 1331 | + | ||
| 1332 | + for (let i = 0; i < frames.length; i++) { | ||
| 1333 | + const positions = lineToPositions.get(frames[i]); | ||
| 1334 | + if (positions === undefined) { | ||
| 1335 | + lineToPositions.set(frames[i], [i]); | ||
| 1336 | + } else { | ||
| 1337 | + positions[positions.length] = i; | ||
| 1338 | + } | ||
| 1339 | + } | ||
| 1340 | + | ||
| 1341 | + const minimumDuplicateRange = 3; | ||
| 1342 | + // Not enough duplicate lines to consider collapsing | ||
| 1343 | + if (frames.length - lineToPositions.size <= minimumDuplicateRange) { | ||
| 1344 | + return result; | ||
| 1345 | + } | ||
| 1346 | + | ||
| 1347 | + for (let i = 0; i < frames.length - minimumDuplicateRange; i++) { | ||
| 1348 | + const positions = lineToPositions.get(frames[i]); | ||
| 1349 | + // Find the next occurrence of the same line after i, if any | ||
| 1350 | + if (positions.length === 1 || positions[positions.length - 1] === i) { | ||
| 1351 | + continue; | ||
| 1352 | + } | ||
| 1353 | + | ||
| 1354 | + const current = positions.indexOf(i) + 1; | ||
| 1355 | + if (current === positions.length) { | ||
| 1356 | + continue; | ||
| 1357 | + } | ||
| 1358 | + | ||
| 1359 | + // Theoretical maximum range, adjusted while iterating | ||
| 1360 | + let range = positions[positions.length - 1] - i; | ||
| 1361 | + if (range < minimumDuplicateRange) { | ||
| 1362 | + continue; | ||
| 1363 | + } | ||
| 1364 | + let extraSteps; | ||
| 1365 | + if (current + 1 < positions.length) { | ||
| 1366 | + // Optimize initial step size by choosing the greatest common divisor (GCD) | ||
| 1367 | + // of all candidate distances to the same frame line. This tends to match | ||
| 1368 | + // the true repeating block size and minimizes fallback iterations. | ||
| 1369 | + let gcdRange = 0; | ||
| 1370 | + for (let j = current; j < positions.length; j++) { | ||
| 1371 | + let distance = positions[j] - i; | ||
| 1372 | + while (distance !== 0) { | ||
| 1373 | + const remainder = gcdRange % distance; | ||
| 1374 | + if (gcdRange !== 0) { | ||
| 1375 | + // Add other possible ranges as fallback | ||
| 1376 | + extraSteps ??= new SafeSet(); | ||
| 1377 | + extraSteps.add(gcdRange); | ||
| 1378 | + } | ||
| 1379 | + gcdRange = distance; | ||
| 1380 | + distance = remainder; | ||
| 1381 | + } | ||
| 1382 | + if (gcdRange === 1) break; | ||
| 1383 | + } | ||
| 1384 | + range = gcdRange; | ||
| 1385 | + if (extraSteps) { | ||
| 1386 | + extraSteps.delete(range); | ||
| 1387 | + extraSteps = [...extraSteps]; | ||
| 1388 | + } | ||
| 1389 | + } | ||
| 1390 | + let maxRange = range; | ||
| 1391 | + let maxDuplicates = 0; | ||
| 1392 | + | ||
| 1393 | + let duplicateRanges = 0; | ||
| 1394 | + | ||
| 1395 | + for (let nextStart = i + range; /* ignored */ ; nextStart += range) { | ||
| 1396 | + let equalFrames = 0; | ||
| 1397 | + for (let j = 0; j < range; j++) { | ||
| 1398 | + if (frames[i + j] !== frames[nextStart + j]) { | ||
| 1399 | + break; | ||
| 1400 | + } | ||
| 1401 | + equalFrames++; | ||
| 1402 | + } | ||
| 1403 | + // Adjust the range to match different type of ranges. | ||
| 1404 | + if (equalFrames !== range) { | ||
| 1405 | + if (!extraSteps?.length) { | ||
| 1406 | + break; | ||
| 1407 | + } | ||
| 1408 | + // Memorize former range in case the smaller one would hide less. | ||
| 1409 | + if (duplicateRanges !== 0 && maxRange * maxDuplicates < range * duplicateRanges) { | ||
| 1410 | + maxRange = range; | ||
| 1411 | + maxDuplicates = duplicateRanges; | ||
| 1412 | + } | ||
| 1413 | + range = extraSteps.pop(); | ||
| 1414 | + nextStart = i; | ||
| 1415 | + duplicateRanges = 0; | ||
| 1416 | + continue; | ||
| 1417 | + } | ||
| 1418 | + duplicateRanges++; | ||
| 1419 | + } | ||
| 1420 | + | ||
| 1421 | + if (maxDuplicates !== 0 && maxRange * maxDuplicates >= range * duplicateRanges) { | ||
| 1422 | + range = maxRange; | ||
| 1423 | + duplicateRanges = maxDuplicates; | ||
| 1424 | + } | ||
| 1425 | + | ||
| 1426 | + if (duplicateRanges * range >= 3) { | ||
| 1427 | + result.push(i + range, range, duplicateRanges); | ||
| 1428 | + // Skip over the collapsed portion to avoid overlapping matches. | ||
| 1429 | + i += range * (duplicateRanges + 1) - 1; | ||
| 1430 | + } | ||
| 1431 | + } | ||
| 1432 | + | ||
| 1433 | + return result; | ||
| 1325 | 1434 | } | |
| 1326 | 1435 | ||
| 1327 | 1436 | function getStackString(ctx, error) { | |
@@ -1355,14 +1464,33 @@ function getStackFrames(ctx, err, stack) { | |||
| 1355 | 1464 | const causeStackStart = StringPrototypeIndexOf(causeStack, '\n at'); | |
| 1356 | 1465 | if (causeStackStart !== -1) { | |
| 1357 | 1466 | const causeFrames = StringPrototypeSplit(StringPrototypeSlice(causeStack, causeStackStart + 1), '\n'); | |
| 1358 | - const { len, offset } = identicalSequenceRange(frames, causeFrames); | ||
| 1467 | + const { 0: len, 1: offset } = identicalSequenceRange(frames, causeFrames); | ||
| 1359 | 1468 | if (len > 0) { | |
| 1360 | 1469 | const skipped = len - 2; | |
| 1361 | 1470 | const msg = ` ... ${skipped} lines matching cause stack trace ...`; | |
| 1362 | 1471 | frames.splice(offset + 1, skipped, ctx.stylize(msg, 'undefined')); | |
| 1363 | 1472 | } | |
| 1364 | 1473 | } | |
| 1365 | 1474 | } | |
| 1475 | + | ||
| 1476 | + // Remove recursive repetitive stack frames in long stacks | ||
| 1477 | + if (frames.length > 10) { | ||
| 1478 | + const ranges = getDuplicateErrorFrameRanges(frames); | ||
| 1479 | + | ||
| 1480 | + for (let i = ranges.length - 3; i >= 0; i -= 3) { | ||
| 1481 | + const offset = ranges[i]; | ||
| 1482 | + const length = ranges[i + 1]; | ||
| 1483 | + const duplicateRanges = ranges[i + 2]; | ||
| 1484 | + | ||
| 1485 | + const msg = ` ... collapsed ${length * duplicateRanges} duplicate lines ` + | ||
| 1486 | + 'matching above ' + | ||
| 1487 | + (duplicateRanges > 1 ? | ||
| 1488 | + `${length} lines ${duplicateRanges} times...` : | ||
| 1489 | + 'lines ...'); | ||
| 1490 | + frames.splice(offset, length * duplicateRanges, ctx.stylize(msg, 'undefined')); | ||
| 1491 | + } | ||
| 1492 | + } | ||
| 1493 | + | ||
| 1366 | 1494 | return frames; | |
| 1367 | 1495 | } | |
| 1368 | 1496 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2918,6 +2918,175 @@ assert.strictEqual( | |||
| 2918 | 2918 | process.cwd = originalCWD; | |
| 2919 | 2919 | } | |
| 2920 | 2920 | ||
| 2921 | + { | ||
| 2922 | + // Use a fake stack to verify the expected colored outcome. | ||
| 2923 | + const err = new Error('Hide duplicate frames in long stack'); | ||
| 2924 | + err.stack = [ | ||
| 2925 | + 'Error: Hide duplicate frames in long stack', | ||
| 2926 | + ' at A.<anonymous> (/foo/node_modules/bar/baz.js:2:7)', | ||
| 2927 | + ' at A.<anonymous> (/foo/node_modules/bar/baz.js:2:7)', | ||
| 2928 | + ' at Module._compile (node:internal/modules/cjs/loader:827:30)', | ||
| 2929 | + ' at Fancy (node:vm:697:32)', | ||
| 2930 | + ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)', | ||
| 2931 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 2932 | + ' at Fancy (node:vm:697:32)', | ||
| 2933 | + ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)', | ||
| 2934 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 2935 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 2936 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 2937 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 2938 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 2939 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 2940 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 2941 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', | ||
| 2942 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 2943 | + ' at Array.forEach (<anonymous>)', | ||
| 2944 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 2945 | + ' at Array.forEach (<anonymous>)', | ||
| 2946 | + ` at foobar/test/parallel/test-util-inspect.js:2760:12`, | ||
| 2947 | + ` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`, | ||
| 2948 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', | ||
| 2949 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 2950 | + ' at Array.forEach (<anonymous>)', | ||
| 2951 | + ` at foobar/test/parallel/test-util-inspect.js:2760:12`, | ||
| 2952 | + ` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`, | ||
| 2953 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', | ||
| 2954 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 2955 | + ' at Array.forEach (<anonymous>)', | ||
| 2956 | + ` at foobar/test/parallel/test-util-inspect.js:2760:12`, | ||
| 2957 | + ` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`, | ||
| 2958 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', | ||
| 2959 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 2960 | + ' at Array.forEach (<anonymous>)', | ||
| 2961 | + ` at foobar/test/parallel/test-util-inspect.js:2760:12`, | ||
| 2962 | + ` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`, | ||
| 2963 | + ' at /test/test-util-inspect.js:2239:9', | ||
| 2964 | + ' at getActual (node:assert:592:5)', | ||
| 2965 | + ' at /test/test-util-inspect.js:2239:9', | ||
| 2966 | + ' at getActual (node:assert:592:5)', | ||
| 2967 | + ' at /test/test-util-inspect.js:2239:9', | ||
| 2968 | + ' at getActual (node:assert:592:5)', | ||
| 2969 | + ].join('\n'); | ||
| 2970 | + | ||
| 2971 | + assert.strictEqual( | ||
| 2972 | + util.inspect(err, { colors: true }), | ||
| 2973 | + 'Error: Hide duplicate frames in long stack\n' + | ||
| 2974 | + ' at A.<anonymous> (/foo/node_modules/\x1B[4mbar\x1B[24m/baz.js:2:7)\n' + | ||
| 2975 | + ' at A.<anonymous> (/foo/node_modules/\x1B[4mbar\x1B[24m/baz.js:2:7)\n' + | ||
| 2976 | + '\x1B[90m at Module._compile (node:internal/modules/cjs/loader:827:30)\x1B[39m\n' + | ||
| 2977 | + '\x1B[90m at Fancy (node:vm:697:32)\x1B[39m\n' + | ||
| 2978 | + ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)\n' + | ||
| 2979 | + '\x1B[90m at Function.Module._load (node:internal/modules/cjs/loader:621:3)\x1B[39m\n' + | ||
| 2980 | + '\x1B[90m ... collapsed 3 duplicate lines matching above lines ...\x1B[39m\n' + | ||
| 2981 | + | ||
| 2982 | + '\x1B[90m at Function.Module._load (node:internal/modules/cjs/loader:621:3)\x1B[39m\n' + | ||
| 2983 | + '\x1B[90m ... collapsed 5 duplicate lines matching above 1 lines 5 times...\x1B[39m\n' + | ||
| 2984 | + | ||
| 2985 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)\n' + | ||
| 2986 | + '\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' + | ||
| 2987 | + ' at Array.forEach (<anonymous>)\n' + | ||
| 2988 | + '\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' + | ||
| 2989 | + ' at Array.forEach (<anonymous>)\n' + | ||
| 2990 | + ' at foobar/test/parallel/test-util-inspect.js:2760:12\n' + | ||
| 2991 | + ' at Object.<anonymous> (foobar/node_modules/\x1B[4mm\x1B[24m/folder/file.js:2753:10)\n' + | ||
| 2992 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)\n' + | ||
| 2993 | + '\x1B[90m ... collapsed 10 duplicate lines matching above 5 lines 2 times...\x1B[39m\n' + | ||
| 2994 | + | ||
| 2995 | + '\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' + | ||
| 2996 | + ' at Array.forEach (<anonymous>)\n' + | ||
| 2997 | + ' at foobar/test/parallel/test-util-inspect.js:2760:12\n' + | ||
| 2998 | + ' at Object.<anonymous> (foobar/node_modules/\x1B[4mm\x1B[24m/folder/file.js:2753:10)\n' + | ||
| 2999 | + ' at /test/test-util-inspect.js:2239:9\n' + | ||
| 3000 | + '\x1B[90m at getActual (node:assert:592:5)\x1B[39m\n' + | ||
| 3001 | + '\x1B[90m ... collapsed 4 duplicate lines matching above 2 lines 2 times...\x1B[39m', | ||
| 3002 | + ); | ||
| 3003 | + | ||
| 3004 | + // Use a fake stack to verify the expected colored outcome. | ||
| 3005 | + const err2 = new Error('Hide duplicate frames in long stack'); | ||
| 3006 | + err2.stack = [ | ||
| 3007 | + 'Error: Hide duplicate frames in long stack', | ||
| 3008 | + ' at A.<anonymous> (/foo/node_modules/bar/baz.js:2:7)', | ||
| 3009 | + ' at A.<anonymous> (/foo/node_modules/bar/baz.js:2:7)', | ||
| 3010 | + ' at Module._compile (node:internal/modules/cjs/loader:827:30)', | ||
| 3011 | + | ||
| 3012 | + // 3 | ||
| 3013 | + ' at Fancy (node:vm:697:32)', | ||
| 3014 | + ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)', | ||
| 3015 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3016 | + ' at Fancy (node:vm:697:32)', | ||
| 3017 | + ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)', | ||
| 3018 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3019 | + | ||
| 3020 | + // 6 * 1 | ||
| 3021 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3022 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3023 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3024 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3025 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3026 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3027 | + ' at Function.Module._load (node:internal/modules/cjs/loader:621:3)', | ||
| 3028 | + | ||
| 3029 | + // 10 | ||
| 3030 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 3031 | + ' at Array.forEach (<anonymous>)', | ||
| 3032 | + ` at foobar/test/parallel/test-util-inspect.js:2760:12`, | ||
| 3033 | + ` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`, | ||
| 3034 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', | ||
| 3035 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', | ||
| 3036 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 3037 | + ' at Array.forEach (<anonymous>)', | ||
| 3038 | + ` at foobar/test/parallel/test-util-inspect.js:2760:12`, | ||
| 3039 | + ` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`, | ||
| 3040 | + | ||
| 3041 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 3042 | + ' at Array.forEach (<anonymous>)', | ||
| 3043 | + ` at foobar/test/parallel/test-util-inspect.js:2760:12`, | ||
| 3044 | + ` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`, | ||
| 3045 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', | ||
| 3046 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)', | ||
| 3047 | + ' at require (node:internal/modules/helpers:14:16)', | ||
| 3048 | + ' at Array.forEach (<anonymous>)', | ||
| 3049 | + ` at foobar/test/parallel/test-util-inspect.js:2760:12`, | ||
| 3050 | + ` at Object.<anonymous> (foobar/node_modules/m/folder/file.js:2753:10)`, | ||
| 3051 | + | ||
| 3052 | + // 2 * 2 | ||
| 3053 | + ' at /test/test-util-inspect.js:2239:9', | ||
| 3054 | + ' at getActual (node:assert:592:5)', | ||
| 3055 | + ' at /test/test-util-inspect.js:2239:9', | ||
| 3056 | + ' at getActual (node:assert:592:5)', | ||
| 3057 | + ' at /test/test-util-inspect.js:2239:9', | ||
| 3058 | + ' at getActual (node:assert:592:5)', | ||
| 3059 | + ].join('\n'); | ||
| 3060 | + | ||
| 3061 | + assert.strictEqual( | ||
| 3062 | + util.inspect(err2, { colors: true }), | ||
| 3063 | + 'Error: Hide duplicate frames in long stack\n' + | ||
| 3064 | + ' at A.<anonymous> (/foo/node_modules/\x1B[4mbar\x1B[24m/baz.js:2:7)\n' + | ||
| 3065 | + ' at A.<anonymous> (/foo/node_modules/\x1B[4mbar\x1B[24m/baz.js:2:7)\n' + | ||
| 3066 | + '\x1B[90m at Module._compile (node:internal/modules/cjs/loader:827:30)\x1B[39m\n' + | ||
| 3067 | + '\x1B[90m at Fancy (node:vm:697:32)\x1B[39m\n' + | ||
| 3068 | + ' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)\n' + | ||
| 3069 | + '\x1B[90m at Function.Module._load (node:internal/modules/cjs/loader:621:3)\x1B[39m\n' + | ||
| 3070 | + '\x1B[90m ... collapsed 3 duplicate lines matching above lines ...\x1B[39m\n' + | ||
| 3071 | + '\x1B[90m at Function.Module._load (node:internal/modules/cjs/loader:621:3)\x1B[39m\n' + | ||
| 3072 | + '\x1B[90m ... collapsed 6 duplicate lines matching above 1 lines 6 times...\x1B[39m\n' + | ||
| 3073 | + '\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' + | ||
| 3074 | + ' at Array.forEach (<anonymous>)\n' + | ||
| 3075 | + ' at foobar/test/parallel/test-util-inspect.js:2760:12\n' + | ||
| 3076 | + ' at Object.<anonymous> (foobar/node_modules/\x1B[4mm\x1B[24m/folder/file.js:2753:10)\n' + | ||
| 3077 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)\n' + | ||
| 3078 | + ' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)\n' + | ||
| 3079 | + '\x1B[90m at require (node:internal/modules/helpers:14:16)\x1B[39m\n' + | ||
| 3080 | + ' at Array.forEach (<anonymous>)\n' + | ||
| 3081 | + ' at foobar/test/parallel/test-util-inspect.js:2760:12\n' + | ||
| 3082 | + ' at Object.<anonymous> (foobar/node_modules/\x1B[4mm\x1B[24m/folder/file.js:2753:10)\n' + | ||
| 3083 | + '\x1B[90m ... collapsed 10 duplicate lines matching above lines ...\x1B[39m\n' + | ||
| 3084 | + ' at /test/test-util-inspect.js:2239:9\n' + | ||
| 3085 | + '\x1B[90m at getActual (node:assert:592:5)\x1B[39m\n' + | ||
| 3086 | + '\x1B[90m ... collapsed 4 duplicate lines matching above 2 lines 2 times...\x1B[39m', | ||
| 3087 | + ); | ||
| 3088 | + } | ||
| 3089 | + | ||
| 2921 | 3090 | { | |
| 2922 | 3091 | // Cross platform checks. | |
| 2923 | 3092 | const err = new Error('foo'); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments