Repository: prettier/prettier (JavaScript)
Date: 2021-01-23 04:38:24+00:00
Task type: Feature

Issue:
Trailing commas for type parameters in TypeScript
**Prettier 1.9.2**

~~I would paste a playground link but no TypeScript support ;)~~ I'm silly: [Playground link](https://prettier.io/playground/#N4Igxg9gdgLgprEAuEMCeAHOACAKnAWwwBsBDeAIQEsoATGgc2wF4AdKbT7AH2wEEMJOPiJlKNelAbsuPbBVJRFpESXJxqdRjK68AwqQBOhiDFVi4AOQi047ANzt26LNgCyNuMWu2WOzrwCQh623p7+cgpKSiFePnYcutgGxqaxYbYOTlBwAB4YEIYw2JBQAM7FYIZw6gDKEACuhmBWngBiJgTm6pqSTMzYADwRuN3iWlLYefB0ZXiEauN9LPOiPRKMADQj9U0t8VO5M7Rzu82tvgNn++FQAHwAFPBrS4xIeGMaG1IAlCx32GAEQA9AAqbAAOih2FBwPYAF97CBNiAIBgYFRoGVkKAjCYAO4ABSMCGxKFIADcIFRaMiQKQKsgAGakYhlOAogBGhlIYAA1nAYLUMLzGMgYIYGhyQLQIGBmaz2SiaOyioSeQwCKQFWzpQArMq5Cg8-mC2qkAhwAAyNDgOqVqIaMAwToATPbpSLDKrkKhMHAylUqOi6RhDDQYAB1GkwAAWyAAHAAGFFhiDsyM8jC+sMBuCGCl2lHVACODSo1XVpE12qQLN1KPZBCo4sl0rKjGIcAAig1THa64rpTBSJzo7Q48hXSiJaQqMRGHoIAQtb6oNAiyAGuzcKOyfX2fD4UA)
```sh
--parser typescript
```

**Input:**
```typescript
type TemplateBinding =
    | AppleTemplateBinding
    | BananaTemplateBinding
    | CarrotTemplateNode
;

type ModelNode =
    | AppleModelNode
    | BananaModelNode
    | CarrotModelNode
;

export const createSourceNodeFromTemplateBinding = <
    TTemplateBinding extends TemplateBinding = TemplateBinding,
    TSourceNode extends SourceNode = SourceNode
>(templateBinding: TTemplateBinding) => {
    /* ... */
};
```

**Expected behavior:**

There should be a trailing comma after `= SourceNode`.

**Actual behavior:**

As of https://github.com/prettier/prettier/commit/fa708d102a6a4993d1bc548732ba1a6cda48e095, it will not.

TypeScript 2.7 will be the first version to allow it (https://github.com/Microsoft/TypeScript/issues/16152). Should prettier add them back if TS is > 2.7?

  


Issue discussion:
Prettier rolls in it's own copy of the TypeScript parser, and we don't detect the version of TypeScript at all. Does trailing commas in the case even add any value? It's not like you ever have a large list of type parameters where diffing is a problem?
It's not a problem, just a convenience. I would prefer having them for the same reason I'd like to have them in arrays and object literals (though admittedly not with the same level of convenience).

Perhaps this can be revisited if & when the minimum TypeScript version is 2.7...
  
Edit: Ooh or: `--parser typescript@2.7`? Would require changing all those `options.parser === "typescript"` lines to some form of `parserIsLanguage(options.parser, "typescript")`.
  
I don't think such a small thing warrants the maintenance overhead of such a flag. 
We could only add it when you set `--trailing-comma all`, which assumes you’re using recent versions of stuff.
Related: https://github.com/prettier/prettier/pull/3313#issuecomment-346620500
This issue causes a failure to compile `.tsx` (React) files after prettier formatting for this case:
```typescript
const f = <T, >() => 1
```
The trailing comma is needed especially for `.tsx` files for the compiler to recognize that this is a type and not a `jsx` tag
@sharno Prettier already seems to handle the trailing comma in that case:

```
❯ prettier --version
1.18.2

❯ cat test.tsx
const f = <T, >() => 1⏎                                                                                  

❯ prettier test.tsx
const f = <T,>() => 1;
```
@lydell I see, it seems to be a problem in https://github.com/prettier/prettier-vscode


Solution patch:
diff --git a/changelog_unreleased/typescript/10109.md b/changelog_unreleased/typescript/10109.md
new file mode 100644
index 000000000000..1a54cb3b3ee0
--- /dev/null
+++ b/changelog_unreleased/typescript/10109.md
@@ -0,0 +1,43 @@
+#### Print trailing commas for TS type parameters (#10109 by @sosukesuzuki)
+
+Initially, Prettier printed trailing commas in TypeScript type parameters with `--trailing-comma=all`, but TypeScript did not support it at the time, so the feature was removed. However, it is supported now (since TypeScript 2.7 released in January 2018).
+
+<!-- prettier-ignore -->
+```ts
+// Input
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH
+>;
+
+// Prettier stable
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH
+>;
+
+// Prettier main
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH,
+>;
+
+```
diff --git a/src/language-js/print/type-parameters.js b/src/language-js/print/type-parameters.js
index ca1ecb06c6ae..a747967fd083 100644
--- a/src/language-js/print/type-parameters.js
+++ b/src/language-js/print/type-parameters.js
@@ -52,17 +52,24 @@ function printTypeParameters(path, options, print, paramsKey) {
     ];
   }
 
