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;