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
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),
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 ( / ^ e r r o r $ \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+ * @param {string } Error#stack
69+ * @param {Function } formatInternal Format a string in an "internal" color
70+ * @param {string|null } [eToString] Error#toString() to help remove
71+ * noise from Node.js/V8 stack traces.
72+ */
73+ export function annotateStacktrace ( stack , formatInternal , eToString = null ) {
74+ const frames = stack . split ( '\n' ) ;
75+ const annotated = [ ] ;
76+ if ( eToString && eToString . indexOf ( frames [ 0 ] ) !== - 1 ) {
77+ // In Firefox and Safari e.stack starts with frame 0, but in V8 (Chrome/Node.js),
78+ // e.stack starts first stringified message. Preserve this separately,
79+ // so that, below, we can distinguish between internal frames on top
80+ // (to remove) vs later internal frames (to format differently).
81+ annotated . push ( frames . shift ( ) ) ;
82+ }
83+ let initialInternal = true ;
84+ for ( let i = 0 ; i < frames . length ; i ++ ) {
85+ const frame = frames [ i ] ;
86+ const isInternal = (
87+ ( fileName && frame . indexOf ( fileName ) !== - 1 )
88+ // Support Node 16+: ESM-style
89+ // "at wrap (node:internal/modules/cjs/loader:1)"
90+ || frame . indexOf ( 'node:internal/' ) !== - 1
91+ // Support Node 10-14 (CJS-style)
92+ // "at load (internal/modules/cjs/loader.js:7)"
93+ || frame . match ( / ^ \s + a t .+ \( i n t e r n a l [ ^ ) ] * \) $ / )
94+ ) ;
95+ if ( ! isInternal ) {
96+ initialInternal = false ;
97+ }
98+ // Remove initial internal frames entirely.
99+ if ( ! initialInternal ) {
100+ annotated . push ( isInternal ? formatInternal ( frame ) : frame ) ;
101+ }
102+ }
103+
104+ return annotated . join ( '\n' ) ;
105+ }
43106
44107export function extractStacktrace ( e , offset ) {
45108 offset = offset === undefined ? 4 : offset ;
46109
47110 // Support: IE9, e.stack is not supported, we will return undefined
48111 if ( e && e . stack ) {
49112 const stack = e . stack . split ( '\n' ) ;
113+ // In Firefox and Safari, e.stack starts immediately with the first frame.
114+ //
115+ // In V8 (Chrome/Node.js), the stack starts first with a stringified error message,
116+ // and the real stack starting on line 2.
50117 if ( / ^ e r r o r $ / i. test ( stack [ 0 ] ) ) {
51118 stack . shift ( ) ;
52119 }
@@ -69,8 +136,9 @@ export function extractStacktrace (e, offset) {
69136export function sourceFromStacktrace ( offset ) {
70137 let error = new Error ( ) ;
71138
72- // Support: Safari <=7 only, IE <=10 - 11 only
73- // Not all browsers generate the `stack` property for `new Error()`, see also #636
139+ // Support: IE 9-11, iOS 7
140+ // Not all browsers generate the `stack` property for `new Error()`
141+ // See also https://github.com/qunitjs/qunit/issues/636
74142 if ( ! error . stack ) {
75143 try {
76144 throw error ;
0 commit comments