mirror of
https://github.com/nodejs/node.git
synced 2025-05-02 20:31:36 +00:00
util: add sourcemap support to getCallSites
PR-URL: https://github.com/nodejs/node/pull/55589 Fixes: https://github.com/nodejs/node/issues/55109 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
bdc266269c
commit
d35cde624f
@ -3899,7 +3899,7 @@ The `util.getCallSite` API has been removed. Please use [`util.getCallSites()`][
|
|||||||
[`url.parse()`]: url.md#urlparseurlstring-parsequerystring-slashesdenotehost
|
[`url.parse()`]: url.md#urlparseurlstring-parsequerystring-slashesdenotehost
|
||||||
[`url.resolve()`]: url.md#urlresolvefrom-to
|
[`url.resolve()`]: url.md#urlresolvefrom-to
|
||||||
[`util._extend()`]: util.md#util_extendtarget-source
|
[`util._extend()`]: util.md#util_extendtarget-source
|
||||||
[`util.getCallSites()`]: util.md#utilgetcallsitesframecount
|
[`util.getCallSites()`]: util.md#utilgetcallsitesframecountoroptions-options
|
||||||
[`util.getSystemErrorName()`]: util.md#utilgetsystemerrornameerr
|
[`util.getSystemErrorName()`]: util.md#utilgetsystemerrornameerr
|
||||||
[`util.inspect()`]: util.md#utilinspectobject-options
|
[`util.inspect()`]: util.md#utilinspectobject-options
|
||||||
[`util.inspect.custom`]: util.md#utilinspectcustom
|
[`util.inspect.custom`]: util.md#utilinspectcustom
|
||||||
|
@ -364,7 +364,7 @@ util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 });
|
|||||||
// when printed to a terminal.
|
// when printed to a terminal.
|
||||||
```
|
```
|
||||||
|
|
||||||
## `util.getCallSites(frameCount)`
|
## `util.getCallSites(frameCountOrOptions, [options])`
|
||||||
|
|
||||||
> Stability: 1.1 - Active development
|
> Stability: 1.1 - Active development
|
||||||
|
|
||||||
@ -372,8 +372,11 @@ util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 });
|
|||||||
added: v22.9.0
|
added: v22.9.0
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* `frameCount` {number} Number of frames to capture as call site objects.
|
* `frameCount` {number} Optional number of frames to capture as call site objects.
|
||||||
**Default:** `10`. Allowable range is between 1 and 200.
|
**Default:** `10`. Allowable range is between 1 and 200.
|
||||||
|
* `options` {Object} Optional
|
||||||
|
* `sourceMap` {boolean} Reconstruct the original location in the stacktrace from the source-map.
|
||||||
|
Enabled by default with the flag `--enable-source-maps`.
|
||||||
* Returns: {Object\[]} An array of call site objects
|
* Returns: {Object\[]} An array of call site objects
|
||||||
* `functionName` {string} Returns the name of the function associated with this call site.
|
* `functionName` {string} Returns the name of the function associated with this call site.
|
||||||
* `scriptName` {string} Returns the name of the resource that contains the script for the
|
* `scriptName` {string} Returns the name of the resource that contains the script for the
|
||||||
@ -421,6 +424,33 @@ function anotherFunction() {
|
|||||||
anotherFunction();
|
anotherFunction();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It is possible to reconstruct the original locations by setting the option `sourceMap` to `true`.
|
||||||
|
If the source map is not available, the original location will be the same as the current location.
|
||||||
|
When the `--enable-source-maps` flag is enabled, for example when using `--experimental-transform-types`,
|
||||||
|
`sourceMap` will be true by default.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import util from 'node:util';
|
||||||
|
|
||||||
|
interface Foo {
|
||||||
|
foo: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callSites = util.getCallSites({ sourceMap: true });
|
||||||
|
|
||||||
|
// With sourceMap:
|
||||||
|
// Function Name: ''
|
||||||
|
// Script Name: example.js
|
||||||
|
// Line Number: 7
|
||||||
|
// Column Number: 26
|
||||||
|
|
||||||
|
// Without sourceMap:
|
||||||
|
// Function Name: ''
|
||||||
|
// Script Name: example.js
|
||||||
|
// Line Number: 2
|
||||||
|
// Column Number: 26
|
||||||
|
```
|
||||||
|
|
||||||
## `util.getSystemErrorName(err)`
|
## `util.getSystemErrorName(err)`
|
||||||
|
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
|
84
lib/util.js
84
lib/util.js
@ -24,6 +24,7 @@
|
|||||||
const {
|
const {
|
||||||
ArrayIsArray,
|
ArrayIsArray,
|
||||||
ArrayPrototypePop,
|
ArrayPrototypePop,
|
||||||
|
ArrayPrototypePush,
|
||||||
Error,
|
Error,
|
||||||
ErrorCaptureStackTrace,
|
ErrorCaptureStackTrace,
|
||||||
FunctionPrototypeBind,
|
FunctionPrototypeBind,
|
||||||
@ -61,6 +62,7 @@ const {
|
|||||||
validateNumber,
|
validateNumber,
|
||||||
validateString,
|
validateString,
|
||||||
validateOneOf,
|
validateOneOf,
|
||||||
|
validateObject,
|
||||||
} = require('internal/validators');
|
} = require('internal/validators');
|
||||||
const {
|
const {
|
||||||
isReadableStream,
|
isReadableStream,
|
||||||
@ -74,11 +76,13 @@ function lazyUtilColors() {
|
|||||||
utilColors ??= require('internal/util/colors');
|
utilColors ??= require('internal/util/colors');
|
||||||
return utilColors;
|
return utilColors;
|
||||||
}
|
}
|
||||||
|
const { getOptionValue } = require('internal/options');
|
||||||
|
|
||||||
const binding = internalBinding('util');
|
const binding = internalBinding('util');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
deprecate,
|
deprecate,
|
||||||
|
getLazy,
|
||||||
getSystemErrorMap,
|
getSystemErrorMap,
|
||||||
getSystemErrorName: internalErrorName,
|
getSystemErrorName: internalErrorName,
|
||||||
getSystemErrorMessage: internalErrorMessage,
|
getSystemErrorMessage: internalErrorMessage,
|
||||||
@ -328,14 +332,90 @@ function parseEnv(content) {
|
|||||||
return binding.parseEnv(content);
|
return binding.parseEnv(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lazySourceMap = getLazy(() => require('internal/source_map/source_map_cache'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} CallSite // The call site
|
||||||
|
* @property {string} scriptName // The name of the resource that contains the
|
||||||
|
* script for the function for this StackFrame
|
||||||
|
* @property {string} functionName // The name of the function associated with this stack frame
|
||||||
|
* @property {number} lineNumber // The number, 1-based, of the line for the associate function call
|
||||||
|
* @property {number} columnNumber // The 1-based column offset on the line for the associated function call
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CallSite} callSite // The call site object to reconstruct from source map
|
||||||
|
* @returns {CallSite | undefined} // The reconstructed call site object
|
||||||
|
*/
|
||||||
|
function reconstructCallSite(callSite) {
|
||||||
|
const { scriptName, lineNumber, column } = callSite;
|
||||||
|
const sourceMap = lazySourceMap().findSourceMap(scriptName);
|
||||||
|
if (!sourceMap) return;
|
||||||
|
const entry = sourceMap.findEntry(lineNumber - 1, column - 1);
|
||||||
|
if (!entry?.originalSource) return;
|
||||||
|
return {
|
||||||
|
__proto__: null,
|
||||||
|
// If the name is not found, it is an empty string to match the behavior of `util.getCallSite()`
|
||||||
|
functionName: entry.name ?? '',
|
||||||
|
scriptName: entry.originalSource,
|
||||||
|
lineNumber: entry.originalLine + 1,
|
||||||
|
column: entry.originalColumn + 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* The call site array to map
|
||||||
|
* @param {CallSite[]} callSites
|
||||||
|
* Array of objects with the reconstructed call site
|
||||||
|
* @returns {CallSite[]}
|
||||||
|
*/
|
||||||
|
function mapCallSite(callSites) {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < callSites.length; ++i) {
|
||||||
|
const callSite = callSites[i];
|
||||||
|
const found = reconstructCallSite(callSite);
|
||||||
|
ArrayPrototypePush(result, found ?? callSite);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} CallSiteOptions // The call site options
|
||||||
|
* @property {boolean} sourceMap // Enable source map support
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the callSite
|
* Returns the callSite
|
||||||
* @param {number} frameCount
|
* @param {number} frameCount
|
||||||
* @returns {object}
|
* @param {CallSiteOptions} options
|
||||||
|
* @returns {CallSite[]}
|
||||||
*/
|
*/
|
||||||
function getCallSites(frameCount = 10) {
|
function getCallSites(frameCount = 10, options) {
|
||||||
|
// If options is not provided check if frameCount is an object
|
||||||
|
if (options === undefined) {
|
||||||
|
if (typeof frameCount === 'object') {
|
||||||
|
// If frameCount is an object, it is the options object
|
||||||
|
options = frameCount;
|
||||||
|
validateObject(options, 'options');
|
||||||
|
validateBoolean(options.sourceMap, 'options.sourceMap');
|
||||||
|
frameCount = 10;
|
||||||
|
} else {
|
||||||
|
// If options is not provided, set it to an empty object
|
||||||
|
options = {};
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// If options is provided, validate it
|
||||||
|
validateObject(options, 'options');
|
||||||
|
validateBoolean(options.sourceMap, 'options.sourceMap');
|
||||||
|
}
|
||||||
|
|
||||||
// Using kDefaultMaxCallStackSizeToCapture as reference
|
// Using kDefaultMaxCallStackSizeToCapture as reference
|
||||||
validateNumber(frameCount, 'frameCount', 1, 200);
|
validateNumber(frameCount, 'frameCount', 1, 200);
|
||||||
|
// If options.sourceMaps is true or if sourceMaps are enabled but the option.sourceMaps is not set explictly to false
|
||||||
|
if (options.sourceMap === true || (getOptionValue('--enable-source-maps') && options.sourceMap !== false)) {
|
||||||
|
return mapCallSite(binding.getCallSites(frameCount));
|
||||||
|
}
|
||||||
return binding.getCallSites(frameCount);
|
return binding.getCallSites(frameCount);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
10
test/fixtures/typescript/ts/test-get-callsite-explicit.ts
vendored
Normal file
10
test/fixtures/typescript/ts/test-get-callsite-explicit.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const { getCallSites } = require('node:util');
|
||||||
|
|
||||||
|
interface CallSite {
|
||||||
|
A;
|
||||||
|
B;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callSite = getCallSites({ sourceMap: false })[0];
|
||||||
|
|
||||||
|
console.log('mapCallSite: ', callSite);
|
10
test/fixtures/typescript/ts/test-get-callsite.ts
vendored
Normal file
10
test/fixtures/typescript/ts/test-get-callsite.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const { getCallSites } = require('node:util');
|
||||||
|
|
||||||
|
interface CallSite {
|
||||||
|
A;
|
||||||
|
B;
|
||||||
|
}
|
||||||
|
|
||||||
|
const callSite = getCallSites()[0];
|
||||||
|
|
||||||
|
console.log('getCallSite: ', callSite);
|
@ -53,7 +53,17 @@ const assert = require('node:assert');
|
|||||||
code: 'ERR_OUT_OF_RANGE'
|
code: 'ERR_OUT_OF_RANGE'
|
||||||
}));
|
}));
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
getCallSites({});
|
getCallSites([]);
|
||||||
|
}, common.expectsError({
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE'
|
||||||
|
}));
|
||||||
|
assert.throws(() => {
|
||||||
|
getCallSites({}, {});
|
||||||
|
}, common.expectsError({
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE'
|
||||||
|
}));
|
||||||
|
assert.throws(() => {
|
||||||
|
getCallSites(10, 10);
|
||||||
}, common.expectsError({
|
}, common.expectsError({
|
||||||
code: 'ERR_INVALID_ARG_TYPE'
|
code: 'ERR_INVALID_ARG_TYPE'
|
||||||
}));
|
}));
|
||||||
@ -104,3 +114,51 @@ const assert = require('node:assert');
|
|||||||
assert.notStrictEqual(callSites.length, 0);
|
assert.notStrictEqual(callSites.length, 0);
|
||||||
Error.stackTraceLimit = originalStackTraceLimit;
|
Error.stackTraceLimit = originalStackTraceLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const { status, stderr, stdout } = spawnSync(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-transform-types',
|
||||||
|
fixtures.path('typescript/ts/test-get-callsite.ts'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const output = stdout.toString();
|
||||||
|
assert.strictEqual(stderr.toString(), '');
|
||||||
|
assert.match(output, /lineNumber: 8/);
|
||||||
|
assert.match(output, /column: 18/);
|
||||||
|
assert.match(output, /test-get-callsite\.ts/);
|
||||||
|
assert.strictEqual(status, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const { status, stderr, stdout } = spawnSync(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-transform-types',
|
||||||
|
'--no-enable-source-maps',
|
||||||
|
fixtures.path('typescript/ts/test-get-callsite.ts'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const output = stdout.toString();
|
||||||
|
assert.strictEqual(stderr.toString(), '');
|
||||||
|
// Line should be wrong when sourcemaps are disable
|
||||||
|
assert.match(output, /lineNumber: 2/);
|
||||||
|
assert.match(output, /column: 18/);
|
||||||
|
assert.match(output, /test-get-callsite\.ts/);
|
||||||
|
assert.strictEqual(status, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Source maps should be disabled when options.sourceMap is false
|
||||||
|
const { status, stderr, stdout } = spawnSync(process.execPath, [
|
||||||
|
'--no-warnings',
|
||||||
|
'--experimental-transform-types',
|
||||||
|
fixtures.path('typescript/ts/test-get-callsite-explicit.ts'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const output = stdout.toString();
|
||||||
|
assert.strictEqual(stderr.toString(), '');
|
||||||
|
assert.match(output, /lineNumber: 2/);
|
||||||
|
assert.match(output, /column: 18/);
|
||||||
|
assert.match(output, /test-get-callsite-explicit\.ts/);
|
||||||
|
assert.strictEqual(status, 0);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user