text: var assert = require("assert");
  var types = require("./types");
  var n = types.namedTypes;
  var isNumber = types.builtInTypes.number;
  var isArray = types.builtInTypes.array;
  var Path = require("./path");
  var Scope = require("./scope");
  
  function NodePath(value, parentPath, name) {
      assert.ok(this instanceof NodePath);
      Path.call(this, value, parentPath, name);
  }
  
  require("util").inherits(NodePath, Path);
  var NPp = NodePath.prototype;
  
  Object.defineProperties(NPp, {
      node: {
          get: function() {
              Object.defineProperty(this, "node", {
                  configurable: true, // Enable deletion.
                  value: this._computeNode()
              });
  
              return this.node;
          }
      },
  
      parent: {
          get: function() {
              Object.defineProperty(this, "parent", {
                  configurable: true, // Enable deletion.
                  value: this._computeParent()
              });
  
              return this.parent;
          }
      },
  
      scope: {
          get: function() {
              Object.defineProperty(this, "scope", {
                  configurable: true, // Enable deletion.
                  value: this._computeScope()
              });
  
              return this.scope;
          }
      }
  });
  
  NPp.replace = function() {
      delete this.node;
      delete this.parent;
      delete this.scope;
      return Path.prototype.replace.apply(this, arguments);
  };
  
  // The value of the first ancestor Path whose value is a Node.
  NPp._computeNode = function() {
      var value = this.value;
      if (n.Node.check(value)) {
          return value;
      }
  
      var pp = this.parentPath;
      return pp && pp.node || null;
  };
  
  // The first ancestor Path whose value is a Node distinct from this.node.
  NPp._computeParent = function() {
      var value = this.value;
      var pp = this.parentPath;
  
      if (!n.Node.check(value)) {
          while (pp && !n.Node.check(pp.value)) {
              pp = pp.parentPath;
          }
  
          if (pp) {
              pp = pp.parentPath;
          }
      }
  
      while (pp && !n.Node.check(pp.value)) {
          pp = pp.parentPath;
      }
  
      return pp || null;
  };
  
  // The closest enclosing scope that governs this node.
  NPp._computeScope = function() {
      var value = this.value;
      var pp = this.parentPath;
      var scope = pp && pp.scope;
  
      if (n.Node.check(value) &&
          Scope.isEstablishedBy(value)) {
          scope = new Scope(this, scope);
      }
  
      return scope || null;
  };
  
  NPp.getValueProperty = function(name) {
      return types.getFieldValue(this.value, name);
  };
  
  /**
   * Determine whether this.node needs to be wrapped in parentheses in order
   * for a parser to reproduce the same local AST structure.
   *
   * For instance, in the expression `(1 + 2) * 3`, the BinaryExpression
   * whose operator is "+" needs parentheses, because `1 + 2 * 3` would
   * parse differently.
   *
   * If assumeExpressionContext === true, we don't worry about edge cases
   * like an anonymous FunctionExpression appearing lexically first in its
   * enclosing statement and thus needing parentheses to avoid being parsed
   * as a FunctionDeclaration with a missing name.
   */
  NPp.needsParens = function(assumeExpressionContext) {
      if (!this.parent)
          return false;
  
      var node = this.node;
  
      // If this NodePath object is not the direct owner of this.node, then
      // we do not need parentheses here, though the direct owner might need
      // parentheses.
      if (node !== this.value)
          return false;
  
      var parent = this.parent.node;
  
      assert.notStrictEqual(node, parent);
  
      if (!n.Expression.check(node))
          return false;
  
      if (isUnaryLike(node))
          return n.MemberExpression.check(parent)
              && this.name === "object"
              && parent.object === node;
  
      if (isBinary(node)) {
          if (n.CallExpression.check(parent) &&
              this.name === "callee") {
              assert.strictEqual(parent.callee, node);
              return true;
          }
  
          if (isUnaryLike(parent))
              return true;
  
          if (n.MemberExpression.check(parent) &&
              this.name === "object") {
              assert.strictEqual(parent.object, node);
              return true;
          }
  
          if (isBinary(parent)) {
              var po = parent.operator;
              var pp = PRECEDENCE[po];
              var no = node.operator;
              var np = PRECEDENCE[no];
  
              if (pp > np) {
                  return true;
              }
  
              if (pp === np && this.name === "right") {
                  assert.strictEqual(parent.right, node);
                  return true;
              }
          }
      }
  
      if (n.SequenceExpression.check(node)) {
          if (n.ForStatement.check(parent)) {
              // Although parentheses wouldn't hurt around sequence
              // expressions in the head of for loops, traditional style
              // dictates that e.g. i++, j++ should not be wrapped with
              // parentheses.
              return false;
          }
  
          if (n.ExpressionStatement.check(parent) &&
              this.name === "expression") {
              return false;
          }
  
          // Otherwise err on the side of overparenthesization, adding
          // explicit exceptions above if this proves overzealous.
          return true;
      }
  
      if (n.YieldExpression.check(node))
          return isBinary(parent)
              || n.CallExpression.check(parent)
              || n.MemberExpression.check(parent)
              || n.NewExpression.check(parent)
              || n.ConditionalExpression.check(parent)
              || isUnaryLike(parent)
              || n.YieldExpression.check(parent);
  
      if (n.NewExpression.check(parent) &&
          this.name === "callee") {
          assert.strictEqual(parent.callee, node);
          return containsCallExpression(node);
      }
  
      if (n.Literal.check(node) &&
          isNumber.check(node.value) &&
          n.MemberExpression.check(parent) &&
          this.name === "object") {
          assert.strictEqual(parent.object, node);
          return true;
      }
  
      if (n.AssignmentExpression.check(node) ||
          n.ConditionalExpression.check(node)) {
          if (isUnaryLike(parent))
              return true;
  
          if (isBinary(parent))
              return true;
  
          if (n.CallExpression.check(parent) &&
              this.name === "callee") {
              assert.strictEqual(parent.callee, node);
              return true;
          }
  
          if (n.ConditionalExpression.check(parent) &&
              this.name === "test") {
              assert.strictEqual(parent.test, node);
              return true;
          }
  
          if (n.MemberExpression.check(parent) &&
              this.name === "object") {
              assert.strictEqual(parent.object, node);
              return true;
          }
      }
  
      if (assumeExpressionContext !== true &&
          !this.canBeFirstInStatement() &&
          this.firstInStatement())
          return true;
  
      return false;
  };
  
  function isBinary(node) {
      return n.BinaryExpression.check(node)
          || n.LogicalExpression.check(node);
  }
  
  function isUnaryLike(node) {
      return n.UnaryExpression.check(node)
          // I considered making SpreadElement and SpreadProperty subtypes
          // of UnaryExpression, but they're not really Expression nodes.
          || (n.SpreadElement && n.SpreadElement.check(node))
          || (n.SpreadProperty && n.SpreadProperty.check(node));
  }
  
  var PRECEDENCE = {};
  [["||"],
   ["&&"],
   ["|"],
   ["^"],
   ["&"],
   ["==", "===", "!=", "!=="],
   ["<", ">", "<=", ">=", "in", "instanceof"],
   [">>", "<<", ">>>"],
   ["+", "-"],
   ["*", "/", "%"]
  ].forEach(function(tier, i) {
      tier.forEach(function(op) {
          PRECEDENCE[op] = i;
      });
  });
  
  function containsCallExpression(node) {
      if (n.CallExpression.check(node)) {
          return true;
      }
  
      if (isArray.check(node)) {
          return node.some(containsCallExpression);
      }
  
      if (n.Node.check(node)) {
          return types.someField(node, function(name, child) {
              return containsCallExpression(child);
          });
      }
  
      return false;
  }
  
  NPp.canBeFirstInStatement = function() {
      var node = this.node;
      return !n.FunctionExpression.check(node)
          && !n.ObjectExpression.check(node);
  };
  
  NPp.firstInStatement = function() {
      return firstInStatement(this);
  };
  
  function firstInStatement(path) {
      for (var node, parent; path.parent; path = path.parent) {
          node = path.node;
          parent = path.parent.node;
  
          if (n.BlockStatement.check(parent) &&
              path.parent.name === "body" &&
              path.name === 0) {
              assert.strictEqual(parent.body[0], node);
              return true;
          }
  
          if (n.ExpressionStatement.check(parent) &&
              path.name === "expression") {
              assert.strictEqual(parent.expression, node);
              return true;
          }
  
          if (n.SequenceExpression.check(parent) &&
              path.parent.name === "expressions" &&
              path.name === 0) {
              assert.strictEqual(parent.expressions[0], node);
              continue;
          }
  
          if (n.CallExpression.check(parent) &&
              path.name === "callee") {
              assert.strictEqual(parent.callee, node);
              continue;
          }
  
          if (n.MemberExpression.check(parent) &&
              path.name === "object") {
              assert.strictEqual(parent.object, node);
              continue;
          }
  
          if (n.ConditionalExpression.check(parent) &&
              path.name === "test") {
              assert.strictEqual(parent.test, node);
              continue;
          }
  
          if (isBinary(parent) &&
              path.name === "left") {
              assert.strictEqual(parent.left, node);
              continue;
          }
  
          if (n.UnaryExpression.check(parent) &&
              !parent.prefix &&
              path.name === "argument") {
              assert.strictEqual(parent.argument, node);
              continue;
          }
  
          return false;
      }
  
      return true;
  }
  
  module.exports = NodePath;
  