diff --git a/lib/6to5/types.js b/lib/6to5/types.js index ae8bc0013b..6cf4871fe5 100644 --- a/lib/6to5/types.js +++ b/lib/6to5/types.js @@ -3,6 +3,9 @@ var n = require("acorn-ast-types").namedTypes; var _ = require("lodash"); exports.FUNCTION_TYPES = ["ArrowFunctionExpression", "FunctionDeclaration", "FunctionExpression"]; +exports.PATTERN_TYPES = ["ArrayPattern", "ObjectPattern"]; +exports.BINARY_TYPES = ["BinaryExpression", "LogicalExpression"]; +exports.UNARY_TYPES = ["UnaryExpression", "SpreadElement", "SpreadProperty"]; exports.aliases = { ArrowFunctionExpression: ["Function"], @@ -10,12 +13,21 @@ exports.aliases = { FunctionExpression: ["Function"] }; + +exports.isUnaryLike = function (node) { + return _.contains(exports.UNARY_TYPES, node.type); +}; + +exports.isBinary = function (node) { + return _.contains(exports.BINARY_TYPES, node.type); +}; + exports.isFunction = function (node) { return _.contains(exports.FUNCTION_TYPES, node.type); }; exports.isPattern = function (node) { - return node.type === "ArrayPattern" || node.type === "ObjectPattern"; + return _.contains(exports.PATTERN_TYPES, node.type); }; exports.isReferenced = function (node, parent) { @@ -59,3 +71,128 @@ exports.ensureExpressionType = function (node) { }[node.type] || node.type; return node; }; + +exports.needsParans = function (node, parent) { + //if (!n.Expression.check(node)) return false; + + if (exports.isUnaryLike(node)) { + return parent.type === "MemberExpression" && parent.object === node; + } + + if (exports.isBinary(node)) { + if (parent.type === "CallExpression" && parent.callee === node) { + return true; + } + + if (exports.isUnaryLike(parent)) { + return true; + } + + if (parent.type === "MemberExpression" && parent.object === node) { + return true; + } + + if (exports.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 && parent.right === node) { + return true; + } + } + } + + if (node.type === "SequenceExpression") { + if (parent.type === "ForStatement") { + // 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 (parent.type === "ExpressionStatement" && parent.expression === node) { + return false; + } + + // Otherwise err on the side of overparenthesization, adding + // explicit exceptions above if this proves overzealous. + return true; + } + + if (node.type === "YieldExpression") { + return exports.isBinary(parent) + || exports.isUnaryLike(parent) + || parent.type === "CallExpression" + || parent.type === "MemberExpression" + || parent.type === "NewExpression" + || parent.type === "ConditionalExpression" + || parent.type === "YieldExpression"; + } + + //if (parent.type === "NewExpression" && parent.callee === node) { + // return containsCallExpression(node); + //} + + if (node.type === "Literal" && _.isNumber(node.value) && parent.type === "MemberExpression" && parent.object === node) { + return true; + } + + if (node.type === "AssignmentExpression" || node.type === "ConditionalExpression") { + if (exports.isUnaryLike(parent)) { + return true; + } + + if (exports.isBinary(parent)) { + return true; + } + + if (parent.type === "CallExpression" && parent.callee === node) { + return true; + } + + if (parent.type === "ConditionalExpression" && parent.test === node) { + return true; + } + + if (parent.type === "MemberExpression" && parent.object === node) { + return true; + } + } + + if (node.type === "FunctionExpression" && parent.type === "CallExpression" && parent.callee === node) { + return true; + } + + return false; +}; + +exports.canBeFirstInStatement = function (node) { + return node.type !== "FunctionExpression" && node.type !== "ObjectExpression"; +}; + +var PRECEDENCE = {}; + +_.each([ + ["||"], + ["&&"], + ["|"], + ["^"], + ["&"], + ["==", "===", "!=", "!=="], + ["<", ">", "<=", ">=", "in", "instanceof"], + [">>", "<<", ">>>"], + ["+", "-"], + ["*", "/", "%"] +], function(tier, i) { + _.each(tier, function(op) { + PRECEDENCE[op] = i; + }); +}); +