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

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

  if (typeof node === "string") {
    return node;
  }

  switch (node.type) {
    case "front-matter":
      return concat([node.raw, hardline]);
    case "css-root": {
      const nodes = printNodeSequence(path, options, print);
      const after = node.raws.after.trim();

      return concat([
        nodes,
        after ? ` ${after}` : "",
        nodes.parts.length ? hardline : "",
      ]);
    }
    case "css-comment": {
      const isInlineComment = node.inline || node.raws.inline;

      const text = options.originalText.slice(locStart(node), locEnd(node));

      return isInlineComment ? text.trimEnd() : text;
    }
    case "css-rule": {
      return concat([
        path.call(print, "selector"),
        node.important ? " !important" : "",
        node.nodes
          ? concat([
              node.selector &&
              node.selector.type === "selector-unknown" &&
              lastLineHasInlineComment(node.selector.value)
                ? line
                : " ",
              "{",
              node.nodes.length > 0
                ? indent(
                    concat([hardline, printNodeSequence(path, options, print)])
                  )
                : "",
              hardline,
              "}",
              isDetachedRulesetDeclarationNode(node) ? ";" : "",
            ])
          : ";",
      ]);
    }
    case "css-decl": {
      const parentNode = path.getParentNode();

      const { between: rawBetween } = node.raws;
      const trimmedBetween = rawBetween.trim();
      const isColon = trimmedBetween === ":";

      let value = hasComposesNode(node)
        ? removeLines(path.call(print, "value"))
        : path.call(print, "value");

      if (!isColon && lastLineHasInlineComment(trimmedBetween)) {
        value = indent(concat([hardline, dedent(value)]));
      }

      return concat([
        node.raws.before.replace(/[\s;]/g, ""),
        insideICSSRuleNode(path) ? node.prop : maybeToLowerCase(node.prop),
        trimmedBetween.startsWith("//") ? " " : "",
        trimmedBetween,
        node.extend ? "" : " ",
        isLessParser(options) && node.extend && node.selector
          ? concat(["extend(", path.call(print, "selector"), ")"])
          : "",
        value,
        node.raws.important
          ? node.raws.important.replace(/\s*!\s*important/i, " !important")
          : node.important
          ? " !important"
          : "",
        node.raws.scssDefault
          ? node.raws.scssDefault.replace(/\s*!default/i, " !default")
          : node.scssDefault
          ? " !default"
          : "",
        node.raws.scssGlobal
          ? node.raws.scssGlobal.replace(/\s*!global/i, " !global")
          : node.scssGlobal
          ? " !global"
          : "",
        node.nodes
          ? concat([
              " {",
              indent(
                concat([softline, printNodeSequence(path, options, print)])
              ),
              softline,
              "}",
            ])
          : isTemplatePropNode(node) &&
            !parentNode.raws.semicolon &&
            options.originalText[locEnd(node) - 1] !== ";"
          ? ""
          : options.__isHTMLStyleAttribute && isLastNode(path, node)
          ? ifBreak(";", "")
          : ";",
      ]);
    }
    case "css-atrule": {
      const parentNode = path.getParentNode();
      const isTemplatePlaceholderNodeWithoutSemiColon =
        isTemplatePlaceholderNode(node) &&
        !parentNode.raws.semicolon &&
        options.originalText[locEnd(node) - 1] !== ";";

      if (isLessParser(options)) {
        if (node.mixin) {
          return concat([
            path.call(print, "selector"),
            node.important ? " !important" : "",
            isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";",
          ]);
        }

        if (node.function) {
          return concat([
            node.name,
            concat([path.call(print, "params")]),
            isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";",
          ]);
        }

        if (node.variable) {
          return concat([
            "@",
            node.name,
            ": ",
            node.value ? concat([path.call(print, "value")]) : "",
            node.raws.between.trim() ? node.raws.between.trim() + " " : "",
            node.nodes
              ? concat([
                  "{",
                  indent(
                    concat([
                      node.nodes.length > 0 ? softline : "",
                      printNodeSequence(path, options, print),
                    ])
                  ),
                  softline,
                  "}",
                ])
              : "",
            isTemplatePlaceholderNodeWithoutSemiColon ? "" : ";",
          ]);
        }
      }

      return concat([
        "@",
        // If a Less file ends up being parsed with the SCSS parser, Less
        // variable declarations will be parsed as at-rules with names ending
        // with a colon, so keep the original case then.
        isDetachedRulesetCallNode(node) || node.name.endsWith(":")
          ? node.name
          : maybeToLowerCase(node.name),
        node.params
          ? concat([
              isDetachedRulesetCallNode(node)
                ? ""
                : isTemplatePlaceholderNode(node)
                ? node.raws.afterName === ""
                  ? ""
                  : node.name.endsWith(":")
                  ? " "
                  : /^\s*\n\s*\n/.test(node.raws.afterName)
                  ? concat([hardline, hardline])
                  : /^\s*\n/.test(node.raws.afterName)
                  ? hardline
                  : " "
                : " ",
              path.call(print, "params"),
            ])
          : "",
        node.selector
          ? indent(concat([" ", path.call(print, "selector")]))
          : "",
        node.value
          ? group(
              concat([
                " ",
                path.call(print, "value"),
                isSCSSControlDirectiveNode(node)
                  ? hasParensAroundNode(node)
                    ? " "
                    : line
                  : "",
              ])
            )
          : node.name === "else"
          ? " "
          : "",
        node.nodes
          ? concat([
              isSCSSControlDirectiveNode(node)
                ? ""
                : (node.selector &&
                    !node.selector.nodes &&
                    typeof node.selector.value === "string" &&
                    lastLineHasInlineComment(node.selector.value)) ||
                  (!node.selector &&
                    typeof node.params === "string" &&
                    lastLineHasInlineComment(node.params))
                ? line
                : " ",
              "{",
              indent(
                concat([
                  node.nodes.length > 0 ? softline : "",
                  printNodeSequence(path, options, print),
                ])
              ),
              softline,
              "}",
            ])
          : isTemplatePlaceholderNodeWithoutSemiColon
          ? ""
          : ";",
      ]);
    }
    // postcss-media-query-parser
    case "media-query-list": {
      const parts = [];
      path.each((childPath) => {
        const node = childPath.getValue();
        if (node.type === "media-query" && node.value === "") {
          return;
        }
        parts.push(childPath.call(print));
      }, "nodes");

      return group(indent(join(line, parts)));
    }
    case "media-query": {
      return concat([
        join(" ", path.map(print, "nodes")),
        isLastNode(path, node) ? "" : ",",
      ]);
    }
    case "media-type": {
      return adjustNumbers(adjustStrings(node.value, options));
    }
    case "media-feature-expression": {
      if (!node.nodes) {
        return node.value;
      }
      return concat(["(", concat(path.map(print, "nodes")), ")"]);
    }
    case "media-feature": {
      return maybeToLowerCase(
        adjustStrings(node.value.replace(/ +/g, " "), options)
      );
    }
    case "media-colon": {
      return concat([node.value, " "]);
    }
    case "media-value": {
      return adjustNumbers(adjustStrings(node.value, options));
    }
    case "media-keyword": {
      return adjustStrings(node.value, options);
    }
    case "media-url": {
      return adjustStrings(
        node.value.replace(/^url\(\s+/gi, "url(").replace(/\s+\)$/gi, ")"),
        options
      );
    }
    case "media-unknown": {
      return node.value;
    }
    // postcss-selector-parser
    case "selector-root": {
      return group(
        concat([
          insideAtRuleNode(path, "custom-selector")
            ? concat([getAncestorNode(path, "css-atrule").customSelector, line])
            : "",
          join(
            concat([
              ",",
              insideAtRuleNode(path, ["extend", "custom-selector", "nest"])
                ? line
                : hardline,
            ]),
            path.map(print, "nodes")
          ),
        ])
      );
    }
    case "selector-selector": {
      return group(indent(concat(path.map(print, "nodes"))));
    }
    case "selector-comment": {
      return node.value;
    }
    case "selector-string": {
      return adjustStrings(node.value, options);
    }
    case "selector-tag": {
      const parentNode = path.getParentNode();
      const index = parentNode && parentNode.nodes.indexOf(node);
      const prevNode = index && parentNode.nodes[index - 1];

      return concat([
        node.namespace
          ? concat([node.namespace === true ? "" : node.namespace.trim(), "|"])
          : "",
        prevNode.type === "selector-nesting"
          ? node.value
          : adjustNumbers(
              isKeyframeAtRuleKeywords(path, node.value)
                ? node.value.toLowerCase()
                : node.value
            ),
      ]);
    }
    case "selector-id": {
      return concat(["#", node.value]);
    }
    case "selector-class": {
      return concat([".", adjustNumbers(adjustStrings(node.value, options))]);
    }
    case "selector-attribute": {
      return concat([
        "[",
        node.namespace
          ? concat([node.namespace === true ? "" : node.namespace.trim(), "|"])
          : "",
        node.attribute.trim(),
        node.operator ? node.operator : "",
        node.value
          ? quoteAttributeValue(
              adjustStrings(node.value.trim(), options),
              options
            )
          : "",
        node.insensitive ? " i" : "",
        "]",
      ]);
    }
    case "selector-combinator": {
      if (
        node.value === "+" ||
        node.value === ">" ||
        node.value === "~" ||
        node.value === ">>>"
      ) {
        const parentNode = path.getParentNode();
        const leading =
          parentNode.type === "selector-selector" &&
          parentNode.nodes[0] === node
            ? ""
            : line;

        return concat([leading, node.value, isLastNode(path, node) ? "" : " "]);
      }

      const leading = node.value.trim().startsWith("(") ? line : "";
      const value =
        adjustNumbers(adjustStrings(node.value.trim(), options)) || line;

      return concat([leading, value]);
    }
    case "selector-universal": {
      return concat([
        node.namespace
          ? concat([node.namespace === true ? "" : node.namespace.trim(), "|"])
          : "",
        node.value,
      ]);
    }
    case "selector-pseudo": {
      return concat([
        maybeToLowerCase(node.value),
        node.nodes && node.nodes.length > 0
          ? concat(["(", join(", ", path.map(print, "nodes")), ")"])
          : "",
      ]);
    }
    case "selector-nesting": {
      return node.value;
    }
    case "selector-unknown": {
      const ruleAncestorNode = getAncestorNode(path, "css-rule");

      // Nested SCSS property
      if (ruleAncestorNode && ruleAncestorNode.isSCSSNesterProperty) {
        return adjustNumbers(
          adjustStrings(maybeToLowerCase(node.value), options)
        );
      }

      // originalText has to be used for Less, see replaceQuotesInInlineComments in loc.js
      const parentNode = path.getParentNode();
      if (parentNode.raws && parentNode.raws.selector) {
        const start = locStart(parentNode);
        const end = start + parentNode.raws.selector.length;
        return options.originalText.slice(start, end).trim();
      }

      // Same reason above
      const grandParent = path.getParentNode(1);
      if (
        parentNode.type === "value-paren_group" &&
        grandParent &&
        grandParent.type === "value-func" &&
        grandParent.value === "selector"
      ) {
        const start = locStart(parentNode.open) + 1;
        const end = locEnd(parentNode.close) - 1;
        const selector = options.originalText.slice(start, end).trim();

        return lastLineHasInlineComment(selector)
          ? concat([breakParent, selector])
          : selector;
      }

      return node.value;
    }
    // postcss-values-parser
    case "value-value":
    case "value-root": {
      return path.call(print, "group");
    }
    case "value-comment": {
      return options.originalText.slice(locStart(node), locEnd(node));
    }
    case "value-comma_group": {
      const parentNode = path.getParentNode();
      const parentParentNode = path.getParentNode(1);
      const declAncestorProp = getPropOfDeclNode(path);
      const isGridValue =
        declAncestorProp &&
        parentNode.type === "value-value" &&
        (declAncestorProp === "grid" ||
          declAncestorProp.startsWith("grid-template"));
      const atRuleAncestorNode = getAncestorNode(path, "css-atrule");
      const isControlDirective =
        atRuleAncestorNode && isSCSSControlDirectiveNode(atRuleAncestorNode);

      const printed = path.map(print, "groups");
      const parts = [];
      const insideURLFunction = insideValueFunctionNode(path, "url");

      let insideSCSSInterpolationInString = false;
      let didBreak = false;
      for (let i = 0; i < node.groups.length; ++i) {
        parts.push(printed[i]);

        const iPrevNode = node.groups[i - 1];
        const iNode = node.groups[i];
        const iNextNode = node.groups[i + 1];
        const iNextNextNode = node.groups[i + 2];

        if (insideURLFunction) {
          if (
            (iNextNode && isAdditionNode(iNextNode)) ||
            isAdditionNode(iNode)
          ) {
            parts.push(" ");
          }
          continue;
        }

        // Ignore after latest node (i.e. before semicolon)
        if (!iNextNode) {
          continue;
        }

        // styled.div` background: var(--${one}); `
        if (
          iNode.type === "value-word" &&
          iNode.value.endsWith("-") &&
          isAtWordPlaceholderNode(iNextNode)
        ) {
          continue;
        }

        // Ignore spaces before/after string interpolation (i.e. `"#{my-fn("_")}"`)
        const isStartSCSSInterpolationInString =
          iNode.type === "value-string" && iNode.value.startsWith("#{");
        const isEndingSCSSInterpolationInString =
          insideSCSSInterpolationInString &&
          iNextNode.type === "value-string" &&
          iNextNode.value.endsWith("}");

        if (
          isStartSCSSInterpolationInString ||
          isEndingSCSSInterpolationInString
        ) {
          insideSCSSInterpolationInString = !insideSCSSInterpolationInString;

          continue;
        }

        if (insideSCSSInterpolationInString) {
          continue;
        }

        // Ignore colon (i.e. `:`)
        if (isColonNode(iNode) || isColonNode(iNextNode)) {
          continue;
        }

        // Ignore `@` in Less (i.e. `@@var;`)
        if (iNode.type === "value-atword" && iNode.value === "") {
          continue;
        }

        // Ignore `~` in Less (i.e. `content: ~"^//* some horrible but needed css hack";`)
        if (iNode.value === "~") {
          continue;
        }

        // Ignore escape `\`
        if (
          iNode.value &&
          iNode.value.includes("\\") &&
          iNextNode &&
          iNextNode.type !== "value-comment"
        ) {
          continue;
        }

        // Ignore escaped `/`
        if (
          iPrevNode &&
          iPrevNode.value &&
          iPrevNode.value.indexOf("\\") === iPrevNode.value.length - 1 &&
          iNode.type === "value-operator" &&
          iNode.value === "/"
        ) {
          continue;
        }

        // Ignore `\` (i.e. `$variable: \@small;`)
        if (iNode.value === "\\") {
          continue;
        }

        // Ignore `$$` (i.e. `background-color: $$(style)Color;`)
        if (isPostcssSimpleVarNode(iNode, iNextNode)) {
          continue;
        }

        // Ignore spaces after `#` and after `{` and before `}` in SCSS interpolation (i.e. `#{variable}`)
        if (
          isHashNode(iNode) ||
          isLeftCurlyBraceNode(iNode) ||
          isRightCurlyBraceNode(iNextNode) ||
          (isLeftCurlyBraceNode(iNextNode) && hasEmptyRawBefore(iNextNode)) ||
          (isRightCurlyBraceNode(iNode) && hasEmptyRawBefore(iNextNode))
        ) {
          continue;
        }

        // Ignore css variables and interpolation in SCSS (i.e. `--#{$var}`)
        if (iNode.value === "--" && isHashNode(iNextNode)) {
          continue;
        }

        // Formatting math operations
        const isMathOperator = isMathOperatorNode(iNode);
        const isNextMathOperator = isMathOperatorNode(iNextNode);

        // Print spaces before and after math operators beside SCSS interpolation as is
        // (i.e. `#{$var}+5`, `#{$var} +5`, `#{$var}+ 5`, `#{$var} + 5`)
        // (i.e. `5+#{$var}`, `5 +#{$var}`, `5+ #{$var}`, `5 + #{$var}`)
        if (
          ((isMathOperator && isHashNode(iNextNode)) ||
            (isNextMathOperator && isRightCurlyBraceNode(iNode))) &&
          hasEmptyRawBefore(iNextNode)
        ) {
          continue;
        }

        // Print spaces before and after addition and subtraction math operators as is in `calc` function
        // due to the fact that it is not valid syntax
        // (i.e. `calc(1px+1px)`, `calc(1px+ 1px)`, `calc(1px +1px)`, `calc(1px + 1px)`)
        if (
          insideValueFunctionNode(path, "calc") &&
          (isAdditionNode(iNode) ||
            isAdditionNode(iNextNode) ||
            isSubtractionNode(iNode) ||
            isSubtractionNode(iNextNode)) &&
          hasEmptyRawBefore(iNextNode)
        ) {
          continue;
        }

        // Print spaces after `+` and `-` in color adjuster functions as is (e.g. `color(red l(+ 20%))`)
        // Adjusters with signed numbers (e.g. `color(red l(+20%))`) output as-is.
        const isColorAdjusterNode =
          (isAdditionNode(iNode) || isSubtractionNode(iNode)) &&
          i === 0 &&
          (iNextNode.type === "value-number" || iNextNode.isHex) &&
          parentParentNode &&
          isColorAdjusterFuncNode(parentParentNode) &&
          !hasEmptyRawBefore(iNextNode);

        const requireSpaceBeforeOperator =
          (iNextNextNode && iNextNextNode.type === "value-func") ||
          (iNextNextNode && isWordNode(iNextNextNode)) ||
          iNode.type === "value-func" ||
          isWordNode(iNode);
        const requireSpaceAfterOperator =
          iNextNode.type === "value-func" ||
          isWordNode(iNextNode) ||
          (iPrevNode && iPrevNode.type === "value-func") ||
          (iPrevNode && isWordNode(iPrevNode));

        // Formatting `/`, `+`, `-` sign
        if (
          !(isMultiplicationNode(iNextNode) || isMultiplicationNode(iNode)) &&
          !insideValueFunctionNode(path, "calc") &&
          !isColorAdjusterNode &&
          ((isDivisionNode(iNextNode) && !requireSpaceBeforeOperator) ||
            (isDivisionNode(iNode) && !requireSpaceAfterOperator) ||
            (isAdditionNode(iNextNode) && !requireSpaceBeforeOperator) ||
            (isAdditionNode(iNode) && !requireSpaceAfterOperator) ||
            isSubtractionNode(iNextNode) ||
            isSubtractionNode(iNode)) &&
          (hasEmptyRawBefore(iNextNode) ||
            (isMathOperator &&
              (!iPrevNode || (iPrevNode && isMathOperatorNode(iPrevNode)))))
        ) {
          continue;
        }

        // Add `hardline` after inline comment (i.e. `// comment\n foo: bar;`)
        if (isInlineValueCommentNode(iNode)) {
          if (parentNode.type === "value-paren_group") {
            parts.push(dedent(hardline));
            continue;
          }
          parts.push(hardline);
          continue;
        }

        // Handle keywords in SCSS control directive
        if (
          isControlDirective &&
          (isEqualityOperatorNode(iNextNode) ||
            isRelationalOperatorNode(iNextNode) ||
            isIfElseKeywordNode(iNextNode) ||
            isEachKeywordNode(iNode) ||
            isForKeywordNode(iNode))
        ) {
          parts.push(" ");

          continue;
        }

        // At-rule `namespace` should be in one line
        if (
          atRuleAncestorNode &&
          atRuleAncestorNode.name.toLowerCase() === "namespace"
        ) {
          parts.push(" ");

          continue;
        }

        // Formatting `grid` property
        if (isGridValue) {
          if (
            iNode.source &&
            iNextNode.source &&
            iNode.source.start.line !== iNextNode.source.start.line
          ) {
            parts.push(hardline);

            didBreak = true;
          } else {
            parts.push(" ");
          }

          continue;
        }

        // Add `space` before next math operation
        // Note: `grip` property have `/` delimiter and it is not math operation, so
        // `grid` property handles above
        if (isNextMathOperator) {
          parts.push(" ");

          continue;
        }
        // allow function(returns-list($list)...)
        if (iNextNode && iNextNode.value === "...") {
          continue;
        }

        if (
          isAtWordPlaceholderNode(iNode) &&
          isAtWordPlaceholderNode(iNextNode) &&
          locEnd(iNode) === locStart(iNextNode)
        ) {
          continue;
        }

        // Be default all values go through `line`
        parts.push(line);
      }

      if (didBreak) {
        parts.unshift(hardline);
      }

      if (isControlDirective) {
        return group(indent(concat(parts)));
      }

      // Indent is not needed for import url when url is very long
      // and node has two groups
      // when type is value-comma_group
      // example @import url("verylongurl") projection,tv
      if (insideURLFunctionInImportAtRuleNode(path)) {
        return group(fill(parts));
      }

      return group(indent(fill(parts)));
    }
    case "value-paren_group": {
      const parentNode = path.getParentNode();

      if (
        parentNode &&
        isURLFunctionNode(parentNode) &&
        (node.groups.length === 1 ||
          (node.groups.length > 0 &&
            node.groups[0].type === "value-comma_group" &&
            node.groups[0].groups.length > 0 &&
            node.groups[0].groups[0].type === "value-word" &&
            node.groups[0].groups[0].value.startsWith("data:")))
      ) {
        return concat([
          node.open ? path.call(print, "open") : "",
          join(",", path.map(print, "groups")),
          node.close ? path.call(print, "close") : "",
        ]);
      }

      if (!node.open) {
        const printed = path.map(print, "groups");
        const res = [];

        for (let i = 0; i < printed.length; i++) {
          if (i !== 0) {
            res.push(concat([",", line]));
          }
          res.push(printed[i]);
        }

        return group(indent(fill(res)));
      }

      const isSCSSMapItem = isSCSSMapItemNode(path);

      const lastItem = node.groups[node.groups.length - 1];
      const isLastItemComment = lastItem && lastItem.type === "value-comment";

      return group(
        concat([
          node.open ? path.call(print, "open") : "",
          indent(
            concat([
              softline,
              join(
                concat([",", line]),
                path.map((childPath) => {
                  const node = childPath.getValue();
                  const printed = print(childPath);

                  // Key/Value pair in open paren already indented
                  if (
                    isKeyValuePairNode(node) &&
                    node.type === "value-comma_group" &&
                    node.groups &&
                    node.groups[2] &&
                    node.groups[2].type === "value-paren_group"
                  ) {
                    printed.contents.contents.parts[1] = group(
                      printed.contents.contents.parts[1]
                    );

                    return group(dedent(printed));
                  }

                  return printed;
                }, "groups")
              ),
            ])
          ),
          ifBreak(
            !isLastItemComment &&
              isSCSS(options.parser, options.originalText) &&
              isSCSSMapItem &&
              shouldPrintComma(options)
              ? ","
              : ""
          ),
          softline,
          node.close ? path.call(print, "close") : "",
        ]),
        {
          shouldBreak: isSCSSMapItem,
        }
      );
    }
    case "value-func": {
      return concat([
        node.value,
        insideAtRuleNode(path, "supports") && isMediaAndSupportsKeywords(node)
          ? " "
          : "",
        path.call(print, "group"),
      ]);
    }
    case "value-paren": {
      return node.value;
    }
    case "value-number": {
      return concat([printCssNumber(node.value), maybeToLowerCase(node.unit)]);
    }
    case "value-operator": {
      return node.value;
    }
    case "value-word": {
      if ((node.isColor && node.isHex) || isWideKeywords(node.value)) {
        return node.value.toLowerCase();
      }

      return node.value;
    }
    case "value-colon": {
      const parentNode = path.getParentNode();
      const index = parentNode && parentNode.groups.indexOf(node);
      const prevNode = index && parentNode.groups[index - 1];
      return concat([
        node.value,
        // Don't add spaces on escaped colon `:`, e.g: grid-template-rows: [row-1-00\:00] auto;
        (prevNode && prevNode.value[prevNode.value.length - 1] === "\\") ||
        // Don't add spaces on `:` in `url` function (i.e. `url(fbglyph: cross-outline, fig-white)`)
        insideValueFunctionNode(path, "url")
          ? ""
          : line,
      ]);
    }
    // TODO: confirm this code is dead
    /* istanbul ignore next */
    case "value-comma": {
      return concat([node.value, " "]);
    }
    case "value-string": {
      return printString(
        node.raws.quote + node.value + node.raws.quote,
        options
      );
    }
    case "value-atword": {
      return concat(["@", node.value]);
    }
    case "value-unicode-range": {
      return node.value;
    }
    case "value-unknown": {
      return node.value;
    }
    default:
      /* istanbul ignore next */
      throw new Error(`Unknown postcss type ${JSON.stringify(node.type)}`);
  }
}