Repository: prettier/prettier (JavaScript) — Bug Fix

Issue:
Bug with type annotation, keep removing and adding parentheses
Following https://github.com/prettier/prettier/issues/5247
I think, introduced in https://github.com/prettier/prettier/commit/30979ed10471731f293da3f254f2267dd760c060

**Prettier 1.16.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuc0DOMAEAzCFMC8mAFAJRIlmEB8mAbhAJYAmpNJ59Tz7UArgBsBAbgA6UcZCgZMAIwCGAJ0IcKVArQYt2ZClp4bM-IcJAAaEBAAOMRumSgliiAHcACkoRpkIefvMgsoryYADWcDAAylYhjFAA5sgwinxwFgAWMAC2AgDq6YzwaDFgcJFehYx0hQCePmBo3hZxaHCKMG7B8VnyyNjyAq0WAFZoAB4AQsFhEZHyWXAAMnFwfQNDIKNjkXHxAnAAinwQ8GuDaSAxiq2KPjA1VnBoYIqMNgFWr7C5LDDpyAAOAAMFk+EFauWCVh8nyebToqwsijgAEc+IxkZ15N1ekh+ucLK0sowkikLmhdvsjidVnj1hcYPJZD9mH9kAAmCzJeSMAS7ADCECyPR8UGgiJAfFaABUmd46ecAL6KoA)
```sh
--parser typescript
```

**Input:**
```jsx
const foo = (): (() => void) => (): void => null;
const bar = (): () => void => (): void => null;
```

**Output:**
```jsx
const foo = (): () => void => (): void => null;
const bar = (): (() => void) => (): void => null;
```

**Expected behavior:**
A choice between `(): () => void => (): void => null;` and `(): (() => void) => (): void => null;`.
I think the second one is better.


Discussion:
That is a regression in the sense that 1.15 had a clear preference for `(): () => void`, which is better. Unless there is an ambiguity in the type expression you should not need parens, and there is never an ambiguity with a function type unless you need to `|` or `&` the function type itself with another type.

To be honest, your type is confusing and redundant, and took me a while to understand what was going on 😵

```ts
type OuterReturnType = () => void
type InnerReturnType = void
const foo = (): OuterReturnType => (): InnerReturnType => null;
```

Resolving patch:
diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index 38d0f0857371..5e3e12ffd71c 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -121,8 +121,34 @@ Examples:
   _foo <InlineJSX /> bar_
   ```
 
+- TypeScript: Stable parentheses for function type in the return type of arrow function ([#5790] by [@ikatyang])
+
+  There's a regression introduced in 1.16 that
+  parentheses for function type in the return type of arrow function were kept adding/removing.
+  Their parentheses are always printed now.
+
+  <!-- prettier-ignore -->
+  ```ts
+  // Input
+  const foo = (): (() => void) => (): void => null;
+  const bar = (): () => void => (): void => null;
+
+  // First Output (Prettier stable)
+  const foo = (): () => void => (): void => null;
+  const bar = (): (() => void) => (): void => null;
+
+  // Second Output (Prettier stable)
+  const foo = (): (() => void) => (): void => null;
+  const bar = (): () => void => (): void => null;
+
+  // Output (Prettier master)
+  const foo = (): (() => void) => (): void => null;
+  const bar = (): (() => void) => (): void => null;
+  ```
+
 [@ikatyang]: https://github.com/ikatyang
 [@simenb]: https://github.com/SimenB
 [#5778]: https://github.com/prettier/prettier/pull/5778
 [#5783]: https://github.com/prettier/prettier/pull/5783
 [#5785]: https://github.com/prettier/prettier/pull/5785
+[#5790]: https://github.com/prettier/prettier/pull/5790
diff --git a/src/language-js/needs-parens.js b/src/language-js/needs-parens.js
index 4a6a50977660..47714279db73 100644
--- a/src/language-js/needs-parens.js
+++ b/src/language-js/needs-parens.js
@@ -353,6 +353,20 @@ function needsParens(path, options) {
 
     case "TSParenthesizedType": {
       const grandParent = path.getParentNode(1);
+
+      /**
+       * const foo = (): (() => void) => (): void => null;
+       *                 ^          ^
+       */
+      if (
+        getUnparenthesizedNode(node).type === "TSFunctionType" &&
+        parent.type === "TSTypeAnnotation" &&
+        grandParent.type === "ArrowFunctionExpression" &&
+        grandParent.returnType === parent
+      ) {
+        return true;
+      }
+
       if (
         (parent.type === "TSTypeParameter" ||
           parent.type === "TypeParameter" ||
@@ -719,6 +733,12 @@ function isStatement(node) {
   );
 }
 
+function getUnparenthesizedNode(node) {
+  return node.type === "TSParenthesizedType"
+    ? getUnparenthesizedNode(node.typeAnnotation)
+    : node;
+}
+
 function endsWithRightBracket(node) {
   switch (node.type) {
     case "ObjectExpression":


Test patch:
diff --git a/tests/typescript_function_type/__snapshots__/jsfmt.spec.js.snap b/tests/typescript_function_type/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..f03a65c296cc
--- /dev/null
+++ b/tests/typescript_function_type/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,19 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`type-annotation.ts 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+const foo = (): () => void => (): void => null;
+const bar = (): (() => void) => (): void => null;
+const baz = (): ((() => void)) => (): void => null;
+
+=====================================output=====================================
+const foo = (): (() => void) => (): void => null;
+const bar = (): (() => void) => (): void => null;
+const baz = (): (() => void) => (): void => null;
+
+================================================================================
+`;
diff --git a/tests/typescript_function_type/jsfmt.spec.js b/tests/typescript_function_type/jsfmt.spec.js
new file mode 100644
index 000000000000..2ea3bb6eb2e4
--- /dev/null
+++ b/tests/typescript_function_type/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname, ["typescript"]);
diff --git a/tests/typescript_function_type/type-annotation.ts b/tests/typescript_function_type/type-annotation.ts
new file mode 100644
index 000000000000..5ea614ce5d61
--- /dev/null
+++ b/tests/typescript_function_type/type-annotation.ts
@@ -0,0 +1,3 @@
+const foo = (): () => void => (): void => null;
+const bar = (): (() => void) => (): void => null;
+const baz = (): ((() => void)) => (): void => null;