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);

};

NPp.prune = function() {

var remainingNodePath = this.parent;

this.replace();

if (n.VariableDeclaration.check(remainingNodePath.node)) {
    var declarations = remainingNodePath.get('declarations').value;
    if (!declarations || declarations.length === 0) {
        return remainingNodePath.prune();
    }
} else if (n.ExpressionStatement.check(remainingNodePath.node)) {
    if (!remainingNodePath.get('expression').value) {
        return remainingNodePath.prune();
    }
}

return remainingNodePath;

};

// 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;