src/language-yaml/printer-yaml.js/_print
function _print(node, parentNode, path, options, print) {
  switch (node.type) {
    case "root":
      return concat([
        join(
          hardline,
          path.map((childPath, index) => {
            const document = node.children[index];
            const nextDocument = node.children[index + 1];
            return concat([
              print(childPath),
              shouldPrintDocumentEndMarker(document, nextDocument)
                ? concat([
                    hardline,
                    "...",
                    hasTrailingComment(document)
                      ? concat([" ", path.call(print, "trailingComment")])
                      : "",
                  ])
                : !nextDocument || hasTrailingComment(nextDocument.head)
                ? ""
                : concat([hardline, "---"]),
            ]);
          }, "children")
        ),
        node.children.length === 0 ||
        ((lastDescendantNode) =>
          isNode(lastDescendantNode, ["blockLiteral", "blockFolded"]) &&
          lastDescendantNode.chomping === "keep")(getLastDescendantNode(node))
          ? ""
          : hardline,
      ]);
    case "document": {
      const nextDocument = parentNode.children[path.getName() + 1];
      return join(
        hardline,
        [
          shouldPrintDocumentHeadEndMarker(
            node,
            nextDocument,
            parentNode,
            options
          ) === "head"
            ? join(
                hardline,
                [
                  node.head.children.length === 0 &&
                  node.head.endComments.length === 0
                    ? ""
                    : path.call(print, "head"),
                  concat([
                    "---",
                    hasTrailingComment(node.head)
                      ? concat([
                          " ",
                          path.call(print, "head", "trailingComment"),
                        ])
                      : "",
                  ]),
                ].filter(Boolean)
              )
            : "",
          shouldPrintDocumentBody(node) ? path.call(print, "body") : "",
        ].filter(Boolean)
      );
    }
    case "documentHead":
      return join(
        hardline,
        [].concat(path.map(print, "children"), path.map(print, "endComments"))
      );
    case "documentBody": {
      const children = join(hardline, path.map(print, "children")).parts;
      const endComments = join(hardline, path.map(print, "endComments")).parts;
      const separator =
        children.length === 0 || endComments.length === 0
          ? ""
          : ((lastDescendantNode) =>
              isNode(lastDescendantNode, ["blockFolded", "blockLiteral"])
                ? lastDescendantNode.chomping === "keep"
                  ? // there's already a newline printed at the end of blockValue (chomping=keep, lastDescendant=true)
                    ""
                  : // an extra newline for better readability
                    concat([hardline, hardline])
                : hardline)(getLastDescendantNode(node));
      return concat([].concat(children, separator, endComments));
    }
    case "directive":
      return concat(["%", join(" ", [node.name].concat(node.parameters))]);
    case "comment":
      return concat(["#", node.value]);
    case "alias":
      return concat(["*", node.value]);
    case "tag":
      return options.originalText.slice(
        node.position.start.offset,
        node.position.end.offset
      );
    case "anchor":
      return concat(["&", node.value]);
    case "plain":
      return printFlowScalarContent(
        node.type,
        options.originalText.slice(
          node.position.start.offset,
          node.position.end.offset
        ),
        options
      );
    case "quoteDouble":
    case "quoteSingle": {
      const singleQuote = "'";
      const doubleQuote = '"';

      const raw = options.originalText.slice(
        node.position.start.offset + 1,
        node.position.end.offset - 1
      );

      if (
        (node.type === "quoteSingle" && raw.includes("\\")) ||
        (node.type === "quoteDouble" && /\\[^"]/.test(raw))
      ) {
        // only quoteDouble can use escape chars
        // and quoteSingle do not need to escape backslashes
        const originalQuote =
          node.type === "quoteDouble" ? doubleQuote : singleQuote;
        return concat([
          originalQuote,
          printFlowScalarContent(node.type, raw, options),
          originalQuote,
        ]);
      } else if (raw.includes(doubleQuote)) {
        return concat([
          singleQuote,
          printFlowScalarContent(
            node.type,
            node.type === "quoteDouble"
              ? raw
                  // double quote needs to be escaped by backslash in quoteDouble
                  .replace(/\\"/g, doubleQuote)
                  .replace(/'/g, singleQuote.repeat(2))
              : raw,
            options
          ),
          singleQuote,
        ]);
      }

      if (raw.includes(singleQuote)) {
        return concat([
          doubleQuote,
          printFlowScalarContent(
            node.type,
            node.type === "quoteSingle"
              ? // single quote needs to be escaped by 2 single quotes in quoteSingle
                raw.replace(/''/g, singleQuote)
              : raw,
            options
          ),
          doubleQuote,
        ]);
      }

      const quote = options.singleQuote ? singleQuote : doubleQuote;
      return concat([
        quote,
        printFlowScalarContent(node.type, raw, options),
        quote,
      ]);
    }
    case "blockFolded":
    case "blockLiteral": {
      const parentIndent = getAncestorCount(path, (ancestorNode) =>
        isNode(ancestorNode, ["sequence", "mapping"])
      );
      const isLastDescendant = isLastDescendantNode(path);
      return concat([
        node.type === "blockFolded" ? ">" : "|",
        node.indent === null ? "" : node.indent.toString(),
        node.chomping === "clip" ? "" : node.chomping === "keep" ? "+" : "-",
        hasIndicatorComment(node)
          ? concat([" ", path.call(print, "indicatorComment")])
          : "",
        (node.indent === null ? dedent : dedentToRoot)(
          align(
            node.indent === null
              ? options.tabWidth
              : node.indent - 1 + parentIndent,
            concat(
              getBlockValueLineContents(node, {
                parentIndent,
                isLastDescendant,
                options,
              }).reduce(
                (reduced, lineWords, index, lineContents) =>
                  reduced.concat(
                    index === 0 ? hardline : "",
                    fill(join(line, lineWords).parts),
                    index !== lineContents.length - 1
                      ? lineWords.length === 0
                        ? hardline
                        : markAsRoot(literalline)
                      : node.chomping === "keep" && isLastDescendant
                      ? lineWords.length === 0
                        ? dedentToRoot(hardline)
                        : dedentToRoot(literalline)
                      : ""
                  ),
                []
              )
            )
          )
        ),
      ]);
    }
    case "sequence":
      return join(hardline, path.map(print, "children"));
    case "sequenceItem":
      return concat([
        "- ",
        align(2, !node.content ? "" : path.call(print, "content")),
      ]);
    case "mappingKey":
      return !node.content ? "" : path.call(print, "content");
    case "mappingValue":
      return !node.content ? "" : path.call(print, "content");
    case "mapping":
      return join(hardline, path.map(print, "children"));
    case "mappingItem":
    case "flowMappingItem": {
      const isEmptyMappingKey = isEmptyNode(node.key);
      const isEmptyMappingValue = isEmptyNode(node.value);

      if (isEmptyMappingKey && isEmptyMappingValue) {
        return concat([": "]);
      }

      const key = path.call(print, "key");
      const value = path.call(print, "value");

      if (isEmptyMappingValue) {
        return node.type === "flowMappingItem" &&
          parentNode.type === "flowMapping"
          ? key
          : node.type === "mappingItem" &&
            isAbsolutelyPrintedAsSingleLineNode(node.key.content, options) &&
            !hasTrailingComment(node.key.content) &&
            (!parentNode.tag ||
              parentNode.tag.value !== "tag:yaml.org,2002:set")
          ? concat([key, needsSpaceInFrontOfMappingValue(node) ? " " : "", ":"])
          : concat(["? ", align(2, key)]);
      }

      if (isEmptyMappingKey) {
        return concat([": ", align(2, value)]);
      }

      const groupId = Symbol("mappingKey");

      const forceExplicitKey =
        hasLeadingComments(node.value) || !isInlineNode(node.key.content);

      return forceExplicitKey
        ? concat([
            "? ",
            align(2, key),
            hardline,
            join(
              "",
              path
                .map(print, "value", "leadingComments")
                .map((comment) => concat([comment, hardline]))
            ),
            ": ",
            align(2, value),
          ])
        : // force singleline
        isSingleLineNode(node.key.content) &&
          !hasLeadingComments(node.key.content) &&
          !hasMiddleComments(node.key.content) &&
          !hasTrailingComment(node.key.content) &&
          !hasEndComments(node.key) &&
          !hasLeadingComments(node.value.content) &&
          !hasMiddleComments(node.value.content) &&
          !hasEndComments(node.value) &&
          isAbsolutelyPrintedAsSingleLineNode(node.value.content, options)
        ? concat([
            key,
            needsSpaceInFrontOfMappingValue(node) ? " " : "",
            ": ",
            value,
          ])
        : conditionalGroup([
            concat([
              group(
                concat([ifBreak("? "), group(align(2, key), { id: groupId })])
              ),
              ifBreak(
                concat([hardline, ": ", align(2, value)]),
                indent(
                  concat([
                    needsSpaceInFrontOfMappingValue(node) ? " " : "",
                    ":",
                    hasLeadingComments(node.value.content) ||
                    (hasEndComments(node.value) &&
                      node.value.content &&
                      !isNode(node.value.content, ["mapping", "sequence"])) ||
                    (parentNode.type === "mapping" &&
                      hasTrailingComment(node.key.content) &&
                      isInlineNode(node.value.content)) ||
                    (isNode(node.value.content, ["mapping", "sequence"]) &&
                      node.value.content.tag === null &&
                      node.value.content.anchor === null)
                      ? hardline
                      : !node.value.content
                      ? ""
                      : line,
                    value,
                  ])
                ),
                { groupId }
              ),
            ]),
          ]);
    }
    case "flowMapping":
    case "flowSequence": {
      const openMarker = node.type === "flowMapping" ? "{" : "[";
      const closeMarker = node.type === "flowMapping" ? "}" : "]";
      const bracketSpacing =
        node.type === "flowMapping" &&
        node.children.length !== 0 &&
        options.bracketSpacing
          ? line
          : softline;
      const isLastItemEmptyMappingItem =
        node.children.length !== 0 &&
        ((lastItem) =>
          lastItem.type === "flowMappingItem" &&
          isEmptyNode(lastItem.key) &&
          isEmptyNode(lastItem.value))(getLast(node.children));
      const trailingComma =
        options.trailingComma === "none" ? "" : ifBreak(",", "");
      return concat([
        openMarker,
        indent(
          concat([
            bracketSpacing,
            concat(
              path.map(
                (childPath, index) =>
                  concat([
                    print(childPath),
                    index === node.children.length - 1
                      ? ""
                      : concat([
                          ",",
                          line,
                          node.children[index].position.start.line !==
                          node.children[index + 1].position.start.line
                            ? printNextEmptyLine(
                                childPath,
                                options.originalText
                              )
                            : "",
                        ]),
                  ]),
                "children"
              )
            ),
            trailingComma,
            hasEndComments(node)
              ? concat([
                  hardline,
                  join(hardline, path.map(print, "endComments")),
                ])
              : "",
          ])
        ),
        isLastItemEmptyMappingItem ? "" : bracketSpacing,
        closeMarker,
      ]);
    }
    case "flowSequenceItem":
      return path.call(print, "content");
    // istanbul ignore next
    default:
      throw new Error(`Unexpected node type ${node.type}`);
  }

  function indent(doc) {
    return docBuilders.align(" ".repeat(options.tabWidth), doc);
  }
}