@@ -47,45 +47,48 @@ const prepareStackTrace = (globalThis, error, trace) => {
4747 }
4848
4949 let errorSource = '' ;
50- let firstLine ;
51- let firstColumn ;
50+ let lastSourceMap ;
51+ let lastFileName ;
5252 const preparedTrace = ArrayPrototypeJoin ( ArrayPrototypeMap ( trace , ( t , i ) => {
53- if ( i === 0 ) {
54- firstLine = t . getLineNumber ( ) ;
55- firstColumn = t . getColumnNumber ( ) ;
56- }
5753 let str = i !== 0 ? '\n at ' : '' ;
5854 str = `${ str } ${ t } ` ;
5955 try {
60- const sm = findSourceMap ( t . getFileName ( ) ) ;
56+ // A stack trace will often have several call sites in a row within the
57+ // same file, cache the source map and file content accordingly:
58+ const fileName = t . getFileName ( ) ;
59+ const sm = fileName === lastFileName ?
60+ lastSourceMap :
61+ findSourceMap ( fileName ) ;
62+ lastSourceMap = sm ;
63+ lastFileName = fileName ;
6164 if ( sm ) {
62- // Source Map V3 lines/columns use zero-based offsets whereas, in
63- // stack traces, they start at 1/1.
65+ // Source Map V3 lines/columns start at 0/0 whereas stack traces
66+ // start at 1/1:
6467 const {
6568 originalLine,
6669 originalColumn,
67- originalSource
70+ originalSource,
6871 } = sm . findEntry ( t . getLineNumber ( ) - 1 , t . getColumnNumber ( ) - 1 ) ;
6972 if ( originalSource && originalLine !== undefined &&
7073 originalColumn !== undefined ) {
74+ const name = getOriginalSymbolName ( sm , trace , i ) ;
7175 if ( i === 0 ) {
72- firstLine = originalLine + 1 ;
73- firstColumn = originalColumn + 1 ;
74-
75- // Show error in original source context to help user pinpoint it:
7676 errorSource = getErrorSource (
77- sm . payload ,
77+ sm ,
7878 originalSource ,
79- firstLine ,
80- firstColumn
79+ originalLine ,
80+ originalColumn
8181 ) ;
8282 }
8383 // Show both original and transpiled stack trace information:
84+ const prefix = ( name && name !== t . getFunctionName ( ) ) ?
85+ `\n -> at ${ name } ` :
86+ '\n ->' ;
8487 const originalSourceNoScheme =
8588 StringPrototypeStartsWith ( originalSource , 'file://' ) ?
8689 fileURLToPath ( originalSource ) : originalSource ;
87- str += `\n -> ${ originalSourceNoScheme } :${ originalLine + 1 } :` +
88- `${ originalColumn + 1 } ` ;
90+ str += `${ prefix } ( ${ originalSourceNoScheme } :${ originalLine + 1 } :` +
91+ `${ originalColumn + 1 } ) ` ;
8992 }
9093 }
9194 } catch ( err ) {
@@ -96,18 +99,69 @@ const prepareStackTrace = (globalThis, error, trace) => {
9699 return `${ errorSource } ${ errorString } \n at ${ preparedTrace } ` ;
97100} ;
98101
102+ // Transpilers may have removed the original symbol name used in the stack
103+ // trace, if possible restore it from the names field of the source map:
104+ function getOriginalSymbolName ( sourceMap , trace , curIndex ) {
105+ // First check for a symbol name associated with the enclosing function:
106+ const enclosingEntry = sourceMap . findEntry (
107+ trace [ curIndex ] . getEnclosingLineNumber ( ) - 1 ,
108+ trace [ curIndex ] . getEnclosingColumnNumber ( ) - 1
109+ ) ;
110+ if ( enclosingEntry . name ) return enclosingEntry . name ;
111+ // Fallback to using the symbol name attached to the next stack frame:
112+ const currentFileName = trace [ curIndex ] . getFileName ( ) ;
113+ const nextCallSite = trace [ curIndex + 1 ] ;
114+ if ( nextCallSite && currentFileName === nextCallSite . getFileName ( ) ) {
115+ const { name } = sourceMap . findEntry (
116+ nextCallSite . getLineNumber ( ) - 1 ,
117+ nextCallSite . getColumnNumber ( ) - 1
118+ ) ;
119+ return name ;
120+ }
121+ }
122+
99123// Places a snippet of code from where the exception was originally thrown
100124// above the stack trace. This logic is modeled after GetErrorSource in
101125// node_errors.cc.
102- function getErrorSource ( payload , originalSource , firstLine , firstColumn ) {
126+ function getErrorSource (
127+ sourceMap ,
128+ originalSourcePath ,
129+ originalLine ,
130+ originalColumn
131+ ) {
103132 let exceptionLine = '' ;
104- const originalSourceNoScheme =
105- StringPrototypeStartsWith ( originalSource , 'file://' ) ?
106- fileURLToPath ( originalSource ) : originalSource ;
133+ const originalSourcePathNoScheme =
134+ StringPrototypeStartsWith ( originalSourcePath , 'file://' ) ?
135+ fileURLToPath ( originalSourcePath ) : originalSourcePath ;
136+ const source = getOriginalSource (
137+ sourceMap . payload ,
138+ originalSourcePathNoScheme
139+ ) ;
140+ const lines = StringPrototypeSplit ( source , / \r ? \n / , originalLine + 1 ) ;
141+ const line = lines [ originalLine ] ;
142+ if ( ! line ) return exceptionLine ;
143+
144+ // Display ^ in appropriate position, regardless of whether tabs or
145+ // spaces are used:
146+ let prefix = '' ;
147+ for ( const character of StringPrototypeSlice ( line , 0 , originalColumn + 1 ) ) {
148+ prefix += ( character === '\t' ) ? '\t' :
149+ StringPrototypeRepeat ( ' ' , getStringWidth ( character ) ) ;
150+ }
151+ prefix = StringPrototypeSlice ( prefix , 0 , - 1 ) ; // The last character is '^'.
107152
153+ exceptionLine =
154+ `${ originalSourcePathNoScheme } :${ originalLine + 1 } \n${ line } \n${ prefix } ^\n\n` ;
155+ return exceptionLine ;
156+ }
157+
158+ function getOriginalSource ( payload , originalSourcePath ) {
108159 let source ;
160+ const originalSourcePathNoScheme =
161+ StringPrototypeStartsWith ( originalSourcePath , 'file://' ) ?
162+ fileURLToPath ( originalSourcePath ) : originalSourcePath ;
109163 const sourceContentIndex =
110- ArrayPrototypeIndexOf ( payload . sources , originalSource ) ;
164+ ArrayPrototypeIndexOf ( payload . sources , originalSourcePath ) ;
111165 if ( payload . sourcesContent ?. [ sourceContentIndex ] ) {
112166 // First we check if the original source content was provided in the
113167 // source map itself:
@@ -116,29 +170,13 @@ function getErrorSource(payload, originalSource, firstLine, firstColumn) {
116170 // If no sourcesContent was found, attempt to load the original source
117171 // from disk:
118172 try {
119- source = readFileSync ( originalSourceNoScheme , 'utf8' ) ;
173+ source = readFileSync ( originalSourcePathNoScheme , 'utf8' ) ;
120174 } catch ( err ) {
121175 debug ( err ) ;
122- return '' ;
176+ source = '' ;
123177 }
124178 }
125-
126- const lines = StringPrototypeSplit ( source , / \r ? \n / , firstLine ) ;
127- const line = lines [ firstLine - 1 ] ;
128- if ( ! line ) return exceptionLine ;
129-
130- // Display ^ in appropriate position, regardless of whether tabs or
131- // spaces are used:
132- let prefix = '' ;
133- for ( const character of StringPrototypeSlice ( line , 0 , firstColumn ) ) {
134- prefix += ( character === '\t' ) ? '\t' :
135- StringPrototypeRepeat ( ' ' , getStringWidth ( character ) ) ;
136- }
137- prefix = StringPrototypeSlice ( prefix , 0 , - 1 ) ; // The last character is '^'.
138-
139- exceptionLine =
140- `${ originalSourceNoScheme } :${ firstLine } \n${ line } \n${ prefix } ^\n\n` ;
141- return exceptionLine ;
179+ return source ;
142180}
143181
144182module . exports = {
0 commit comments