@@ -24,6 +24,8 @@ const {
2424const { fileURLToPath } = require ( 'internal/url' ) ;
2525const { setGetSourceMapErrorSource } = internalBinding ( 'errors' ) ;
2626
27+ const kStackLineAt = '\n at ' ;
28+
2729// Create a prettified stacktrace, inserting context from source maps
2830// if possible.
2931function prepareStackTraceWithSourceMaps ( error , trace ) {
@@ -40,75 +42,98 @@ function prepareStackTraceWithSourceMaps(error, trace) {
4042
4143 let lastSourceMap ;
4244 let lastFileName ;
43- const preparedTrace = ArrayPrototypeJoin ( ArrayPrototypeMap ( trace , ( t , i ) => {
44- const str = '\n at ' ;
45+ const preparedTrace = ArrayPrototypeJoin ( ArrayPrototypeMap ( trace , ( callSite , i ) => {
4546 try {
4647 // A stack trace will often have several call sites in a row within the
4748 // same file, cache the source map and file content accordingly:
48- let fileName = t . getFileName ( ) ;
49+ let fileName = callSite . getFileName ( ) ;
4950 if ( fileName === undefined ) {
50- fileName = t . getEvalOrigin ( ) ;
51+ fileName = callSite . getEvalOrigin ( ) ;
5152 }
5253 const sm = fileName === lastFileName ?
5354 lastSourceMap :
5455 findSourceMap ( fileName ) ;
5556 lastSourceMap = sm ;
5657 lastFileName = fileName ;
5758 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 ] ) } ` ;
8760 }
8861 } catch ( err ) {
8962 debug ( err ) ;
9063 }
91- return `${ str } ${ t } ` ;
64+ return `${ kStackLineAt } ${ callSite } ` ;
9265 } ) , '' ) ;
9366 return `${ errorString } ${ preparedTrace } ` ;
9467}
9568
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+
96122// Transpilers may have removed the original symbol name used in the stack
97123// 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 ) {
99125 // First check for a symbol name associated with the enclosing function:
100126 const enclosingEntry = sourceMap . findEntry (
101- trace [ curIndex ] . getEnclosingLineNumber ( ) - 1 ,
102- trace [ curIndex ] . getEnclosingColumnNumber ( ) - 1 ,
127+ callSite . getEnclosingLineNumber ( ) - 1 ,
128+ callSite . getEnclosingColumnNumber ( ) - 1 ,
103129 ) ;
104130 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 ( ) ) {
109134 const { name } = sourceMap . findEntry (
110- nextCallSite . getLineNumber ( ) - 1 ,
111- nextCallSite . getColumnNumber ( ) - 1 ,
135+ callerCallSite . getLineNumber ( ) - 1 ,
136+ callerCallSite . getColumnNumber ( ) - 1 ,
112137 ) ;
113138 return name ;
114139 }
0 commit comments