+  // Keep comma if the file extension is .tsx and
+  // has one type parameter that isn't extend with any types.
+  // Because, otherwise formatted result will be invalid as tsx.
+  const trailingComma =
+    getFunctionParameters(n).length === 1 &&
+    isTSXFile(options) &&
+    !n[paramsKey][0].constraint &&
+    path.getParentNode().type === "ArrowFunctionExpression"
+      ? ","
+      : shouldPrintComma(options, "all")
+      ? ifBreak(",")
+      : "";
+
   return group(
     [
       "<",
       indent([softline, join([",", line], path.map(print, paramsKey))]),
-      ifBreak(
-        options.parser !== "typescript" &&
-          options.parser !== "babel-ts" &&
-          shouldPrintComma(options, "all")
-          ? ","
-          : ""
-      ),
+      trailingComma,
       softline,
       ">",
     ],
@@ -124,19 +131,6 @@ function printTypeParameter(path, options, print) {
     parts.push(" = ", path.call(print, "default"));
   }
 
-  // Keep comma if the file extension is .tsx and
-  // has one type parameter that isn't extend with any types.
-  // Because, otherwise formatted result will be invalid as tsx.
-  const grandParent = path.getNode(2);
-  if (
-    getFunctionParameters(parent).length === 1 &&
-    isTSXFile(options) &&
-    !n.constraint &&
-    grandParent.type === "ArrowFunctionExpression"
-  ) {
-    parts.push(",");
-  }
-
   return parts;
 }
 


