src/language-js/print/type-parameters.js/printTypeParameters
function printTypeParameters(path, options, print, paramsKey) {
  const node = path.getValue();

  if (!node[paramsKey]) {
    return "";
  }

  // for TypeParameterDeclaration typeParameters is a single node
  if (!Array.isArray(node[paramsKey])) {
    return print(paramsKey);
  }

  const grandparent = path.getNode(2);
  const isParameterInTestCall = grandparent && isTestCall(grandparent);

  const isArrowFunctionVariable = path.match(
    (node) =>
      !(node[paramsKey].length === 1 && isObjectType(node[paramsKey][0])),
    undefined,
    (node, name) => name === "typeAnnotation",
    (node) => node.type === "Identifier",
    isArrowFunctionVariableDeclarator
  );

  const shouldInline =
    !isArrowFunctionVariable &&
    (isParameterInTestCall ||
      node[paramsKey].length === 0 ||
      (node[paramsKey].length === 1 &&
        (node[paramsKey][0].type === "NullableTypeAnnotation" ||
          shouldHugType(node[paramsKey][0]))));

  if (shouldInline) {
    return [
      "<",
      join(", ", path.map(print, paramsKey)),
      printDanglingCommentsForInline(path, options),
      ">",
    ];
  }

  // 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 =
    node.type === "TSTypeParameterInstantiation" // https://github.com/microsoft/TypeScript/issues/21984
      ? ""
      : getFunctionParameters(node).length === 1 &&
        isTSXFile(options) &&
        !node[paramsKey][0].constraint &&
        path.getParentNode().type === "ArrowFunctionExpression"
      ? ","
      : shouldPrintComma(options, "all")
      ? ifBreak(",")
      : "";

  return group(
    [
      "<",
      indent([softline, join([",", line], path.map(print, paramsKey))]),
      trailingComma,
      softline,
      ">",
    ],
    { id: getTypeParametersGroupId(node) }
  );
}