Repository: prettier/prettier (JavaScript)
Date: 2017-02-05 01:53:51+00:00
Task type: Feature

Issue:
Print `x: () => void` instead of `x(): void` for flow types
```js
type T = { method: () => void };
```

currently outputs

```js
type T = { method(): void };
```

but in practice, most people are using the first version in their code. Since they have the same AST, prettier needs to make a call. I think it's better to use the first one all the time. This would also be consistent with types inside of function calls.

https://jlongster.github.io/prettier/#%7B%22content%22%3A%22type%20T%20%3D%20%7B%20method%3A%20()%20%3D%3E%20void%20%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%7D%7D


Issue discussion:
Also, if you want to make it optional, the first form just requires adding `?` whereas the second one first requires to convert it to the first one and then add `?`
```js
function isObjectTypePropertyAMethod(node) {
  return util.locStart(node.key) === util.locStart(node.value);
}
```

is a hack to figure out if a ObjectTypeProperty is a method until it is exposed by the parsers.

Solution patch:
diff --git a/src/printer.js b/src/printer.js
index 6eff3025c6c7..174c3beaef7a 100644
--- a/src/printer.js
+++ b/src/printer.js
@@ -1377,9 +1377,15 @@ function genericPrintNoParens(path, options, print) {
         namedTypes.ObjectTypeProperty.check(parent) ||
         namedTypes.ObjectTypeCallProperty.check(parent) ||
         namedTypes.DeclareFunction.check(path.getParentNode(2)));
+
       var needsColon = isArrowFunctionTypeAnnotation &&
         namedTypes.TypeAnnotation.check(parent);
 
+      if (isObjectTypePropertyAFunction(parent)) {
+        isArrowFunctionTypeAnnotation = true;
+        needsColon = true;
+      }
+
       if (needsColon) {
         parts.push(": ");
       }
@@ -1499,6 +1505,11 @@ function genericPrintNoParens(path, options, print) {
       var isFunction = !n.variance &&
         !n.optional &&
         n.value.type === "FunctionTypeAnnotation";
+
+      if (isObjectTypePropertyAFunction(n)) {
+        isFunction = true;
+      }
+
       return concat([
         n.static ? "static " : "",
         variance,
@@ -2673,6 +2684,16 @@ function isLastStatement(path) {
   return body && body[body.length - 1] === node;
 }
 
+// Hack to differentiate between the following two which have the same ast
+// type T = { method: () => void };
+// type T = { method(): void };
+function isObjectTypePropertyAFunction(node) {
+  return node.type === "ObjectTypeProperty" &&
+    node.value.type === "FunctionTypeAnnotation" &&
+    !node.static &&
+    util.locStart(node.key) !== util.locStart(node.value);
+}
+
 function shouldPrintSameLine(node) {
   const type = node.type;
   return namedTypes.Literal.check(node) ||


Test patch:
diff --git a/tests/flow/constructor_annots/__snapshots__/jsfmt.spec.js.snap b/tests/flow/constructor_annots/__snapshots__/jsfmt.spec.js.snap
index e3ff68427ba9..d1f66e222ec9 100644
--- a/tests/flow/constructor_annots/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/constructor_annots/__snapshots__/jsfmt.spec.js.snap
@@ -38,7 +38,7 @@ exports.Foo = Foo;
 
 // so you want to type Foo, by declaring it as a class
 interface IFooPrototype {
-  m(): number
+  m: () => number
 }
 interface IFoo extends IFooPrototype {
   static (): void,
diff --git a/tests/flow/object-method/__snapshots__/jsfmt.spec.js.snap b/tests/flow/object-method/__snapshots__/jsfmt.spec.js.snap
index ba57ea5ccead..13d76e2539e3 100644
--- a/tests/flow/object-method/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/object-method/__snapshots__/jsfmt.spec.js.snap
@@ -129,7 +129,7 @@ function bar(f: () => void) {
 
 bar(foo); // error, since \`this\` is used non-trivially in \`foo\`
 
-function qux(o: { f(): void }) {
+function qux(o: { f: () => void }) {
   o.f(); // passing o as \`this\`
 }
 
diff --git a/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap b/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
index 5d8d537ba462..e569f13dd7f3 100644
--- a/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/refinements/__snapshots__/jsfmt.spec.js.snap
@@ -43,7 +43,7 @@ function foo(x: ?number) {
 
 type Bar = {
   parent: ?Bar,
-  doStuff(): void
+  doStuff: () => void
 };
 
 function bar0(x: Bar) {
diff --git a/tests/flow/taint/__snapshots__/jsfmt.spec.js.snap b/tests/flow/taint/__snapshots__/jsfmt.spec.js.snap
index 856977b2f853..5a9b44db67ff 100644
--- a/tests/flow/taint/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/taint/__snapshots__/jsfmt.spec.js.snap
@@ -151,13 +151,13 @@ function foo(x: $Tainted<string>, o: Object) {
   // Error
   o.f(x);
 }
-function foo1(x: $Tainted<string>, o: { f(y: $Tainted<string>): void }) {
+function foo1(x: $Tainted<string>, o: { f: (y: $Tainted<string>) => void }) {
   o.f(x);
 }
 function foo2(o1: Object, o2: { t: $Tainted<string> }) {
   o1.f(o2.t);
 }
-function foo3<T>(x: $Tainted<T>, o: { f(y: $Tainted<T>): void }) {
+function foo3<T>(x: $Tainted<T>, o: { f: (y: $Tainted<T>) => void }) {
   o.f(x);
 }
 function f_foo1(x: $Tainted<string>, f: Function) {
diff --git a/tests/flow/type_only_vars/__snapshots__/jsfmt.spec.js.snap b/tests/flow/type_only_vars/__snapshots__/jsfmt.spec.js.snap
index 119c8288b33f..bedfebb27002 100644
--- a/tests/flow/type_only_vars/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/flow/type_only_vars/__snapshots__/jsfmt.spec.js.snap
@@ -57,7 +57,7 @@ import typeof A from \"./A.js\";
 import type { Foo, Bar as Baz } from \"./A.js\";
 
 type duck = {
-  quack(): string
+  quack: () => string
 };
 
 // These string types should confict with the imported types
diff --git a/tests/flow_method/__snapshots__/jsfmt.spec.js.snap b/tests/flow_method/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..59d94ddce260
--- /dev/null
+++ b/tests/flow_method/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,14 @@
+exports[`test method.js 1`] = `
+"type T = { method: () => void };
+type T = { method(): void };
+declare class X { method(): void }
+declare function f(): void;
+var f: () => void;
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+type T = { method: () => void };
+type T = { method(): void };
+declare class X { method(): void }
+declare function f(): void;
+var f: () => void;
+"
+`;
diff --git a/tests/flow_method/jsfmt.spec.js b/tests/flow_method/jsfmt.spec.js
new file mode 100644
index 000000000000..989047bccc52
--- /dev/null
+++ b/tests/flow_method/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname);
diff --git a/tests/flow_method/method.js b/tests/flow_method/method.js
new file mode 100644
index 000000000000..405f3fe2b774
--- /dev/null
+++ b/tests/flow_method/method.js
@@ -0,0 +1,5 @@
+type T = { method: () => void };
+type T = { method(): void };
+declare class X { method(): void }
+declare function f(): void;
+var f: () => void;