Skip to content

Commit d4e7926

Browse files
marco-ippolitoaduh95
authored andcommitted
util: add sourcemap support to getCallSites
PR-URL: #55589 Fixes: #55109 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
1 parent 00e092b commit d4e7926

File tree

6 files changed

+194
-6
lines changed

6 files changed

+194
-6
lines changed

doc/api/deprecations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3892,7 +3892,7 @@ The `util.getCallSite` API has been removed. Please use [`util.getCallSites()`][
38923892
[`url.parse()`]: url.md#urlparseurlstring-parsequerystring-slashesdenotehost
38933893
[`url.resolve()`]: url.md#urlresolvefrom-to
38943894
[`util._extend()`]: util.md#util_extendtarget-source
3895-
[`util.getCallSites()`]: util.md#utilgetcallsitesframecount
3895+
[`util.getCallSites()`]: util.md#utilgetcallsitesframecountoroptions-options
38963896
[`util.getSystemErrorName()`]: util.md#utilgetsystemerrornameerr
38973897
[`util.inspect()`]: util.md#utilinspectobject-options
38983898
[`util.inspect.custom`]: util.md#utilinspectcustom

doc/api/util.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,16 +364,19 @@ util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 });
364364
// when printed to a terminal.
365365
```
366366

367-
## `util.getCallSites(frameCount)`
367+
## `util.getCallSites(frameCountOrOptions, [options])`
368368

369369
> Stability: 1.1 - Active development
370370
371371
<!-- YAML
372372
added: v22.9.0
373373
-->
374374

375-
* `frameCount` {number} Number of frames to capture as call site objects.
375+
* `frameCount` {number} Optional number of frames to capture as call site objects.
376376
**Default:** `10`. Allowable range is between 1 and 200.
377+
* `options` {Object} Optional
378+
* `sourceMap` {boolean} Reconstruct the original location in the stacktrace from the source-map.
379+
Enabled by default with the flag `--enable-source-maps`.
377380
* Returns: {Object\[]} An array of call site objects
378381
* `functionName` {string} Returns the name of the function associated with this call site.
379382
* `scriptName` {string} Returns the name of the resource that contains the script for the
@@ -421,6 +424,33 @@ function anotherFunction() {
421424
anotherFunction();
422425
```
423426

427+
It is possible to reconstruct the original locations by setting the option `sourceMap` to `true`.
428+
If the source map is not available, the original location will be the same as the current location.
429+
When the `--enable-source-maps` flag is enabled, for example when using `--experimental-transform-types`,
430+
`sourceMap` will be true by default.
431+
432+
```ts
433+
import util from 'node:util';
434+
435+
interface Foo {
436+
foo: string;
437+
}
438+
439+
const callSites = util.getCallSites({ sourceMap: true });
440+
441+
// With sourceMap:
442+
// Function Name: ''
443+
// Script Name: example.js
444+
// Line Number: 7
445+
// Column Number: 26
446+
447+
// Without sourceMap:
448+
// Function Name: ''
449+
// Script Name: example.js
450+
// Line Number: 2
451+
// Column Number: 26
452+
```
453+
424454
## `util.getSystemErrorName(err)`
425455

426456
<!-- YAML

