text: /**
   * Copyright 2013 Facebook, Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  
  /*jslint node: true*/
  var Syntax = require('esprima-fb').Syntax;
  var leadingIndentRegexp = /(^|\n)( {2}|\t)/g;
  
  /**
   * A `state` object represents the state of the parser. It has "local" and
   * "global" parts. Global contains parser position, source, etc. Local contains
   * scope based properties like current class name. State should contain all the
   * info required for transformation. It's the only mandatory object that is
   * being passed to every function in transform chain.
   *
   * @param  {string} source
   * @param  {object} transformOptions
   * @return {object}
   */
  function createState(source, rootNode, transformOptions) {
    return {
      /**
       * A tree representing the current local scope (and its lexical scope chain)
       * Useful for tracking identifiers from parent scopes, etc.
       * @type {Object}
       */
      localScope: {
        parentNode: rootNode,
        parentScope: null,
        identifiers: {}
      },
      /**
       * The name (and, if applicable, expression) of the super class
       * @type {Object}
       */
      superClass: null,
      /**
       * The namespace to use when munging identifiers
       * @type {String}
       */
      mungeNamespace: '',
      /**
       * Ref to the node for the FunctionExpression of the enclosing
       * MethodDefinition
       * @type {Object}
       */
      methodFuncNode: null,
      /**
       * Name of the enclosing class
       * @type {String}
       */
      className: null,
      /**
       * Whether we're currently within a `strict` scope
       * @type {Bool}
       */
      scopeIsStrict: null,
      /**
       * Indentation offset
       * @type {Number}
       */
      indentBy: 0,
      /**
       * Global state (not affected by updateState)
       * @type {Object}
       */
      g: {
        /**
         * A set of general options that transformations can consider while doing
         * a transformation:
         *
         * - minify
         *   Specifies that transformation steps should do their best to minify
         *   the output source when possible. This is useful for places where
         *   minification optimizations are possible with higher-level context
         *   info than what jsxmin can provide.
         *
         *   For example, the ES6 class transform will minify munged private
         *   variables if this flag is set.
         */
        opts: transformOptions,
        /**
         * Current position in the source code
         * @type {Number}
         */
        position: 0,
        /**
         * Auxiliary data to be returned by transforms
         * @type {Object}
         */
        extra: {},
        /**
         * Buffer containing the result
         * @type {String}
         */
        buffer: '',
        /**
         * Source that is being transformed
         * @type {String}
         */
        source: source,
  
        /**
         * Cached parsed docblock (see getDocblock)
         * @type {object}
         */
        docblock: null,
  
        /**
         * Whether the thing was used
         * @type {Boolean}
         */
        tagNamespaceUsed: false,
  
        /**
         * If using bolt xjs transformation
         * @type {Boolean}
         */
        isBolt: undefined,
  
        /**
         * Whether to record source map (expensive) or not
         * @type {SourceMapGenerator|null}
         */
        sourceMap: null,
  
        /**
         * Filename of the file being processed. Will be returned as a source
         * attribute in the source map
         */
        sourceMapFilename: 'source.js',
  
        /**
         * Only when source map is used: last line in the source for which
         * source map was generated
         * @type {Number}
         */
        sourceLine: 1,
  
        /**
         * Only when source map is used: last line in the buffer for which
         * source map was generated
         * @type {Number}
         */
        bufferLine: 1,
  
        /**
         * The top-level Program AST for the original file.
         */
        originalProgramAST: null,
  
        sourceColumn: 0,
        bufferColumn: 0
      }
    };
  }
  
  /**
   * Updates a copy of a given state with "update" and returns an updated state.
   *
   * @param  {object} state
   * @param  {object} update
   * @return {object}
   */
  function updateState(state, update) {
    var ret = Object.create(state);
    Object.keys(update).forEach(function(updatedKey) {
      ret[updatedKey] = update[updatedKey];
    });
    return ret;
  }
  
  /**
   * Given a state fill the resulting buffer from the original source up to
   * the end
   *
   * @param {number} end
   * @param {object} state
   * @param {?function} contentTransformer Optional callback to transform newly
   *                                       added content.
   */
  function catchup(end, state, contentTransformer) {
    if (end < state.g.position) {
      // cannot move backwards
      return;
    }
    var source = state.g.source.substring(state.g.position, end);
    var transformed = updateIndent(source, state);
    if (state.g.sourceMap && transformed) {
      // record where we are
      state.g.sourceMap.addMapping({
        generated: { line: state.g.bufferLine, column: state.g.bufferColumn },
        original: { line: state.g.sourceLine, column: state.g.sourceColumn },
        source: state.g.sourceMapFilename
      });
  
      // record line breaks in transformed source
      var sourceLines = source.split('\n');
      var transformedLines = transformed.split('\n');
      // Add line break mappings between last known mapping and the end of the
      // added piece. So for the code piece
      //  (foo, bar);
      // > var x = 2;
      // > var b = 3;
      //   var c =
      // only add lines marked with ">": 2, 3.
      for (var i = 1; i < sourceLines.length - 1; i++) {
        state.g.sourceMap.addMapping({
          generated: { line: state.g.bufferLine, column: 0 },
          original: { line: state.g.sourceLine, column: 0 },
          source: state.g.sourceMapFilename
        });
        state.g.sourceLine++;
        state.g.bufferLine++;
      }
      // offset for the last piece
      if (sourceLines.length > 1) {
        state.g.sourceLine++;
        state.g.bufferLine++;
        state.g.sourceColumn = 0;
        state.g.bufferColumn = 0;
      }
      state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;
      state.g.bufferColumn +=
        transformedLines[transformedLines.length - 1].length;
    }
    state.g.buffer +=
      contentTransformer ? contentTransformer(transformed) : transformed;
    state.g.position = end;
  }
  
  /**
   * Removes all non-whitespace characters
   */
  var reNonWhite = /(\S)/g;
  function stripNonWhite(value) {
    return value.replace(reNonWhite, function() {
      return '';
    });
  }
  
  /**
   * Catches up as `catchup` but removes all non-whitespace characters.
   */
  function catchupWhiteSpace(end, state) {
    catchup(end, state, stripNonWhite);
  }
  
  /**
   * Removes all non-newline characters
   */
  var reNonNewline = /[^\n]/g;
  function stripNonNewline(value) {
    return value.replace(reNonNewline, function() {
      return '';
    });
  }
  
  /**
   * Catches up as `catchup` but removes all non-newline characters.
   *
   * Equivalent to appending as many newlines as there are in the original source
   * between the current position and `end`.
   */
  function catchupNewlines(end, state) {
    catchup(end, state, stripNonNewline);
  }
  
  
  /**
   * Same as catchup but does not touch the buffer
   *
   * @param  {number} end
   * @param  {object} state
   */
  function move(end, state) {
    // move the internal cursors
    if (state.g.sourceMap) {
      if (end < state.g.position) {
        state.g.position = 0;
        state.g.sourceLine = 1;
        state.g.sourceColumn = 0;
      }
  
      var source = state.g.source.substring(state.g.position, end);
      var sourceLines = source.split('\n');
      if (sourceLines.length > 1) {
        state.g.sourceLine += sourceLines.length - 1;
        state.g.sourceColumn = 0;
      }
      state.g.sourceColumn += sourceLines[sourceLines.length - 1].length;
    }
    state.g.position = end;
  }
  
  /**
   * Appends a string of text to the buffer
   *
   * @param {string} str
   * @param {object} state
   */
  function append(str, state) {
    if (state.g.sourceMap && str) {
      state.g.sourceMap.addMapping({
        generated: { line: state.g.bufferLine, column: state.g.bufferColumn },
        original: { line: state.g.sourceLine, column: state.g.sourceColumn },
        source: state.g.sourceMapFilename
      });
      var transformedLines = str.split('\n');
      if (transformedLines.length > 1) {
        state.g.bufferLine += transformedLines.length - 1;
        state.g.bufferColumn = 0;
      }
      state.g.bufferColumn +=
        transformedLines[transformedLines.length - 1].length;
    }
    state.g.buffer += str;
  }
  
  /**
   * Update indent using state.indentBy property. Indent is measured in
   * double spaces. Updates a single line only.
   *
   * @param {string} str
   * @param {object} state
   * @return {string}
   */
  function updateIndent(str, state) {
    var indentBy = state.indentBy;
    if (indentBy < 0) {
      for (var i = 0; i < -indentBy; i++) {
        str = str.replace(leadingIndentRegexp, '$1');
      }
    } else {
      for (var i = 0; i < indentBy; i++) {
        str = str.replace(leadingIndentRegexp, '$1$2$2');
      }
    }
    return str;
  }
  
  /**
   * Calculates indent from the beginning of the line until "start" or the first
   * character before start.
   * @example
   *   "  foo.bar()"
   *         ^
   *       start
   *   indent will be "  "
   *
   * @param  {number} start
   * @param  {object} state
   * @return {string}
   */
  function indentBefore(start, state) {
    var end = start;
    start = start - 1;
  
    while (start > 0 && state.g.source[start] != '\n') {
      if (!state.g.source[start].match(/[ \t]/)) {
        end = start;
      }
      start--;
    }
    return state.g.source.substring(start + 1, end);
  }
  
  function getDocblock(state) {
    if (!state.g.docblock) {
      var docblock = require('./docblock');
      state.g.docblock =
        docblock.parseAsObject(docblock.extract(state.g.source));
    }
    return state.g.docblock;
  }
  
  function identWithinLexicalScope(identName, state, stopBeforeNode) {
    var currScope = state.localScope;
    while (currScope) {
      if (currScope.identifiers[identName] !== undefined) {
        return true;
      }
  
      if (stopBeforeNode && currScope.parentNode === stopBeforeNode) {
        break;
      }
  
      currScope = currScope.parentScope;
    }
    return false;
  }
  
  function identInLocalScope(identName, state) {
    return state.localScope.identifiers[identName] !== undefined;
  }
  
  /**
   * @param {object} boundaryNode
   * @param {?array} path
   * @return {?object} node
   */
  function initScopeMetadata(boundaryNode, path, node) {
    return {
      boundaryNode: boundaryNode,
      bindingPath: path,
      bindingNode: node
    };
  }
  
  function declareIdentInLocalScope(identName, metaData, state) {
    state.localScope.identifiers[identName] = {
      boundaryNode: metaData.boundaryNode,
      path: metaData.path,
      node: metaData.node,
      state: Object.create(state)
    };
  }
  
  function getLexicalBindingMetadata(identName, state) {
    return state.localScope.identifiers[identName];
  }
  
  /**
   * Apply the given analyzer function to the current node. If the analyzer
   * doesn't return false, traverse each child of the current node using the given
   * traverser function.
   *
   * @param {function} analyzer
   * @param {function} traverser
   * @param {object} node
   * @param {function} visitor
   * @param {array} path
   * @param {object} state
   */
  function analyzeAndTraverse(analyzer, traverser, node, path, state) {
    if (node.type) {
      if (analyzer(node, path, state) === false) {
        return;
      }
      path.unshift(node);
    }
  
    getOrderedChildren(node).forEach(function(child) {
      traverser(child, path, state);
    });
  
    node.type && path.shift();
  }
  
  /**
   * It is crucial that we traverse in order, or else catchup() on a later
   * node that is processed out of order can move the buffer past a node
   * that we haven't handled yet, preventing us from modifying that node.
   *
   * This can happen when a node has multiple properties containing children.
   * For example, XJSElement nodes have `openingElement`, `closingElement` and
   * `children`. If we traverse `openingElement`, then `closingElement`, then
   * when we get to `children`, the buffer has already caught up to the end of
   * the closing element, after the children.
   *
   * This is basically a Schwartzian transform. Collects an array of children,
   * each one represented as [child, startIndex]; sorts the array by start
   * index; then traverses the children in that order.
   */
  function getOrderedChildren(node) {
    var queue = [];
    for (var key in node) {
      if (node.hasOwnProperty(key)) {
        enqueueNodeWithStartIndex(queue, node[key]);
      }
    }
    queue.sort(function(a, b) { return a[1] - b[1]; });
    return queue.map(function(pair) { return pair[0]; });
  }
  
  /**
   * Helper function for analyzeAndTraverse which queues up all of the children
   * of the given node.
   *
   * Children can also be found in arrays, so we basically want to merge all of
   * those arrays together so we can sort them and then traverse the children
   * in order.
   *
   * One example is the Program node. It contains `body` and `comments`, both
   * arrays. Lexographically, comments are interspersed throughout the body
   * nodes, but esprima's AST groups them together.
   */
  function enqueueNodeWithStartIndex(queue, node) {
    if (typeof node !== 'object' || node === null) {
      return;
    }
    if (node.range) {
      queue.push([node, node.range[0]]);
    } else if (Array.isArray(node)) {
      for (var ii = 0; ii < node.length; ii++) {
        enqueueNodeWithStartIndex(queue, node[ii]);
      }
    }
  }
  
  /**
   * Checks whether a node or any of its sub-nodes contains
   * a syntactic construct of the passed type.
   * @param {object} node - AST node to test.
   * @param {string} type - node type to lookup.
   */
  function containsChildOfType(node, type) {
    var foundMatchingChild = false;
    function nodeTypeAnalyzer(node) {
      if (node.type === type) {
        foundMatchingChild = true;
        return false;
      }
    }
    function nodeTypeTraverser(child, path, state) {
      if (!foundMatchingChild) {
        foundMatchingChild = containsChildOfType(child, type);
      }
    }
    analyzeAndTraverse(
      nodeTypeAnalyzer,
      nodeTypeTraverser,
      node,
      []
    );
    return foundMatchingChild;
  }
  
  var scopeTypes = {};
  scopeTypes[Syntax.FunctionExpression] = true;
  scopeTypes[Syntax.FunctionDeclaration] = true;
  scopeTypes[Syntax.Program] = true;
  
  function getBoundaryNode(path) {
    for (var ii = 0; ii < path.length; ++ii) {
      if (scopeTypes[path[ii].type]) {
        return path[ii];
      }
    }
    throw new Error(
      'Expected to find a node with one of the following types in path:\n' +
      JSON.stringify(Object.keys(scopeTypes))
    );
  }
  
  exports.append = append;
  exports.catchup = catchup;
  exports.catchupWhiteSpace = catchupWhiteSpace;
  exports.catchupNewlines = catchupNewlines;
  exports.containsChildOfType = containsChildOfType;
  exports.createState = createState;
  exports.declareIdentInLocalScope = declareIdentInLocalScope;
  exports.getBoundaryNode = getBoundaryNode;
  exports.getDocblock = getDocblock;
  exports.getLexicalBindingMetadata = getLexicalBindingMetadata;
  exports.initScopeMetadata = initScopeMetadata;
  exports.identWithinLexicalScope = identWithinLexicalScope;
  exports.identInLocalScope = identInLocalScope;
  exports.indentBefore = indentBefore;
  exports.move = move;
  exports.scopeTypes = scopeTypes;
  exports.updateIndent = updateIndent;
  exports.updateState = updateState;
  exports.analyzeAndTraverse = analyzeAndTraverse;
  exports.getOrderedChildren = getOrderedChildren;
  