@@ -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