Repository: prettier/prettier (JavaScript)
Date: 2017-01-22 22:17:01+00:00
Task type: Bug Fix

Issue:
Missing parentheses around `extends (expression with operators)`
Input:

```js
class eujwdqvgbybofs extends (a || b) {}
```

Output:

```js
class eujwdqvgbybofs extends a || b {}
```

... which fails to parse.

https://jlongster.github.io/prettier/#%7B%22content%22%3A%22class%20eujwdqvgbybofs%20extends%20(a%20%7C%7C%20b)%20%7B%7D%22%2C%22options%22%3A%7B%22printWidth%22%3A80%2C%22tabWidth%22%3A2%2C%22singleQuote%22%3Afalse%2C%22trailingComma%22%3Afalse%2C%22bracketSpacing%22%3Atrue%2C%22doc%22%3Afalse%7D%7D

Found using the fuzzer in #371


Issue discussion:
Similar cases:

```js
class eujwdqvgbybofs extends (void 2e308) {}
```

```js
class eujwdqvgbybofs extends (1 + 1) {}
```


Solution patch:
diff --git a/src/fast-path.js b/src/fast-path.js
index e2af664f055c..74e994d07607 100644
--- a/src/fast-path.js
+++ b/src/fast-path.js
@@ -192,12 +192,26 @@ FPp.needsParens = function(assumeExpressionContext) {
     return false;
   }
 
-  // Add parens around a `class` that extends an expression (it should
-  // parse correctly, even if it's invalid)
+  // Add parens around the extends clause of a class. It is needed for almost
+  // all expressions.
   if (
     parent.type === "ClassDeclaration" &&
-      parent.superClass === node &&
-      node.type === "AwaitExpression"
+      parent.superClass === node && (
+        node.type === "ArrowFunctionExpression" ||
+        node.type === "AssignmentExpression" ||
+        node.type === "AwaitExpression" ||
+        node.type === "BinaryExpression" ||
+        node.type === "ConditionalExpression" ||
+        node.type === "LogicalExpression" ||
+        node.type === "NewExpression" ||
+        node.type === "ObjectExpression" ||
+        node.type === "ParenthesizedExpression" ||
+        node.type === "SequenceExpression" ||
+        node.type === "TaggedTemplateExpression" ||
+        node.type === "UnaryExpression" ||
+        node.type === "UpdateExpression" ||
+        node.type === "YieldExpression"
+      )
   ) {
     return true;
   }


Test patch:
diff --git a/tests/class_extends/__snapshots__/jsfmt.spec.js.snap b/tests/class_extends/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..fe29f87d4cc9
--- /dev/null
+++ b/tests/class_extends/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,116 @@
+exports[`test extends.js 1`] = `
+"// \"ArrowFunctionExpression\"
+class a extends (() => {}) {}
+
+// \"AssignmentExpression\"
+class a extends (b = c) {}
+
+// \"AwaitExpression\"
+async function f() {
+  class a extends (await b) {}
+}
+
+// \"BinaryExpression\"
+class a extends (b + c) {}
+
+// \"CallExpression\"
+class a extends b() {}
+
+// \"ClassExpression\"
+class a extends class {} {}
+
+// \"ConditionalExpression\"
+class a extends (b ? c : d) {}
+
+// \"FunctionExpression\"
+class a extends (function() {}) {}
+
+// \"LogicalExpression\"
+class a extends (b || c) {}
+
+// \"MemberExpression\"
+class a extends b.c {}
+
+// \"NewExpression\"
+class a extends (new B()) {}
+
+// \"ObjectExpression\"
+class a extends ({}) {}
+
+// \"SequenceExpression\"
+class a extends (b, c) {}
+
+// \"TaggedTemplateExpression\"
+class a extends \`\` {}
+
+// \"UnaryExpression\"
+class a extends (void b) {}
+
+// \"UpdateExpression\"
+class a extends (++b) {}
+
+// \"YieldExpression\"
+function* f() {
+  // Flow has a bug parsing it.
+  // class a extends (yield 1) {}
+}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// \"ArrowFunctionExpression\"
+class a extends (() => {
+}) {}
+
+// \"AssignmentExpression\"
+class a extends (b = c) {}
+
+// \"AwaitExpression\"
+async function f() {
+  class a extends (await b) {}
+}
+
+// \"BinaryExpression\"
+class a extends (b + c) {}
+
+// \"CallExpression\"
+class a extends b() {}
+
+// \"ClassExpression\"
+class a extends class {} {}
+
+// \"ConditionalExpression\"
+class a extends (b ? c : d) {}
+
+// \"FunctionExpression\"
+class a extends function() {
+} {}
+
+// \"LogicalExpression\"
+class a extends (b || c) {}
+
+// \"MemberExpression\"
+class a extends b.c {}
+
+// \"NewExpression\"
+class a extends (new B()) {}
+
+// \"ObjectExpression\"
+class a extends ({}) {}
+
+// \"SequenceExpression\"
+class a extends (b, c) {}
+
+// \"TaggedTemplateExpression\"
+class a extends \`\` {}
+
+// \"UnaryExpression\"
+class a extends (void b) {}
+
+// \"UpdateExpression\"
+class a extends (++b) {}
+
+// \"YieldExpression\"
+function* f() {
+  // Flow has a bug parsing it.
+  // class a extends (yield 1) {}
+}
+"
+`;
diff --git a/tests/class_extends/extends.js b/tests/class_extends/extends.js
new file mode 100644
index 000000000000..6debb835b951
--- /dev/null
+++ b/tests/class_extends/extends.js
@@ -0,0 +1,55 @@
+// "ArrowFunctionExpression"
+class a extends (() => {}) {}
+
+// "AssignmentExpression"
+class a extends (b = c) {}
+
+// "AwaitExpression"
+async function f() {
+  class a extends (await b) {}
+}
+
+// "BinaryExpression"
+class a extends (b + c) {}
+
+// "CallExpression"
+class a extends b() {}
+
+// "ClassExpression"
+class a extends class {} {}
+
+// "ConditionalExpression"
+class a extends (b ? c : d) {}
+
+// "FunctionExpression"
+class a extends (function() {}) {}
+
+// "LogicalExpression"
+class a extends (b || c) {}
+
+// "MemberExpression"
+class a extends b.c {}
+
+// "NewExpression"
+class a extends (new B()) {}
+
+// "ObjectExpression"
+class a extends ({}) {}
+
+// "SequenceExpression"
+class a extends (b, c) {}
+
+// "TaggedTemplateExpression"
+class a extends `` {}
+
+// "UnaryExpression"
+class a extends (void b) {}
+
+// "UpdateExpression"
+class a extends (++b) {}
+
+// "YieldExpression"
+function* f() {
+  // Flow has a bug parsing it.
+  // class a extends (yield 1) {}
+}
diff --git a/tests/class_extends/jsfmt.spec.js b/tests/class_extends/jsfmt.spec.js
new file mode 100644
index 000000000000..989047bccc52
--- /dev/null
+++ b/tests/class_extends/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);