Skip to content

Commit e26ffe7

Browse files
authored
module: add SourceMap.findOrigin
This adds the `SourceMap.findOrigin(lineNumber, columnNumber)` method, for finding the origin source file and 1-indexed line and column numbers corresponding to the 1-indexed line and column numbers from a call site in generated source code. Fix: #47770 PR-URL: #47790 Fixes: #47770 Reviewed-By: Benjamin Gruenbaum <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]>
1 parent e934003 commit e26ffe7

File tree

3 files changed

+115
-22
lines changed

3 files changed

+115
-22
lines changed

doc/api/module.md

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -293,23 +293,67 @@ Creates a new `sourceMap` instance.
293293
294294
Getter for the payload used to construct the [`SourceMap`][] instance.
295295
296-
#### `sourceMap.findEntry(lineNumber, columnNumber)`
296+
#### `sourceMap.findEntry(lineOffset, columnOffset)`
297297
298-
* `lineNumber` {number}
299-
* `columnNumber` {number}
298+
* `lineOffset` {number} The zero-indexed line number offset in
299+
the generated source
300+
* `columnOffset` {number} The zero-indexed column number offset
301+
in the generated source
300302
* Returns: {Object}
301303
302-
Given a line number and column number in the generated source file, returns
303-
an object representing the position in the original file. The object returned
304-
consists of the following keys:
305-
306-
* generatedLine: {number}
307-
* generatedColumn: {number}
308-
* originalSource: {string}
309-
* originalLine: {number}
310-
* originalColumn: {number}
304+
Given a line offset and column offset in the generated source
305+
file, returns an object representing the SourceMap range in the
306+
original file if found, or an empty object if not.
307+
308+
The object returned contains the following keys:
309+
310+
* generatedLine: {number} The line offset of the start of the
311+
range in the generated source
312+
* generatedColumn: {number} The column offset of start of the
313+
range in the generated source
314+
* originalSource: {string} The file name of the original source,
315+
as reported in the SourceMap
316+
* originalLine: {number} The line offset of the start of the
317+
range in the original source
318+
* originalColumn: {number} The column offset of start of the
319+
range in the original source
311320
* name: {string}
312321
322+
The returned value represents the raw range as it appears in the
323+
SourceMap, based on zero-indexed offsets, _not_ 1-indexed line and
324+
column numbers as they appear in Error messages and CallSite
325+
objects.
326+
327+
To get the corresponding 1-indexed line and column numbers from a
328+
lineNumber and columnNumber as they are reported by Error stacks
329+
and CallSite objects, use `sourceMap.findOrigin(lineNumber,
330+
columnNumber)`
331+
332+
#### `sourceMap.findOrigin(lineNumber, columnNumber)`
333+
334+
* `lineNumber` {number} The 1-indexed line number of the call
335+
site in the generated source
336+
* `columnOffset` {number} The 1-indexed column number
337+
of the call site in the generated source
338+
* Returns: {Object}
339+
340+
Given a 1-indexed lineNumber and columnNumber from a call site in
341+
the generated source, find the corresponding call site location
342+
in the original source.
343+
344+
If the lineNumber and columnNumber provided are not found in any
345+
source map, then an empty object is returned. Otherwise, the
346+
returned object contains the following keys:
347+
348+
* name: {string | undefined} The name of the range in the
349+
source map, if one was provided
350+
* fileName: {string} The file name of the original source, as
351+
reported in the SourceMap
352+
* lineNumber: {number} The 1-indexed lineNumber of the
353+
corresponding call site in the original source
354+
* columnNumber: {number} The 1-indexed columnNumber of the
355+
corresponding call site in the original source
356+
313357
[CommonJS]: modules.md
314358
[ES Modules]: esm.md
315359
[Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej

lib/internal/source_map/source_map.js

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,28 +169,28 @@ class SourceMap {
169169
};
170170

171171
/**
172-
* @param {number} lineNumber in compiled resource
173-
* @param {number} columnNumber in compiled resource
174-
* @return {?Array}
172+
* @param {number} lineOffset 0-indexed line offset in compiled resource
173+
* @param {number} columnOffset 0-indexed column offset in compiled resource
174+
* @return {object} representing start of range if found, or empty object
175175
*/
176-
findEntry(lineNumber, columnNumber) {
176+
findEntry(lineOffset, columnOffset) {
177177
let first = 0;
178178
let count = this.#mappings.length;
179179
while (count > 1) {
180180
const step = count >> 1;
181181
const middle = first + step;
182182
const mapping = this.#mappings[middle];
183-
if (lineNumber < mapping[0] ||
184-
(lineNumber === mapping[0] && columnNumber < mapping[1])) {
183+
if (lineOffset < mapping[0] ||
184+
(lineOffset === mapping[0] && columnOffset < mapping[1])) {
185185
count = step;
186186
} else {
187187
first = middle;
188188
count -= step;
189189
}
190190
}
191191
const entry = this.#mappings[first];
192-
if (!first && entry && (lineNumber < entry[0] ||
193-
(lineNumber === entry[0] && columnNumber < entry[1]))) {
192+
if (!first && entry && (lineOffset < entry[0] ||
193+
(lineOffset === entry[0] && columnOffset < entry[1]))) {
194194
return {};
195195
} else if (!entry) {
196196
return {};
@@ -205,6 +205,32 @@ class SourceMap {
205205
};
206206
}
207207

208+
/**
209+
* @param {number} lineNumber 1-indexed line number in compiled resource call site
210+
* @param {number} columnNumber 1-indexed column number in compiled resource call site
211+
* @return {object} representing origin call site if found, or empty object
212+
*/
213+
findOrigin(lineNumber, columnNumber) {
214+
const range = this.findEntry(lineNumber - 1, columnNumber - 1);
215+
if (
216+
range.originalSource === undefined ||
217+
range.originalLine === undefined ||
218+
range.originalColumn === undefined ||
219+
range.generatedLine === undefined ||
220+
range.generatedColumn === undefined
221+
) {
222+
return {};
223+
}
224+
const lineOffset = lineNumber - range.generatedLine;
225+
const columnOffset = columnNumber - range.generatedColumn;
226+
return {
227+
name: range.name,
228+
fileName: range.originalSource,
229+
lineNumber: range.originalLine + lineOffset,
230+
columnNumber: range.originalColumn + columnOffset,
231+
};
232+
}
233+
208234
/**
209235
* @override
210236
*/

test/parallel/test-source-map-api.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ const { readFileSync } = require('fs');
4949
assert.strictEqual(originalLine, 2);
5050
assert.strictEqual(originalColumn, 4);
5151
assert(originalSource.endsWith('disk.js'));
52+
const {
53+
fileName,
54+
lineNumber,
55+
columnNumber,
56+
} = sourceMap.findOrigin(1, 30);
57+
assert.strictEqual(fileName, originalSource);
58+
assert.strictEqual(lineNumber, 3);
59+
assert.strictEqual(columnNumber, 6);
5260
}
5361

5462
// findSourceMap() can be used in Error.prepareStackTrace() to lookup
@@ -89,6 +97,18 @@ const { readFileSync } = require('fs');
8997
assert.strictEqual(originalLine, 17);
9098
assert.strictEqual(originalColumn, 10);
9199
assert(originalSource.endsWith('typescript-throw.ts'));
100+
101+
const {
102+
fileName,
103+
lineNumber,
104+
columnNumber,
105+
} = sourceMap.findOrigin(
106+
callSite.getLineNumber(),
107+
callSite.getColumnNumber()
108+
);
109+
assert.strictEqual(fileName, originalSource);
110+
assert.strictEqual(lineNumber, 18);
111+
assert.strictEqual(columnNumber, 11);
92112
}
93113

94114
// SourceMap can be instantiated with Source Map V3 object as payload.
@@ -112,8 +132,8 @@ const { readFileSync } = require('fs');
112132
assert.notStrictEqual(payload.sources, sourceMap.payload.sources);
113133
}
114134

115-
// findEntry() must return empty object instead error when
116-
// receive a malformed mappings.
135+
// findEntry() and findOrigin() must return empty object instead of
136+
// error when receiving a malformed mappings.
117137
{
118138
const payload = JSON.parse(readFileSync(
119139
require.resolve('../fixtures/source-map/disk.map'), 'utf8'
@@ -124,6 +144,9 @@ const { readFileSync } = require('fs');
124144
const result = sourceMap.findEntry(0, 5);
125145
assert.strictEqual(typeof result, 'object');
126146
assert.strictEqual(Object.keys(result).length, 0);
147+
const origin = sourceMap.findOrigin(0, 5);
148+
assert.strictEqual(typeof origin, 'object');
149+
assert.strictEqual(Object.keys(origin).length, 0);
127150
}
128151

129152
// SourceMap can be instantiated with Index Source Map V3 object as payload.

0 commit comments

Comments
 (0)