lib/util.js

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
const {
2525
ArrayIsArray,
2626
ArrayPrototypePop,
27+
ArrayPrototypePush,
2728
Error,
2829
ErrorCaptureStackTrace,
2930
FunctionPrototypeBind,
@@ -61,6 +62,7 @@ const {
6162
validateNumber,
6263
validateString,
6364
validateOneOf,
65+
validateObject,
6466
} = require('internal/validators');
6567
const {
6668
isReadableStream,
@@ -74,11 +76,13 @@ function lazyUtilColors() {
7476
utilColors ??= require('internal/util/colors');
7577
return utilColors;
7678
}
79+
const { getOptionValue } = require('internal/options');
7780

7881
const binding = internalBinding('util');
7982

8083
const {
8184
deprecate,
85+
getLazy,
8286
getSystemErrorMap,
8387
getSystemErrorName: internalErrorName,
8488
getSystemErrorMessage: internalErrorMessage,
@@ -328,14 +332,90 @@ function parseEnv(content) {
328332
return binding.parseEnv(content);
329333
}
330334

335+
const lazySourceMap = getLazy(() => require('internal/source_map/source_map_cache'));
336+
337+
/**
338+
* @typedef {object} CallSite // The call site
339+
* @property {string} scriptName // The name of the resource that contains the
340+
* script for the function for this StackFrame
341+
* @property {string} functionName // The name of the function associated with this stack frame
342+
* @property {number} lineNumber // The number, 1-based, of the line for the associate function call
343+
* @property {number} columnNumber // The 1-based column offset on the line for the associated function call
344+
*/
345+
346+
/**
347+
* @param {CallSite} callSite // The call site object to reconstruct from source map
348+
* @returns {CallSite | undefined} // The reconstructed call site object
349+
*/
350+
function reconstructCallSite(callSite) {
351+
const { scriptName, lineNumber, column } = callSite;
352+
const sourceMap = lazySourceMap().findSourceMap(scriptName);
353+
if (!sourceMap) return;
354+
const entry = sourceMap.findEntry(lineNumber - 1, column - 1);
355+
if (!entry?.originalSource) return;
356+
return {
357+
__proto__: null,
358+
// If the name is not found, it is an empty string to match the behavior of `util.getCallSite()`
359+
functionName: entry.name ?? '',
360+
scriptName: entry.originalSource,
361+
lineNumber: entry.originalLine + 1,
362+
column: entry.originalColumn + 1,
363+
};
364+
}
365+
366+
/**
367+
*
368+
* The call site array to map
369+
* @param {CallSite[]} callSites
370+
* Array of objects with the reconstructed call site
371+
* @returns {CallSite[]}
372+
*/
373+
function mapCallSite(callSites) {
374+
const result = [];
375+
for (let i = 0; i < callSites.length; ++i) {
376+
const callSite = callSites[i];
377+
const found = reconstructCallSite(callSite);
378+
ArrayPrototypePush(result, found ?? callSite);
379+
}
380+
return result;
381+
}
382+
383+
/**
384+
* @typedef {object} CallSiteOptions // The call site options
385+
* @property {boolean} sourceMap // Enable source map support
386+
*/
387+
331388
/**
332389
* Returns the callSite
333390
* @param {number} frameCount
334-
* @returns {object}
391+
* @param {CallSiteOptions} options
392+
* @returns {CallSite[]}
335393
*/
336-
function getCallSites(frameCount = 10) {
394+
function getCallSites(frameCount = 10, options) {
395+
// If options is not provided check if frameCount is an object
396+
if (options === undefined) {
397+
if (typeof frameCount === 'object') {
398+
// If frameCount is an object, it is the options object
399+
options = frameCount;
400+
validateObject(options, 'options');
401+
validateBoolean(options.sourceMap, 'options.sourceMap');
402+
frameCount = 10;
403+
} else {
404+
// If options is not provided, set it to an empty object
405+
options = {};
406+
};
407+
} else {
408+
// If options is provided, validate it
409+
validateObject(options, 'options');
410+
validateBoolean(options.sourceMap, 'options.sourceMap');
411+
}
412+
337413
// Using kDefaultMaxCallStackSizeToCapture as reference
338414
validateNumber(frameCount, 'frameCount', 1, 200);
415+
// If options.sourceMaps is true or if sourceMaps are enabled but the option.sourceMaps is not set explictly to false
416+
if (options.sourceMap === true || (getOptionValue('--enable-source-maps') && options.sourceMap !== false)) {
417+
return mapCallSite(binding.getCallSites(frameCount));
418+
}
339419
return binding.getCallSites(frameCount);
340420
};
341421

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const { getCallSites } = require('node:util');
2+
3+
interface CallSite {
4+
A;
5+
B;
6+
}
7+
8+
const callSite = getCallSites({ sourceMap: false })[0];
9+
10+
console.log('mapCallSite: ', callSite);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const { getCallSites } = require('node:util');
2+
3+
interface CallSite {
4+
A;
5+
B;
6+
}
7+
8+
const callSite = getCallSites()[0];
9+
10+
console.log('getCallSite: ', callSite);

test/parallel/test-util-getcallsites.js

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,17 @@ const assert = require('node:assert');
5353
code: 'ERR_OUT_OF_RANGE'
5454
}));
5555
assert.throws(() => {
56-
getCallSites({});
56+
getCallSites([]);
57+
}, common.expectsError({
58+
code: 'ERR_INVALID_ARG_TYPE'
59+
}));
60+
assert.throws(() => {
61+
getCallSites({}, {});
62+
}, common.expectsError({
63+
code: 'ERR_INVALID_ARG_TYPE'
64+
}));
65+
assert.throws(() => {
66+
getCallSites(10, 10);
5767
}, common.expectsError({
5868
code: 'ERR_INVALID_ARG_TYPE'
5969
}));
@@ -104,3 +114,51 @@ const assert = require('node:assert');
104114
assert.notStrictEqual(callSites.length, 0);
105115
Error.stackTraceLimit = originalStackTraceLimit;
106116
}
117+
118+
{
119+
const { status, stderr, stdout } = spawnSync(process.execPath, [
120+
'--no-warnings',
121+
'--experimental-transform-types',
122+
fixtures.path('typescript/ts/test-get-callsite.ts'),
123+
]);
124+
125+
const output = stdout.toString();
126+
assert.strictEqual(stderr.toString(), '');
127+
assert.match(output, /lineNumber: 8/);
128+
assert.match(output, /column: 18/);
129+
assert.match(output, /test-get-callsite\.ts/);
130+
assert.strictEqual(status, 0);
131+
}
132+
133+
{
134+
const { status, stderr, stdout } = spawnSync(process.execPath, [
135+
'--no-warnings',
136+
'--experimental-transform-types',
137+
'--no-enable-source-maps',
138+
fixtures.path('typescript/ts/test-get-callsite.ts'),
139+
]);
140+
141+
const output = stdout.toString();
142+
assert.strictEqual(stderr.toString(), '');
143+
// Line should be wrong when sourcemaps are disable
144+
assert.match(output, /lineNumber: 2/);
145+
assert.match(output, /column: 18/);
146+
assert.match(output, /test-get-callsite\.ts/);
147+
assert.strictEqual(status, 0);
148+
}
149+
150+
{
151+
// Source maps should be disabled when options.sourceMap is false
152+
const { status, stderr, stdout } = spawnSync(process.execPath, [
153+
'--no-warnings',
154+
'--experimental-transform-types',
155+
fixtures.path('typescript/ts/test-get-callsite-explicit.ts'),
156+
]);
157+
158+
const output = stdout.toString();
159+
assert.strictEqual(stderr.toString(), '');
160+
assert.match(output, /lineNumber: 2/);
161+
assert.match(output, /column: 18/);
162+
assert.match(output, /test-get-callsite-explicit\.ts/);
163+
assert.strictEqual(status, 0);
164+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy