Repository: jlongster/prettier (js)

Issue:
Export default sometimes needs parentheses
In particular,

```js
export default function f(){}
```

introduces `f` into the local scope, but

```js
export default (function f(){});
```

does not. (The former is a `FunctionDeclaration`, the latter a `FunctionExpression`). But prettier formats these identically.

This can lead to prettier generating invalid programs:

```js
let a;
export default (function a(){});
```
is a valid program, but running it through prettier generates
```js
let a;
export default function a() {
}
```
which is not (it has a duplicate lexical binding).

Similarly for async functions, generators, and classes.

Pull request description:
Add parens for default export FunctionExpressions
Fixes #344 and adds a test for FunctionDeclaration as well

Patch:
diff --git a/src/fast-path.js b/src/fast-path.js
index 9c2797b06..ab64afae1 100644
--- a/src/fast-path.js
+++ b/src/fast-path.js
@@ -362,6 +362,9 @@ FPp.needsParens = function(assumeExpressionContext) {
       }
 
       switch (parent.type) {
+        case "ExportDefaultDeclaration":
+          return node.type !== "ArrowFunctionExpression";
+
         case "ExpressionStatement":
         case "MemberExpression":
         case "TaggedTemplateExpression":
@@ -376,7 +379,13 @@ FPp.needsParens = function(assumeExpressionContext) {
       }
 
     case "ClassExpression":
-      return parent.type === "ExpressionStatement";
+      switch (parent.type) {
+        case "ExportDefaultDeclaration":
+        case "ExpressionStatement":
+          return true;
+      }
+
+      return false;
 
     case "ObjectExpression":
       if (parent.type === "ArrowFunctionExpression" && name === "body") {
diff --git a/src/printer.js b/src/printer.js
index 2468bbdba..e09a5beda 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -1828,8 +1828,7 @@ function printExportDeclaration(path, options, print) {
     if (
       decl.type === "ExportDefaultDeclaration" &&
         (decl.declaration.type !== "ClassDeclaration" &&
-          decl.declaration.type !== "FunctionDeclaration" &&
-          decl.declaration.type !== "FunctionExpression")
+          decl.declaration.type !== "FunctionDeclaration")
     ) {
       parts.push(";");
     }


Test patch:
diff --git a/tests/es6modules/__snapshots__/jsfmt.spec.js.snap b/tests/es6modules/__snapshots__/jsfmt.spec.js.snap
index 6d3b824a2..e68b9c779 100644
--- a/tests/es6modules/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/es6modules/__snapshots__/jsfmt.spec.js.snap
@@ -1362,14 +1362,52 @@ export default foo();
 "
 `;
 
-exports[`test export_default_function_expression.js 1`] = `
-"export default (function() {});
+exports[`test export_default_class_declaration.js 1`] = `
+"export default class Foo {}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export default class Foo {}
+"
+`;
+
+exports[`test export_default_class_expression.js 1`] = `
+"export default (class foobar {})
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export default (class foobar {});
+"
+`;
+
+exports[`test export_default_function_declaration.js 1`] = `
+"export default function() {}
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 export default function() {
 }
 "
 `;
 
+exports[`test export_default_function_declaration_named.js 1`] = `
+"export default function f(){}
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export default function f() {
+}
+"
+`;
+
+exports[`test export_default_function_expression.js 1`] = `
+"export default (function() {});
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export default (function() {
+});
+"
+`;
+
+exports[`test export_default_function_expression_named.js 1`] = `
+"export default (function f(){})
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+export default (function f() {
+});
+"
+`;
+
 exports[`test export_default_new_expression.js 1`] = `
 "export default new Foo();
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/es6modules/export_default_class_declaration.js b/tests/es6modules/export_default_class_declaration.js
new file mode 100644
index 000000000..780411100
--- /dev/null
+++ b/tests/es6modules/export_default_class_declaration.js
@@ -0,0 +1,1 @@
+export default class Foo {}
diff --git a/tests/es6modules/export_default_class_expression.js b/tests/es6modules/export_default_class_expression.js
new file mode 100644
index 000000000..aee3c8d33
--- /dev/null
+++ b/tests/es6modules/export_default_class_expression.js
@@ -0,0 +1,1 @@
+export default (class foobar {})
diff --git a/tests/es6modules/export_default_function_declaration.js b/tests/es6modules/export_default_function_declaration.js
new file mode 100644
index 000000000..6ab80bc8d
--- /dev/null
+++ b/tests/es6modules/export_default_function_declaration.js
@@ -0,0 +1,1 @@
+export default function() {}
diff --git a/tests/es6modules/export_default_function_declaration_named.js b/tests/es6modules/export_default_function_declaration_named.js
new file mode 100644
index 000000000..a5705610d
--- /dev/null
+++ b/tests/es6modules/export_default_function_declaration_named.js
@@ -0,0 +1,1 @@
+export default function f(){}
diff --git a/tests/es6modules/export_default_function_expression_named.js b/tests/es6modules/export_default_function_expression_named.js
new file mode 100644
index 000000000..bc5f708dc
--- /dev/null
+++ b/tests/es6modules/export_default_function_expression_named.js
@@ -0,0 +1,1 @@
+export default (function f(){})