Repository: prettier/prettier (JavaScript) — Bug Fix

Issue:
Statement-position expression whose LHS starts with '{' needs parens
Prettier incorrectly drops the parentheses in e.g.
```js
({} && a, b);
```

Related: #912


Discussion:

Resolving patch:
diff --git a/src/fast-path.js b/src/fast-path.js
index 60d027835b3a..f61da57765cf 100644
--- a/src/fast-path.js
+++ b/src/fast-path.js
@@ -245,14 +245,16 @@ FPp.needsParens = function(assumeExpressionContext) {
     return true;
   }
 
+  if (
+    ((parent.type === "ArrowFunctionExpression" && parent.body === node) ||
+    parent.type === "ExpressionStatement") &&
+    startsWithOpenCurlyBrace(node)
+  ) {
+    return true;
+  }
+
   switch (node.type) {
     case "CallExpression":
-      if (
-        node.callee.type === "ObjectExpression" &&
-        parent.type === "ArrowFunctionExpression"
-      ) {
-        return true;
-      }
       return false;
 
     case "SpreadElement":
@@ -305,13 +307,6 @@ FPp.needsParens = function(assumeExpressionContext) {
         return true;
       }
 
-      if (
-        node.operator === "instanceof" &&
-        parent.type === "ArrowFunctionExpression"
-      ) {
-        return true;
-      }
-
     case "LogicalExpression":
       switch (parent.type) {
         case "CallExpression":
@@ -525,17 +520,6 @@ FPp.needsParens = function(assumeExpressionContext) {
 
       return false;
 
-    case "ObjectExpression":
-      if (parent.type === "ArrowFunctionExpression" && name === "body") {
-        return true;
-      }
-      if (parent.type === "TaggedTemplateExpression") {
-        return true;
-      }
-      if (parent.type === "MemberExpression") {
-        return name === "object" && parent.object === node;
-      }
-
     case "StringLiteral":
       if (parent.type === "ExpressionStatement") {
         return true;
@@ -583,12 +567,41 @@ function containsCallExpression(node) {
   return false;
 }
 
+function startsWithOpenCurlyBrace(node) {
+  node = getLeftMost(node);
+  switch (node.type) {
+    case "ObjectExpression":
+      return true;
+    case "MemberExpression":
+      return startsWithOpenCurlyBrace(node.object);
+    case "TaggedTemplateExpression":
+      return startsWithOpenCurlyBrace(node.tag);
+    case "CallExpression":
+      return startsWithOpenCurlyBrace(node.callee);
+    case "ConditionalExpression":
+      return startsWithOpenCurlyBrace(node.test);
+    case "UpdateExpression":
+      return !node.prefix && startsWithOpenCurlyBrace(node.argument);
+    case "BindExpression":
+      return node.object && startsWithOpenCurlyBrace(node.object);
+    case "SequenceExpression":
+      return startsWithOpenCurlyBrace(node.expressions[0])
+    default:
+      return false;
+  }
+}
+
+function getLeftMost(node) {
+  if (node.left) {
+    return getLeftMost(node.left);
+  } else {
+    return node;
+  }
+}
+
 FPp.canBeFirstInStatement = function() {
-  var node = this.getNode();
-  return !n.FunctionExpression.check(node) &&
-    !n.ObjectExpression.check(node) &&
-    !n.ClassExpression.check(node) &&
-    !(n.AssignmentExpression.check(node) && n.ObjectPattern.check(node.left));
+  const node = this.getNode();
+  return !n.FunctionExpression.check(node) && !n.ClassExpression.check(node);
 };
 
 FPp.firstInStatement = function() {


Test patch:
diff --git a/tests/arrows/__snapshots__/jsfmt.spec.js.snap b/tests/arrows/__snapshots__/jsfmt.spec.js.snap
index b565b0ecb489..74396bddc0f7 100644
--- a/tests/arrows/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/arrows/__snapshots__/jsfmt.spec.js.snap
@@ -10,6 +10,23 @@ new (() => {});
 if ((() => {}) ? 1 : 0) {}
 let f = () => ({}())
 let a = () => ({} instanceof a);
+a = () => ({} && a);
+a = () => ({}() && a);
+a = () => ({} && a && b);
+a = () => ({} + a);
+a = () => ({}()() && a);
+a = () => ({}.b && a);
+a = () => ({}[b] && a);
+a = () => ({}\`\` && a);
+a = () => ({} = 0);
+a = () => ({}, a);
+a => a instanceof {};
+a => ({}().b && 0)
+a => ({}::b()\`\`[''].c++ && 0 ? 0 : 0)
+a => ({}().c = 0)
+x => ({}()())
+x => ({}()\`\`)
+x => ({}().b)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 (a => {}).length;
 typeof (() => {});
@@ -21,6 +38,23 @@ if ((() => {}) ? 1 : 0) {
 }
 let f = () => ({}());
 let a = () => ({} instanceof a);
+a = () => ({} && a);
+a = () => ({}() && a);
+a = () => ({} && a && b);
+a = () => ({} + a);
+a = () => ({}()() && a);
+a = () => ({}.b && a);
+a = () => ({}[b] && a);
+a = () => ({}\`\` && a);
+a = () => ({} = 0);
+a = () => ({}, a);
+(a => a instanceof {});
+(a => ({}().b && 0));
+(a => ({}::b()\`\`[\\"\\"].c++ && 0 ? 0 : 0));
+(a => ({}().c = 0));
+(x => ({}()()));
+(x => ({}()\`\`));
+(x => ({}().b));
 "
 `;
 
diff --git a/tests/arrows/arrow_function_expression.js b/tests/arrows/arrow_function_expression.js
index 71251bea0953..9bd8c88be385 100644
--- a/tests/arrows/arrow_function_expression.js
+++ b/tests/arrows/arrow_function_expression.js
@@ -7,3 +7,20 @@ new (() => {});
 if ((() => {}) ? 1 : 0) {}
 let f = () => ({}())
 let a = () => ({} instanceof a);
+a = () => ({} && a);
+a = () => ({}() && a);
+a = () => ({} && a && b);
+a = () => ({} + a);
+a = () => ({}()() && a);
+a = () => ({}.b && a);
+a = () => ({}[b] && a);
+a = () => ({}`` && a);
+a = () => ({} = 0);
+a = () => ({}, a);
+a => a instanceof {};
+a => ({}().b && 0)
+a => ({}::b()``[''].c++ && 0 ? 0 : 0)
+a => ({}().c = 0)
+x => ({}()())
+x => ({}()``)
+x => ({}().b)
diff --git a/tests/arrows/jsfmt.spec.js b/tests/arrows/jsfmt.spec.js
index 989047bccc52..939578260648 100644
--- a/tests/arrows/jsfmt.spec.js
+++ b/tests/arrows/jsfmt.spec.js
@@ -1,1 +1,1 @@
-run_spec(__dirname);
+run_spec(__dirname, {parser: 'babylon'});
diff --git a/tests/flow/arith/__snapshots__/jsfmt.spec.js.snap b/tests/flow/arith/__snapshots__/jsfmt.spec.js.snap
index 81a8170a1b30..7996a15ff869 100644
--- a/tests/flow/arith/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/arith/__snapshots__/jsfmt.spec.js.snap
@@ -162,7 +162,7 @@ let tests = [
     x + \\"\\"; // error
     \\"\\" + x; // error
     x + {}; // error
-    ({}) + x; // error
+    ({} + x); // error
   },
 
   // when one side is a string or number and the other is invalid, we
@@ -310,10 +310,10 @@ let tests = [
 \\"foo\\" < 1; // error
 \\"foo\\" < \\"bar\\";
 1 < { foo: 1 }; // error
-({ foo: 1 }) < 1; // error
-({ foo: 1 }) < { foo: 1 }; // error
+({ foo: 1 } < 1); // error
+({ foo: 1 } < { foo: 1 }); // error
 \\"foo\\" < { foo: 1 }; // error
-({ foo: 1 }) < \\"foo\\"; // error
+({ foo: 1 } < \\"foo\\"); // error
 
 var x = (null: ?number);
 1 < x; // 2 errors: null !~> number; undefined !~> number
diff --git a/tests/flow/binary/__snapshots__/jsfmt.spec.js.snap b/tests/flow/binary/__snapshots__/jsfmt.spec.js.snap
index c19f968c71b0..8f5a54fcbe2d 100644
--- a/tests/flow/binary/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/binary/__snapshots__/jsfmt.spec.js.snap
@@ -92,7 +92,7 @@ let tests = [
   function() {
     null in {}; // error
     void 0 in {}; // error
-    ({}) in {}; // error
+    ({} in {}); // error
     [] in {}; // error
     false in []; // error
   },
diff --git a/tests/flow/object-method/__snapshots__/jsfmt.spec.js.snap b/tests/flow/object-method/__snapshots__/jsfmt.spec.js.snap
index e50c533cb8d2..5d1dad11a5c3 100644
--- a/tests/flow/object-method/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/object-method/__snapshots__/jsfmt.spec.js.snap
@@ -126,7 +126,7 @@ function foo() {
 
 function bar(f: () => void) {
   f(); // passing global object as \`this\`
-  ({ f }).f(); // passing container object as \`this\`
+  ({ f }.f()); // passing container object as \`this\`
 }
 
 bar(foo); // error, since \`this\` is used non-trivially in \`foo\`
diff --git a/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap b/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
index 58f0ca973c0d..eef9be20d99e 100644
--- a/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
@@ -2582,7 +2582,7 @@ let tests = [
   function(x: A) {
     if (x.kind === null.toString()) {
     } // error, method on null
-    if (({ kind: 1 }).kind === null.toString()) {
+    if ({ kind: 1 }.kind === null.toString()) {
     } // error, method on null
   },
 
diff --git a/tests/method-chain/__snapshots__/jsfmt.spec.js.snap b/tests/method-chain/__snapshots__/jsfmt.spec.js.snap
index 0ca8d7e0ad53..b6ea592cc3ea 100644
--- a/tests/method-chain/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/method-chain/__snapshots__/jsfmt.spec.js.snap
@@ -295,8 +295,8 @@ exports[`test.js 1`] = `
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 method().then(x => x)[\\"abc\\"](x => x)[abc](x => x);
 
-({}).a().b();
-({}).a().b();
+({}.a().b());
+({}.a().b());
 "
 `;
 
diff --git a/tests/objects/__snapshots__/jsfmt.spec.js.snap b/tests/objects/__snapshots__/jsfmt.spec.js.snap
index 5d0dc92a773d..e7281f353765 100644
--- a/tests/objects/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/objects/__snapshots__/jsfmt.spec.js.snap
@@ -4,10 +4,22 @@ exports[`expression.js 1`] = `
 "() => ({}\`\`);
 ({})\`\`;
 a = () => ({}).x;
+({} && a, b);
+({}::b, 0);
+({}::b()\`\`[''].c++ && 0 ? 0 : 0, 0);
+({}(), 0);
+({} = 0);
+(({} = 0), 1);
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-(() => ({})\`\`);
-({})\`\`;
-a = () => ({}).x;
+(() => ({}\`\`));
+({}\`\`);
+a = () => ({}.x);
+({} && a, b);
+({}::b, 0);
+({}::b()\`\`[\\"\\"].c++ && 0 ? 0 : 0, 0);
+({}(), 0);
+({} = 0);
+({} = 0), 1;
 "
 `;
 
diff --git a/tests/objects/expression.js b/tests/objects/expression.js
index 861275eb0819..e707621e6df4 100644
--- a/tests/objects/expression.js
+++ b/tests/objects/expression.js
@@ -1,3 +1,9 @@
 () => ({}``);
 ({})``;
 a = () => ({}).x;
+({} && a, b);
+({}::b, 0);
+({}::b()``[''].c++ && 0 ? 0 : 0, 0);
+({}(), 0);
+({} = 0);
+(({} = 0), 1);
diff --git a/tests/objects/jsfmt.spec.js b/tests/objects/jsfmt.spec.js
index 989047bccc52..939578260648 100644
--- a/tests/objects/jsfmt.spec.js
+++ b/tests/objects/jsfmt.spec.js
@@ -1,1 +1,1 @@
-run_spec(__dirname);
+run_spec(__dirname, {parser: 'babylon'});
diff --git a/tests/template/__snapshots__/jsfmt.spec.js.snap b/tests/template/__snapshots__/jsfmt.spec.js.snap
index af996d488e93..68f92fab318d 100644
--- a/tests/template/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/template/__snapshots__/jsfmt.spec.js.snap
@@ -125,7 +125,7 @@ b.c\`\`;
 new B()\`\`;
 
 // \\"ObjectExpression\\"
-({})\`\`;
+({}\`\`);
 
 // \\"SequenceExpression\\"
 (b, c)\`\`;