/* -*- 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 
 | 
 */ 
 | 
  
 | 
const SourceMapGenerator = require("./source-map-generator").SourceMapGenerator; 
 | 
const 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). 
 | 
const REGEX_NEWLINE = /(\r?\n)/; 
 | 
  
 | 
// Newline character code for charCodeAt() comparisons 
 | 
const 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! 
 | 
const 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. 
 | 
 */ 
 | 
class SourceNode { 
 | 
  constructor(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. 
 | 
   */ 
 | 
  static fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) { 
 | 
    // The SourceNode we want to fill with the generated code 
 | 
    // and the SourceMap 
 | 
    const 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`. 
 | 
    const remainingLines = aGeneratedCode.split(REGEX_NEWLINE); 
 | 
    let remainingLinesIndex = 0; 
 | 
    const shiftNextLine = function() { 
 | 
      const lineContents = getNextLine(); 
 | 
      // The last line of a file might not have a newline. 
 | 
      const newLine = getNextLine() || ""; 
 | 
      return lineContents + newLine; 
 | 
  
 | 
      function getNextLine() { 
 | 
        return remainingLinesIndex < remainingLines.length ? 
 | 
            remainingLines[remainingLinesIndex++] : undefined; 
 | 
      } 
 | 
    }; 
 | 
  
 | 
    // We need to remember the position of "remainingLines" 
 | 
    let 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. 
 | 
    let lastMapping = null; 
 | 
    let nextLine; 
 | 
  
 | 
    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" 
 | 
          nextLine = remainingLines[remainingLinesIndex] || ""; 
 | 
          const 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) { 
 | 
        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) { 
 | 
      const 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 { 
 | 
        const 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. 
 | 
   */ 
 | 
  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. 
 | 
   */ 
 | 
  prepend(aChunk) { 
 | 
    if (Array.isArray(aChunk)) { 
 | 
      for (let 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. 
 | 
   */ 
 | 
  walk(aFn) { 
 | 
    let chunk; 
 | 
    for (let 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. 
 | 
   */ 
 | 
  join(aSep) { 
 | 
    let newChildren; 
 | 
    let i; 
 | 
    const 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. 
 | 
   */ 
 | 
  replaceRight(aPattern, aReplacement) { 
 | 
    const 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 
 | 
   */ 
 | 
  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. 
 | 
   */ 
 | 
  walkSourceContents(aFn) { 
 | 
    for (let i = 0, len = this.children.length; i < len; i++) { 
 | 
      if (this.children[i][isSourceNode]) { 
 | 
        this.children[i].walkSourceContents(aFn); 
 | 
      } 
 | 
    } 
 | 
  
 | 
    const sources = Object.keys(this.sourceContents); 
 | 
    for (let 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. 
 | 
   */ 
 | 
  toString() { 
 | 
    let str = ""; 
 | 
    this.walk(function(chunk) { 
 | 
      str += chunk; 
 | 
    }); 
 | 
    return str; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Returns the string representation of this source node along with a source 
 | 
   * map. 
 | 
   */ 
 | 
  toStringWithSourceMap(aArgs) { 
 | 
    const generated = { 
 | 
      code: "", 
 | 
      line: 1, 
 | 
      column: 0 
 | 
    }; 
 | 
    const map = new SourceMapGenerator(aArgs); 
 | 
    let sourceMappingActive = false; 
 | 
    let lastOriginalSource = null; 
 | 
    let lastOriginalLine = null; 
 | 
    let lastOriginalColumn = null; 
 | 
    let 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 (let 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 }; 
 | 
  } 
 | 
} 
 | 
  
 | 
exports.SourceNode = SourceNode; 
 |