src/language-html/printer-html.js/genericPrint
function genericPrint(path, options, print) {
  const node = path.getValue();

  switch (node.type) {
    case "front-matter":
      return concat(replaceEndOfLineWith(node.raw, literalline));
    case "root":
      if (options.__onHtmlRoot) {
        options.__onHtmlRoot(node);
      }
      // use original concat to not break stripTrailingHardline
      return builders.concat([
        group(printChildren(path, options, print)),
        hardline,
      ]);
    case "element":
    case "ieConditionalComment": {
      if (shouldPreserveContent(node, options)) {
        return concat(
          [].concat(
            printOpeningTagPrefix(node, options),
            group(printOpeningTag(path, options, print)),
            replaceEndOfLineWith(getNodeContent(node, options), literalline),
            printClosingTag(node, options),
            printClosingTagSuffix(node, options)
          )
        );
      }
      /**
       * do not break:
       *
       *     <div>{{
       *         ~
       *       interpolation
       *     }}</div>
       *            ~
       *
       * exception: break if the opening tag breaks
       *
       *     <div
       *       long
       *           ~
       *       >{{
       *         interpolation
       *       }}</div
       *              ~
       *     >
       */
      const shouldHugContent =
        node.children.length === 1 &&
        node.firstChild.type === "interpolation" &&
        node.firstChild.isLeadingSpaceSensitive &&
        !node.firstChild.hasLeadingSpaces &&
        node.lastChild.isTrailingSpaceSensitive &&
        !node.lastChild.hasTrailingSpaces;
      const attrGroupId = Symbol("element-attr-group-id");
      return concat([
        group(
          concat([
            group(printOpeningTag(path, options, print), { id: attrGroupId }),
            node.children.length === 0
              ? node.hasDanglingSpaces && node.isDanglingSpaceSensitive
                ? line
                : ""
              : concat([
                  forceBreakContent(node) ? breakParent : "",
                  ((childrenDoc) =>
                    shouldHugContent
                      ? ifBreak(indent(childrenDoc), childrenDoc, {
                          groupId: attrGroupId,
                        })
                      : (isScriptLikeTag(node) ||
                          isVueCustomBlock(node, options)) &&
                        node.parent.type === "root" &&
                        options.parser === "vue" &&
                        !options.vueIndentScriptAndStyle
                      ? childrenDoc
                      : indent(childrenDoc))(
                    concat([
                      shouldHugContent
                        ? ifBreak(softline, "", { groupId: attrGroupId })
                        : node.firstChild.hasLeadingSpaces &&
                          node.firstChild.isLeadingSpaceSensitive
                        ? line
                        : node.firstChild.type === "text" &&
                          node.isWhitespaceSensitive &&
                          node.isIndentationSensitive
                        ? dedentToRoot(softline)
                        : softline,
                      printChildren(path, options, print),
                    ])
                  ),
                  (
                    node.next
                      ? needsToBorrowPrevClosingTagEndMarker(node.next)
                      : needsToBorrowLastChildClosingTagEndMarker(node.parent)
                  )
                    ? node.lastChild.hasTrailingSpaces &&
                      node.lastChild.isTrailingSpaceSensitive
                      ? " "
                      : ""
                    : shouldHugContent
                    ? ifBreak(softline, "", { groupId: attrGroupId })
                    : node.lastChild.hasTrailingSpaces &&
                      node.lastChild.isTrailingSpaceSensitive
                    ? line
                    : (node.lastChild.type === "comment" ||
                        (node.lastChild.type === "text" &&
                          node.isWhitespaceSensitive &&
                          node.isIndentationSensitive)) &&
                      new RegExp(
                        `\\n[\\t ]{${
                          options.tabWidth *
                          countParents(
                            path,
                            (n) => n.parent && n.parent.type !== "root"
                          )
                        }}$`
                      ).test(node.lastChild.value)
                    ? /**
                       *     <div>
                       *       <pre>
                       *         something
                       *       </pre>
                       *            ~
                       *     </div>
                       */
                      ""
                    : softline,
                ]),
          ])
        ),
        printClosingTag(node, options),
      ]);
    }
    case "ieConditionalStartComment":
    case "ieConditionalEndComment":
      return concat([printOpeningTagStart(node), printClosingTagEnd(node)]);
    case "interpolation":
      return concat([
        printOpeningTagStart(node, options),
        concat(path.map(print, "children")),
        printClosingTagEnd(node, options),
      ]);
    case "text": {
      if (node.parent.type === "interpolation") {
        // replace the trailing literalline with hardline for better readability
        const trailingNewlineRegex = /\n[^\S\n]*?$/;
        const hasTrailingNewline = trailingNewlineRegex.test(node.value);
        const value = hasTrailingNewline
          ? node.value.replace(trailingNewlineRegex, "")
          : node.value;
        return concat([
          concat(replaceEndOfLineWith(value, literalline)),
          hasTrailingNewline ? hardline : "",
        ]);
      }
      return fill(
        normalizeParts(
          [].concat(
            printOpeningTagPrefix(node, options),
            getTextValueParts(node),
            printClosingTagSuffix(node, options)
          )
        )
      );
    }
    case "docType":
      return concat([
        group(
          concat([
            printOpeningTagStart(node, options),
            " ",
            node.value.replace(/^html\b/i, "html").replace(/\s+/g, " "),
          ])
        ),
        printClosingTagEnd(node, options),
      ]);
    case "comment": {
      return concat([
        printOpeningTagPrefix(node, options),
        concat(
          replaceEndOfLineWith(
            options.originalText.slice(
              options.locStart(node),
              options.locEnd(node)
            ),
            literalline
          )
        ),
        printClosingTagSuffix(node, options),
      ]);
    }
    case "attribute": {
      if (node.value === null) {
        return node.rawName;
      }
      const value = unescapeQuoteEntities(node.value);
      const singleQuoteCount = countChars(value, "'");
      const doubleQuoteCount = countChars(value, '"');
      const quote = singleQuoteCount < doubleQuoteCount ? "'" : '"';
      return concat([
        node.rawName,
        concat([
          "=",
          quote,
          concat(
            replaceEndOfLineWith(
              quote === '"'
                ? value.replace(/"/g, "&quot;")
                : value.replace(/'/g, "&apos;"),
              literalline
            )
          ),
          quote,
        ]),
      ]);
    }
    case "yaml":
    case "toml":
      return concat(replaceEndOfLineWith(node.raw, literalline));
    default:
      throw new Error(`Unexpected node type ${node.type}`);
  }
}