Repository: prettier/prettier (JavaScript)
Date: 2021-05-19 20:55:28+00:00
Task type: Bug Fix

Issue:
Ugly formatting for complex Map types in Flow and TS
**Prettier 2.3.0**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuc0DOMAEBbAhgByUwFkCAeAHSk0wDEBXKMGAS2gBoqbT8yMAnFlADmmAD6YAbhBYATdpmBTcAG3pwiAVSgqIuWXFkAROGjCD8MCP0wBfAHxV7mALyYocAO4kCACgCUANxUIOwgEJZsUGjIoLj8-BCeAArxCDEoqp64AJ4xYQBG-LhgANZwMADK+CVCwsgw-OphcNgFhgayADK4IvS4wnC01ngwrCLIILj0VqEgABYw2CoA6vMs8Gg1YHCV6Rsskhs5k2Bo+SBCaHD8MMnFwnjIAGaq12EAVmgAHgBCxWUKpVcNg4F0hHAXm84J8fpU6io4ABFegQeBQlTvEA1fjXfiTAq4doqOb4QSwFZyGDzZAADgADGEyRBritivhJmTTDdJJCwgBHVHwe4RDJTNAAWg8hkMc34cEFLHl9wGTyQr0xMJA12wLAaTS1aARyKFkPV0LCMCJlNk1OQACZLcUWCo6gBhCDYNUgUwAVjm9GuABUiRkNVjJOoAJJQAywSrmFiWACCscqMByiIx11stiAA)
<!-- prettier-ignore -->
```sh
--parser babel
```

**Input:**
<!-- prettier-ignore -->
```jsx
const map: Map<
  Function,
  Map<string | void, { value: UnloadedDescriptor }>
> = new Map();

```

**Output:**
<!-- prettier-ignore -->
```jsx
const map: Map<Function, Map<string | void, { value: UnloadedDescriptor }>> =
  new Map();

```

**Expected behavior:**
Same as input
```jsx
const map: Map<
  Function,
  Map<string | void, { value: UnloadedDescriptor }>
> = new Map();

```

Since 2.3.0. Feedback from https://github.com/babel/babel/pull/13288/files#r629071597


Issue discussion:

Solution patch:
diff --git a/changelog_unreleased/typescript/10916.md b/changelog_unreleased/typescript/10916.md
new file mode 100644
index 000000000000..68f41577b51f
--- /dev/null
+++ b/changelog_unreleased/typescript/10916.md
@@ -0,0 +1,21 @@
+#### Break the LHS of assignments that has complex type parameters (#10916 by @sosukesuzuki)
+
+<!-- prettier-ignore -->
+```ts
+// Input
+const map: Map<
+  Function,
+  Map<string | void, { value: UnloadedDescriptor }>
+> = new Map();
+
+// Prettier stable
+const map: Map<Function, Map<string | void, { value: UnloadedDescriptor }>> =
+  new Map();
+
+// Prettier main
+const map: Map<
+  Function,
+  Map<string | void, { value: UnloadedDescriptor }>
+> = new Map();
+
+```
diff --git a/src/language-js/print/assignment.js b/src/language-js/print/assignment.js
index e9f4d910c224..95828da79994 100644
--- a/src/language-js/print/assignment.js
+++ b/src/language-js/print/assignment.js
@@ -136,7 +136,11 @@ function chooseLayout(path, options, print, leftDoc, rightPropertyName) {
     return "never-break-after-operator";
   }
 
