You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							414 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
	
	
							414 lines
						
					
					
						
							14 KiB
						
					
					
				/* -*- 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;
 |