◐ Shell
clean mode source ↗

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');

7476

const { isBuffer } = require('buffer').Buffer;

7577

const {

@@ -84,11 +86,13 @@ function lazyUtilColors() {

8486

utilColors ??= require('internal/util/colors');

8587

return utilColors;

8688

}

89+

const { getOptionValue } = require('internal/options');

87908891

const binding = internalBinding('util');

89929093

const {

9194

deprecate,

95+

getLazy,

9296

getSystemErrorMap,

9397

getSystemErrorName: internalErrorName,

9498

getSystemErrorMessage: internalErrorMessage,

@@ -472,14 +476,90 @@ function parseEnv(content) {

472476

return 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

482558

validateNumber(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+

}

483563

return binding.getCallSites(frameCount);

484564

};

485565