Skip to content

Commit 9e2675f

Browse files
module: add module.mapCallSite()
1 parent 2505e21 commit 9e2675f

File tree

5 files changed

+118
-2
lines changed

5 files changed

+118
-2
lines changed

doc/api/module.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,37 @@ isBuiltin('fs'); // true
235235
isBuiltin('wss'); // false
236236
```
237237
238+
### `module.mapCallSite(callSite)`
239+
240+
<!-- YAML
241+
added: REPLACEME
242+
-->
243+
244+
> Stability: 1.1 - Active development
245+
246+
* `callSite` {Object | Array} A [CallSite][] object or an array of CallSite objects.
247+
* Returns: {Object | Array} The original source code location(s) for the given CallSite object(s).
248+
249+
Reconstructs the original source code location from a [CallSite][] object through the source map.
250+
251+
```mjs
252+
import { mapCallSite } from 'node:module';
253+
import { getCallSite } from 'node:util';
254+
255+
mapCallSite(getCallSite()); // Reconstructs the original source code location for the whole stack
256+
257+
mapCallSite(getCallSite()[0]); // Reconstructs the original source code location for the first frame
258+
```
259+
260+
```cjs
261+
const { mapCallSite } = require('node:module');
262+
const { getCallSite } = require('node:util');
263+
264+
mapCallSite(getCallSite()); // Reconstructs the original source code location for the whole stack
265+
266+
mapCallSite(getCallSite()[0]); // Reconstructs the original source code location for the first frame
267+
```
268+
238269
### `module.register(specifier[, parentURL][, options])`
239270
240271
<!-- YAML
@@ -1315,6 +1346,7 @@ returned object contains the following keys:
13151346
* columnNumber: {number} The 1-indexed columnNumber of the
13161347
corresponding call site in the original source
13171348
1349+
[CallSite]: util.md#util.getcallsite
13181350
[CommonJS]: modules.md
13191351
[Conditional exports]: packages.md#conditional-exports
13201352
[Customization hooks]: #customization-hooks

lib/internal/source_map/source_map_cache.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
const {
4+
ArrayIsArray,
45
ArrayPrototypePush,
56
JSONParse,
67
RegExpPrototypeExec,
@@ -15,7 +16,7 @@ let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => {
1516
debug = fn;
1617
});
1718

18-
const { validateBoolean } = require('internal/validators');
19+
const { validateBoolean, validateObject, validateString, validateNumber } = require('internal/validators');
1920
const {
2021
setSourceMapsEnabled: setSourceMapsNative,
2122
} = internalBinding('errors');
@@ -351,11 +352,54 @@ function findSourceMap(sourceURL) {
351352
return sourceMap;
352353
}
353354

355+
function reconstructCallSite(callSite) {
356+
const { scriptName, lineNumber, column } = callSite;
357+
const sourceMap = findSourceMap(scriptName);
358+
if (!sourceMap) return;
359+
const entry = sourceMap.findEntry(lineNumber - 1, column - 1);
360+
if (!entry?.originalSource) return;
361+
return {
362+
__proto__: null,
363+
functionName: callSite.name,
364+
lineNumber: entry.originalLine + 1,
365+
column: entry.originalColumn + 1,
366+
scriptName: entry.originalSource,
367+
};
368+
}
369+
370+
function validateCallSite(callSite) {
371+
validateObject(callSite, 'callSites');
372+
validateString(callSite.scriptName, 'callSites.scriptName');
373+
validateString(callSite.functionName, 'callSites.functionName');
374+
validateNumber(callSite.lineNumber, 'callSites.lineNumber');
375+
validateNumber(callSite.column, 'callSites.column');
376+
}
377+
378+
/**
379+
*
380+
* @param {object} callSites The call site object to map (ex `util.getCallSite()`)
381+
* @returns {object | object[]} An object or array of objects with the reconstructed call site
382+
*/
383+
function mapCallSite(callSites) {
384+
if (ArrayIsArray(callSites)) {
385+
const result = [];
386+
for (const callSite of callSites) {
387+
validateCallSite(callSite);
388+
const found = reconstructCallSite(callSite);
389+
ArrayPrototypePush(result, found ?? callSite);
390+
}
391+
return result;
392+
}
393+
validateCallSite(callSites);
394+
return reconstructCallSite(callSites) ?? callSites;
395+
}
396+
354397
module.exports = {
355398
findSourceMap,
356399
getSourceMapsEnabled,
357400
setSourceMapsEnabled,
358401
maybeCacheSourceMap,
359402
maybeCacheGeneratedSourceMap,
403+
mapCallSite,
360404
sourceMapCacheToObject,
361405
};

lib/module.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const { findSourceMap } = require('internal/source_map/source_map_cache');
3+
const { findSourceMap, mapCallSite } = require('internal/source_map/source_map_cache');
44
const { Module } = require('internal/modules/cjs/loader');
55
const { register } = require('internal/modules/esm/loader');
66
const { SourceMap } = require('internal/source_map/source_map');
@@ -20,4 +20,5 @@ Module.enableCompileCache = enableCompileCache;
2020
Module.flushCompileCache = flushCompileCache;
2121
Module.stripTypeScriptTypes = stripTypeScriptTypes;
2222
Module.getCompileCacheDir = getCompileCacheDir;
23+
Module.mapCallSite = mapCallSite;
2324
module.exports = Module;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { spawnPromisified } from '../common/index.mjs';
2+
import * as fixtures from '../common/fixtures.mjs';
3+
import { strictEqual, match, throws, deepStrictEqual } from 'node:assert';
4+
import { test } from 'node:test';
5+
import { mapCallSite } from 'node:module';
6+
import { getCallSite } from 'node:util';
7+
8+
test('module.mapCallSite', async () => {
9+
throws(() => mapCallSite('not an object'), { code: 'ERR_INVALID_ARG_TYPE' });
10+
deepStrictEqual(mapCallSite([]), []);
11+
throws(() => mapCallSite({}), { code: 'ERR_INVALID_ARG_TYPE' });
12+
13+
const callSite = getCallSite();
14+
deepStrictEqual(callSite, mapCallSite(callSite));
15+
deepStrictEqual(callSite[0], mapCallSite(callSite[0]));
16+
});
17+
18+
19+
test('module.mapCallSite should reconstruct ts callsite', async () => {
20+
const result = await spawnPromisified(process.execPath, [
21+
'--no-warnings',
22+
'--experimental-transform-types',
23+
fixtures.path('typescript/ts/test-callsite.ts'),
24+
]);
25+
const output = result.stdout.toString().trim();
26+
strictEqual(result.stderr, '');
27+
match(output, /lineNumber: 9/);
28+
match(output, /column: 25/);
29+
strictEqual(result.code, 0);
30+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { getCallSite } = require('node:util');
2+
const { mapCallSite } = require('node:module');
3+
4+
interface CallSite {
5+
A;
6+
B;
7+
}
8+
9+
console.log(mapCallSite(getCallSite()[0]));

0 commit comments

Comments
 (0)