-  if (isComplexDestructuring(node) || isComplexTypeAliasParams(node)) {
+  if (
+    isComplexDestructuring(node) ||
+    isComplexTypeAliasParams(node) ||
+    hasComplexTypeAnnotation(node)
+  ) {
     return "break-lhs";
   }
 
@@ -269,6 +273,45 @@ function isTypeAlias(node) {
   return node.type === "TSTypeAliasDeclaration" || node.type === "TypeAlias";
 }
 
+function hasComplexTypeAnnotation(node) {
+  if (node.type !== "VariableDeclarator") {
+    return false;
+  }
+  const { typeAnnotation } = node.id;
+  if (!typeAnnotation || !typeAnnotation.typeAnnotation) {
+    return false;
+  }
+  const typeParams = getTypeParametersFromTypeReference(
+    typeAnnotation.typeAnnotation
+  );
+  return (
+    isNonEmptyArray(typeParams) &&
+    typeParams.length > 1 &&
+    typeParams.some(
+      (param) =>
+        isNonEmptyArray(getTypeParametersFromTypeReference(param)) ||
+        param.type === "TSConditionalType"
+    )
+  );
+}
+
+function getTypeParametersFromTypeReference(node) {
+  if (
+    isTypeReference(node) &&
+    node.typeParameters &&
+    node.typeParameters.params
+  ) {
+    return node.typeParameters.params;
+  }
+  return null;
+}
+
+function isTypeReference(node) {
+  return (
+    node.type === "TSTypeReference" || node.type === "GenericTypeAnnotation"
+  );
+}
+
 /**
  * A chain with no calls at all or whose calls are all without arguments or with lone short arguments,
  * excluding chains printed by `printMemberChain`


Test patch:
diff --git a/tests/format/flow/assignments/__snapshots__/jsfmt.spec.js.snap b/tests/format/flow/assignments/__snapshots__/jsfmt.spec.js.snap
new file mode 100644
index 000000000000..9d921c9cb686
--- /dev/null
+++ b/tests/format/flow/assignments/__snapshots__/jsfmt.spec.js.snap
@@ -0,0 +1,29 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`issue-10850.js format 1`] = `
+====================================options=====================================
+parsers: ["babel-flow", "flow"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+const map: Map<Function, Map<string | void, { value: UnloadedDescriptor }>> =
+  new Map();
+
+const map: Map<Function, FunctionFunctionFunctionFunctionffFunction> =
+  new Map();
+
+const map: Map<Function, Foo<S>> = new Map();
+
+=====================================output=====================================
+const map: Map<
+  Function,
+  Map<string | void, { value: UnloadedDescriptor }>
+> = new Map();
+
+const map: Map<Function, FunctionFunctionFunctionFunctionffFunction> =
+  new Map();
+
+const map: Map<Function, Foo<S>> = new Map();
+
+================================================================================
+`;
diff --git a/tests/format/flow/assignments/issue-10850.js b/tests/format/flow/assignments/issue-10850.js
new file mode 100644
index 000000000000..c9408527e060
--- /dev/null
+++ b/tests/format/flow/assignments/issue-10850.js
@@ -0,0 +1,7 @@
+const map: Map<Function, Map<string | void, { value: UnloadedDescriptor }>> =
+  new Map();
+
+const map: Map<Function, FunctionFunctionFunctionFunctionffFunction> =
+  new Map();
+
+const map: Map<Function, Foo<S>> = new Map();
diff --git a/tests/format/flow/assignments/jsfmt.spec.js b/tests/format/flow/assignments/jsfmt.spec.js
new file mode 100644
index 000000000000..6da232e653c1
--- /dev/null
+++ b/tests/format/flow/assignments/jsfmt.spec.js
@@ -0,0 +1,1 @@
+run_spec(__dirname, ["babel-flow", "flow"]);
diff --git a/tests/format/typescript/assignment/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/assignment/__snapshots__/jsfmt.spec.js.snap
index 84ee6556f1c6..5f5eb0b6dc70 100644
--- a/tests/format/typescript/assignment/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/assignment/__snapshots__/jsfmt.spec.js.snap
@@ -180,6 +180,42 @@ const firestorePersonallyIdentifiablePaths: Array<Collections.Users.Entity> =
 ================================================================================
 `;
 
+exports[`issue-10850.ts format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+const map: Map<Function, Map<string | void, { value: UnloadedDescriptor }>> =
+  new Map();
+
+const map: Map<Function, Condition extends Foo ? FooFooFoo : BarBarBar> =
+  new Map();
+
+const map: Map<Function, FunctionFunctionFunctionFunctionffFunction> =
+  new Map();
+
+const map: Map<Function, Foo<S>> = new Map();
+
+=====================================output=====================================
+const map: Map<
+  Function,
+  Map<string | void, { value: UnloadedDescriptor }>
+> = new Map();
+
+const map: Map<
+  Function,
+  Condition extends Foo ? FooFooFoo : BarBarBar
+> = new Map();
+
+const map: Map<Function, FunctionFunctionFunctionFunctionffFunction> =
+  new Map();
+
+const map: Map<Function, Foo<S>> = new Map();
+
+================================================================================
+`;
+
 exports[`lone-arg.ts format 1`] = `
 ====================================options=====================================
 parsers: ["typescript"]
diff --git a/tests/format/typescript/assignment/issue-10850.ts b/tests/format/typescript/assignment/issue-10850.ts
new file mode 100644
index 000000000000..4064c5b19411
--- /dev/null
+++ b/tests/format/typescript/assignment/issue-10850.ts
@@ -0,0 +1,10 @@
+const map: Map<Function, Map<string | void, { value: UnloadedDescriptor }>> =
+  new Map();
+
+const map: Map<Function, Condition extends Foo ? FooFooFoo : BarBarBar> =
+  new Map();
+
+const map: Map<Function, FunctionFunctionFunctionFunctionffFunction> =
+  new Map();
+
+const map: Map<Function, Foo<S>> = new Map();