Skip to content

Commit ab88523

Browse files
committed
Core: Exclude or grey internal frames from stack traces in TAP reporter
Cherry-picked from 9fed286 (3.0.0-dev): > Core: Exclude or grey internal frames from stack traces in TAP reporter > Internal frames are those from qunit.js, or Node.js runtime. > > * Remove any internal frames from the top of the stack. > * Grey out later internal frames anywhere in the stack. > > This change is applied to the TAP reporter, which the QUnit CLI uses > by default. Cherry-picked from 95105aa (3.0.0-dev): > Fix fragile code in stracktrace.js that previously worked only because > of Babel transformations masking a violation of the Temporal Dead Zone > between `const fileName` and the functions it uses to compute that > value.
1 parent 6abf4de commit ab88523

File tree

2 files changed

+75
-16
lines changed

2 files changed

+75
-16
lines changed

src/core/stacktrace.js

Lines changed: 73 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
//
33
// This should reduce a raw stack trace like this:
44
//
5-
// > foo.broken()@/src/foo.js
6-
// > Bar@/src/bar.js
5+
// > foo.broken()@/example/foo.js
6+
// > Bar@/example/bar.js
77
// > @/test/bar.test.js
88
// > @/lib/qunit.js:500:12
99
// > @/lib/qunit.js:100:28
@@ -13,8 +13,8 @@
1313
//
1414
// and shorten it to show up until the end of the user's bar.test.js code.
1515
//
16-
// > foo.broken()@/src/foo.js
17-
// > Bar@/src/bar.js
16+
// > foo.broken()@/example/foo.js
17+
// > Bar@/example/bar.js
1818
// > @/test/bar.test.js
1919
//
2020
// QUnit will obtain one example trace (once per process/pageload suffices),
@@ -31,22 +31,79 @@
3131
//
3232
// See also:
3333
// - https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
34-
//
35-
const fileName = (sourceFromStacktrace(0) || '')
36-
// Global replace, because a frame like localhost:4000/lib/qunit.js:1234:50,
37-
// would otherwise (harmlessly, but uselessly) remove only the port (first match).
38-
// https://github.com/qunitjs/qunit/issues/1769
39-
.replace(/(:\d+)+\)?/g, '')
40-
// Remove anything prior to the last slash (Unix/Windows) from the last frame,
41-
// leaving only "qunit.js".
42-
.replace(/.+[/\\]/, '');
34+
35+
function qunitFileName () {
36+
let error = new Error();
37+
if (!error.stack) {
38+
// Copy of sourceFromStacktrace() to avoid circular dependency
39+
// Support: IE 9-11
40+
try {
41+
throw error;
42+
} catch (err) {
43+
error = err;
44+
}
45+
}
46+
return (error.stack || '')
47+
// Copy of extractStacktrace() to avoid circular dependency
48+
// Support: V8/Chrome
49+
.replace(/^error$\n/im, '')
50+
.split('\n')[0]
51+
// Global replace, because a frame like localhost:4000/lib/qunit.js:1234:50,
52+
// would otherwise (harmlessly, but uselessly) remove only the port (first match).
53+
// https://github.com/qunitjs/qunit/issues/1769
54+
.replace(/(:\d+)+\)?/g, '')
55+
// Remove anything prior to the last slash (Unix/Windows) from the last frame,
56+
// leaving only "qunit.js".
57+
.replace(/.+[/\\]/, '');
58+
}
59+
60+
const fileName = qunitFileName();
61+
62+
/**
63+
* Responsibilities:
64+
* - For internal errors from QUnit itself, remove the first qunit.js frames.
65+
* - For errors in Node.js, format any remaining qunit.js and node:internal
66+
* frames as internal (i.e. grey out).
67+
*/
68+
export function annotateStacktrace (e, formatInternal) {
69+
if (!e || !e.stack) {
70+
return String(e);
71+
}
72+
const frames = e.stack.split('\n');
73+
const annotated = [];
74+
if (e.toString().indexOf(frames[0]) !== -1) {
75+
// In Firefox and Safari e.stack starts with frame 0, but in V8 (Chrome/Node.js),
76+
// e.stack starts first stringified message. Preserve this separately,
77+
// so that, below, we can distinguish between internal frames on top
78+
// (to remove) vs later internal frames (to format differently).
79+
annotated.push(frames.shift());
80+
}
81+
let initialInternal = true;
82+
for (let i = 0; i < frames.length; i++) {
83+
const frame = frames[i];
84+
const isInternal = ((fileName && frame.indexOf(fileName) !== -1) || frame.indexOf('node:internal/') !== -1);
85+
if (!isInternal) {
86+
initialInternal = false;
87+
}
88+
// Remove initial internal frames entirely.
89+
if (!initialInternal) {
90+
annotated.push(isInternal ? formatInternal(frame) : frame);
91+
}
92+
}
93+
94+
return annotated.join('\n');
95+
}
4396

4497
export function extractStacktrace (e, offset) {
4598
offset = offset === undefined ? 4 : offset;
4699

47100
// Support: IE9, e.stack is not supported, we will return undefined
48101
if (e && e.stack) {
49102
const stack = e.stack.split('\n');
103+
// In Firefox and Safari, e.stack starts immediately with the first frame.
104+
//
105+
// In V8 (Chrome/Node.js), the stack starts first with a stringified error message,
106+
// and the real stack starting on line 2.
50107
if (/^error$/i.test(stack[0])) {
51108
stack.shift();
52109
}
@@ -69,8 +126,9 @@ export function extractStacktrace (e, offset) {
69126
export function sourceFromStacktrace (offset) {
70127
let error = new Error();
71128

72-
// Support: Safari <=7 only, IE <=10 - 11 only
73-
// Not all browsers generate the `stack` property for `new Error()`, see also #636
129+
// Support: IE 9-11, iOS 7
130+
// Not all browsers generate the `stack` property for `new Error()`
131+
// See also https://github.com/qunitjs/qunit/issues/636
74132
if (!error.stack) {
75133
try {
76134
throw error;

src/reporters/TapReporter.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import kleur from 'kleur';
22
import { errorString } from '../core/utilities';
33
import { console } from '../globals';
4+
import { annotateStacktrace } from '../core/stacktrace';
45

56
/**
67
* Format a given value into YAML.
@@ -276,7 +277,7 @@ export default class TapReporter {
276277
out += `\n message: ${prettyYamlValue(errorString(error))}`;
277278
out += `\n severity: ${prettyYamlValue('failed')}`;
278279
if (error && error.stack) {
279-
out += `\n stack: ${prettyYamlValue(error.stack + '\n')}`;
280+
out += `\n stack: ${prettyYamlValue(annotateStacktrace(error, kleur.grey) + '\n')}`;
280281
}
281282
out += '\n ...';
282283
this.log(out);

0 commit comments

Comments
 (0)