From dc2fa2cdd002394d6ffe6384e09696d3696a8122 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Wed, 8 Mar 2017 13:11:53 -0800 Subject: [PATCH] Fix #951: properly parenthesize ** expressions (#952) --- src/fast-path.js | 20 +++++++----------- src/printer.js | 4 +++- src/util.js | 3 ++- .../__snapshots__/jsfmt.spec.js.snap | 21 +++++++++++++++++++ tests/binary-expressions/exp.js | 8 +++++++ 5 files changed, 41 insertions(+), 15 deletions(-) create mode 100644 tests/binary-expressions/exp.js diff --git a/src/fast-path.js b/src/fast-path.js index 5de1b2653..d4535be9c 100644 --- a/src/fast-path.js +++ b/src/fast-path.js @@ -232,19 +232,6 @@ FPp.needsParens = function(assumeExpressionContext) { return true; } - // The left-hand side of the ** exponentiation operator must always - // be parenthesized unless it's an ident or literal - if ( - parent.type === "BinaryExpression" && - parent.operator === "**" && - parent.left === node && - node.type !== "Identifier" && - node.type !== "Literal" && - node.type !== "NumericLiteral" - ) { - return true; - } - if ( parent.type === "ArrowFunctionExpression" && parent.body === node && startsWithNoLookaheadToken(node, /* forbidFunctionAndClass */ false) || parent.type === "ExpressionStatement" && startsWithNoLookaheadToken(node, /* forbidFunctionAndClass */ true) @@ -285,6 +272,9 @@ FPp.needsParens = function(assumeExpressionContext) { case "CallExpression": return name === "callee" && parent.callee == node; + case "BinaryExpression": + return parent.operator === "**" && name === "left"; + default: return false; } @@ -332,6 +322,10 @@ FPp.needsParens = function(assumeExpressionContext) { return true; } + if (no === "**" && po === "**") { + return name === "left"; + } + if (pp === np && name === "right") { assert.strictEqual(parent.right, node); return true; diff --git a/src/printer.js b/src/printer.js index fe0e79d6a..1a74e1bf1 100644 --- a/src/printer.js +++ b/src/printer.js @@ -2783,10 +2783,12 @@ function printBinaryishExpressions(path, parts, print, options, isNested) { // is where the rest of the expression will exist. Binary // expressions on the right side mean they have a difference // precedence level and should be treated as a separate group, so - // print them normally. + // print them normally. (This doesn't hold for the `**` operator, + // which is unique in that it is right-associative.) if ( util.getPrecedence(node.left.operator) === util.getPrecedence(node.operator) + && node.operator !== "**" ) { // Flatten them out by recursively calling this function. The // printed values will all be appended to `parts`. diff --git a/src/util.js b/src/util.js index b3fc595a3..7cfcb1066 100644 --- a/src/util.js +++ b/src/util.js @@ -294,7 +294,8 @@ var PRECEDENCE = {}; ["<", ">", "<=", ">=", "in", "instanceof"], [">>", "<<", ">>>"], ["+", "-"], - ["*", "/", "%", "**"] + ["*", "/", "%"], + ["**"] ].forEach(function(tier, i) { tier.forEach(function(op) { PRECEDENCE[op] = i; diff --git a/tests/binary-expressions/__snapshots__/jsfmt.spec.js.snap b/tests/binary-expressions/__snapshots__/jsfmt.spec.js.snap index 9ef9bc361..25ec6e85c 100644 --- a/tests/binary-expressions/__snapshots__/jsfmt.spec.js.snap +++ b/tests/binary-expressions/__snapshots__/jsfmt.spec.js.snap @@ -19,6 +19,27 @@ function f() { " `; +exports[`exp.js 1`] = ` +"a ** b ** c; +(a ** b) ** c; +a.b ** c; +(-a) ** b; +a ** -b; +-(a**b); +(a * b) ** c; +a ** (b * c); +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +a ** b ** c; +(a ** b) ** c; +a.b ** c; +(-a) ** b; +a ** -b; +-(a ** b); +(a * b) ** c; +a ** (b * c); +" +`; + exports[`inline-object-array.js 1`] = ` "prevState = prevState || { catalogs: [], diff --git a/tests/binary-expressions/exp.js b/tests/binary-expressions/exp.js new file mode 100644 index 000000000..903466894 --- /dev/null +++ b/tests/binary-expressions/exp.js @@ -0,0 +1,8 @@ +a ** b ** c; +(a ** b) ** c; +a.b ** c; +(-a) ** b; +a ** -b; +-(a**b); +(a * b) ** c; +a ** (b * c);