@@ -47,45 +47,48 @@ const prepareStackTrace = (globalThis, error, trace) => {
47
47
}
48
48
49
49
let errorSource = '' ;
50
- let firstLine ;
51
- let firstColumn ;
50
+ let lastSourceMap ;
51
+ let lastFileName ;
52
52
const preparedTrace = ArrayPrototypeJoin ( ArrayPrototypeMap ( trace , ( t , i ) => {
53
- if ( i === 0 ) {
54
- firstLine = t . getLineNumber ( ) ;
55
- firstColumn = t . getColumnNumber ( ) ;
56
- }
57
53
let str = i !== 0 ? '\n at ' : '' ;
58
54
str = `${ str } ${ t } ` ;
59
55
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 ;
61
64
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:
64
67
const {
65
68
originalLine,
66
69
originalColumn,
67
- originalSource
70
+ originalSource,
68
71
} = sm . findEntry ( t . getLineNumber ( ) - 1 , t . getColumnNumber ( ) - 1 ) ;
69
72
if ( originalSource && originalLine !== undefined &&
70
73
originalColumn !== undefined ) {
74
+ const name = getOriginalSymbolName ( sm , trace , i ) ;
71
75
if ( i === 0 ) {
72
- firstLine = originalLine + 1 ;
73
- firstColumn = originalColumn + 1 ;
74
-
75
- // Show error in original source context to help user pinpoint it:
76
76
errorSource = getErrorSource (
77
- sm . payload ,
77
+ sm ,
78
78
originalSource ,
79
- firstLine ,
80
- firstColumn
79
+ originalLine ,
80
+ originalColumn
81
81
) ;
82
82
}
83
83
// Show both original and transpiled stack trace information:
84
+ const prefix = ( name && name !== t . getFunctionName ( ) ) ?
85
+ `\n -> at ${ name } ` :
86
+ '\n ->' ;
84
87
const originalSourceNoScheme =
85
88
StringPrototypeStartsWith ( originalSource , 'file://' ) ?
86
89
fileURLToPath ( originalSource ) : originalSource ;
87
- str += `\n -> ${ originalSourceNoScheme } :${ originalLine + 1 } :` +
88
- `${ originalColumn + 1 } ` ;
90
+ str += `${ prefix } ( ${ originalSourceNoScheme } :${ originalLine + 1 } :` +
91
+ `${ originalColumn + 1 } ) ` ;
89
92
}
90
93
}
91
94
} catch ( err ) {
@@ -96,18 +99,69 @@ const prepareStackTrace = (globalThis, error, trace) => {
96
99
return `${ errorSource } ${ errorString } \n at ${ preparedTrace } ` ;
97
100
} ;
98
101
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
+
99
123
// Places a snippet of code from where the exception was originally thrown
100
124
// above the stack trace. This logic is modeled after GetErrorSource in
101
125
// node_errors.cc.
102
- function getErrorSource ( payload , originalSource , firstLine , firstColumn ) {
126
+ function getErrorSource (
127
+ sourceMap ,
128
+ originalSourcePath ,
129
+ originalLine ,
130
+ originalColumn
131
+ ) {
103
132
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 '^'.
107
152
153
+ exceptionLine =
154
+ `${ originalSourcePathNoScheme } :${ originalLine + 1 } \n${ line } \n${ prefix } ^\n\n` ;
155
+ return exceptionLine ;
156
+ }
157
+
158
+ function getOriginalSource ( payload , originalSourcePath ) {
108
159
let source ;
160
+ const originalSourcePathNoScheme =
161
+ StringPrototypeStartsWith ( originalSourcePath , 'file://' ) ?
162
+ fileURLToPath ( originalSourcePath ) : originalSourcePath ;
109
163
const sourceContentIndex =
110
- ArrayPrototypeIndexOf ( payload . sources , originalSource ) ;
164
+ ArrayPrototypeIndexOf ( payload . sources , originalSourcePath ) ;
111
165
if ( payload . sourcesContent ?. [ sourceContentIndex ] ) {
112
166
// First we check if the original source content was provided in the
113
167
// source map itself:
@@ -116,29 +170,13 @@ function getErrorSource(payload, originalSource, firstLine, firstColumn) {
116
170
// If no sourcesContent was found, attempt to load the original source
117
171
// from disk:
118
172
try {
119
- source = readFileSync ( originalSourceNoScheme , 'utf8' ) ;
173
+ source = readFileSync ( originalSourcePathNoScheme , 'utf8' ) ;
120
174
} catch ( err ) {
121
175
debug ( err ) ;
122
- return '' ;
176
+ source = '' ;
123
177
}
124
178
}
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 ;
142
180
}
143
181
144
182
module . exports = {
0 commit comments