src/language-handlebars/printer-glimmer.js/print
function print(path, options, print) {
  const n = path.getValue();

  /* istanbul ignore if*/
  if (!n) {
    return "";
  }

  if (hasPrettierIgnore(path)) {
    const startOffset = locationToOffset(
      options.originalText,
      n.loc.start.line - 1,
      n.loc.start.column
    );
    const endOffset = locationToOffset(
      options.originalText,
      n.loc.end.line - 1,
      n.loc.end.column
    );

    const ignoredText = options.originalText.slice(startOffset, endOffset);
    return ignoredText;
  }

  switch (n.type) {
    case "Block":
    case "Program":
    case "Template": {
      return group(concat(path.map(print, "body")));
    }
    case "ElementNode": {
      // TODO: make it whitespace sensitive
      const bim = isNextNodeOfSomeType(path, ["ElementNode"]) ? hardline : "";

      if (isVoid(n)) {
        return concat([group(printStartingTag(path, print)), bim]);
      }

      const isWhitespaceOnly = n.children.every((n) => isWhitespaceNode(n));

      return concat([
        group(printStartingTag(path, print)),
        group(
          concat([
            isWhitespaceOnly ? "" : indent(printChildren(path, options, print)),
            n.children.length ? hardline : "",
            concat(["</", n.tag, ">"]),
          ])
        ),
        bim,
      ]);
    }
    case "BlockStatement": {
      const pp = path.getParentNode(1);

      const isElseIf =
        pp &&
        pp.inverse &&
        pp.inverse.body.length === 1 &&
        pp.inverse.body[0] === n &&
        pp.inverse.body[0].path.parts[0] === "if";

      if (isElseIf) {
        return concat([
          printElseIfBlock(path, print),
          printProgram(path, print),
          printInverse(path, print),
        ]);
      }

      return concat([
        printOpenBlock(path, print),
        group(
          concat([
            printProgram(path, print),
            printInverse(path, print),
            printCloseBlock(path, print),
          ])
        ),
      ]);
    }
    case "ElementModifierStatement": {
      return group(
        concat(["{{", printPathAndParams(path, print), softline, "}}"])
      );
    }
    case "MustacheStatement": {
      const isParentOfSpecifiedTypes = isParentOfSomeType(path, [
        "AttrNode",
        "ConcatStatement",
      ]);

      const isChildOfElementNodeAndDoesNotHaveParams =
        isParentOfSomeType(path, ["ElementNode"]) &&
        doesNotHaveHashParams(n) &&
        doesNotHavePositionalParams(n);

      const shouldBreakOpeningMustache =
        isParentOfSpecifiedTypes || isChildOfElementNodeAndDoesNotHaveParams;

      return group(
        concat([
          printOpeningMustache(n),
          shouldBreakOpeningMustache ? indent(softline) : "",
          printPathAndParams(path, print),
          softline,
          printClosingMustache(n),
        ])
      );
    }

    case "SubExpression": {
      return group(
        concat([
          "(",
          printSubExpressionPathAndParams(path, print),
          softline,
          ")",
        ])
      );
    }
    case "AttrNode": {
      const isText = n.value.type === "TextNode";
      const isEmptyText = isText && n.value.chars === "";

      // If the text is empty and the value's loc start and end columns are the
      // same, there is no value for this AttrNode and it should be printed
      // without the `=""`. Example: `<img data-test>` -> `<img data-test>`
      const isEmptyValue =
        isEmptyText && n.value.loc.start.column === n.value.loc.end.column;
      if (isEmptyValue) {
        return concat([n.name]);
      }
      const value = path.call(print, "value");
      const quotedValue = isText
        ? printStringLiteral(value.parts.join(), options)
        : value;
      return concat([n.name, "=", quotedValue]);
    }

    case "ConcatStatement": {
      const quote = options.singleQuote ? "'" : '"';
      return concat([
        quote,
        ...path.map((partPath) => print(partPath), "parts"),
        quote,
      ]);
    }

    case "Hash": {
      return concat([join(line, path.map(print, "pairs"))]);
    }
    case "HashPair": {
      return concat([n.key, "=", path.call(print, "value")]);
    }
    case "TextNode": {
      const maxLineBreaksToPreserve = 2;
      const isFirstElement = !getPreviousNode(path);
      const isLastElement = !getNextNode(path);
      const isWhitespaceOnly = !/\S/.test(n.chars);
      const lineBreaksCount = countNewLines(n.chars);

      let leadingLineBreaksCount = countLeadingNewLines(n.chars);
      let trailingLineBreaksCount = countTrailingNewLines(n.chars);

      if (
        (isFirstElement || isLastElement) &&
        isWhitespaceOnly &&
        isParentOfSomeType(path, ["Block", "ElementNode", "Template"])
      ) {
        return "";
      }

      if (isWhitespaceOnly && lineBreaksCount) {
        leadingLineBreaksCount = Math.min(
          lineBreaksCount,
          maxLineBreaksToPreserve
        );
        trailingLineBreaksCount = 0;
      } else {
        if (isNextNodeOfSomeType(path, ["BlockStatement", "ElementNode"])) {
          trailingLineBreaksCount = Math.max(trailingLineBreaksCount, 1);
        }

        if (isPreviousNodeOfSomeType(path, ["BlockStatement", "ElementNode"])) {
          leadingLineBreaksCount = Math.max(leadingLineBreaksCount, 1);
        }
      }

      let leadingSpace = "";
      let trailingSpace = "";

      // preserve a space inside of an attribute node where whitespace present,
      // when next to mustache statement.
      const inAttrNode = path.stack.includes("attributes");
      if (inAttrNode) {
        if (isParentOfSomeType(path, ["ConcatStatement"])) {
          if (isPreviousNodeOfSomeType(path, ["MustacheStatement"])) {
            leadingSpace = " ";
          }
          if (isNextNodeOfSomeType(path, ["MustacheStatement"])) {
            trailingSpace = " ";
          }
        }
      } else {
        if (
          trailingLineBreaksCount === 0 &&
          isNextNodeOfSomeType(path, ["MustacheStatement"])
        ) {
          trailingSpace = " ";
        }

        if (
          leadingLineBreaksCount === 0 &&
          isPreviousNodeOfSomeType(path, ["MustacheStatement"])
        ) {
          leadingSpace = " ";
        }

        if (isFirstElement) {
          leadingLineBreaksCount = 0;
          leadingSpace = "";
        }

        if (isLastElement) {
          trailingLineBreaksCount = 0;
          trailingSpace = "";
        }
      }

      let text = n.chars;
      /* if `{{my-component}}` (or any text starting with a mustache)
       * makes it to the TextNode,
       * it means it was escaped,
       * so let's print it escaped, ie.; `\{{my-component}}` */
      if (text.startsWith("{{") && text.includes("}}")) {
        text = "\\" + text;
      }

      return concat([
        ...generateHardlines(leadingLineBreaksCount, maxLineBreaksToPreserve),
        text.replace(/^\s+/g, leadingSpace).replace(/\s+$/, trailingSpace),
        ...generateHardlines(trailingLineBreaksCount, maxLineBreaksToPreserve),
      ]);
    }
    case "MustacheCommentStatement": {
      const dashes = n.value.includes("}}") ? "--" : "";
      return concat(["{{!", dashes, n.value, dashes, "}}"]);
    }
    case "PathExpression": {
      return n.original;
    }
    case "BooleanLiteral": {
      return String(n.value);
    }
    case "CommentStatement": {
      return concat(["<!--", n.value, "-->"]);
    }
    case "StringLiteral": {
      return printStringLiteral(n.value, options);
    }
    case "NumberLiteral": {
      return String(n.value);
    }
    case "UndefinedLiteral": {
      return "undefined";
    }
    case "NullLiteral": {
      return "null";
    }

    /* istanbul ignore next */
    default:
      throw new Error("unknown glimmer type: " + JSON.stringify(n.type));
  }
}