414 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			14 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
 | |
|  */
 | |
| 
 | |
| 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;
 |