EASYAIuniappNewUI/node_modules/@dcloudio/uni-stacktracey/lib/source-map/lib/source-map-consumer.js
2025-02-08 18:50:38 +08:00

1303 lines
41 KiB
JavaScript

/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*/
const util = require('./util')
const binarySearch = require('./binary-search')
const ArraySet = require('./array-set').ArraySet
const base64VLQ = require('./base64-vlq') // eslint-disable-line no-unused-vars
const readWasm = require('./read-wasm')
const wasm = require('./wasm')
const INTERNAL = Symbol('smcInternal')
class SourceMapConsumer {
constructor(aSourceMap, aSourceMapURL) {
// If the constructor was called by super(), just return Promise<this>.
// Yes, this is a hack to retain the pre-existing API of the base-class
// constructor also being an async factory function.
if (aSourceMap == INTERNAL) {
return Promise.resolve(this)
}
return _factory(aSourceMap, aSourceMapURL)
}
static initialize(opts) {
readWasm.initialize(opts['lib/mappings.wasm'])
}
static fromSourceMap(aSourceMap, aSourceMapURL) {
return _factoryBSM(aSourceMap, aSourceMapURL)
}
/**
* Construct a new `SourceMapConsumer` from `rawSourceMap` and `sourceMapUrl`
* (see the `SourceMapConsumer` constructor for details. Then, invoke the `async
* function f(SourceMapConsumer) -> T` with the newly constructed consumer, wait
* for `f` to complete, call `destroy` on the consumer, and return `f`'s return
* value.
*
* You must not use the consumer after `f` completes!
*
* By using `with`, you do not have to remember to manually call `destroy` on
* the consumer, since it will be called automatically once `f` completes.
*
* ```js
* const xSquared = await SourceMapConsumer.with(
* myRawSourceMap,
* null,
* async function (consumer) {
* // Use `consumer` inside here and don't worry about remembering
* // to call `destroy`.
*
* const x = await whatever(consumer);
* return x * x;
* }
* );
*
* // You may not use that `consumer` anymore out here; it has
* // been destroyed. But you can use `xSquared`.
* console.log(xSquared);
* ```
*/
static async with(rawSourceMap, sourceMapUrl, f) {
const consumer = await new SourceMapConsumer(rawSourceMap, sourceMapUrl)
try {
return await f(consumer)
} finally {
consumer.destroy()
}
}
/**
* Parse the mappings in a string in to a data structure which we can easily
* query (the ordered arrays in the `this.__generatedMappings` and
* `this.__originalMappings` properties).
*/
_parseMappings(aStr, aSourceRoot) {
throw new Error('Subclasses must implement _parseMappings')
}
/**
* Iterate over each mapping between an original source/line/column and a
* generated line/column in this source map.
*
* @param Function aCallback
* The function that is called with each mapping.
* @param Object aContext
* Optional. If specified, this object will be the value of `this` every
* time that `aCallback` is called.
* @param aOrder
* Either `SourceMapConsumer.GENERATED_ORDER` or
* `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
* iterate over the mappings sorted by the generated file's line/column
* order or the original's source/line/column order, respectively. Defaults to
* `SourceMapConsumer.GENERATED_ORDER`.
*/
eachMapping(aCallback, aContext, aOrder) {
throw new Error('Subclasses must implement eachMapping')
}
/**
* Returns all generated line and column information for the original source,
* line, and column provided. If no column is provided, returns all mappings
* corresponding to a either the line we are searching for or the next
* closest line that has any mappings. Otherwise, returns all mappings
* corresponding to the given line and either the column we are searching for
* or the next closest column that has any offsets.
*
* The only argument is an object with the following properties:
*
* - source: The filename of the original source.
* - line: The line number in the original source. The line number is 1-based.
* - column: Optional. the column number in the original source.
* The column number is 0-based.
*
* and an array of objects is returned, each with the following properties:
*
* - line: The line number in the generated source, or null. The
* line number is 1-based.
* - column: The column number in the generated source, or null.
* The column number is 0-based.
*/
allGeneratedPositionsFor(aArgs) {
throw new Error('Subclasses must implement allGeneratedPositionsFor')
}
destroy() {
throw new Error('Subclasses must implement destroy')
}
}
/**
* The version of the source mapping spec that we are consuming.
*/
SourceMapConsumer.prototype._version = 3
SourceMapConsumer.GENERATED_ORDER = 1
SourceMapConsumer.ORIGINAL_ORDER = 2
SourceMapConsumer.GREATEST_LOWER_BOUND = 1
SourceMapConsumer.LEAST_UPPER_BOUND = 2
exports.SourceMapConsumer = SourceMapConsumer
/**
* A BasicSourceMapConsumer instance represents a parsed source map which we can
* query for information about the original file positions by giving it a file
* position in the generated source.
*
* The first parameter is the raw source map (either as a JSON string, or
* already parsed to an object). According to the spec, source maps have the
* following attributes:
*
* - version: Which version of the source map spec this map is following.
* - sources: An array of URLs to the original source files.
* - names: An array of identifiers which can be referenced by individual mappings.
* - sourceRoot: Optional. The URL root from which all sources are relative.
* - sourcesContent: Optional. An array of contents of the original source files.
* - mappings: A string of base64 VLQs which contain the actual mappings.
* - file: Optional. The generated file this source map is associated with.
*
* Here is an example source map, taken from the source map spec[0]:
*
* {
* version : 3,
* file: "out.js",
* sourceRoot : "",
* sources: ["foo.js", "bar.js"],
* names: ["src", "maps", "are", "fun"],
* mappings: "AA,AB;;ABCDE;"
* }
*
* The second parameter, if given, is a string whose value is the URL
* at which the source map was found. This URL is used to compute the
* sources array.
*
* [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
*/
class BasicSourceMapConsumer extends SourceMapConsumer {
constructor(aSourceMap, aSourceMapURL) {
return super(INTERNAL).then((that) => {
let sourceMap = aSourceMap
if (typeof aSourceMap === 'string') {
sourceMap = util.parseSourceMapInput(aSourceMap)
}
const version = util.getArg(sourceMap, 'version')
let sources = util.getArg(sourceMap, 'sources')
// Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
// requires the array) to play nice here.
const names = util.getArg(sourceMap, 'names', [])
let sourceRoot = util.getArg(sourceMap, 'sourceRoot', null)
const sourcesContent = util.getArg(sourceMap, 'sourcesContent', null)
const mappings = util.getArg(sourceMap, 'mappings')
const file = util.getArg(sourceMap, 'file', null)
// Once again, Sass deviates from the spec and supplies the version as a
// string rather than a number, so we use loose equality checking here.
if (version != that._version) {
throw new Error('Unsupported version: ' + version)
}
if (sourceRoot) {
sourceRoot = util.normalize(sourceRoot)
}
sources = sources
.map(String)
// Some source maps produce relative source paths like "./foo.js" instead of
// "foo.js". Normalize these first so that future comparisons will succeed.
// See bugzil.la/1090768.
.map(util.normalize)
// Always ensure that absolute sources are internally stored relative to
// the source root, if the source root is absolute. Not doing this would
// be particularly problematic when the source root is a prefix of the
// source (valid, but why??). See github issue #199 and bugzil.la/1188982.
.map(function (source) {
return sourceRoot &&
util.isAbsolute(sourceRoot) &&
util.isAbsolute(source)
? util.relative(sourceRoot, source)
: source
})
// Pass `true` below to allow duplicate names and sources. While source maps
// are intended to be compressed and deduplicated, the TypeScript compiler
// sometimes generates source maps with duplicates in them. See Github issue
// #72 and bugzil.la/889492.
that._names = ArraySet.fromArray(names.map(String), true)
that._sources = ArraySet.fromArray(sources, true)
that._absoluteSources = that._sources.toArray().map(function (s) {
return util.computeSourceURL(sourceRoot, s, aSourceMapURL)
})
that.sourceRoot = sourceRoot
that.sourcesContent = sourcesContent
that._mappings = mappings
that._sourceMapURL = aSourceMapURL
that.file = file
that._computedColumnSpans = false
that._mappingsPtr = 0
that._wasm = null
return wasm().then((w) => {
that._wasm = w
return that
})
})
}
/**
* Utility function to find the index of a source. Returns -1 if not
* found.
*/
_findSourceIndex(aSource) {
let relativeSource = aSource
if (this.sourceRoot != null) {
relativeSource = util.relative(this.sourceRoot, relativeSource)
}
if (this._sources.has(relativeSource)) {
return this._sources.indexOf(relativeSource)
}
// Maybe aSource is an absolute URL as returned by |sources|. In
// this case we can't simply undo the transform.
for (let i = 0; i < this._absoluteSources.length; ++i) {
if (this._absoluteSources[i] == aSource) {
return i
}
}
return -1
}
/**
* Create a BasicSourceMapConsumer from a SourceMapGenerator.
*
* @param SourceMapGenerator aSourceMap
* The source map that will be consumed.
* @param String aSourceMapURL
* The URL at which the source map can be found (optional)
* @returns BasicSourceMapConsumer
*/
static fromSourceMap(aSourceMap, aSourceMapURL) {
return new BasicSourceMapConsumer(aSourceMap.toString())
}
get sources() {
return this._absoluteSources.slice()
}
_getMappingsPtr() {
if (this._mappingsPtr === 0) {
this._parseMappings(this._mappings, this.sourceRoot)
}
return this._mappingsPtr
}
/**
* Parse the mappings in a string in to a data structure which we can easily
* query (the ordered arrays in the `this.__generatedMappings` and
* `this.__originalMappings` properties).
*/
_parseMappings(aStr, aSourceRoot) {
const size = aStr.length
const mappingsBufPtr = this._wasm.exports.allocate_mappings(size)
const mappingsBuf = new Uint8Array(
this._wasm.exports.memory.buffer,
mappingsBufPtr,
size
)
for (let i = 0; i < size; i++) {
mappingsBuf[i] = aStr.charCodeAt(i)
}
const mappingsPtr = this._wasm.exports.parse_mappings(mappingsBufPtr)
if (!mappingsPtr) {
const error = this._wasm.exports.get_last_error()
let msg = `Error parsing mappings (code ${error}): `
// XXX: keep these error codes in sync with `fitzgen/source-map-mappings`.
switch (error) {
case 1:
msg +=
'the mappings contained a negative line, column, source index, or name index'
break
case 2:
msg += 'the mappings contained a number larger than 2**32'
break
case 3:
msg += 'reached EOF while in the middle of parsing a VLQ'
break
case 4:
msg += 'invalid base 64 character while parsing a VLQ'
break
default:
msg += 'unknown error code'
break
}
throw new Error(msg)
}
this._mappingsPtr = mappingsPtr
}
eachMapping(aCallback, aContext, aOrder) {
const context = aContext || null
const order = aOrder || SourceMapConsumer.GENERATED_ORDER
const sourceRoot = this.sourceRoot
this._wasm.withMappingCallback(
(mapping) => {
if (mapping.source !== null) {
mapping.source = this._sources.at(mapping.source)
mapping.source = util.computeSourceURL(
sourceRoot,
mapping.source,
this._sourceMapURL
)
if (mapping.name !== null) {
mapping.name = this._names.at(mapping.name)
}
}
aCallback.call(context, mapping)
},
() => {
switch (order) {
case SourceMapConsumer.GENERATED_ORDER:
this._wasm.exports.by_generated_location(this._getMappingsPtr())
break
case SourceMapConsumer.ORIGINAL_ORDER:
this._wasm.exports.by_original_location(this._getMappingsPtr())
break
default:
throw new Error('Unknown order of iteration.')
}
}
)
}
allGeneratedPositionsFor(aArgs) {
let source = util.getArg(aArgs, 'source')
const originalLine = util.getArg(aArgs, 'line')
const originalColumn = aArgs.column || 0
source = this._findSourceIndex(source)
if (source < 0) {
return []
}
if (originalLine < 1) {
throw new Error('Line numbers must be >= 1')
}
if (originalColumn < 0) {
throw new Error('Column numbers must be >= 0')
}
const mappings = []
this._wasm.withMappingCallback(
(m) => {
let lastColumn = m.lastGeneratedColumn
if (this._computedColumnSpans && lastColumn === null) {
lastColumn = Infinity
}
mappings.push({
line: m.generatedLine,
column: m.generatedColumn,
lastColumn,
})
},
() => {
this._wasm.exports.all_generated_locations_for(
this._getMappingsPtr(),
source,
originalLine - 1,
'column' in aArgs,
originalColumn
)
}
)
return mappings
}
destroy() {
if (this._mappingsPtr !== 0) {
this._wasm.exports.free_mappings(this._mappingsPtr)
this._mappingsPtr = 0
}
}
/**
* Compute the last column for each generated mapping. The last column is
* inclusive.
*/
computeColumnSpans() {
if (this._computedColumnSpans) {
return
}
this._wasm.exports.compute_column_spans(this._getMappingsPtr())
this._computedColumnSpans = true
}
/**
* Returns the original source, line, and column information for the generated
* source's line and column positions provided. The only argument is an object
* with the following properties:
*
* - line: The line number in the generated source. The line number
* is 1-based.
* - column: The column number in the generated source. The column
* number is 0-based.
* - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
* 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
* closest element that is smaller than or greater than the one we are
* searching for, respectively, if the exact element cannot be found.
* Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
*
* and an object is returned with the following properties:
*
* - source: The original source file, or null.
* - line: The line number in the original source, or null. The
* line number is 1-based.
* - column: The column number in the original source, or null. The
* column number is 0-based.
* - name: The original identifier, or null.
*/
originalPositionFor(aArgs) {
const needle = {
generatedLine: util.getArg(aArgs, 'line'),
generatedColumn: util.getArg(aArgs, 'column'),
}
if (needle.generatedLine < 1) {
throw new Error('Line numbers must be >= 1')
}
if (needle.generatedColumn < 0) {
throw new Error('Column numbers must be >= 0')
}
let bias = util.getArg(
aArgs,
'bias',
SourceMapConsumer.GREATEST_LOWER_BOUND
)
if (bias == null) {
bias = SourceMapConsumer.GREATEST_LOWER_BOUND
}
let mapping
this._wasm.withMappingCallback(
(m) => (mapping = m),
() => {
this._wasm.exports.original_location_for(
this._getMappingsPtr(),
needle.generatedLine - 1,
needle.generatedColumn,
bias
)
}
)
if (mapping) {
if (mapping.generatedLine === needle.generatedLine) {
let source = util.getArg(mapping, 'source', null)
if (source !== null) {
source = this._sources.at(source)
source = util.computeSourceURL(
this.sourceRoot,
source,
this._sourceMapURL
)
}
let name = util.getArg(mapping, 'name', null)
if (name !== null) {
name = this._names.at(name)
}
return {
source,
line: util.getArg(mapping, 'originalLine', null),
column: util.getArg(mapping, 'originalColumn', null),
name,
}
}
}
return {
source: null,
line: null,
column: null,
name: null,
}
}
/**
* Return true if we have the source content for every source in the source
* map, false otherwise.
*/
hasContentsOfAllSources() {
if (!this.sourcesContent) {
return false
}
return (
this.sourcesContent.length >= this._sources.size() &&
!this.sourcesContent.some(function (sc) {
return sc == null
})
)
}
/**
* Returns the original source content. The only argument is the url of the
* original source file. Returns null if no original source content is
* available.
*/
sourceContentFor(aSource, nullOnMissing) {
if (!this.sourcesContent) {
return null
}
const index = this._findSourceIndex(aSource)
if (index >= 0) {
return this.sourcesContent[index]
}
let relativeSource = aSource
if (this.sourceRoot != null) {
relativeSource = util.relative(this.sourceRoot, relativeSource)
}
let url
if (this.sourceRoot != null && (url = util.urlParse(this.sourceRoot))) {
// XXX: file:// URIs and absolute paths lead to unexpected behavior for
// many users. We can help them out when they expect file:// URIs to
// behave like it would if they were running a local HTTP server. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
const fileUriAbsPath = relativeSource.replace(/^file:\/\//, '')
if (url.scheme == 'file' && this._sources.has(fileUriAbsPath)) {
return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
}
if (
(!url.path || url.path == '/') &&
this._sources.has('/' + relativeSource)
) {
return this.sourcesContent[this._sources.indexOf('/' + relativeSource)]
}
}
// This function is used recursively from
// IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
// don't want to throw if we can't find the source - we just want to
// return null, so we provide a flag to exit gracefully.
if (nullOnMissing) {
return null
}
throw new Error('"' + relativeSource + '" is not in the SourceMap.')
}
/**
* Returns the generated line and column information for the original source,
* line, and column positions provided. The only argument is an object with
* the following properties:
*
* - source: The filename of the original source.
* - line: The line number in the original source. The line number
* is 1-based.
* - column: The column number in the original source. The column
* number is 0-based.
* - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
* 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
* closest element that is smaller than or greater than the one we are
* searching for, respectively, if the exact element cannot be found.
* Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
*
* and an object is returned with the following properties:
*
* - line: The line number in the generated source, or null. The
* line number is 1-based.
* - column: The column number in the generated source, or null.
* The column number is 0-based.
*/
generatedPositionFor(aArgs) {
let source = util.getArg(aArgs, 'source')
source = this._findSourceIndex(source)
if (source < 0) {
return {
line: null,
column: null,
lastColumn: null,
}
}
const needle = {
source,
originalLine: util.getArg(aArgs, 'line'),
originalColumn: util.getArg(aArgs, 'column'),
}
if (needle.originalLine < 1) {
throw new Error('Line numbers must be >= 1')
}
if (needle.originalColumn < 0) {
throw new Error('Column numbers must be >= 0')
}
let bias = util.getArg(
aArgs,
'bias',
SourceMapConsumer.GREATEST_LOWER_BOUND
)
if (bias == null) {
bias = SourceMapConsumer.GREATEST_LOWER_BOUND
}
let mapping
this._wasm.withMappingCallback(
(m) => (mapping = m),
() => {
this._wasm.exports.generated_location_for(
this._getMappingsPtr(),
needle.source,
needle.originalLine - 1,
needle.originalColumn,
bias
)
}
)
if (mapping) {
if (mapping.source === needle.source) {
let lastColumn = mapping.lastGeneratedColumn
if (this._computedColumnSpans && lastColumn === null) {
lastColumn = Infinity
}
return {
line: util.getArg(mapping, 'generatedLine', null),
column: util.getArg(mapping, 'generatedColumn', null),
lastColumn,
}
}
}
return {
line: null,
column: null,
lastColumn: null,
}
}
}
BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer
exports.BasicSourceMapConsumer = BasicSourceMapConsumer
/**
* An IndexedSourceMapConsumer instance represents a parsed source map which
* we can query for information. It differs from BasicSourceMapConsumer in
* that it takes "indexed" source maps (i.e. ones with a "sections" field) as
* input.
*
* The first parameter is a raw source map (either as a JSON string, or already
* parsed to an object). According to the spec for indexed source maps, they
* have the following attributes:
*
* - version: Which version of the source map spec this map is following.
* - file: Optional. The generated file this source map is associated with.
* - sections: A list of section definitions.
*
* Each value under the "sections" field has two fields:
* - offset: The offset into the original specified at which this section
* begins to apply, defined as an object with a "line" and "column"
* field.
* - map: A source map definition. This source map could also be indexed,
* but doesn't have to be.
*
* Instead of the "map" field, it's also possible to have a "url" field
* specifying a URL to retrieve a source map from, but that's currently
* unsupported.
*
* Here's an example source map, taken from the source map spec[0], but
* modified to omit a section which uses the "url" field.
*
* {
* version : 3,
* file: "app.js",
* sections: [{
* offset: {line:100, column:10},
* map: {
* version : 3,
* file: "section.js",
* sources: ["foo.js", "bar.js"],
* names: ["src", "maps", "are", "fun"],
* mappings: "AAAA,E;;ABCDE;"
* }
* }],
* }
*
* The second parameter, if given, is a string whose value is the URL
* at which the source map was found. This URL is used to compute the
* sources array.
*
* [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
*/
class IndexedSourceMapConsumer extends SourceMapConsumer {
constructor(aSourceMap, aSourceMapURL) {
return super(INTERNAL).then((that) => {
let sourceMap = aSourceMap
if (typeof aSourceMap === 'string') {
sourceMap = util.parseSourceMapInput(aSourceMap)
}
const version = util.getArg(sourceMap, 'version')
const sections = util.getArg(sourceMap, 'sections')
if (version != that._version) {
throw new Error('Unsupported version: ' + version)
}
that._sources = new ArraySet()
that._names = new ArraySet()
that.__generatedMappings = null
that.__originalMappings = null
that.__generatedMappingsUnsorted = null
that.__originalMappingsUnsorted = null
let lastOffset = {
line: -1,
column: 0,
}
return Promise.all(
sections.map((s) => {
if (s.url) {
// The url field will require support for asynchronicity.
// See https://github.com/mozilla/source-map/issues/16
throw new Error(
'Support for url field in sections not implemented.'
)
}
const offset = util.getArg(s, 'offset')
const offsetLine = util.getArg(offset, 'line')
const offsetColumn = util.getArg(offset, 'column')
if (
offsetLine < lastOffset.line ||
(offsetLine === lastOffset.line && offsetColumn < lastOffset.column)
) {
throw new Error(
'Section offsets must be ordered and non-overlapping.'
)
}
lastOffset = offset
const cons = new SourceMapConsumer(
util.getArg(s, 'map'),
aSourceMapURL
)
return cons.then((consumer) => {
return {
generatedOffset: {
// The offset fields are 0-based, but we use 1-based indices when
// encoding/decoding from VLQ.
generatedLine: offsetLine + 1,
generatedColumn: offsetColumn + 1,
},
consumer,
}
})
})
).then((s) => {
that._sections = s
return that
})
})
}
// `__generatedMappings` and `__originalMappings` are arrays that hold the
// parsed mapping coordinates from the source map's "mappings" attribute. They
// are lazily instantiated, accessed via the `_generatedMappings` and
// `_originalMappings` getters respectively, and we only parse the mappings
// and create these arrays once queried for a source location. We jump through
// these hoops because there can be many thousands of mappings, and parsing
// them is expensive, so we only want to do it if we must.
//
// Each object in the arrays is of the form:
//
// {
// generatedLine: The line number in the generated code,
// generatedColumn: The column number in the generated code,
// source: The path to the original source file that generated this
// chunk of code,
// originalLine: The line number in the original source that
// corresponds to this chunk of generated code,
// originalColumn: The column number in the original source that
// corresponds to this chunk of generated code,
// name: The name of the original symbol which generated this chunk of
// code.
// }
//
// All properties except for `generatedLine` and `generatedColumn` can be
// `null`.
//
// `_generatedMappings` is ordered by the generated positions.
//
// `_originalMappings` is ordered by the original positions.
get _generatedMappings() {
if (!this.__generatedMappings) {
this._sortGeneratedMappings()
}
return this.__generatedMappings
}
get _originalMappings() {
if (!this.__originalMappings) {
this._sortOriginalMappings()
}
return this.__originalMappings
}
get _generatedMappingsUnsorted() {
if (!this.__generatedMappingsUnsorted) {
this._parseMappings(this._mappings, this.sourceRoot)
}
return this.__generatedMappingsUnsorted
}
get _originalMappingsUnsorted() {
if (!this.__originalMappingsUnsorted) {
this._parseMappings(this._mappings, this.sourceRoot)
}
return this.__originalMappingsUnsorted
}
_sortGeneratedMappings() {
const mappings = this._generatedMappingsUnsorted
mappings.sort(util.compareByGeneratedPositionsDeflated)
this.__generatedMappings = mappings
}
_sortOriginalMappings() {
const mappings = this._originalMappingsUnsorted
mappings.sort(util.compareByOriginalPositions)
this.__originalMappings = mappings
}
/**
* The list of original sources.
*/
get sources() {
const sources = []
for (let i = 0; i < this._sections.length; i++) {
for (let j = 0; j < this._sections[i].consumer.sources.length; j++) {
sources.push(this._sections[i].consumer.sources[j])
}
}
return sources
}
/**
* Returns the original source, line, and column information for the generated
* source's line and column positions provided. The only argument is an object
* with the following properties:
*
* - line: The line number in the generated source. The line number
* is 1-based.
* - column: The column number in the generated source. The column
* number is 0-based.
*
* and an object is returned with the following properties:
*
* - source: The original source file, or null.
* - line: The line number in the original source, or null. The
* line number is 1-based.
* - column: The column number in the original source, or null. The
* column number is 0-based.
* - name: The original identifier, or null.
*/
originalPositionFor(aArgs) {
const needle = {
generatedLine: util.getArg(aArgs, 'line'),
generatedColumn: util.getArg(aArgs, 'column'),
}
// Find the section containing the generated position we're trying to map
// to an original position.
const sectionIndex = binarySearch.search(
needle,
this._sections,
function (aNeedle, section) {
const cmp =
aNeedle.generatedLine - section.generatedOffset.generatedLine
if (cmp) {
return cmp
}
return aNeedle.generatedColumn - section.generatedOffset.generatedColumn
}
)
const section = this._sections[sectionIndex]
if (!section) {
return {
source: null,
line: null,
column: null,
name: null,
}
}
return section.consumer.originalPositionFor({
line: needle.generatedLine - (section.generatedOffset.generatedLine - 1),
column:
needle.generatedColumn -
(section.generatedOffset.generatedLine === needle.generatedLine
? section.generatedOffset.generatedColumn - 1
: 0),
bias: aArgs.bias,
})
}
/**
* Return true if we have the source content for every source in the source
* map, false otherwise.
*/
hasContentsOfAllSources() {
return this._sections.every(function (s) {
return s.consumer.hasContentsOfAllSources()
})
}
/**
* Returns the original source content. The only argument is the url of the
* original source file. Returns null if no original source content is
* available.
*/
sourceContentFor(aSource, nullOnMissing) {
for (let i = 0; i < this._sections.length; i++) {
const section = this._sections[i]
const content = section.consumer.sourceContentFor(aSource, true)
if (content) {
return content
}
}
if (nullOnMissing) {
return null
}
throw new Error('"' + aSource + '" is not in the SourceMap.')
}
/**
* Returns the generated line and column information for the original source,
* line, and column positions provided. The only argument is an object with
* the following properties:
*
* - source: The filename of the original source.
* - line: The line number in the original source. The line number
* is 1-based.
* - column: The column number in the original source. The column
* number is 0-based.
*
* and an object is returned with the following properties:
*
* - line: The line number in the generated source, or null. The
* line number is 1-based.
* - column: The column number in the generated source, or null.
* The column number is 0-based.
*/
generatedPositionFor(aArgs) {
for (let i = 0; i < this._sections.length; i++) {
const section = this._sections[i]
// Only consider this section if the requested source is in the list of
// sources of the consumer.
if (
section.consumer._findSourceIndex(util.getArg(aArgs, 'source')) === -1
) {
continue
}
const generatedPosition = section.consumer.generatedPositionFor(aArgs)
if (generatedPosition) {
const ret = {
line:
generatedPosition.line +
(section.generatedOffset.generatedLine - 1),
column:
generatedPosition.column +
(section.generatedOffset.generatedLine === generatedPosition.line
? section.generatedOffset.generatedColumn - 1
: 0),
}
return ret
}
}
return {
line: null,
column: null,
}
}
/**
* Parse the mappings in a string in to a data structure which we can easily
* query (the ordered arrays in the `this.__generatedMappings` and
* `this.__originalMappings` properties).
*/
_parseMappings(aStr, aSourceRoot) {
const generatedMappings = (this.__generatedMappingsUnsorted = [])
const originalMappings = (this.__originalMappingsUnsorted = [])
for (let i = 0; i < this._sections.length; i++) {
const section = this._sections[i]
const sectionMappings = []
section.consumer.eachMapping((m) => sectionMappings.push(m))
for (let j = 0; j < sectionMappings.length; j++) {
const mapping = sectionMappings[j]
// TODO: test if null is correct here. The original code used
// `source`, which would actually have gotten used as null because
// var's get hoisted.
// See: https://github.com/mozilla/source-map/issues/333
let source = util.computeSourceURL(
section.consumer.sourceRoot,
null,
this._sourceMapURL
)
this._sources.add(source)
source = this._sources.indexOf(source)
let name = null
if (mapping.name) {
this._names.add(mapping.name)
name = this._names.indexOf(mapping.name)
}
// The mappings coming from the consumer for the section have
// generated positions relative to the start of the section, so we
// need to offset them to be relative to the start of the concatenated
// generated file.
const adjustedMapping = {
source,
generatedLine:
mapping.generatedLine + (section.generatedOffset.generatedLine - 1),
generatedColumn:
mapping.generatedColumn +
(section.generatedOffset.generatedLine === mapping.generatedLine
? section.generatedOffset.generatedColumn - 1
: 0),
originalLine: mapping.originalLine,
originalColumn: mapping.originalColumn,
name,
}
generatedMappings.push(adjustedMapping)
if (typeof adjustedMapping.originalLine === 'number') {
originalMappings.push(adjustedMapping)
}
}
}
}
eachMapping(aCallback, aContext, aOrder) {
const context = aContext || null
const order = aOrder || SourceMapConsumer.GENERATED_ORDER
let mappings
switch (order) {
case SourceMapConsumer.GENERATED_ORDER:
mappings = this._generatedMappings
break
case SourceMapConsumer.ORIGINAL_ORDER:
mappings = this._originalMappings
break
default:
throw new Error('Unknown order of iteration.')
}
const sourceRoot = this.sourceRoot
mappings
.map(function (mapping) {
let source = null
if (mapping.source !== null) {
source = this._sources.at(mapping.source)
source = util.computeSourceURL(sourceRoot, source, this._sourceMapURL)
}
return {
source,
generatedLine: mapping.generatedLine,
generatedColumn: mapping.generatedColumn,
originalLine: mapping.originalLine,
originalColumn: mapping.originalColumn,
name: mapping.name === null ? null : this._names.at(mapping.name),
}
}, this)
.forEach(aCallback, context)
}
/**
* Find the mapping that best matches the hypothetical "needle" mapping that
* we are searching for in the given "haystack" of mappings.
*/
_findMapping(aNeedle, aMappings, aLineName, aColumnName, aComparator, aBias) {
// To return the position we are searching for, we must first find the
// mapping for the given position and then return the opposite position it
// points to. Because the mappings are sorted, we can use binary search to
// find the best mapping.
if (aNeedle[aLineName] <= 0) {
throw new TypeError(
'Line must be greater than or equal to 1, got ' + aNeedle[aLineName]
)
}
if (aNeedle[aColumnName] < 0) {
throw new TypeError(
'Column must be greater than or equal to 0, got ' + aNeedle[aColumnName]
)
}
return binarySearch.search(aNeedle, aMappings, aComparator, aBias)
}
allGeneratedPositionsFor(aArgs) {
const line = util.getArg(aArgs, 'line')
// When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
// returns the index of the closest mapping less than the needle. By
// setting needle.originalColumn to 0, we thus find the last mapping for
// the given line, provided such a mapping exists.
const needle = {
source: util.getArg(aArgs, 'source'),
originalLine: line,
originalColumn: util.getArg(aArgs, 'column', 0),
}
needle.source = this._findSourceIndex(needle.source)
if (needle.source < 0) {
return []
}
if (needle.originalLine < 1) {
throw new Error('Line numbers must be >= 1')
}
if (needle.originalColumn < 0) {
throw new Error('Column numbers must be >= 0')
}
const mappings = []
let index = this._findMapping(
needle,
this._originalMappings,
'originalLine',
'originalColumn',
util.compareByOriginalPositions,
binarySearch.LEAST_UPPER_BOUND
)
if (index >= 0) {
let mapping = this._originalMappings[index]
if (aArgs.column === undefined) {
const originalLine = mapping.originalLine
// Iterate until either we run out of mappings, or we run into
// a mapping for a different line than the one we found. Since
// mappings are sorted, this is guaranteed to find all mappings for
// the line we found.
while (mapping && mapping.originalLine === originalLine) {
let lastColumn = mapping.lastGeneratedColumn
if (this._computedColumnSpans && lastColumn === null) {
lastColumn = Infinity
}
mappings.push({
line: util.getArg(mapping, 'generatedLine', null),
column: util.getArg(mapping, 'generatedColumn', null),
lastColumn,
})
mapping = this._originalMappings[++index]
}
} else {
const originalColumn = mapping.originalColumn
// Iterate until either we run out of mappings, or we run into
// a mapping for a different line than the one we were searching for.
// Since mappings are sorted, this is guaranteed to find all mappings for
// the line we are searching for.
while (
mapping &&
mapping.originalLine === line &&
mapping.originalColumn == originalColumn
) {
let lastColumn = mapping.lastGeneratedColumn
if (this._computedColumnSpans && lastColumn === null) {
lastColumn = Infinity
}
mappings.push({
line: util.getArg(mapping, 'generatedLine', null),
column: util.getArg(mapping, 'generatedColumn', null),
lastColumn,
})
mapping = this._originalMappings[++index]
}
}
}
return mappings
}
destroy() {
for (let i = 0; i < this._sections.length; i++) {
this._sections[i].consumer.destroy()
}
}
}
exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer
/*
* Cheat to get around inter-twingled classes. `factory()` can be at the end
* where it has access to non-hoisted classes, but it gets hoisted itself.
*/
function _factory(aSourceMap, aSourceMapURL) {
let sourceMap = aSourceMap
if (typeof aSourceMap === 'string') {
sourceMap = util.parseSourceMapInput(aSourceMap)
}
const consumer =
sourceMap.sections != null
? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL)
: new BasicSourceMapConsumer(sourceMap, aSourceMapURL)
return Promise.resolve(consumer)
}
function _factoryBSM(aSourceMap, aSourceMapURL) {
return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL)
}