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; | |