Repository: apple/pkl
File: pkl-formatter/src/main/kotlin/org/pkl/formatter/Builder.kt (Kotlin)

Code before:
    val nodes =
      groupNonPrefixes(node) { children ->
        val nodes =
          formatGenericWithGen(children, { _, _ -> if (sameLine) Space else spaceOrLine() }) {
            node,
            _ ->
            if ((node.isExpressionOrPropertyBody()) && !sameLine) {
              indent(format(node))
            } else format(node)
          }
        groupOnSpace(nodes)
      }
    return Nodes(nodes)
  }

  private fun Node.isExpressionOrPropertyBody(): Boolean =
    type.isExpression ||
      type == NodeType.CLASS_PROPERTY_BODY ||
      type == NodeType.OBJECT_PROPERTY_BODY

  private fun formatClassPropertyHeader(node: Node): FormatNode {
    return Group(newId(), formatGeneric(node.children, spaceOrLine()))
  }

  private fun formatClassPropertyHeaderBegin(node: Node): FormatNode {
    return Group(newId(), formatGeneric(node.children, spaceOrLine()))
  }

  private fun formatClassPropertyBody(node: Node): FormatNode {
    return Nodes(formatGeneric(node.children, null))
  }

  private fun formatClassMethod(node: Node): FormatNode {
    val prefixes = mutableListOf<FormatNode>()
    val nodes =
      if (node.children[0].type == NodeType.CLASS_METHOD_HEADER) node.children
      else {
        val idx = node.children.indexOfFirst { it.type == NodeType.CLASS_METHOD_HEADER }
        val prefixNodes = node.children.subList(0, idx)
        prefixes += formatGeneric(prefixNodes, null)
        prefixes += getSeparator(prefixNodes.last(), node.children[idx], forceLine())
        node.children.subList(idx, node.children.size)
      }

    // Separate header (before =) and body (= and after)
    val bodyIdx = nodes.indexOfFirst { it.type == NodeType.CLASS_METHOD_BODY } - 1
    val header = if (bodyIdx < 0) nodes else nodes.subList(0, bodyIdx)
    val headerGroupId = newId()
    val methodGroupId = newId()
    val headerNodes =
      formatGenericWithGen(header, spaceOrLine()) { node, _ ->

Review comment (bug):
We are losing the comments between the identifier and the argument list. (A bug I just pointed out in the other comment).
We should have a test for it in `expr-chain-grouping.pkl`.

Code after:
          val (callChain, argsList) = splitFunctionCallNode(flat)
          val leadingNodes = indentAfterFirstNewline(formatGeneric(callChain, separator), true)
          val trailingNodes = formatGeneric(argsList, separator)
          val sep = getBaseSeparator(callChain.last(), argsList.first())
          if (sep != null) {
            leadingNodes + sep + trailingNodes
          } else {
            leadingNodes + trailingNodes
          }
        }
        methodCallCount > 0 && indexBeforeFirstMethodCall > 0 -> {
          val leading = flat.subList(0, indexBeforeFirstMethodCall)
          val trailing = flat.subList(indexBeforeFirstMethodCall, flat.size)
          val leadingNodes = indentAfterFirstNewline(formatGeneric(leading, separator), true)
          val trailingNodes = formatGeneric(trailing, separator)
          leadingNodes + line() + trailingNodes
        }
        else -> formatGeneric(flat, separator)
      }

    val shouldGroup = node.children.size == flat.size
    return Group(newId(), indentAfterFirstNewline(nodes, shouldGroup))
  }

  /**
   * Split a function call node to extract its identifier into the leading group. For example,
   * `foo.bar(5)` becomes: leading gets `foo.bar`, rest gets `(5)`.
   */
  private fun splitFunctionCallNode(nodes: List<Node>): Pair<List<Node>, List<Node>> {
    assert(nodes.isNotEmpty())
    val lastNode = nodes.last()
    val argListIdx = lastNode.children.indexOfFirst { it.type == NodeType.ARGUMENT_LIST }
    val leading = nodes.subList(0, nodes.lastIndex) + lastNode.children.subList(0, argListIdx)
    val trailing = lastNode.children.subList(argListIdx, lastNode.children.size)
    return leading to trailing
  }

  private fun isMethodCall(node: Node): Boolean {
    if (node.type != NodeType.UNQUALIFIED_ACCESS_EXPR) return false
    for (child in node.children) {
      if (child.type == NodeType.ARGUMENT_LIST) {
        return true
      }
    }
    return false
  }

  private fun formatAmendsExtendsClause(node: Node): FormatNode {
    val prefix = formatGeneric(node.children.dropLast(1), spaceOrLine())
    // string constant
    val suffix = Indent(listOf(format(node.children.last())))