Skip to content

Commit f0709fd

Browse files
isaacsRafaelGSS
authored andcommitted
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 112335e commit f0709fd

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
@@ -198,23 +198,67 @@ Creates a new `sourceMap` instance.
198198
199199
Getter for the payload used to construct the [`SourceMap`][] instance.
200200
201-
#### `sourceMap.findEntry(lineNumber, columnNumber)`
201+
#### `sourceMap.findEntry(lineOffset, columnOffset)`
202202
203-
* `lineNumber` {number}
204-
* `columnNumber` {number}
203+
* `lineOffset` {number} The zero-indexed line number offset in
204+
the generated source
205+
* `columnOffset` {number} The zero-indexed column number offset
206+
in the generated source
205207
* Returns: {Object}
206208
207-
Given a line number and column number in the generated source file, returns
208-
an object representing the position in the original file. The object returned
209-
consists of the following keys:
210-
211-
* generatedLine: {number}
212-
* generatedColumn: {number}
213-
* originalSource: {string}
214-
* originalLine: {number}
215-
* originalColumn: {number}
209+
Given a line offset and column offset in the generated source
210+
file, returns an object representing the SourceMap range in the
211+
original file if found, or an empty object if not.
212+
213+
The object returned contains the following keys:
214+
215+
* generatedLine: {number} The line offset of the start of the
216+
range in the generated source
217+
* generatedColumn: {number} The column offset of start of the
218+
range in the generated source
219+
* originalSource: {string} The file name of the original source,
220+
as reported in the SourceMap
221+
* originalLine: {number} The line offset of the start of the
222+
range in the original source
223+
* originalColumn: {number} The column offset of start of the
224+
range in the original source
216225
* name: {string}
217226
227+
The returned value represents the raw range as it appears in the
228+
SourceMap, based on zero-indexed offsets, _not_ 1-indexed line and
229+
column numbers as they appear in Error messages and CallSite
230+
objects.
231+
232+
To get the corresponding 1-indexed line and column numbers from a
233+
lineNumber and columnNumber as they are reported by Error stacks
234+
and CallSite objects, use `sourceMap.findOrigin(lineNumber,
235+
columnNumber)`
236+
237+
#### `sourceMap.findOrigin(lineNumber, columnNumber)`
238+
239+
* `lineNumber` {number} The 1-indexed line number of the call
240+
site in the generated source
241+
* `columnOffset` {number} The 1-indexed column number
242+
of the call site in the generated source
243+
* Returns: {Object}
244+
245+
Given a 1-indexed lineNumber and columnNumber from a call site in
246+
the generated source, find the corresponding call site location
247+
in the original source.
248+
249+
If the lineNumber and columnNumber provided are not found in any
250+
source map, then an empty object is returned. Otherwise, the
251+
returned object contains the following keys:
252+
253+
* name: {string | undefined} The name of the range in the
254+
source map, if one was provided
255+
* fileName: {string} The file name of the original source, as
256+
reported in the SourceMap
257+
* lineNumber: {number} The 1-indexed lineNumber of the
258+
corresponding call site in the original source
259+
* columnNumber: {number} The 1-indexed columnNumber of the
260+
corresponding call site in the original source
261+
218262
[CommonJS]: modules.md
219263
[ES Modules]: esm.md
220264
[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)