Skip to content

Commit 071eaad

Browse files
isaacsruyadorno
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 <benjamingr@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
1 parent 3363cfa commit 071eaad

File tree

3 files changed

+115
-22
lines changed

3 files changed

+115
-22
lines changed

doc/api/module.md

+56-12
Original file line numberDiff line numberDiff line change
@@ -196,23 +196,67 @@ Creates a new `sourceMap` instance.
196196
197197
Getter for the payload used to construct the [`SourceMap`][] instance.
198198
199-
#### `sourceMap.findEntry(lineNumber, columnNumber)`
199+
#### `sourceMap.findEntry(lineOffset, columnOffset)`
200200
201-
* `lineNumber` {number}
202-
* `columnNumber` {number}
201+
* `lineOffset` {number} The zero-indexed line number offset in
202+
the generated source
203+
* `columnOffset` {number} The zero-indexed column number offset
204+
in the generated source
203205
* Returns: {Object}
204206
205-
Given a line number and column number in the generated source file, returns
206-
an object representing the position in the original file. The object returned
207-
consists of the following keys:
208-
209-
* generatedLine: {number}
210-
* generatedColumn: {number}
211-
* originalSource: {string}
212-
* originalLine: {number}
213-
* originalColumn: {number}
207+
Given a line offset and column offset in the generated source
208+
file, returns an object representing the SourceMap range in the
209+
original file if found, or an empty object if not.
210+
211+
The object returned contains the following keys:
212+
213+
* generatedLine: {number} The line offset of the start of the
214+
range in the generated source
215+
* generatedColumn: {number} The column offset of start of the
216+
range in the generated source
217+
* originalSource: {string} The file name of the original source,
218+
as reported in the SourceMap
219+
* originalLine: {number} The line offset of the start of the
220+
range in the original source
221+
* originalColumn: {number} The column offset of start of the
222+
range in the original source
214223
* name: {string}
215224
225+
The returned value represents the raw range as it appears in the
226+
SourceMap, based on zero-indexed offsets, _not_ 1-indexed line and
227+
column numbers as they appear in Error messages and CallSite
228+
objects.
229+
230+
To get the corresponding 1-indexed line and column numbers from a
231+
lineNumber and columnNumber as they are reported by Error stacks
232+
and CallSite objects, use `sourceMap.findOrigin(lineNumber,
233+
columnNumber)`
234+
235+
#### `sourceMap.findOrigin(lineNumber, columnNumber)`
236+
237+
* `lineNumber` {number} The 1-indexed line number of the call
238+
site in the generated source
239+
* `columnOffset` {number} The 1-indexed column number
240+
of the call site in the generated source
241+
* Returns: {Object}
242+
243+
Given a 1-indexed lineNumber and columnNumber from a call site in
244+
the generated source, find the corresponding call site location
245+
in the original source.
246+
247+
If the lineNumber and columnNumber provided are not found in any
248+
source map, then an empty object is returned. Otherwise, the
249+
returned object contains the following keys:
250+
251+
* name: {string | undefined} The name of the range in the
252+
source map, if one was provided
253+
* fileName: {string} The file name of the original source, as
254+
reported in the SourceMap
255+
* lineNumber: {number} The 1-indexed lineNumber of the
256+
corresponding call site in the original source
257+
* columnNumber: {number} The 1-indexed columnNumber of the
258+
corresponding call site in the original source
259+
216260
[CommonJS]: modules.md
217261
[ES Modules]: esm.md
218262
[Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej

lib/internal/source_map/source_map.js

+34-8
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

+25-2
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)