Skip to content

Commit 9777431

Browse files
module: add module.mapCallSite()
1 parent 4354a1d commit 9777431

File tree

5 files changed

+118
-3
lines changed

5 files changed

+118
-3
lines changed

doc/api/module.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,37 @@ isBuiltin('fs'); // true
317317
isBuiltin('wss'); // false
318318
```
319319
320+
### `module.mapCallSite(callSite)`
321+
322+
<!-- YAML
323+
added: REPLACEME
324+
-->
325+
326+
> Stability: 1.1 - Active development
327+
328+
* `callSite` {Object | Array} A [CallSite][] object or an array of CallSite objects.
329+
* Returns: {Object | Array} The original source code location(s) for the given CallSite object(s).
330+
331+
Reconstructs the original source code location from a [CallSite][] object through the source map.
332+
333+
```mjs
334+
import { mapCallSite } from 'node:module';
335+
import { getCallSite } from 'node:util';
336+
337+
mapCallSite(getCallSite()); // Reconstructs the original source code location for the whole stack
338+
339+
mapCallSite(getCallSite()[0]); // Reconstructs the original source code location for the first frame
340+
```
341+
342+
```cjs
343+
const { mapCallSite } = require('node:module');
344+
const { getCallSite } = require('node:util');
345+
346+
mapCallSite(getCallSite()); // Reconstructs the original source code location for the whole stack
347+
348+
mapCallSite(getCallSite()[0]); // Reconstructs the original source code location for the first frame
349+
```
350+
320351
### `module.register(specifier[, parentURL][, options])`
321352
322353
<!-- YAML
@@ -1397,6 +1428,7 @@ returned object contains the following keys:
13971428
* columnNumber: {number} The 1-indexed columnNumber of the
13981429
corresponding call site in the original source
13991430
1431+
[CallSite]: util.md#utilgetcallsiteframes
14001432
[CommonJS]: modules.md
14011433
[Conditional exports]: packages.md#conditional-exports
14021434
[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 & 2 deletions
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');
@@ -24,5 +24,5 @@ Module.findPackageJSON = findPackageJSON;
2424
Module.flushCompileCache = flushCompileCache;
2525
Module.getCompileCacheDir = getCompileCacheDir;
2626
Module.stripTypeScriptTypes = stripTypeScriptTypes;
27-
27+
Module.mapCallSite = mapCallSite;
2828
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)