Repository: prettier/prettier (JavaScript)

Issue:
Improve React forwardRef with named function expression
**Prettier 2.4.1**
[Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEBLAtgBwgJxgAmHwDNcB3AQxwBMAlOY-AXxJwnXwB0Qc4KwY3ANycoGbHnwwAnpjiF8AGVRQA1gAU2mAM74KugCoALOOjgBVAJLK1miDuat2XEDBNmAtAFdUw0eNwCIhtVPUN3C2sVUJZiNg5uN1M4b18QEShROAAPCQIZOSVouwcAXnwAeXRUGAAeY2SrEJLtABoXfW58AB8XI15ibgA+DKzcwPxIKG0CEPxy0hxKGnpiWoAJAwBZRQBBKDAjXABRABtk2HbmrW0hgApRfBIvA5hUaCK1O8wb9oGASkIjye+F4MC8OCg+HqESa0VBDFKwAGLAAblRUBRYKVuNQGBQvKdBCBCAA6ck-ezaFgAehGwKYon+oygOTyk2gM0+qgA6jUjIpoABzAByFDM8xI5CodAYG22ewORxwZwuMCuxRu92BxBeAneUJC31+COIgOAwKeYIhUJhjSialNSJR+HROEx2Nx+MJxLJFJutPpUOYTJZbImUy5uxwbDIksWy1la02O32hxO5zMl25LXuxqpfwYgNKQyBwetkOhDTMcMdA2dDDRGKxMBxIDxxAJRK6wHJpMpOkDGSYzNEY3ZkYI0djfLcgqgovF8gW0pWcpTivTKszCHVOa1D2D+Z0hbN81LFuDVrg4MrdprDtC9eRjddzc97e93ZJvf9VKHDKhqIICtCA9hvJyyCgFQsbqFQCDaMgIAUKclDSIhoEAEY4Pwqg3gAykuNhwMgnanNocBYThYB4TA+GYPwKhCsgMA4F4lEgKYmFwNQeLUIoWJCl4FBCnAABiuDoBQMBvAuSEEjAEAgSARgwOgpw8kYNRwNoDFgHA+EITUqCojU0hIWA2gYWg0xwHgmgiVJpEoRRoEAFbaNk+FMecACKXgQPAznkRxDE4BROBIZhFDcacHgwNZPwqDAfLUG4yAABwAAygZSFE8jhmBIT8Ol2aiJGgQAjgF8AtPJ2geKyPE8cpvDVagvAOUKTlIGRrkgBR1QsWxHHaD5cD+YFJG9S5HEwDFqXpUgABMoGsRQqCnExADC7A9Zx2gAKzKV4FEGDFiEzSFoGouxlhQHisD4WA7qYDA+zUPhMjnMF-UedkABC1G0YRZjEb9cBMEwQA)

```sh
# Options (if any):
--parser babel-ts
```

**Input:**

```jsx
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
  function Link(props, ref) {
    return <ThemeUILink ref={ref} variant="default" {...props} />;
  }
);
```

**Output:**

```jsx
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
  props,
  ref
) {
  return <ThemeUILink ref={ref} variant="default" {...props} />;
});
```

**Expected behavior:**

The input is much prettier than the output, and more readily reveals what the code is doing. In the output, it's very easy to miss the named function expression ([which is important because of React DevTools display names](https://reactjs.org/docs/forwarding-refs.html#displaying-a-custom-name-in-devtools)).

Compare to how Prettier formats these alternatives, which are all much better:

```jsx
export const LinkWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
  function LinkWithLongName(props, ref) {
    return <ThemeUILink ref={ref} variant="default" {...props} />;
  }
);

export const Arrow = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
  return <ThemeUILink ref={ref} variant="default" {...props} />;
});

export const ArrowWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
  (props, ref) => {
    return <ThemeUILink ref={ref} variant="default" {...props} />;
  }
);
```

It also makes for some terrible diffs - renaming `Link` to `LinkWithLongName` actually _reduces_ total LOC:

```diff
-export const Link = forwardRef<HTMLAnchorElement, LinkProps>(function Link(
-  props,
-  ref
-) {
-  return <ThemeLink ref={ref} variant="default" {...props} as={GatsbyLink} />;
-});
+export const LinkWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
+  function LinkWithLongName(props, ref) {
+    return <ThemeLink ref={ref} variant="default" {...props} as={GatsbyLink} />;
+  }
+);
```


Hints:

Patch:
diff --git a/src/language-js/print/function.js b/src/language-js/print/function.js
--- a/src/language-js/print/function.js
+++ b/src/language-js/print/function.js
@@ -57,7 +57,13 @@ function printFunction(path, print, options, args) {
     args?.expandLastArg
   ) {
     const parent = path.getParentNode();
-    if (isCallExpression(parent) && getCallArguments(parent).length > 1) {
+    if (
+      isCallExpression(parent) &&
+      (getCallArguments(parent).length > 1 ||
+        getFunctionParameters(node).every(
+          (param) => param.type === "Identifier" && !param.typeAnnotation
+        ))
+    ) {
       expandArg = true;
     }
   }


Test patch:
diff --git a/tests/format/typescript/last-argument-expansion/__snapshots__/jsfmt.spec.js.snap b/tests/format/typescript/last-argument-expansion/__snapshots__/jsfmt.spec.js.snap
--- a/tests/format/typescript/last-argument-expansion/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/format/typescript/last-argument-expansion/__snapshots__/jsfmt.spec.js.snap
@@ -197,3 +197,69 @@ var listener = DOM.listen(
 
 ================================================================================
 `;
+
+exports[`forward-ref.ts format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+                                                                                | printWidth
+=====================================input======================================
+export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  }
+);
+
+export const LinkWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  }
+);
+
+export const Arrow = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
+  return <ThemeUILink ref={ref} variant="default" {...props} />;
+});
+
+export const ArrowWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
+  (props, ref) => {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  }
+);
+
+const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  },
+);
+
+=====================================output=====================================
+export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  },
+);
+
+export const LinkWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  },
+);
+
+export const Arrow = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
+  return <ThemeUILink ref={ref} variant="default" {...props} />;
+});
+
+export const ArrowWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
+  (props, ref) => {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  },
+);
+
+const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  },
+);
+
+================================================================================
+`;
diff --git a/tests/format/typescript/last-argument-expansion/forward-ref.ts b/tests/format/typescript/last-argument-expansion/forward-ref.ts
new file mode 100644
--- /dev/null
+++ b/tests/format/typescript/last-argument-expansion/forward-ref.ts
@@ -0,0 +1,27 @@
+export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  }
+);
+
+export const LinkWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  }
+);
+
+export const Arrow = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
+  return <ThemeUILink ref={ref} variant="default" {...props} />;
+});
+
+export const ArrowWithLongName = forwardRef<HTMLAnchorElement, LinkProps>(
+  (props, ref) => {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  }
+);
+
+const Link = React.forwardRef<HTMLAnchorElement, LinkProps>(
+  function Link(props, ref) {
+    return <ThemeUILink ref={ref} variant="default" {...props} />;
+  },
+);