Skip to content

Commit

Permalink
module: add module.mapCallSite()
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-ippolito committed Oct 30, 2024
1 parent 4354a1d commit 9777431
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 3 deletions.
32 changes: 32 additions & 0 deletions doc/api/module.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,37 @@ isBuiltin('fs'); // true
isBuiltin('wss'); // false
```
### `module.mapCallSite(callSite)`
<!-- YAML
added: REPLACEME
-->
> Stability: 1.1 - Active development
* `callSite` {Object | Array} A [CallSite][] object or an array of CallSite objects.
* Returns: {Object | Array} The original source code location(s) for the given CallSite object(s).
Reconstructs the original source code location from a [CallSite][] object through the source map.
```mjs
import { mapCallSite } from 'node:module';
import { getCallSite } from 'node:util';

mapCallSite(getCallSite()); // Reconstructs the original source code location for the whole stack

mapCallSite(getCallSite()[0]); // Reconstructs the original source code location for the first frame
```
```cjs
const { mapCallSite } = require('node:module');
const { getCallSite } = require('node:util');

mapCallSite(getCallSite()); // Reconstructs the original source code location for the whole stack

mapCallSite(getCallSite()[0]); // Reconstructs the original source code location for the first frame
```
### `module.register(specifier[, parentURL][, options])`
<!-- YAML
Expand Down Expand Up @@ -1397,6 +1428,7 @@ returned object contains the following keys:
* columnNumber: {number} The 1-indexed columnNumber of the
corresponding call site in the original source
[CallSite]: util.md#utilgetcallsiteframes
[CommonJS]: modules.md
[Conditional exports]: packages.md#conditional-exports
[Customization hooks]: #customization-hooks
Expand Down
46 changes: 45 additions & 1 deletion lib/internal/source_map/source_map_cache.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const {
ArrayIsArray,
ArrayPrototypePush,
JSONParse,
RegExpPrototypeExec,
Expand All @@ -15,7 +16,7 @@ let debug = require('internal/util/debuglog').debuglog('source_map', (fn) => {
debug = fn;
});

const { validateBoolean } = require('internal/validators');
const { validateBoolean, validateObject, validateString, validateNumber } = require('internal/validators');
const {
setSourceMapsEnabled: setSourceMapsNative,
} = internalBinding('errors');
Expand Down Expand Up @@ -351,11 +352,54 @@ function findSourceMap(sourceURL) {
return sourceMap;
}

function reconstructCallSite(callSite) {
const { scriptName, lineNumber, column } = callSite;
const sourceMap = findSourceMap(scriptName);
if (!sourceMap) return;
const entry = sourceMap.findEntry(lineNumber - 1, column - 1);
if (!entry?.originalSource) return;
return {
__proto__: null,
functionName: callSite.name,
lineNumber: entry.originalLine + 1,
column: entry.originalColumn + 1,
scriptName: entry.originalSource,
};
}

function validateCallSite(callSite) {
validateObject(callSite, 'callSites');
validateString(callSite.scriptName, 'callSites.scriptName');
validateString(callSite.functionName, 'callSites.functionName');
validateNumber(callSite.lineNumber, 'callSites.lineNumber');
validateNumber(callSite.column, 'callSites.column');
}

/**
*
* @param {object} callSites The call site object to map (ex `util.getCallSite()`)
* @returns {object | object[]} An object or array of objects with the reconstructed call site
*/
function mapCallSite(callSites) {
if (ArrayIsArray(callSites)) {
const result = [];
for (const callSite of callSites) {
validateCallSite(callSite);
const found = reconstructCallSite(callSite);
ArrayPrototypePush(result, found ?? callSite);
}
return result;
}
validateCallSite(callSites);
return reconstructCallSite(callSites) ?? callSites;
}

module.exports = {
findSourceMap,
getSourceMapsEnabled,
setSourceMapsEnabled,
maybeCacheSourceMap,
maybeCacheGeneratedSourceMap,
mapCallSite,
sourceMapCacheToObject,
};
4 changes: 2 additions & 2 deletions lib/module.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const { findSourceMap } = require('internal/source_map/source_map_cache');
const { findSourceMap, mapCallSite } = require('internal/source_map/source_map_cache');
const { Module } = require('internal/modules/cjs/loader');
const { register } = require('internal/modules/esm/loader');
const { SourceMap } = require('internal/source_map/source_map');
Expand All @@ -24,5 +24,5 @@ Module.findPackageJSON = findPackageJSON;
Module.flushCompileCache = flushCompileCache;
Module.getCompileCacheDir = getCompileCacheDir;
Module.stripTypeScriptTypes = stripTypeScriptTypes;

Module.mapCallSite = mapCallSite;
module.exports = Module;
30 changes: 30 additions & 0 deletions test/es-module/test-module-get-callsite.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { spawnPromisified } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { strictEqual, match, throws, deepStrictEqual } from 'node:assert';
import { test } from 'node:test';
import { mapCallSite } from 'node:module';
import { getCallSite } from 'node:util';

test('module.mapCallSite', async () => {
throws(() => mapCallSite('not an object'), { code: 'ERR_INVALID_ARG_TYPE' });
deepStrictEqual(mapCallSite([]), []);
throws(() => mapCallSite({}), { code: 'ERR_INVALID_ARG_TYPE' });

const callSite = getCallSite();
deepStrictEqual(callSite, mapCallSite(callSite));
deepStrictEqual(callSite[0], mapCallSite(callSite[0]));
});


test('module.mapCallSite should reconstruct ts callsite', async () => {
const result = await spawnPromisified(process.execPath, [
'--no-warnings',
'--experimental-transform-types',
fixtures.path('typescript/ts/test-callsite.ts'),
]);
const output = result.stdout.toString().trim();
strictEqual(result.stderr, '');
match(output, /lineNumber: 9/);
match(output, /column: 25/);
strictEqual(result.code, 0);
});
9 changes: 9 additions & 0 deletions test/fixtures/typescript/ts/test-callsite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { getCallSite } = require('node:util');
const { mapCallSite } = require('node:module');

interface CallSite {
A;
B;
}

console.log(mapCallSite(getCallSite()[0]));

0 comments on commit 9777431

Please sign in to comment.