src/language-js/printer-estree.js/genericPrint
function genericPrint(path, options, print, args) {
  const printed = printPathNoParens(path, options, print, args);
  if (!printed) {
    return "";
  }

  const node = path.getValue();
  const { type } = node;
  // Their decorators are handled themselves, and they can't have parentheses
  if (
    type === "ClassMethod" ||
    type === "ClassPrivateMethod" ||
    type === "ClassProperty" ||
    type === "ClassAccessorProperty" ||
    type === "PropertyDefinition" ||
    type === "TSAbstractPropertyDefinition" ||
    type === "ClassPrivateProperty" ||
    type === "MethodDefinition" ||
    type === "TSAbstractMethodDefinition" ||
    type === "TSDeclareMethod"
  ) {
    return printed;
  }

  let parts = [printed];

  const printedDecorators = printDecorators(path, options, print);
  const isClassExpressionWithDecorators =
    node.type === "ClassExpression" && printedDecorators;
  // Nodes (except `ClassExpression`) with decorators can't have parentheses and don't need leading semicolons
  if (printedDecorators) {
    parts = [...printedDecorators, printed];

    if (!isClassExpressionWithDecorators) {
      return group(parts);
    }
  }

  const needsParens = pathNeedsParens(path, options);

  if (!needsParens) {
    if (args && args.needsSemi) {
      parts.unshift(";");
    }

    // In member-chain print, it add `label` to the doc, if we return array here it will be broken
    if (parts.length === 1 && parts[0] === printed) {
      return printed;
    }

    return parts;
  }

  if (isClassExpressionWithDecorators) {
    parts = [indent([line, ...parts])];
  }

  parts.unshift("(");

  if (args && args.needsSemi) {
    parts.unshift(";");
  }

  if (hasFlowShorthandAnnotationComment(node)) {
    const [comment] = node.trailingComments;
    parts.push(" /*", comment.value.trimStart(), "*/");
    comment.printed = true;
  }

  if (isClassExpressionWithDecorators) {
    parts.push(line);
  }

  parts.push(")");

  return parts;
}