Test patch:
diff --git a/tests/typescript/trailing-comma/__snapshots__/jsfmt.spec.js.snap b/tests/typescript/trailing-comma/__snapshots__/jsfmt.spec.js.snap
index 2728bc25f3df..3fe0b32a0ee9 100644
--- a/tests/typescript/trailing-comma/__snapshots__/jsfmt.spec.js.snap
+++ b/tests/typescript/trailing-comma/__snapshots__/jsfmt.spec.js.snap
@@ -1,5 +1,68 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`arrow-functions.tsx - {"trailingComma":"all"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "all"
+                                                                                | printWidth
+=====================================input======================================
+const f1 = <T,>() => 1;
+const f2 = <
+  Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt,
+>() => 1;
+
+=====================================output=====================================
+const f1 = <T,>() => 1;
+const f2 = <
+  Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt,
+>() => 1;
+
+================================================================================
+`;
+
+exports[`arrow-functions.tsx - {"trailingComma":"es5"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "es5"
+                                                                                | printWidth
+=====================================input======================================
+const f1 = <T,>() => 1;
+const f2 = <
+  Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt,
+>() => 1;
+
+=====================================output=====================================
+const f1 = <T,>() => 1;
+const f2 = <
+  Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt,
+>() => 1;
+
+================================================================================
+`;
+
+exports[`arrow-functions.tsx - {"trailingComma":"none"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "none"
+                                                                                | printWidth
+=====================================input======================================
+const f1 = <T,>() => 1;
+const f2 = <
+  Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt,
+>() => 1;
+
+=====================================output=====================================
+const f1 = <T,>() => 1;
+const f2 = <
+  Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt,
+>() => 1;
+
+================================================================================
+`;
+
 exports[`trailing.ts - {"trailingComma":"all"} format 1`] = `
 ====================================options=====================================
 parsers: ["typescript"]
@@ -24,6 +87,50 @@ const {
   ...rest,
 } = something;
 
+=====================================output=====================================
+export class BaseSingleLevelProfileTargeting<
+  T extends ValidSingleLevelProfileNode,
+> {}
+
+enum Enum {
+  x = 1,
+  y = 2,
+}
+
+const {
+  longKeySoThisWillGoOnMultipleLines,
+  longKeySoThisWillGoOnMultipleLines2,
+  longKeySoThisWillGoOnMultipleLines3,
+  ...rest
+} = something;
+
+================================================================================
+`;
+
+exports[`trailing.ts - {"trailingComma":"es5"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "es5"
+                                                                                | printWidth
+=====================================input======================================
+export class BaseSingleLevelProfileTargeting<
+	T extends ValidSingleLevelProfileNode,
+> {
+}
+
+enum Enum {
+	x = 1,
+	y = 2,
+}
+
+const {
+  longKeySoThisWillGoOnMultipleLines,
+  longKeySoThisWillGoOnMultipleLines2,
+  longKeySoThisWillGoOnMultipleLines3,
+  ...rest,
+} = something;
+
 =====================================output=====================================
 export class BaseSingleLevelProfileTargeting<
   T extends ValidSingleLevelProfileNode
@@ -43,3 +150,146 @@ const {
 
 ================================================================================
 `;
+
+exports[`trailing.ts - {"trailingComma":"none"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "none"
+                                                                                | printWidth
+=====================================input======================================
+export class BaseSingleLevelProfileTargeting<
+	T extends ValidSingleLevelProfileNode,
+> {
+}
+
+enum Enum {
+	x = 1,
+	y = 2,
+}
+
+const {
+  longKeySoThisWillGoOnMultipleLines,
+  longKeySoThisWillGoOnMultipleLines2,
+  longKeySoThisWillGoOnMultipleLines3,
+  ...rest,
+} = something;
+
+=====================================output=====================================
+export class BaseSingleLevelProfileTargeting<
+  T extends ValidSingleLevelProfileNode
+> {}
+
+enum Enum {
+  x = 1,
+  y = 2
+}
+
+const {
+  longKeySoThisWillGoOnMultipleLines,
+  longKeySoThisWillGoOnMultipleLines2,
+  longKeySoThisWillGoOnMultipleLines3,
+  ...rest
+} = something;
+
+================================================================================
+`;
+
+exports[`type-parameters.ts - {"trailingComma":"all"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "all"
+                                                                                | printWidth
+=====================================input======================================
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH
+>;
+
+=====================================output=====================================
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH,
+>;
+
+================================================================================
+`;
+
+exports[`type-parameters.ts - {"trailingComma":"es5"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "es5"
+                                                                                | printWidth
+=====================================input======================================
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH
+>;
+
+=====================================output=====================================
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH
+>;
+
+================================================================================
+`;
+
+exports[`type-parameters.ts - {"trailingComma":"none"} format 1`] = `
+====================================options=====================================
+parsers: ["typescript"]
+printWidth: 80
+trailingComma: "none"
+                                                                                | printWidth
+=====================================input======================================
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH
+>;
+
+=====================================output=====================================
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH
+>;
+
+================================================================================
+`;
diff --git a/tests/typescript/trailing-comma/arrow-functions.tsx b/tests/typescript/trailing-comma/arrow-functions.tsx
new file mode 100644
index 000000000000..a1555939a25a
--- /dev/null
+++ b/tests/typescript/trailing-comma/arrow-functions.tsx
@@ -0,0 +1,4 @@
+const f1 = <T,>() => 1;
+const f2 = <
+  Tttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt,
+>() => 1;
diff --git a/tests/typescript/trailing-comma/jsfmt.spec.js b/tests/typescript/trailing-comma/jsfmt.spec.js
index 8f1fcce6ba7f..87950996e597 100644
--- a/tests/typescript/trailing-comma/jsfmt.spec.js
+++ b/tests/typescript/trailing-comma/jsfmt.spec.js
@@ -1,1 +1,3 @@
 run_spec(__dirname, ["typescript"], { trailingComma: "all" });
+run_spec(__dirname, ["typescript"], { trailingComma: "es5" });
+run_spec(__dirname, ["typescript"], { trailingComma: "none" });
diff --git a/tests/typescript/trailing-comma/type-parameters.ts b/tests/typescript/trailing-comma/type-parameters.ts
new file mode 100644
index 000000000000..058c607a180a
--- /dev/null
+++ b/tests/typescript/trailing-comma/type-parameters.ts
@@ -0,0 +1,10 @@
+var bar: Bar<
+  AAAAAAA,
+  BBBBBBB,
+  CCCCCCC,
+  DDDDDDD,
+  EEEEEEE,
+  FFFFFFF,
+  GGGGGGG,
+  HHHHHHH
+>;