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: https://github.com/nodejs/node/pull/47790 Fixes: https://github.com/nodejs/node/issues/47770 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
This commit is contained in:
parent
e934003811
commit
e26ffe7358
@ -293,23 +293,67 @@ Creates a new `sourceMap` instance.
|
|||||||
|
|
||||||
Getter for the payload used to construct the [`SourceMap`][] instance.
|
Getter for the payload used to construct the [`SourceMap`][] instance.
|
||||||
|
|
||||||
#### `sourceMap.findEntry(lineNumber, columnNumber)`
|
#### `sourceMap.findEntry(lineOffset, columnOffset)`
|
||||||
|
|
||||||
* `lineNumber` {number}
|
* `lineOffset` {number} The zero-indexed line number offset in
|
||||||
* `columnNumber` {number}
|
the generated source
|
||||||
|
* `columnOffset` {number} The zero-indexed column number offset
|
||||||
|
in the generated source
|
||||||
* Returns: {Object}
|
* Returns: {Object}
|
||||||
|
|
||||||
Given a line number and column number in the generated source file, returns
|
Given a line offset and column offset in the generated source
|
||||||
an object representing the position in the original file. The object returned
|
file, returns an object representing the SourceMap range in the
|
||||||
consists of the following keys:
|
original file if found, or an empty object if not.
|
||||||
|
|
||||||
* generatedLine: {number}
|
The object returned contains the following keys:
|
||||||
* generatedColumn: {number}
|
|
||||||
* originalSource: {string}
|
* generatedLine: {number} The line offset of the start of the
|
||||||
* originalLine: {number}
|
range in the generated source
|
||||||
* originalColumn: {number}
|
* generatedColumn: {number} The column offset of start of the
|
||||||
|
range in the generated source
|
||||||
|
* originalSource: {string} The file name of the original source,
|
||||||
|
as reported in the SourceMap
|
||||||
|
* originalLine: {number} The line offset of the start of the
|
||||||
|
range in the original source
|
||||||
|
* originalColumn: {number} The column offset of start of the
|
||||||
|
range in the original source
|
||||||
* name: {string}
|
* name: {string}
|
||||||
|
|
||||||
|
The returned value represents the raw range as it appears in the
|
||||||
|
SourceMap, based on zero-indexed offsets, _not_ 1-indexed line and
|
||||||
|
column numbers as they appear in Error messages and CallSite
|
||||||
|
objects.
|
||||||
|
|
||||||
|
To get the corresponding 1-indexed line and column numbers from a
|
||||||
|
lineNumber and columnNumber as they are reported by Error stacks
|
||||||
|
and CallSite objects, use `sourceMap.findOrigin(lineNumber,
|
||||||
|
columnNumber)`
|
||||||
|
|
||||||
|
#### `sourceMap.findOrigin(lineNumber, columnNumber)`
|
||||||
|
|
||||||
|
* `lineNumber` {number} The 1-indexed line number of the call
|
||||||
|
site in the generated source
|
||||||
|
* `columnOffset` {number} The 1-indexed column number
|
||||||
|
of the call site in the generated source
|
||||||
|
* Returns: {Object}
|
||||||
|
|
||||||
|
Given a 1-indexed lineNumber and columnNumber from a call site in
|
||||||
|
the generated source, find the corresponding call site location
|
||||||
|
in the original source.
|
||||||
|
|
||||||
|
If the lineNumber and columnNumber provided are not found in any
|
||||||
|
source map, then an empty object is returned. Otherwise, the
|
||||||
|
returned object contains the following keys:
|
||||||
|
|
||||||
|
* name: {string | undefined} The name of the range in the
|
||||||
|
source map, if one was provided
|
||||||
|
* fileName: {string} The file name of the original source, as
|
||||||
|
reported in the SourceMap
|
||||||
|
* lineNumber: {number} The 1-indexed lineNumber of the
|
||||||
|
corresponding call site in the original source
|
||||||
|
* columnNumber: {number} The 1-indexed columnNumber of the
|
||||||
|
corresponding call site in the original source
|
||||||
|
|
||||||
[CommonJS]: modules.md
|
[CommonJS]: modules.md
|
||||||
[ES Modules]: esm.md
|
[ES Modules]: esm.md
|
||||||
[Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej
|
[Source map v3 format]: https://sourcemaps.info/spec.html#h.mofvlxcwqzej
|
||||||
|
@ -169,19 +169,19 @@ class SourceMap {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} lineNumber in compiled resource
|
* @param {number} lineOffset 0-indexed line offset in compiled resource
|
||||||
* @param {number} columnNumber in compiled resource
|
* @param {number} columnOffset 0-indexed column offset in compiled resource
|
||||||
* @return {?Array}
|
* @return {object} representing start of range if found, or empty object
|
||||||
*/
|
*/
|
||||||
findEntry(lineNumber, columnNumber) {
|
findEntry(lineOffset, columnOffset) {
|
||||||
let first = 0;
|
let first = 0;
|
||||||
let count = this.#mappings.length;
|
let count = this.#mappings.length;
|
||||||
while (count > 1) {
|
while (count > 1) {
|
||||||
const step = count >> 1;
|
const step = count >> 1;
|
||||||
const middle = first + step;
|
const middle = first + step;
|
||||||
const mapping = this.#mappings[middle];
|
const mapping = this.#mappings[middle];
|
||||||
if (lineNumber < mapping[0] ||
|
if (lineOffset < mapping[0] ||
|
||||||
(lineNumber === mapping[0] && columnNumber < mapping[1])) {
|
(lineOffset === mapping[0] && columnOffset < mapping[1])) {
|
||||||
count = step;
|
count = step;
|
||||||
} else {
|
} else {
|
||||||
first = middle;
|
first = middle;
|
||||||
@ -189,8 +189,8 @@ class SourceMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const entry = this.#mappings[first];
|
const entry = this.#mappings[first];
|
||||||
if (!first && entry && (lineNumber < entry[0] ||
|
if (!first && entry && (lineOffset < entry[0] ||
|
||||||
(lineNumber === entry[0] && columnNumber < entry[1]))) {
|
(lineOffset === entry[0] && columnOffset < entry[1]))) {
|
||||||
return {};
|
return {};
|
||||||
} else if (!entry) {
|
} else if (!entry) {
|
||||||
return {};
|
return {};
|
||||||
@ -205,6 +205,32 @@ class SourceMap {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} lineNumber 1-indexed line number in compiled resource call site
|
||||||
|
* @param {number} columnNumber 1-indexed column number in compiled resource call site
|
||||||
|
* @return {object} representing origin call site if found, or empty object
|
||||||
|
*/
|
||||||
|
findOrigin(lineNumber, columnNumber) {
|
||||||
|
const range = this.findEntry(lineNumber - 1, columnNumber - 1);
|
||||||
|
if (
|
||||||
|
range.originalSource === undefined ||
|
||||||
|
range.originalLine === undefined ||
|
||||||
|
range.originalColumn === undefined ||
|
||||||
|
range.generatedLine === undefined ||
|
||||||
|
range.generatedColumn === undefined
|
||||||
|
) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const lineOffset = lineNumber - range.generatedLine;
|
||||||
|
const columnOffset = columnNumber - range.generatedColumn;
|
||||||
|
return {
|
||||||
|
name: range.name,
|
||||||
|
fileName: range.originalSource,
|
||||||
|
lineNumber: range.originalLine + lineOffset,
|
||||||
|
columnNumber: range.originalColumn + columnOffset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
|
@ -49,6 +49,14 @@ const { readFileSync } = require('fs');
|
|||||||
assert.strictEqual(originalLine, 2);
|
assert.strictEqual(originalLine, 2);
|
||||||
assert.strictEqual(originalColumn, 4);
|
assert.strictEqual(originalColumn, 4);
|
||||||
assert(originalSource.endsWith('disk.js'));
|
assert(originalSource.endsWith('disk.js'));
|
||||||
|
const {
|
||||||
|
fileName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
} = sourceMap.findOrigin(1, 30);
|
||||||
|
assert.strictEqual(fileName, originalSource);
|
||||||
|
assert.strictEqual(lineNumber, 3);
|
||||||
|
assert.strictEqual(columnNumber, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
// findSourceMap() can be used in Error.prepareStackTrace() to lookup
|
// findSourceMap() can be used in Error.prepareStackTrace() to lookup
|
||||||
@ -89,6 +97,18 @@ const { readFileSync } = require('fs');
|
|||||||
assert.strictEqual(originalLine, 17);
|
assert.strictEqual(originalLine, 17);
|
||||||
assert.strictEqual(originalColumn, 10);
|
assert.strictEqual(originalColumn, 10);
|
||||||
assert(originalSource.endsWith('typescript-throw.ts'));
|
assert(originalSource.endsWith('typescript-throw.ts'));
|
||||||
|
|
||||||
|
const {
|
||||||
|
fileName,
|
||||||
|
lineNumber,
|
||||||
|
columnNumber,
|
||||||
|
} = sourceMap.findOrigin(
|
||||||
|
callSite.getLineNumber(),
|
||||||
|
callSite.getColumnNumber()
|
||||||
|
);
|
||||||
|
assert.strictEqual(fileName, originalSource);
|
||||||
|
assert.strictEqual(lineNumber, 18);
|
||||||
|
assert.strictEqual(columnNumber, 11);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SourceMap can be instantiated with Source Map V3 object as payload.
|
// SourceMap can be instantiated with Source Map V3 object as payload.
|
||||||
@ -112,8 +132,8 @@ const { readFileSync } = require('fs');
|
|||||||
assert.notStrictEqual(payload.sources, sourceMap.payload.sources);
|
assert.notStrictEqual(payload.sources, sourceMap.payload.sources);
|
||||||
}
|
}
|
||||||
|
|
||||||
// findEntry() must return empty object instead error when
|
// findEntry() and findOrigin() must return empty object instead of
|
||||||
// receive a malformed mappings.
|
// error when receiving a malformed mappings.
|
||||||
{
|
{
|
||||||
const payload = JSON.parse(readFileSync(
|
const payload = JSON.parse(readFileSync(
|
||||||
require.resolve('../fixtures/source-map/disk.map'), 'utf8'
|
require.resolve('../fixtures/source-map/disk.map'), 'utf8'
|
||||||
@ -124,6 +144,9 @@ const { readFileSync } = require('fs');
|
|||||||
const result = sourceMap.findEntry(0, 5);
|
const result = sourceMap.findEntry(0, 5);
|
||||||
assert.strictEqual(typeof result, 'object');
|
assert.strictEqual(typeof result, 'object');
|
||||||
assert.strictEqual(Object.keys(result).length, 0);
|
assert.strictEqual(Object.keys(result).length, 0);
|
||||||
|
const origin = sourceMap.findOrigin(0, 5);
|
||||||
|
assert.strictEqual(typeof origin, 'object');
|
||||||
|
assert.strictEqual(Object.keys(origin).length, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SourceMap can be instantiated with Index Source Map V3 object as payload.
|
// SourceMap can be instantiated with Index Source Map V3 object as payload.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user