Spaces:
Runtime error
Runtime error
/* -*- 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 | |
*/ | |
var base64VLQ = require('./base64-vlq'); | |
var util = require('./util'); | |
var ArraySet = require('./array-set').ArraySet; | |
var MappingList = require('./mapping-list').MappingList; | |
/** | |
* An instance of the SourceMapGenerator represents a source map which is | |
* being built incrementally. You may pass an object with the following | |
* properties: | |
* | |
* - file: The filename of the generated source. | |
* - sourceRoot: A root for all relative URLs in this source map. | |
*/ | |
function SourceMapGenerator(aArgs) { | |
if (!aArgs) { | |
aArgs = {}; | |
} | |
this._file = util.getArg(aArgs, 'file', null); | |
this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); | |
this._skipValidation = util.getArg(aArgs, 'skipValidation', false); | |
this._sources = new ArraySet(); | |
this._names = new ArraySet(); | |
this._mappings = new MappingList(); | |
this._sourcesContents = null; | |
} | |
SourceMapGenerator.prototype._version = 3; | |
/** | |
* Creates a new SourceMapGenerator based on a SourceMapConsumer | |
* | |
* @param aSourceMapConsumer The SourceMap. | |
*/ | |
SourceMapGenerator.fromSourceMap = | |
function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) { | |
var sourceRoot = aSourceMapConsumer.sourceRoot; | |
var generator = new SourceMapGenerator({ | |
file: aSourceMapConsumer.file, | |
sourceRoot: sourceRoot | |
}); | |
aSourceMapConsumer.eachMapping(function (mapping) { | |
var newMapping = { | |
generated: { | |
line: mapping.generatedLine, | |
column: mapping.generatedColumn | |
} | |
}; | |
if (mapping.source != null) { | |
newMapping.source = mapping.source; | |
if (sourceRoot != null) { | |
newMapping.source = util.relative(sourceRoot, newMapping.source); | |
} | |
newMapping.original = { | |
line: mapping.originalLine, | |
column: mapping.originalColumn | |
}; | |
if (mapping.name != null) { | |
newMapping.name = mapping.name; | |
} | |
} | |
generator.addMapping(newMapping); | |
}); | |
aSourceMapConsumer.sources.forEach(function (sourceFile) { | |
var sourceRelative = sourceFile; | |
if (sourceRoot !== null) { | |
sourceRelative = util.relative(sourceRoot, sourceFile); | |
} | |
if (!generator._sources.has(sourceRelative)) { | |
generator._sources.add(sourceRelative); | |
} | |
var content = aSourceMapConsumer.sourceContentFor(sourceFile); | |
if (content != null) { | |
generator.setSourceContent(sourceFile, content); | |
} | |
}); | |
return generator; | |
}; | |
/** | |
* Add a single mapping from original source line and column to the generated | |
* source's line and column for this source map being created. The mapping | |
* object should have the following properties: | |
* | |
* - generated: An object with the generated line and column positions. | |
* - original: An object with the original line and column positions. | |
* - source: The original source file (relative to the sourceRoot). | |
* - name: An optional original token name for this mapping. | |
*/ | |
SourceMapGenerator.prototype.addMapping = | |
function SourceMapGenerator_addMapping(aArgs) { | |
var generated = util.getArg(aArgs, 'generated'); | |
var original = util.getArg(aArgs, 'original', null); | |
var source = util.getArg(aArgs, 'source', null); | |
var name = util.getArg(aArgs, 'name', null); | |
if (!this._skipValidation) { | |
this._validateMapping(generated, original, source, name); | |
} | |
if (source != null) { | |
source = String(source); | |
if (!this._sources.has(source)) { | |
this._sources.add(source); | |
} | |
} | |
if (name != null) { | |
name = String(name); | |
if (!this._names.has(name)) { | |
this._names.add(name); | |
} | |
} | |
this._mappings.add({ | |
generatedLine: generated.line, | |
generatedColumn: generated.column, | |
originalLine: original != null && original.line, | |
originalColumn: original != null && original.column, | |
source: source, | |
name: name | |
}); | |
}; | |
/** | |
* Set the source content for a source file. | |
*/ | |
SourceMapGenerator.prototype.setSourceContent = | |
function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) { | |
var source = aSourceFile; | |
if (this._sourceRoot != null) { | |
source = util.relative(this._sourceRoot, source); | |
} | |
if (aSourceContent != null) { | |
// Add the source content to the _sourcesContents map. | |
// Create a new _sourcesContents map if the property is null. | |
if (!this._sourcesContents) { | |
this._sourcesContents = Object.create(null); | |
} | |
this._sourcesContents[util.toSetString(source)] = aSourceContent; | |
} else if (this._sourcesContents) { | |
// Remove the source file from the _sourcesContents map. | |
// If the _sourcesContents map is empty, set the property to null. | |
delete this._sourcesContents[util.toSetString(source)]; | |
if (Object.keys(this._sourcesContents).length === 0) { | |
this._sourcesContents = null; | |
} | |
} | |
}; | |
/** | |
* Applies the mappings of a sub-source-map for a specific source file to the | |
* source map being generated. Each mapping to the supplied source file is | |
* rewritten using the supplied source map. Note: The resolution for the | |
* resulting mappings is the minimium of this map and the supplied map. | |
* | |
* @param aSourceMapConsumer The source map to be applied. | |
* @param aSourceFile Optional. The filename of the source file. | |
* If omitted, SourceMapConsumer's file property will be used. | |
* @param aSourceMapPath Optional. The dirname of the path to the source map | |
* to be applied. If relative, it is relative to the SourceMapConsumer. | |
* This parameter is needed when the two source maps aren't in the same | |
* directory, and the source map to be applied contains relative source | |
* paths. If so, those relative source paths need to be rewritten | |
* relative to the SourceMapGenerator. | |
*/ | |
SourceMapGenerator.prototype.applySourceMap = | |
function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) { | |
var sourceFile = aSourceFile; | |
// If aSourceFile is omitted, we will use the file property of the SourceMap | |
if (aSourceFile == null) { | |
if (aSourceMapConsumer.file == null) { | |
throw new Error( | |
'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' + | |
'or the source map\'s "file" property. Both were omitted.' | |
); | |
} | |
sourceFile = aSourceMapConsumer.file; | |
} | |
var sourceRoot = this._sourceRoot; | |
// Make "sourceFile" relative if an absolute Url is passed. | |
if (sourceRoot != null) { | |
sourceFile = util.relative(sourceRoot, sourceFile); | |
} | |
// Applying the SourceMap can add and remove items from the sources and | |
// the names array. | |
var newSources = new ArraySet(); | |
var newNames = new ArraySet(); | |
// Find mappings for the "sourceFile" | |
this._mappings.unsortedForEach(function (mapping) { | |
if (mapping.source === sourceFile && mapping.originalLine != null) { | |
// Check if it can be mapped by the source map, then update the mapping. | |
var original = aSourceMapConsumer.originalPositionFor({ | |
line: mapping.originalLine, | |
column: mapping.originalColumn | |
}); | |
if (original.source != null) { | |
// Copy mapping | |
mapping.source = original.source; | |
if (aSourceMapPath != null) { | |
mapping.source = util.join(aSourceMapPath, mapping.source) | |
} | |
if (sourceRoot != null) { | |
mapping.source = util.relative(sourceRoot, mapping.source); | |
} | |
mapping.originalLine = original.line; | |
mapping.originalColumn = original.column; | |
if (original.name != null) { | |
mapping.name = original.name; | |
} | |
} | |
} | |
var source = mapping.source; | |
if (source != null && !newSources.has(source)) { | |
newSources.add(source); | |
} | |
var name = mapping.name; | |
if (name != null && !newNames.has(name)) { | |
newNames.add(name); | |
} | |
}, this); | |
this._sources = newSources; | |
this._names = newNames; | |
// Copy sourcesContents of applied map. | |
aSourceMapConsumer.sources.forEach(function (sourceFile) { | |
var content = aSourceMapConsumer.sourceContentFor(sourceFile); | |
if (content != null) { | |
if (aSourceMapPath != null) { | |
sourceFile = util.join(aSourceMapPath, sourceFile); | |
} | |
if (sourceRoot != null) { | |
sourceFile = util.relative(sourceRoot, sourceFile); | |
} | |
this.setSourceContent(sourceFile, content); | |
} | |
}, this); | |
}; | |
/** | |
* A mapping can have one of the three levels of data: | |
* | |
* 1. Just the generated position. | |
* 2. The Generated position, original position, and original source. | |
* 3. Generated and original position, original source, as well as a name | |
* token. | |
* | |
* To maintain consistency, we validate that any new mapping being added falls | |
* in to one of these categories. | |
*/ | |
SourceMapGenerator.prototype._validateMapping = | |
function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource, | |
aName) { | |
// When aOriginal is truthy but has empty values for .line and .column, | |
// it is most likely a programmer error. In this case we throw a very | |
// specific error message to try to guide them the right way. | |
// For example: https://github.com/Polymer/polymer-bundler/pull/519 | |
if (aOriginal && typeof aOriginal.line !== 'number' && typeof aOriginal.column !== 'number') { | |
throw new Error( | |
'original.line and original.column are not numbers -- you probably meant to omit ' + | |
'the original mapping entirely and only map the generated position. If so, pass ' + | |
'null for the original mapping instead of an object with empty or null values.' | |
); | |
} | |
if (aGenerated && 'line' in aGenerated && 'column' in aGenerated | |
&& aGenerated.line > 0 && aGenerated.column >= 0 | |
&& !aOriginal && !aSource && !aName) { | |
// Case 1. | |
return; | |
} | |
else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated | |
&& aOriginal && 'line' in aOriginal && 'column' in aOriginal | |
&& aGenerated.line > 0 && aGenerated.column >= 0 | |
&& aOriginal.line > 0 && aOriginal.column >= 0 | |
&& aSource) { | |
// Cases 2 and 3. | |
return; | |
} | |
else { | |
throw new Error('Invalid mapping: ' + JSON.stringify({ | |
generated: aGenerated, | |
source: aSource, | |
original: aOriginal, | |
name: aName | |
})); | |
} | |
}; | |
/** | |
* Serialize the accumulated mappings in to the stream of base 64 VLQs | |
* specified by the source map format. | |
*/ | |
SourceMapGenerator.prototype._serializeMappings = | |
function SourceMapGenerator_serializeMappings() { | |
var previousGeneratedColumn = 0; | |
var previousGeneratedLine = 1; | |
var previousOriginalColumn = 0; | |
var previousOriginalLine = 0; | |
var previousName = 0; | |
var previousSource = 0; | |
var result = ''; | |
var next; | |
var mapping; | |
var nameIdx; | |
var sourceIdx; | |
var mappings = this._mappings.toArray(); | |
for (var i = 0, len = mappings.length; i < len; i++) { | |
mapping = mappings[i]; | |
next = '' | |
if (mapping.generatedLine !== previousGeneratedLine) { | |
previousGeneratedColumn = 0; | |
while (mapping.generatedLine !== previousGeneratedLine) { | |
next += ';'; | |
previousGeneratedLine++; | |
} | |
} | |
else { | |
if (i > 0) { | |
if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) { | |
continue; | |
} | |
next += ','; | |
} | |
} | |
next += base64VLQ.encode(mapping.generatedColumn | |
- previousGeneratedColumn); | |
previousGeneratedColumn = mapping.generatedColumn; | |
if (mapping.source != null) { | |
sourceIdx = this._sources.indexOf(mapping.source); | |
next += base64VLQ.encode(sourceIdx - previousSource); | |
previousSource = sourceIdx; | |
// lines are stored 0-based in SourceMap spec version 3 | |
next += base64VLQ.encode(mapping.originalLine - 1 | |
- previousOriginalLine); | |
previousOriginalLine = mapping.originalLine - 1; | |
next += base64VLQ.encode(mapping.originalColumn | |
- previousOriginalColumn); | |
previousOriginalColumn = mapping.originalColumn; | |
if (mapping.name != null) { | |
nameIdx = this._names.indexOf(mapping.name); | |
next += base64VLQ.encode(nameIdx - previousName); | |
previousName = nameIdx; | |
} | |
} | |
result += next; | |
} | |
return result; | |
}; | |
SourceMapGenerator.prototype._generateSourcesContent = | |
function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) { | |
return aSources.map(function (source) { | |
if (!this._sourcesContents) { | |
return null; | |
} | |
if (aSourceRoot != null) { | |
source = util.relative(aSourceRoot, source); | |
} | |
var key = util.toSetString(source); | |
return Object.prototype.hasOwnProperty.call(this._sourcesContents, key) | |
? this._sourcesContents[key] | |
: null; | |
}, this); | |
}; | |
/** | |
* Externalize the source map. | |
*/ | |
SourceMapGenerator.prototype.toJSON = | |
function SourceMapGenerator_toJSON() { | |
var map = { | |
version: this._version, | |
sources: this._sources.toArray(), | |
names: this._names.toArray(), | |
mappings: this._serializeMappings() | |
}; | |
if (this._file != null) { | |
map.file = this._file; | |
} | |
if (this._sourceRoot != null) { | |
map.sourceRoot = this._sourceRoot; | |
} | |
if (this._sourcesContents) { | |
map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot); | |
} | |
return map; | |
}; | |
/** | |
* Render the source map being generated to a string. | |
*/ | |
SourceMapGenerator.prototype.toString = | |
function SourceMapGenerator_toString() { | |
return JSON.stringify(this.toJSON()); | |
}; | |
exports.SourceMapGenerator = SourceMapGenerator; | |