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 SourceMapGenerator = require('./source-map-generator').SourceMapGenerator; | |
var util = require('./util'); | |
// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other | |
// operating systems these days (capturing the result). | |
var REGEX_NEWLINE = /(\r?\n)/; | |
// Newline character code for charCodeAt() comparisons | |
var NEWLINE_CODE = 10; | |
// Private symbol for identifying `SourceNode`s when multiple versions of | |
// the source-map library are loaded. This MUST NOT CHANGE across | |
// versions! | |
var isSourceNode = "$$$isSourceNode$$$"; | |
/** | |
* SourceNodes provide a way to abstract over interpolating/concatenating | |
* snippets of generated JavaScript source code while maintaining the line and | |
* column information associated with the original source code. | |
* | |
* @param aLine The original line number. | |
* @param aColumn The original column number. | |
* @param aSource The original source's filename. | |
* @param aChunks Optional. An array of strings which are snippets of | |
* generated JS, or other SourceNodes. | |
* @param aName The original identifier. | |
*/ | |
function SourceNode(aLine, aColumn, aSource, aChunks, aName) { | |
this.children = []; | |
this.sourceContents = {}; | |
this.line = aLine == null ? null : aLine; | |
this.column = aColumn == null ? null : aColumn; | |
this.source = aSource == null ? null : aSource; | |
this.name = aName == null ? null : aName; | |
this[isSourceNode] = true; | |
if (aChunks != null) this.add(aChunks); | |
} | |
/** | |
* Creates a SourceNode from generated code and a SourceMapConsumer. | |
* | |
* @param aGeneratedCode The generated code | |
* @param aSourceMapConsumer The SourceMap for the generated code | |
* @param aRelativePath Optional. The path that relative sources in the | |
* SourceMapConsumer should be relative to. | |
*/ | |
SourceNode.fromStringWithSourceMap = | |
function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) { | |
// The SourceNode we want to fill with the generated code | |
// and the SourceMap | |
var node = new SourceNode(); | |
// All even indices of this array are one line of the generated code, | |
// while all odd indices are the newlines between two adjacent lines | |
// (since `REGEX_NEWLINE` captures its match). | |
// Processed fragments are accessed by calling `shiftNextLine`. | |
var remainingLines = aGeneratedCode.split(REGEX_NEWLINE); | |
var remainingLinesIndex = 0; | |
var shiftNextLine = function() { | |
var lineContents = getNextLine(); | |
// The last line of a file might not have a newline. | |
var newLine = getNextLine() || ""; | |
return lineContents + newLine; | |
function getNextLine() { | |
return remainingLinesIndex < remainingLines.length ? | |
remainingLines[remainingLinesIndex++] : undefined; | |
} | |
}; | |
// We need to remember the position of "remainingLines" | |
var lastGeneratedLine = 1, lastGeneratedColumn = 0; | |
// The generate SourceNodes we need a code range. | |
// To extract it current and last mapping is used. | |
// Here we store the last mapping. | |
var lastMapping = null; | |
aSourceMapConsumer.eachMapping(function (mapping) { | |
if (lastMapping !== null) { | |
// We add the code from "lastMapping" to "mapping": | |
// First check if there is a new line in between. | |
if (lastGeneratedLine < mapping.generatedLine) { | |
// Associate first line with "lastMapping" | |
addMappingWithCode(lastMapping, shiftNextLine()); | |
lastGeneratedLine++; | |
lastGeneratedColumn = 0; | |
// The remaining code is added without mapping | |
} else { | |
// There is no new line in between. | |
// Associate the code between "lastGeneratedColumn" and | |
// "mapping.generatedColumn" with "lastMapping" | |
var nextLine = remainingLines[remainingLinesIndex] || ''; | |
var code = nextLine.substr(0, mapping.generatedColumn - | |
lastGeneratedColumn); | |
remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn - | |
lastGeneratedColumn); | |
lastGeneratedColumn = mapping.generatedColumn; | |
addMappingWithCode(lastMapping, code); | |
// No more remaining code, continue | |
lastMapping = mapping; | |
return; | |
} | |
} | |
// We add the generated code until the first mapping | |
// to the SourceNode without any mapping. | |
// Each line is added as separate string. | |
while (lastGeneratedLine < mapping.generatedLine) { | |
node.add(shiftNextLine()); | |
lastGeneratedLine++; | |
} | |
if (lastGeneratedColumn < mapping.generatedColumn) { | |
var nextLine = remainingLines[remainingLinesIndex] || ''; | |
node.add(nextLine.substr(0, mapping.generatedColumn)); | |
remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn); | |
lastGeneratedColumn = mapping.generatedColumn; | |
} | |
lastMapping = mapping; | |
}, this); | |
// We have processed all mappings. | |
if (remainingLinesIndex < remainingLines.length) { | |
if (lastMapping) { | |
// Associate the remaining code in the current line with "lastMapping" | |
addMappingWithCode(lastMapping, shiftNextLine()); | |
} | |
// and add the remaining lines without any mapping | |
node.add(remainingLines.splice(remainingLinesIndex).join("")); | |
} | |
// Copy sourcesContent into SourceNode | |
aSourceMapConsumer.sources.forEach(function (sourceFile) { | |
var content = aSourceMapConsumer.sourceContentFor(sourceFile); | |
if (content != null) { | |
if (aRelativePath != null) { | |
sourceFile = util.join(aRelativePath, sourceFile); | |
} | |
node.setSourceContent(sourceFile, content); | |
} | |
}); | |
return node; | |
function addMappingWithCode(mapping, code) { | |
if (mapping === null || mapping.source === undefined) { | |
node.add(code); | |
} else { | |
var source = aRelativePath | |
? util.join(aRelativePath, mapping.source) | |
: mapping.source; | |
node.add(new SourceNode(mapping.originalLine, | |
mapping.originalColumn, | |
source, | |
code, | |
mapping.name)); | |
} | |
} | |
}; | |
/** | |
* Add a chunk of generated JS to this source node. | |
* | |
* @param aChunk A string snippet of generated JS code, another instance of | |
* SourceNode, or an array where each member is one of those things. | |
*/ | |
SourceNode.prototype.add = function SourceNode_add(aChunk) { | |
if (Array.isArray(aChunk)) { | |
aChunk.forEach(function (chunk) { | |
this.add(chunk); | |
}, this); | |
} | |
else if (aChunk[isSourceNode] || typeof aChunk === "string") { | |
if (aChunk) { | |
this.children.push(aChunk); | |
} | |
} | |
else { | |
throw new TypeError( | |
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk | |
); | |
} | |
return this; | |
}; | |
/** | |
* Add a chunk of generated JS to the beginning of this source node. | |
* | |
* @param aChunk A string snippet of generated JS code, another instance of | |
* SourceNode, or an array where each member is one of those things. | |
*/ | |
SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) { | |
if (Array.isArray(aChunk)) { | |
for (var i = aChunk.length-1; i >= 0; i--) { | |
this.prepend(aChunk[i]); | |
} | |
} | |
else if (aChunk[isSourceNode] || typeof aChunk === "string") { | |
this.children.unshift(aChunk); | |
} | |
else { | |
throw new TypeError( | |
"Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk | |
); | |
} | |
return this; | |
}; | |
/** | |
* Walk over the tree of JS snippets in this node and its children. The | |
* walking function is called once for each snippet of JS and is passed that | |
* snippet and the its original associated source's line/column location. | |
* | |
* @param aFn The traversal function. | |
*/ | |
SourceNode.prototype.walk = function SourceNode_walk(aFn) { | |
var chunk; | |
for (var i = 0, len = this.children.length; i < len; i++) { | |
chunk = this.children[i]; | |
if (chunk[isSourceNode]) { | |
chunk.walk(aFn); | |
} | |
else { | |
if (chunk !== '') { | |
aFn(chunk, { source: this.source, | |
line: this.line, | |
column: this.column, | |
name: this.name }); | |
} | |
} | |
} | |
}; | |
/** | |
* Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between | |
* each of `this.children`. | |
* | |
* @param aSep The separator. | |
*/ | |
SourceNode.prototype.join = function SourceNode_join(aSep) { | |
var newChildren; | |
var i; | |
var len = this.children.length; | |
if (len > 0) { | |
newChildren = []; | |
for (i = 0; i < len-1; i++) { | |
newChildren.push(this.children[i]); | |
newChildren.push(aSep); | |
} | |
newChildren.push(this.children[i]); | |
this.children = newChildren; | |
} | |
return this; | |
}; | |
/** | |
* Call String.prototype.replace on the very right-most source snippet. Useful | |
* for trimming whitespace from the end of a source node, etc. | |
* | |
* @param aPattern The pattern to replace. | |
* @param aReplacement The thing to replace the pattern with. | |
*/ | |
SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) { | |
var lastChild = this.children[this.children.length - 1]; | |
if (lastChild[isSourceNode]) { | |
lastChild.replaceRight(aPattern, aReplacement); | |
} | |
else if (typeof lastChild === 'string') { | |
this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement); | |
} | |
else { | |
this.children.push(''.replace(aPattern, aReplacement)); | |
} | |
return this; | |
}; | |
/** | |
* Set the source content for a source file. This will be added to the SourceMapGenerator | |
* in the sourcesContent field. | |
* | |
* @param aSourceFile The filename of the source file | |
* @param aSourceContent The content of the source file | |
*/ | |
SourceNode.prototype.setSourceContent = | |
function SourceNode_setSourceContent(aSourceFile, aSourceContent) { | |
this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent; | |
}; | |
/** | |
* Walk over the tree of SourceNodes. The walking function is called for each | |
* source file content and is passed the filename and source content. | |
* | |
* @param aFn The traversal function. | |
*/ | |
SourceNode.prototype.walkSourceContents = | |
function SourceNode_walkSourceContents(aFn) { | |
for (var i = 0, len = this.children.length; i < len; i++) { | |
if (this.children[i][isSourceNode]) { | |
this.children[i].walkSourceContents(aFn); | |
} | |
} | |
var sources = Object.keys(this.sourceContents); | |
for (var i = 0, len = sources.length; i < len; i++) { | |
aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]); | |
} | |
}; | |
/** | |
* Return the string representation of this source node. Walks over the tree | |
* and concatenates all the various snippets together to one string. | |
*/ | |
SourceNode.prototype.toString = function SourceNode_toString() { | |
var str = ""; | |
this.walk(function (chunk) { | |
str += chunk; | |
}); | |
return str; | |
}; | |
/** | |
* Returns the string representation of this source node along with a source | |
* map. | |
*/ | |
SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) { | |
var generated = { | |
code: "", | |
line: 1, | |
column: 0 | |
}; | |
var map = new SourceMapGenerator(aArgs); | |
var sourceMappingActive = false; | |
var lastOriginalSource = null; | |
var lastOriginalLine = null; | |
var lastOriginalColumn = null; | |
var lastOriginalName = null; | |
this.walk(function (chunk, original) { | |
generated.code += chunk; | |
if (original.source !== null | |
&& original.line !== null | |
&& original.column !== null) { | |
if(lastOriginalSource !== original.source | |
|| lastOriginalLine !== original.line | |
|| lastOriginalColumn !== original.column | |
|| lastOriginalName !== original.name) { | |
map.addMapping({ | |
source: original.source, | |
original: { | |
line: original.line, | |
column: original.column | |
}, | |
generated: { | |
line: generated.line, | |
column: generated.column | |
}, | |
name: original.name | |
}); | |
} | |
lastOriginalSource = original.source; | |
lastOriginalLine = original.line; | |
lastOriginalColumn = original.column; | |
lastOriginalName = original.name; | |
sourceMappingActive = true; | |
} else if (sourceMappingActive) { | |
map.addMapping({ | |
generated: { | |
line: generated.line, | |
column: generated.column | |
} | |
}); | |
lastOriginalSource = null; | |
sourceMappingActive = false; | |
} | |
for (var idx = 0, length = chunk.length; idx < length; idx++) { | |
if (chunk.charCodeAt(idx) === NEWLINE_CODE) { | |
generated.line++; | |
generated.column = 0; | |
// Mappings end at eol | |
if (idx + 1 === length) { | |
lastOriginalSource = null; | |
sourceMappingActive = false; | |
} else if (sourceMappingActive) { | |
map.addMapping({ | |
source: original.source, | |
original: { | |
line: original.line, | |
column: original.column | |
}, | |
generated: { | |
line: generated.line, | |
column: generated.column | |
}, | |
name: original.name | |
}); | |
} | |
} else { | |
generated.column++; | |
} | |
} | |
}); | |
this.walkSourceContents(function (sourceFile, sourceContent) { | |
map.setSourceContent(sourceFile, sourceContent); | |
}); | |
return { code: generated.code, map: map }; | |
}; | |
exports.SourceNode = SourceNode; | |