| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413 | /* -*- 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() comparisonsvar 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;
 |