text: import Visitor from './visitor';
  
  function WhitespaceControl(options = {}) {
    this.options = options;
  }
  WhitespaceControl.prototype = new Visitor();
  
  WhitespaceControl.prototype.Program = function(program) {
    const doStandalone = !this.options.ignoreStandalone;
  
    let isRoot = !this.isRootSeen;
    this.isRootSeen = true;
  
    let body = program.body;
    for (let i = 0, l = body.length; i < l; i++) {
      let current = body[i],
        strip = this.accept(current);
  
      if (!strip) {
        continue;
      }
  
      let _isPrevWhitespace = isPrevWhitespace(body, i, isRoot),
        _isNextWhitespace = isNextWhitespace(body, i, isRoot),
        openStandalone = strip.openStandalone && _isPrevWhitespace,
        closeStandalone = strip.closeStandalone && _isNextWhitespace,
        inlineStandalone =
          strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace;
  
      if (strip.close) {
        omitRight(body, i, true);
      }
      if (strip.open) {
        omitLeft(body, i, true);
      }
  
      if (doStandalone && inlineStandalone) {
        omitRight(body, i);
  
        if (omitLeft(body, i)) {
          // If we are on a standalone node, save the indent info for partials
          if (current.type === 'PartialStatement') {
            // Pull out the whitespace from the final line
            current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1];
          }
        }
      }
      if (doStandalone && openStandalone) {
        omitRight((current.program || current.inverse).body);
  
        // Strip out the previous content node if it's whitespace only
        omitLeft(body, i);
      }
      if (doStandalone && closeStandalone) {
        // Always strip the next node
        omitRight(body, i);
  
        omitLeft((current.inverse || current.program).body);
      }
    }
  
    return program;
  };
  
  WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function(
    block
  ) {
    this.accept(block.program);
    this.accept(block.inverse);
  
    // Find the inverse program that is involed with whitespace stripping.
    let program = block.program || block.inverse,
      inverse = block.program && block.inverse,
      firstInverse = inverse,
      lastInverse = inverse;
  
    if (inverse && inverse.chained) {
      firstInverse = inverse.body[0].program;
  
      // Walk the inverse chain to find the last inverse that is actually in the chain.
      while (lastInverse.chained) {
        lastInverse = lastInverse.body[lastInverse.body.length - 1].program;
      }
    }
  
    let strip = {
      open: block.openStrip.open,
      close: block.closeStrip.close,
  
      // Determine the standalone candiacy. Basically flag our content as being possibly standalone
      // so our parent can determine if we actually are standalone
      openStandalone: isNextWhitespace(program.body),
      closeStandalone: isPrevWhitespace((firstInverse || program).body)
    };
  
    if (block.openStrip.close) {
      omitRight(program.body, null, true);
    }
  
    if (inverse) {
      let inverseStrip = block.inverseStrip;
  
      if (inverseStrip.open) {
        omitLeft(program.body, null, true);
      }
  
      if (inverseStrip.close) {
        omitRight(firstInverse.body, null, true);
      }
      if (block.closeStrip.open) {
        omitLeft(lastInverse.body, null, true);
      }
  
      // Find standalone else statments
      if (
        !this.options.ignoreStandalone &&
        isPrevWhitespace(program.body) &&
        isNextWhitespace(firstInverse.body)
      ) {
        omitLeft(program.body);
        omitRight(firstInverse.body);
      }
    } else if (block.closeStrip.open) {
      omitLeft(program.body, null, true);
    }
  
    return strip;
  };
  
  WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function(
    mustache
  ) {
    return mustache.strip;
  };
  
  WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function(
    node
  ) {
    /* istanbul ignore next */
    let strip = node.strip || {};
    return {
      inlineStandalone: true,
      open: strip.open,
      close: strip.close
    };
  };
  
  function isPrevWhitespace(body, i, isRoot) {
    if (i === undefined) {
      i = body.length;
    }
  
    // Nodes that end with newlines are considered whitespace (but are special
    // cased for strip operations)
    let prev = body[i - 1],
      sibling = body[i - 2];
    if (!prev) {
      return isRoot;
    }
  
    if (prev.type === 'ContentStatement') {
      return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(
        prev.original
      );
    }
  }
  function isNextWhitespace(body, i, isRoot) {
    if (i === undefined) {
      i = -1;
    }
  
    let next = body[i + 1],
      sibling = body[i + 2];
    if (!next) {
      return isRoot;
    }
  
    if (next.type === 'ContentStatement') {
      return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(
        next.original
      );
    }
  }
  
  // Marks the node to the right of the position as omitted.
  // I.e. {{foo}}' ' will mark the ' ' node as omitted.
  //
  // If i is undefined, then the first child will be marked as such.
  //
  // If multiple is truthy then all whitespace will be stripped out until non-whitespace
  // content is met.
  function omitRight(body, i, multiple) {
    let current = body[i == null ? 0 : i + 1];
    if (
      !current ||
      current.type !== 'ContentStatement' ||
      (!multiple && current.rightStripped)
    ) {
      return;
    }
  
    let original = current.value;
    current.value = current.value.replace(
      multiple ? /^\s+/ : /^[ \t]*\r?\n?/,
      ''
    );
    current.rightStripped = current.value !== original;
  }
  
  // Marks the node to the left of the position as omitted.
  // I.e. ' '{{foo}} will mark the ' ' node as omitted.
  //
  // If i is undefined then the last child will be marked as such.
  //
  // If multiple is truthy then all whitespace will be stripped out until non-whitespace
  // content is met.
  function omitLeft(body, i, multiple) {
    let current = body[i == null ? body.length - 1 : i - 1];
    if (
      !current ||
      current.type !== 'ContentStatement' ||
      (!multiple && current.leftStripped)
    ) {
      return;
    }
  
    // We omit the last node if it's whitespace only and not preceded by a non-content node.
    let original = current.value;
    current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
    current.leftStripped = current.value !== original;
    return current.leftStripped;
  }
  
  export default WhitespaceControl;
  