util: add sourcemap support to getCallSites · nodejs/node@bcfe9c8
@@ -25,6 +25,7 @@ const {
2525 ArrayIsArray,
2626 ArrayPrototypeJoin,
2727 ArrayPrototypePop,
28+ ArrayPrototypePush,
2829 Date,
2930 DatePrototypeGetDate,
3031 DatePrototypeGetHours,
@@ -70,6 +71,7 @@ const {
7071 validateNumber,
7172 validateString,
7273 validateOneOf,
74+ validateObject,
7375} = require('internal/validators');
7476const { isBuffer } = require('buffer').Buffer;
7577const {
@@ -84,11 +86,13 @@ function lazyUtilColors() {
8486utilColors ??= require('internal/util/colors');
8587return utilColors;
8688}
89+const { getOptionValue } = require('internal/options');
87908891const binding = internalBinding('util');
89929093const {
9194 deprecate,
95+ getLazy,
9296 getSystemErrorMap,
9397getSystemErrorName: internalErrorName,
9498getSystemErrorMessage: internalErrorMessage,
@@ -472,14 +476,90 @@ function parseEnv(content) {
472476return binding.parseEnv(content);
473477}
474478479+const lazySourceMap = getLazy(() => require('internal/source_map/source_map_cache'));
480+481+/**
482+ * @typedef {object} CallSite // The call site
483+ * @property {string} scriptName // The name of the resource that contains the
484+ * script for the function for this StackFrame
485+ * @property {string} functionName // The name of the function associated with this stack frame
486+ * @property {number} lineNumber // The number, 1-based, of the line for the associate function call
487+ * @property {number} columnNumber // The 1-based column offset on the line for the associated function call
488+ */
489+490+/**
491+ * @param {CallSite} callSite // The call site object to reconstruct from source map
492+ * @returns {CallSite | undefined} // The reconstructed call site object
493+ */
494+function reconstructCallSite(callSite) {
495+const { scriptName, lineNumber, column } = callSite;
496+const sourceMap = lazySourceMap().findSourceMap(scriptName);
497+if (!sourceMap) return;
498+const entry = sourceMap.findEntry(lineNumber - 1, column - 1);
499+if (!entry?.originalSource) return;
500+return {
501+__proto__: null,
502+// If the name is not found, it is an empty string to match the behavior of `util.getCallSite()`
503+functionName: entry.name ?? '',
504+scriptName: entry.originalSource,
505+lineNumber: entry.originalLine + 1,
506+column: entry.originalColumn + 1,
507+};
508+}
509+510+/**
511+ *
512+ * The call site array to map
513+ * @param {CallSite[]} callSites
514+ * Array of objects with the reconstructed call site
515+ * @returns {CallSite[]}
516+ */
517+function mapCallSite(callSites) {
518+const result = [];
519+for (let i = 0; i < callSites.length; ++i) {
520+const callSite = callSites[i];
521+const found = reconstructCallSite(callSite);
522+ArrayPrototypePush(result, found ?? callSite);
523+}
524+return result;
525+}
526+527+/**
528+ * @typedef {object} CallSiteOptions // The call site options
529+ * @property {boolean} sourceMap // Enable source map support
530+ */
531+475532/**
476533 * Returns the callSite
477534 * @param {number} frameCount
478- * @returns {object}
535+ * @param {CallSiteOptions} options
536+ * @returns {CallSite[]}
479537 */
480-function getCallSites(frameCount = 10) {
538+function getCallSites(frameCount = 10, options) {
539+// If options is not provided check if frameCount is an object
540+if (options === undefined) {
541+if (typeof frameCount === 'object') {
542+// If frameCount is an object, it is the options object
543+options = frameCount;
544+validateObject(options, 'options');
545+validateBoolean(options.sourceMap, 'options.sourceMap');
546+frameCount = 10;
547+} else {
548+// If options is not provided, set it to an empty object
549+options = {};
550+};
551+} else {
552+// If options is provided, validate it
553+validateObject(options, 'options');
554+validateBoolean(options.sourceMap, 'options.sourceMap');
555+}
556+481557// Using kDefaultMaxCallStackSizeToCapture as reference
482558validateNumber(frameCount, 'frameCount', 1, 200);
559+// If options.sourceMaps is true or if sourceMaps are enabled but the option.sourceMaps is not set explictly to false
560+if (options.sourceMap === true || (getOptionValue('--enable-source-maps') && options.sourceMap !== false)) {
561+return mapCallSite(binding.getCallSites(frameCount));
562+}
483563return binding.getCallSites(frameCount);
484564};
485565