@@ -24,6 +24,8 @@ const {
24
24
const { fileURLToPath } = require ( 'internal/url' ) ;
25
25
const { setGetSourceMapErrorSource } = internalBinding ( 'errors' ) ;
26
26
27
+ const kStackLineAt = '\n at ' ;
28
+
27
29
// Create a prettified stacktrace, inserting context from source maps
28
30
// if possible.
29
31
function prepareStackTraceWithSourceMaps ( error , trace ) {
@@ -40,75 +42,98 @@ function prepareStackTraceWithSourceMaps(error, trace) {
40
42
41
43
let lastSourceMap ;
42
44
let lastFileName ;
43
- const preparedTrace = ArrayPrototypeJoin ( ArrayPrototypeMap ( trace , ( t , i ) => {
44
- const str = '\n at ' ;
45
+ const preparedTrace = ArrayPrototypeJoin ( ArrayPrototypeMap ( trace , ( callSite , i ) => {
45
46
try {
46
47
// A stack trace will often have several call sites in a row within the
47
48
// same file, cache the source map and file content accordingly:
48
- let fileName = t . getFileName ( ) ;
49
+ let fileName = callSite . getFileName ( ) ;
49
50
if ( fileName === undefined ) {
50
- fileName = t . getEvalOrigin ( ) ;
51
+ fileName = callSite . getEvalOrigin ( ) ;
51
52
}
52
53
const sm = fileName === lastFileName ?
53
54
lastSourceMap :
54
55
findSourceMap ( fileName ) ;
55
56
lastSourceMap = sm ;
56
57
lastFileName = fileName ;
57
58
if ( sm ) {
58
- // Source Map V3 lines/columns start at 0/0 whereas stack traces
59
- // start at 1/1:
60
- const {
61
- originalLine,
62
- originalColumn,
63
- originalSource,
64
- } = sm . findEntry ( t . getLineNumber ( ) - 1 , t . getColumnNumber ( ) - 1 ) ;
65
- if ( originalSource && originalLine !== undefined &&
66
- originalColumn !== undefined ) {
67
- const name = getOriginalSymbolName ( sm , trace , i ) ;
68
- // Construct call site name based on: v8.dev/docs/stack-trace-api:
69
- const fnName = t . getFunctionName ( ) ?? t . getMethodName ( ) ;
70
- const typeName = t . getTypeName ( ) ;
71
- const namePrefix = typeName !== null && typeName !== 'global' ? `${ typeName } .` : '' ;
72
- const originalName = `${ namePrefix } ${ fnName || '<anonymous>' } ` ;
73
- // The original call site may have a different symbol name
74
- // associated with it, use it:
75
- const prefix = ( name && name !== originalName ) ?
76
- `${ name } ` :
77
- `${ originalName } ` ;
78
- const hasName = ! ! ( name || originalName ) ;
79
- const originalSourceNoScheme =
80
- StringPrototypeStartsWith ( originalSource , 'file://' ) ?
81
- fileURLToPath ( originalSource ) : originalSource ;
82
- // Replace the transpiled call site with the original:
83
- return `${ str } ${ prefix } ${ hasName ? ' (' : '' } ` +
84
- `${ originalSourceNoScheme } :${ originalLine + 1 } :` +
85
- `${ originalColumn + 1 } ${ hasName ? ')' : '' } ` ;
86
- }
59
+ return `${ kStackLineAt } ${ serializeJSStackFrame ( sm , callSite , trace [ i + 1 ] ) } ` ;
87
60
}
88
61
} catch ( err ) {
89
62
debug ( err ) ;
90
63
}
91
- return `${ str } ${ t } ` ;
64
+ return `${ kStackLineAt } ${ callSite } ` ;
92
65
} ) , '' ) ;
93
66
return `${ errorString } ${ preparedTrace } ` ;
94
67
}
95
68
69
+ /**
70
+ * Serialize a single call site in the stack trace.
71
+ * Refer to SerializeJSStackFrame in deps/v8/src/objects/call-site-info.cc for
72
+ * more details about the default ToString(CallSite).
73
+ * The CallSite API is documented at https://v8.dev/docs/stack-trace-api.
74
+ * @param {import('internal/source_map/source_map').SourceMap } sm
75
+ * @param {CallSite } callSite - the CallSite object to be serialized
76
+ * @param {CallSite } callerCallSite - caller site info
77
+ * @returns {string } - the serialized call site
78
+ */
79
+ function serializeJSStackFrame ( sm , callSite , callerCallSite ) {
80
+ // Source Map V3 lines/columns start at 0/0 whereas stack traces
81
+ // start at 1/1:
82
+ const {
83
+ originalLine,
84
+ originalColumn,
85
+ originalSource,
86
+ } = sm . findEntry ( callSite . getLineNumber ( ) - 1 , callSite . getColumnNumber ( ) - 1 ) ;
87
+ if ( originalSource === undefined || originalLine === undefined ||
88
+ originalColumn === undefined ) {
89
+ return `${ callSite } ` ;
90
+ }
91
+ const name = getOriginalSymbolName ( sm , callSite , callerCallSite ) ;
92
+ const originalSourceNoScheme =
93
+ StringPrototypeStartsWith ( originalSource , 'file://' ) ?
94
+ fileURLToPath ( originalSource ) : originalSource ;
95
+ // Construct call site name based on: v8.dev/docs/stack-trace-api:
96
+ const fnName = callSite . getFunctionName ( ) ?? callSite . getMethodName ( ) ;
97
+
98
+ let prefix = '' ;
99
+ if ( callSite . isAsync ( ) ) {
100
+ // Promise aggregation operation frame has no locations. This must be an
101
+ // async stack frame.
102
+ prefix = 'async ' ;
103
+ } else if ( callSite . isConstructor ( ) ) {
104
+ prefix = 'new ' ;
105
+ }
106
+
107
+ const typeName = callSite . getTypeName ( ) ;
108
+ const namePrefix = typeName !== null && typeName !== 'global' ? `${ typeName } .` : '' ;
109
+ const originalName = `${ namePrefix } ${ fnName || '<anonymous>' } ` ;
110
+ // The original call site may have a different symbol name
111
+ // associated with it, use it:
112
+ const mappedName = ( name && name !== originalName ) ?
113
+ `${ name } ` :
114
+ `${ originalName } ` ;
115
+ const hasName = ! ! ( name || originalName ) ;
116
+ // Replace the transpiled call site with the original:
117
+ return `${ prefix } ${ mappedName } ${ hasName ? ' (' : '' } ` +
118
+ `${ originalSourceNoScheme } :${ originalLine + 1 } :` +
119
+ `${ originalColumn + 1 } ${ hasName ? ')' : '' } ` ;
120
+ }
121
+
96
122
// Transpilers may have removed the original symbol name used in the stack
97
123
// trace, if possible restore it from the names field of the source map:
98
- function getOriginalSymbolName ( sourceMap , trace , curIndex ) {
124
+ function getOriginalSymbolName ( sourceMap , callSite , callerCallSite ) {
99
125
// First check for a symbol name associated with the enclosing function:
100
126
const enclosingEntry = sourceMap . findEntry (
101
- trace [ curIndex ] . getEnclosingLineNumber ( ) - 1 ,
102
- trace [ curIndex ] . getEnclosingColumnNumber ( ) - 1 ,
127
+ callSite . getEnclosingLineNumber ( ) - 1 ,
128
+ callSite . getEnclosingColumnNumber ( ) - 1 ,
103
129
) ;
104
130
if ( enclosingEntry . name ) return enclosingEntry . name ;
105
- // Fallback to using the symbol name attached to the next stack frame:
106
- const currentFileName = trace [ curIndex ] . getFileName ( ) ;
107
- const nextCallSite = trace [ curIndex + 1 ] ;
108
- if ( nextCallSite && currentFileName === nextCallSite . getFileName ( ) ) {
131
+ // Fallback to using the symbol name attached to the caller site:
132
+ const currentFileName = callSite . getFileName ( ) ;
133
+ if ( callerCallSite && currentFileName === callerCallSite . getFileName ( ) ) {
109
134
const { name } = sourceMap . findEntry (
110
- nextCallSite . getLineNumber ( ) - 1 ,
111
- nextCallSite . getColumnNumber ( ) - 1 ,
135
+ callerCallSite . getLineNumber ( ) - 1 ,
136
+ callerCallSite . getColumnNumber ( ) - 1 ,
112
137
) ;
113
138
return name ;
114
139
}
0 commit comments