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

  if (shouldRemainTheSameContent(path)) {
    return concat(
      splitText(
        options.originalText.slice(
          node.position.start.offset,
          node.position.end.offset
        ),
        options
      ).map((node) =>
        node.type === "word"
          ? node.value
          : node.value === ""
          ? ""
          : printLine(path, node.value, options)
      )
    );
  }

  switch (node.type) {
    case "front-matter":
      return options.originalText.slice(
        node.position.start.offset,
        node.position.end.offset
      );
    case "root":
      if (node.children.length === 0) {
        return "";
      }
      return concat([
        normalizeDoc(printRoot(path, options, print)),
        !TRAILING_HARDLINE_NODES.has(getLastDescendantNode(node).type)
          ? hardline
          : "",
      ]);
    case "paragraph":
      return printChildren(path, options, print, {
        postprocessor: fill,
      });
    case "sentence":
      return printChildren(path, options, print);
    case "word": {
      let escapedValue = node.value
        .replace(/\*/g, "\\$&") // escape all `*`
        .replace(
          new RegExp(
            [
              `(^|${punctuationPattern})(_+)`,
              `(_+)(${punctuationPattern}|$)`,
            ].join("|"),
            "g"
          ),
          (_, text1, underscore1, underscore2, text2) =>
            (underscore1
              ? `${text1}${underscore1}`
              : `${underscore2}${text2}`
            ).replace(/_/g, "\\_")
        ); // escape all `_` except concating with non-punctuation, e.g. `1_2_3` is not considered emphasis

      const isFirstSentence = (node, name, index) =>
        node.type === "sentence" && index === 0;
      const isLastChildAutolink = (node, name, index) =>
        isAutolink(node.children[index - 1]);

      if (
        escapedValue !== node.value &&
        (path.match(undefined, isFirstSentence, isLastChildAutolink) ||
          path.match(
            undefined,
            isFirstSentence,
            (node, name, index) => node.type === "emphasis" && index === 0,
            isLastChildAutolink
          ))
      ) {
        // backslash is parsed as part of autolinks, so we need to remove it
        escapedValue = escapedValue.replace(/^(\\?[*_])+/, (prefix) =>
          prefix.replace(/\\/g, "")
        );
      }

      return escapedValue;
    }
    case "whitespace": {
      const parentNode = path.getParentNode();
      const index = parentNode.children.indexOf(node);
      const nextNode = parentNode.children[index + 1];

      const proseWrap =
        // leading char that may cause different syntax
        nextNode && /^>|^([*+-]|#{1,6}|\d+[).])$/.test(nextNode.value)
          ? "never"
          : options.proseWrap;

      return printLine(path, node.value, { proseWrap });
    }
    case "emphasis": {
      let style;
      if (isAutolink(node.children[0])) {
        style = options.originalText[node.position.start.offset];
      } else {
        const parentNode = path.getParentNode();
        const index = parentNode.children.indexOf(node);
        const prevNode = parentNode.children[index - 1];
        const nextNode = parentNode.children[index + 1];
        const hasPrevOrNextWord = // `1*2*3` is considered emphasis but `1_2_3` is not
          (prevNode &&
            prevNode.type === "sentence" &&
            prevNode.children.length > 0 &&
            getLast(prevNode.children).type === "word" &&
            !getLast(prevNode.children).hasTrailingPunctuation) ||
          (nextNode &&
            nextNode.type === "sentence" &&
            nextNode.children.length > 0 &&
            nextNode.children[0].type === "word" &&
            !nextNode.children[0].hasLeadingPunctuation);
        style =
          hasPrevOrNextWord || getAncestorNode(path, "emphasis") ? "*" : "_";
      }
      return concat([style, printChildren(path, options, print), style]);
    }
    case "strong":
      return concat(["**", printChildren(path, options, print), "**"]);
    case "delete":
      return concat(["~~", printChildren(path, options, print), "~~"]);
    case "inlineCode": {
      const backtickCount = getMinNotPresentContinuousCount(node.value, "`");
      const style = "`".repeat(backtickCount || 1);
      const gap = backtickCount && !/^\s/.test(node.value) ? " " : "";
      return concat([style, gap, node.value, gap, style]);
    }
    case "wikiLink": {
      let contents = "";
      if (options.proseWrap === "preserve") {
        contents = node.value;
      } else {
        contents = node.value.replace(/[\t\n]+/g, " ");
      }

      return concat(["[[", contents, "]]"]);
    }
    case "link":
      switch (options.originalText[node.position.start.offset]) {
        case "<": {
          const mailto = "mailto:";
          const url =
            // <hello@example.com> is parsed as { url: "mailto:hello@example.com" }
            node.url.startsWith(mailto) &&
            options.originalText.slice(
              node.position.start.offset + 1,
              node.position.start.offset + 1 + mailto.length
            ) !== mailto
              ? node.url.slice(mailto.length)
              : node.url;
          return concat(["<", url, ">"]);
        }
        case "[":
          return concat([
            "[",
            printChildren(path, options, print),
            "](",
            printUrl(node.url, ")"),
            printTitle(node.title, options),
            ")",
          ]);
        default:
          return options.originalText.slice(
            node.position.start.offset,
            node.position.end.offset
          );
      }
    case "image":
      return concat([
        "![",
        node.alt || "",
        "](",
        printUrl(node.url, ")"),
        printTitle(node.title, options),
        ")",
      ]);
    case "blockquote":
      return concat(["> ", align("> ", printChildren(path, options, print))]);
    case "heading":
      return concat([
        "#".repeat(node.depth) + " ",
        printChildren(path, options, print),
      ]);
    case "code": {
      if (node.isIndented) {
        // indented code block
        const alignment = " ".repeat(4);
        return align(
          alignment,
          concat([
            alignment,
            concat(replaceEndOfLineWith(node.value, hardline)),
          ])
        );
      }

      // fenced code block
      const styleUnit = options.__inJsTemplate ? "~" : "`";
      const style = styleUnit.repeat(
        Math.max(3, getMaxContinuousCount(node.value, styleUnit) + 1)
      );
      return concat([
        style,
        node.lang || "",
        node.meta ? " " + node.meta : "",
        hardline,
        concat(
          replaceEndOfLineWith(
            getFencedCodeBlockValue(node, options.originalText),
            hardline
          )
        ),
        hardline,
        style,
      ]);
    }
    case "html": {
      const parentNode = path.getParentNode();
      const value =
        parentNode.type === "root" && getLast(parentNode.children) === node
          ? node.value.trimEnd()
          : node.value;
      const isHtmlComment = /^<!--[\S\s]*-->$/.test(value);
      return concat(
        replaceEndOfLineWith(
          value,
          isHtmlComment ? hardline : markAsRoot(literalline)
        )
      );
    }
    case "list": {
      const nthSiblingIndex = getNthListSiblingIndex(
        node,
        path.getParentNode()
      );

      const isGitDiffFriendlyOrderedList = hasGitDiffFriendlyOrderedList(
        node,
        options
      );

      return printChildren(path, options, print, {
        processor: (childPath, index) => {
          const prefix = getPrefix();
          const childNode = childPath.getValue();

          if (
            childNode.children.length === 2 &&
            childNode.children[1].type === "html" &&
            childNode.children[0].position.start.column !==
              childNode.children[1].position.start.column
          ) {
            return concat([
              prefix,
              printListItem(childPath, options, print, prefix),
            ]);
          }

          return concat([
            prefix,
            align(
              " ".repeat(prefix.length),
              printListItem(childPath, options, print, prefix)
            ),
          ]);

          function getPrefix() {
            const rawPrefix = node.ordered
              ? (index === 0
                  ? node.start
                  : isGitDiffFriendlyOrderedList
                  ? 1
                  : node.start + index) +
                (nthSiblingIndex % 2 === 0 ? ". " : ") ")
              : nthSiblingIndex % 2 === 0
              ? "- "
              : "* ";

            return node.isAligned ||
              /* workaround for https://github.com/remarkjs/remark/issues/315 */ node.hasIndentedCodeblock
              ? alignListPrefix(rawPrefix, options)
              : rawPrefix;
          }
        },
      });
    }
    case "thematicBreak": {
      const counter = getAncestorCounter(path, "list");
      if (counter === -1) {
        return "---";
      }
      const nthSiblingIndex = getNthListSiblingIndex(
        path.getParentNode(counter),
        path.getParentNode(counter + 1)
      );
      return nthSiblingIndex % 2 === 0 ? "***" : "---";
    }
    case "linkReference":
      return concat([
        "[",
        printChildren(path, options, print),
        "]",
        node.referenceType === "full"
          ? concat(["[", node.identifier, "]"])
          : node.referenceType === "collapsed"
          ? "[]"
          : "",
      ]);
    case "imageReference":
      switch (node.referenceType) {
        case "full":
          return concat(["![", node.alt || "", "][", node.identifier, "]"]);
        default:
          return concat([
            "![",
            node.alt,
            "]",
            node.referenceType === "collapsed" ? "[]" : "",
          ]);
      }
    case "definition": {
      const lineOrSpace = options.proseWrap === "always" ? line : " ";
      return group(
        concat([
          concat(["[", node.identifier, "]:"]),
          indent(
            concat([
              lineOrSpace,
              printUrl(node.url),
              node.title === null
                ? ""
                : concat([lineOrSpace, printTitle(node.title, options, false)]),
            ])
          ),
        ])
      );
    }
    // `footnote` requires `.use(footnotes, {inlineNotes: true})`, we are not using this option
    // https://github.com/remarkjs/remark-footnotes#optionsinlinenotes
    /* istanbul ignore next */
    case "footnote":
      return concat(["[^", printChildren(path, options, print), "]"]);
    case "footnoteReference":
      return concat(["[^", node.identifier, "]"]);
    case "footnoteDefinition": {
      const nextNode = path.getParentNode().children[path.getName() + 1];
      const shouldInlineFootnote =
        node.children.length === 1 &&
        node.children[0].type === "paragraph" &&
        (options.proseWrap === "never" ||
          (options.proseWrap === "preserve" &&
            node.children[0].position.start.line ===
              node.children[0].position.end.line));
      return concat([
        "[^",
        node.identifier,
        "]: ",
        shouldInlineFootnote
          ? printChildren(path, options, print)
          : group(
              concat([
                align(
                  " ".repeat(4),
                  printChildren(path, options, print, {
                    processor: (childPath, index) => {
                      return index === 0
                        ? group(concat([softline, childPath.call(print)]))
                        : childPath.call(print);
                    },
                  })
                ),
                nextNode && nextNode.type === "footnoteDefinition"
                  ? softline
                  : "",
              ])
            ),
      ]);
    }
    case "table":
      return printTable(path, options, print);
    case "tableCell":
      return printChildren(path, options, print);
    case "break":
      return /\s/.test(options.originalText[node.position.start.offset])
        ? concat(["  ", markAsRoot(literalline)])
        : concat(["\\", hardline]);
    case "liquidNode":
      return concat(replaceEndOfLineWith(node.value, hardline));
    // MDX
    // fallback to the original text if multiparser failed
    // or `embeddedLanguageFormatting: "off"`
    case "importExport":
      return concat([node.value, hardline]);
    case "jsx":
      return node.value;
    case "math":
      return concat([
        "$$",
        hardline,
        node.value
          ? concat([
              concat(replaceEndOfLineWith(node.value, hardline)),
              hardline,
            ])
          : "",
        "$$",
      ]);
    case "inlineMath": {
      // remark-math trims content but we don't want to remove whitespaces
      // since it's very possible that it's recognized as math accidentally
      return options.originalText.slice(locStart(node), locEnd(node));
    }

    case "tableRow": // handled in "table"
    case "listItem": // handled in "list"
    default:
      /* istanbul ignore next */
      throw new Error(`Unknown markdown type ${JSON.stringify(node.type)}`);
  }
}