Format interpolated expressions as single line (#1247)

This forces iterpolated expressions to be single-line, so that newline
literals within the bounds of two string delimiters can be seen as
verbatime newlines in the resulting string.

Edge case: in the case of a line comment, it's not possible to keep
this as a single line expression.
These are kept as multi-line expressions.

Also:

* Remove `ForceWrap`, this node is not used.
* Rename `StringConstant` -> `StringChars`
This commit is contained in:
Daniel Chao
2025-10-24 03:23:41 -07:00
committed by GitHub
parent cce49a40fa
commit 3223083324
12 changed files with 355 additions and 126 deletions

View File

@@ -37,6 +37,9 @@ internal class Builder(sourceText: String) {
private var id: Int = 0
private val source: CharArray = sourceText.toCharArray()
private var prevNode: Node? = null
private var noNewlines = false
private class CannotAvoidNewline : RuntimeException()
fun format(node: Node): FormatNode {
prevNode = node
@@ -49,9 +52,8 @@ internal class Builder(sourceText: String) {
NodeType.TERMINAL,
NodeType.MODIFIER,
NodeType.IDENTIFIER,
NodeType.STRING_CONSTANT,
NodeType.STRING_CHARS,
NodeType.STRING_ESCAPE,
NodeType.SINGLE_LINE_STRING_LITERAL_EXPR,
NodeType.INT_LITERAL_EXPR,
NodeType.FLOAT_LITERAL_EXPR,
NodeType.BOOL_LITERAL_EXPR,
@@ -64,9 +66,10 @@ internal class Builder(sourceText: String) {
NodeType.NOTHING_TYPE,
NodeType.SHEBANG,
NodeType.OPERATOR -> Text(node.text(source))
NodeType.STRING_NEWLINE -> ForceLine
NodeType.STRING_NEWLINE -> mustForceLine()
NodeType.MODULE_DECLARATION -> formatModuleDeclaration(node)
NodeType.MODULE_DEFINITION -> formatModuleDefinition(node)
NodeType.SINGLE_LINE_STRING_LITERAL_EXPR -> formatSingleLineString(node)
NodeType.MULTI_LINE_STRING_LITERAL_EXPR -> formatMultilineString(node)
NodeType.ANNOTATION -> formatAnnotation(node)
NodeType.TYPEALIAS -> formatTypealias(node)
@@ -77,13 +80,13 @@ internal class Builder(sourceText: String) {
NodeType.PARAMETER_LIST_ELEMENTS -> formatParameterListElements(node)
NodeType.TYPE_PARAMETER_LIST -> formatTypeParameterList(node)
NodeType.TYPE_PARAMETER_LIST_ELEMENTS -> formatParameterListElements(node)
NodeType.TYPE_PARAMETER -> Group(newId(), formatGeneric(node.children, SpaceOrLine))
NodeType.TYPE_PARAMETER -> Group(newId(), formatGeneric(node.children, spaceOrLine()))
NodeType.PARAMETER -> formatParameter(node)
NodeType.EXTENDS_CLAUSE,
NodeType.AMENDS_CLAUSE -> formatAmendsExtendsClause(node)
NodeType.IMPORT_LIST -> formatImportList(node)
NodeType.IMPORT -> formatImport(node)
NodeType.IMPORT_ALIAS -> Group(newId(), formatGeneric(node.children, SpaceOrLine))
NodeType.IMPORT_ALIAS -> Group(newId(), formatGeneric(node.children, spaceOrLine()))
NodeType.CLASS -> formatClass(node)
NodeType.CLASS_HEADER -> formatClassHeader(node)
NodeType.CLASS_HEADER_EXTENDS -> formatClassHeaderExtends(node)
@@ -167,7 +170,7 @@ internal class Builder(sourceText: String) {
private fun formatModule(node: Node): FormatNode {
val nodes =
formatGeneric(node.children) { prev, next ->
if (prev.linesBetween(next) > 1) TWO_NEWLINES else ForceLine
if (prev.linesBetween(next) > 1) TWO_NEWLINES else forceLine()
}
return Nodes(nodes)
}
@@ -179,7 +182,7 @@ internal class Builder(sourceText: String) {
private fun formatModuleDefinition(node: Node): FormatNode {
val (prefixes, nodes) = splitPrefixes(node.children)
val fnodes =
formatGenericWithGen(nodes, SpaceOrLine) { node, next ->
formatGenericWithGen(nodes, spaceOrLine()) { node, next ->
if (next == null) {
indent(format(node))
} else {
@@ -191,7 +194,7 @@ internal class Builder(sourceText: String) {
res
} else {
val sep = getSeparator(prefixes.last(), nodes.first())
Nodes(formatGeneric(prefixes, SpaceOrLine) + listOf(sep, res))
Nodes(formatGeneric(prefixes, spaceOrLine()) + listOf(sep, res))
}
}
@@ -217,10 +220,10 @@ internal class Builder(sourceText: String) {
// short circuit
if (node.children.size == 1) return format(node.children[0])
val first = listOf(format(node.children[0]), Line)
val first = listOf(format(node.children[0]), line())
val nodes =
formatGeneric(node.children.drop(1)) { n1, _ ->
if (n1.type == NodeType.TERMINAL) null else Line
if (n1.type == NodeType.TERMINAL) null else line()
}
return Group(newId(), first + listOf(Indent(nodes)))
}
@@ -240,28 +243,28 @@ internal class Builder(sourceText: String) {
}
private fun formatAmendsExtendsClause(node: Node): FormatNode {
val prefix = formatGeneric(node.children.dropLast(1), SpaceOrLine)
val prefix = formatGeneric(node.children.dropLast(1), spaceOrLine())
// string constant
val suffix = Indent(listOf(format(node.children.last())))
return Group(newId(), prefix + listOf(SpaceOrLine) + suffix)
return Group(newId(), prefix + listOf(spaceOrLine()) + suffix)
}
private fun formatImport(node: Node): FormatNode {
return Group(
newId(),
formatGenericWithGen(node.children, SpaceOrLine) { node, _ ->
formatGenericWithGen(node.children, spaceOrLine()) { node, _ ->
if (node.isTerminal("import")) format(node) else indent(format(node))
},
)
}
private fun formatAnnotation(node: Node): FormatNode {
return Group(newId(), formatGeneric(node.children, SpaceOrLine))
return Group(newId(), formatGeneric(node.children, spaceOrLine()))
}
private fun formatTypealias(node: Node): FormatNode {
val nodes =
groupNonPrefixes(node) { children -> Group(newId(), formatGeneric(children, SpaceOrLine)) }
groupNonPrefixes(node) { children -> Group(newId(), formatGeneric(children, spaceOrLine())) }
return Nodes(nodes)
}
@@ -270,19 +273,19 @@ internal class Builder(sourceText: String) {
}
private fun formatTypealiasBody(node: Node): FormatNode {
return Indent(formatGeneric(node.children, SpaceOrLine))
return Indent(formatGeneric(node.children, spaceOrLine()))
}
private fun formatClass(node: Node): FormatNode {
return Nodes(formatGeneric(node.children, SpaceOrLine))
return Nodes(formatGeneric(node.children, spaceOrLine()))
}
private fun formatClassHeader(node: Node): FormatNode {
return groupOnSpace(formatGeneric(node.children, SpaceOrLine))
return groupOnSpace(formatGeneric(node.children, spaceOrLine()))
}
private fun formatClassHeaderExtends(node: Node): FormatNode {
return indent(Group(newId(), formatGeneric(node.children, SpaceOrLine)))
return indent(Group(newId(), formatGeneric(node.children, spaceOrLine())))
}
private fun formatClassBody(node: Node): FormatNode {
@@ -291,14 +294,14 @@ internal class Builder(sourceText: String) {
// no members
return Nodes(formatGeneric(children, null))
}
return Group(newId(), formatGeneric(children, ForceLine))
return Group(newId(), formatGeneric(children, forceLine()))
}
private fun formatClassBodyElements(node: Node): FormatNode {
val nodes =
formatGeneric(node.children) { prev, next ->
val lineDiff = prev.linesBetween(next)
if (lineDiff > 1 || lineDiff == 0) TWO_NEWLINES else ForceLine
if (lineDiff > 1 || lineDiff == 0) TWO_NEWLINES else forceLine()
}
return Indent(nodes)
}
@@ -313,8 +316,9 @@ internal class Builder(sourceText: String) {
val nodes =
groupNonPrefixes(node) { children ->
val nodes =
formatGenericWithGen(children, { _, _ -> if (sameLine) Space else SpaceOrLine }) { node, _
->
formatGenericWithGen(children, { _, _ -> if (sameLine) Space else spaceOrLine() }) {
node,
_ ->
if ((node.isExpressionOrPropertyBody()) && !sameLine) {
indent(format(node))
} else format(node)
@@ -330,11 +334,11 @@ internal class Builder(sourceText: String) {
type == NodeType.OBJECT_PROPERTY_BODY
private fun formatClassPropertyHeader(node: Node): FormatNode {
return Group(newId(), formatGeneric(node.children, SpaceOrLine))
return Group(newId(), formatGeneric(node.children, spaceOrLine()))
}
private fun formatClassPropertyHeaderBegin(node: Node): FormatNode {
return Group(newId(), formatGeneric(node.children, SpaceOrLine))
return Group(newId(), formatGeneric(node.children, spaceOrLine()))
}
private fun formatClassPropertyBody(node: Node): FormatNode {
@@ -349,7 +353,7 @@ internal class Builder(sourceText: String) {
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)
prefixes += getSeparator(prefixNodes.last(), node.children[idx], forceLine())
node.children.subList(idx, node.children.size)
}
@@ -359,7 +363,7 @@ internal class Builder(sourceText: String) {
val headerGroupId = newId()
val methodGroupId = newId()
val headerNodes =
formatGenericWithGen(header, SpaceOrLine) { node, _ ->
formatGenericWithGen(header, spaceOrLine()) { node, _ ->
if (node.type == NodeType.PARAMETER_LIST) {
formatParameterList(node, id = headerGroupId)
} else {
@@ -385,7 +389,7 @@ internal class Builder(sourceText: String) {
if (isSameLineBody) {
formatGeneric(bodyNodes, Space)
} else {
formatGenericWithGen(bodyNodes, SpaceOrLine) { node, next ->
formatGenericWithGen(bodyNodes, spaceOrLine()) { node, next ->
if (next == null) indent(format(node)) else format(node)
}
}
@@ -409,7 +413,7 @@ internal class Builder(sourceText: String) {
private fun formatParameter(node: Node): FormatNode {
if (node.children.size == 1) return format(node.children[0]) // underscore
return Group(newId(), formatGeneric(node.children, SpaceOrLine))
return Group(newId(), formatGeneric(node.children, spaceOrLine()))
}
private fun formatParameterList(node: Node, id: Int? = null): FormatNode {
@@ -420,9 +424,9 @@ internal class Builder(sourceText: String) {
if (prev.isTerminal("(") || next.isTerminal(")")) {
if (next.isTerminal(")")) {
// trailing comma
IfWrap(groupId, nodes(Text(","), Line), Line)
} else Line
} else SpaceOrLine
ifWrap(groupId, nodes(Text(","), line()), line())
} else line()
} else spaceOrLine()
}
return if (id != null) Nodes(nodes) else Group(groupId, nodes)
}
@@ -436,12 +440,12 @@ internal class Builder(sourceText: String) {
node.children,
{ prev, next ->
if (prev.isTerminal("(") || next.isTerminal(")")) {
val node = if (hasTrailingLambda) Empty else Line
val node = if (hasTrailingLambda) Empty else line()
if (next.isTerminal(")") && !hasTrailingLambda) {
// trailing comma
IfWrap(groupId, nodes(Text(","), node), node)
ifWrap(groupId, nodes(Text(","), node), node)
} else node
} else SpaceOrLine
} else spaceOrLine()
},
) { node, _ ->
if (node.type == NodeType.ARGUMENT_LIST_ELEMENTS) {
@@ -460,9 +464,9 @@ internal class Builder(sourceText: String) {
return if (twoBy2) {
val pairs = pairArguments(children)
val nodes =
formatGenericWithGen(pairs, SpaceOrLine) { node, _ ->
formatGenericWithGen(pairs, spaceOrLine()) { node, _ ->
if (node.type == NodeType.ARGUMENT_LIST_ELEMENTS) {
Group(newId(), formatGeneric(node.children, SpaceOrLine))
Group(newId(), formatGeneric(node.children, spaceOrLine()))
} else {
format(node)
}
@@ -473,17 +477,17 @@ internal class Builder(sourceText: String) {
val splitIndex = children.indexOfLast { it.type in SAME_LINE_EXPRS }
val normalParams = children.subList(0, splitIndex)
val lastParam = children.subList(splitIndex, children.size)
val trailingNode = if (endsWithClosingBracket(children[splitIndex])) Empty else Line
val lastNodes = formatGeneric(lastParam, SpaceOrLine)
val trailingNode = if (endsWithClosingBracket(children[splitIndex])) Empty else line()
val lastNodes = formatGeneric(lastParam, spaceOrLine())
if (normalParams.isEmpty()) {
nodes(Group(newId(), lastNodes), trailingNode)
} else {
val separator = getSeparator(normalParams.last(), lastParam[0], Space)
val paramNodes = formatGeneric(normalParams, SpaceOrLine)
val paramNodes = formatGeneric(normalParams, spaceOrLine())
nodes(Group(newId(), paramNodes), separator, Group(newId(), lastNodes), trailingNode)
}
} else {
Indent(formatGeneric(children, SpaceOrLine))
Indent(formatGeneric(children, spaceOrLine()))
}
}
@@ -531,7 +535,7 @@ internal class Builder(sourceText: String) {
}
private fun formatParameterListElements(node: Node): FormatNode {
return Indent(formatGeneric(node.children, SpaceOrLine))
return Indent(formatGeneric(node.children, spaceOrLine()))
}
private fun formatTypeParameterList(node: Node): FormatNode {
@@ -542,9 +546,9 @@ internal class Builder(sourceText: String) {
if (prev.isTerminal("<") || next.isTerminal(">")) {
if (next.isTerminal(">")) {
// trailing comma
IfWrap(id, nodes(Text(","), Line), Line)
} else Line
} else SpaceOrLine
ifWrap(id, nodes(Text(","), line()), line())
} else line()
} else spaceOrLine()
}
return Group(id, nodes)
}
@@ -552,10 +556,10 @@ internal class Builder(sourceText: String) {
private fun formatObjectParameterList(node: Node): FormatNode {
// object param lists don't have trailing commas, as they have a trailing ->
val groupId = newId()
val nonWrappingNodes = Nodes(formatGeneric(node.children, SpaceOrLine))
val nonWrappingNodes = Nodes(formatGeneric(node.children, spaceOrLine()))
// double indent the params if they wrap
val wrappingNodes = indent(Indent(listOf(Line) + nonWrappingNodes))
return Group(groupId, listOf(IfWrap(groupId, wrappingNodes, nodes(Space, nonWrappingNodes))))
val wrappingNodes = indent(Indent(listOf(line()) + nonWrappingNodes))
return Group(groupId, listOf(ifWrap(groupId, wrappingNodes, nodes(Space, nonWrappingNodes))))
}
private fun formatObjectBody(node: Node): FormatNode {
@@ -568,8 +572,8 @@ internal class Builder(sourceText: String) {
if (next.type == NodeType.OBJECT_PARAMETER_LIST) Empty
else if (prev.isTerminal("{") || next.isTerminal("}")) {
val lines = prev.linesBetween(next)
if (lines == 0) SpaceOrLine else ForceLine
} else SpaceOrLine
if (lines == 0) spaceOrLine() else forceSpaceyLine()
} else spaceOrLine()
},
) { node, _ ->
if (node.type == NodeType.OBJECT_MEMBER_LIST) {
@@ -584,8 +588,8 @@ internal class Builder(sourceText: String) {
formatGeneric(node.children) { prev, next ->
val lines = prev.linesBetween(next)
when (lines) {
0 -> IfWrap(groupId, Line, Text("; "))
1 -> ForceLine
0 -> ifWrap(groupId, line(), Text("; "))
1 -> forceLine()
else -> TWO_NEWLINES
}
}
@@ -593,7 +597,7 @@ internal class Builder(sourceText: String) {
}
private fun formatObjectEntryHeader(node: Node): FormatNode {
return Group(newId(), formatGeneric(node.children, SpaceOrLine))
return Group(newId(), formatGeneric(node.children, spaceOrLine()))
}
private fun formatForGenerator(node: Node): FormatNode {
@@ -603,7 +607,7 @@ internal class Builder(sourceText: String) {
prev.type == NodeType.FOR_GENERATOR_HEADER || next.type == NodeType.FOR_GENERATOR_HEADER
) {
Space
} else SpaceOrLine
} else spaceOrLine()
}
return Group(newId(), nodes)
}
@@ -611,7 +615,7 @@ internal class Builder(sourceText: String) {
private fun formatForGeneratorHeader(node: Node): FormatNode {
val nodes =
formatGeneric(node.children) { prev, next ->
if (prev.isTerminal("(") || next.isTerminal(")")) Line else null
if (prev.isTerminal("(") || next.isTerminal(")")) line() else null
}
return Group(newId(), nodes)
}
@@ -620,7 +624,7 @@ internal class Builder(sourceText: String) {
val nodes =
formatGenericWithGen(
node.children,
{ _, next -> if (next.type in SAME_LINE_EXPRS) Space else SpaceOrLine },
{ _, next -> if (next.type in SAME_LINE_EXPRS) Space else spaceOrLine() },
) { node, _ ->
if (node.type.isExpression && node.type !in SAME_LINE_EXPRS) indent(format(node))
else format(node)
@@ -629,7 +633,7 @@ internal class Builder(sourceText: String) {
}
private fun formatForGeneratorHeaderDefinitionHeader(node: Node): FormatNode {
val nodes = formatGeneric(node.children, SpaceOrLine)
val nodes = formatGeneric(node.children, spaceOrLine())
return Group(newId(), nodes)
}
@@ -643,7 +647,7 @@ internal class Builder(sourceText: String) {
) {
Space
} else {
SpaceOrLine
spaceOrLine()
}
}
return Group(newId(), nodes)
@@ -653,7 +657,9 @@ internal class Builder(sourceText: String) {
val nodes =
formatGenericWithGen(
node.children,
{ prev, next -> if (prev.isTerminal("(") || next.isTerminal(")")) Line else SpaceOrLine },
{ prev, next ->
if (prev.isTerminal("(") || next.isTerminal(")")) line() else spaceOrLine()
},
) { node, _ ->
if (!node.type.isAffix && node.type != NodeType.TERMINAL) {
indent(format(node))
@@ -664,7 +670,7 @@ internal class Builder(sourceText: String) {
private fun formatMemberPredicate(node: Node): FormatNode {
val nodes =
formatGenericWithGen(node.children, SpaceOrLine) { node, next ->
formatGenericWithGen(node.children, spaceOrLine()) { node, next ->
if (next == null && node.type != NodeType.OBJECT_BODY) {
indent(format(node))
} else format(node)
@@ -672,8 +678,46 @@ internal class Builder(sourceText: String) {
return Group(newId(), nodes)
}
private fun formatStringParts(nodes: List<Node>): List<FormatNode> {
return buildList {
var isInStringInterpolation = false
val cursor = nodes.iterator().peekable()
var prev: Node? = null
while (cursor.hasNext()) {
if (isInStringInterpolation) {
val prevNoNewlines = noNewlines
noNewlines = true
val elems = cursor.takeUntilBefore { it.isTerminal(")") }
getSeparator(prev!!, elems.first(), { _, _ -> null })?.let { add(it) }
val formatted =
try {
formatGeneric(elems, null)
} catch (_: CannotAvoidNewline) {
noNewlines = false
formatGeneric(elems, null)
}
addAll(formatted)
getSeparator(elems.last(), cursor.peek(), { _, _ -> null })?.let { add(it) }
noNewlines = prevNoNewlines
isInStringInterpolation = false
continue
}
val elem = cursor.next()
if (elem.type == NodeType.TERMINAL && elem.text().endsWith("(")) {
isInStringInterpolation = true
}
add(format(elem))
prev = elem
}
}
}
private fun formatSingleLineString(node: Node): FormatNode {
return Group(newId(), formatStringParts(node.children))
}
private fun formatMultilineString(node: Node): FormatNode {
val nodes = formatGeneric(node.children, null)
val nodes = formatStringParts(node.children)
return MultilineStringGroup(node.children.last().span.colBegin, nodes)
}
@@ -682,7 +726,7 @@ internal class Builder(sourceText: String) {
formatGeneric(node.children) { _, next ->
if (next.type == NodeType.IF_ELSE_EXPR && next.children[0].type == NodeType.IF_EXPR) {
Space
} else SpaceOrLine
} else spaceOrLine()
}
return Group(newId(), nodes)
}
@@ -690,7 +734,7 @@ internal class Builder(sourceText: String) {
private fun formatIfHeader(node: Node): FormatNode {
val nodes =
formatGeneric(node.children) { _, next ->
if (next.type == NodeType.IF_CONDITION) Space else SpaceOrLine
if (next.type == NodeType.IF_CONDITION) Space else spaceOrLine()
}
return Group(newId(), nodes)
}
@@ -698,7 +742,7 @@ internal class Builder(sourceText: String) {
private fun formatIfCondition(node: Node): FormatNode {
val nodes =
formatGeneric(node.children) { prev, next ->
if (prev.isTerminal("(") || next.isTerminal(")")) Line else SpaceOrLine
if (prev.isTerminal("(") || next.isTerminal(")")) line() else spaceOrLine()
}
return Group(newId(), nodes)
}
@@ -723,12 +767,12 @@ internal class Builder(sourceText: String) {
}
private fun formatNewExpr(node: Node): FormatNode {
val nodes = formatGeneric(node.children, SpaceOrLine)
val nodes = formatGeneric(node.children, spaceOrLine())
return Group(newId(), nodes)
}
private fun formatNewHeader(node: Node): FormatNode {
val nodes = formatGeneric(node.children, SpaceOrLine)
val nodes = formatGeneric(node.children, spaceOrLine())
return Group(newId(), nodes)
}
@@ -737,7 +781,9 @@ internal class Builder(sourceText: String) {
val nodes =
formatGenericWithGen(
node.children,
{ prev, next -> if (prev.isTerminal("(") || next.isTerminal(")")) Line else SpaceOrLine },
{ prev, next ->
if (prev.isTerminal("(") || next.isTerminal(")")) line() else spaceOrLine()
},
) { node, _ ->
if (node.type.isExpression) indent(format(node)) else format(node)
}
@@ -757,7 +803,7 @@ internal class Builder(sourceText: String) {
val expr = body.children.find { it.type.isExpression }!!
isSameLineExpr(expr)
}
val sep = if (sameLine) Space else SpaceOrLine
val sep = if (sameLine) Space else spaceOrLine()
val bodySep = getSeparator(params.last(), rest.first(), sep)
val nodes = formatGeneric(params, sep)
@@ -775,7 +821,7 @@ internal class Builder(sourceText: String) {
val nodes =
formatGenericWithGen(
node.children,
{ _, next -> if (next.type == NodeType.LET_PARAMETER_DEFINITION) Space else SpaceOrLine },
{ _, next -> if (next.type == NodeType.LET_PARAMETER_DEFINITION) Space else spaceOrLine() },
) { node, next ->
if (next == null) {
if (node.type == NodeType.LET_EXPR) {
@@ -791,7 +837,7 @@ internal class Builder(sourceText: String) {
private fun formatLetParameterDefinition(node: Node): FormatNode {
val nodes =
formatGeneric(node.children) { prev, next ->
if (prev.isTerminal("(") || next.isTerminal(")")) Line else SpaceOrLine
if (prev.isTerminal("(") || next.isTerminal(")")) line() else spaceOrLine()
}
return Group(newId(), nodes)
}
@@ -810,17 +856,17 @@ internal class Builder(sourceText: String) {
when (prev.text()) {
".",
"?." -> null
"-" -> SpaceOrLine
"-" -> spaceOrLine()
else -> Space
}
} else if (next.type == NodeType.OPERATOR) {
when (next.text()) {
".",
"?." -> if (hasMultipleLambdas) ForceLine else Line
"?." -> if (hasMultipleLambdas) forceLine() else line()
"-" -> Space
else -> SpaceOrLine
else -> spaceOrLine()
}
} else SpaceOrLine
} else spaceOrLine()
}
val shouldGroup = node.children.size == flat.size
return Group(newId(), indentAfterFirstNewline(nodes, shouldGroup))
@@ -843,7 +889,7 @@ internal class Builder(sourceText: String) {
val nodes =
formatGenericWithGen(
node.children,
{ prev, next -> if (prev.isTerminal("(") || next.isTerminal(")")) Line else null },
{ prev, next -> if (prev.isTerminal("(") || next.isTerminal(")")) line() else null },
) { node, _ ->
if (node.type.isExpression) indent(format(node)) else format(node)
}
@@ -851,13 +897,13 @@ internal class Builder(sourceText: String) {
}
private fun formatDeclaredType(node: Node): FormatNode {
return Nodes(formatGeneric(node.children, SpaceOrLine))
return Nodes(formatGeneric(node.children, spaceOrLine()))
}
private fun formatConstrainedType(node: Node): FormatNode {
val nodes =
formatGeneric(node.children) { _, next ->
if (next.type == NodeType.CONSTRAINED_TYPE_CONSTRAINT) null else SpaceOrLine
if (next.type == NodeType.CONSTRAINED_TYPE_CONSTRAINT) null else spaceOrLine()
}
return Group(newId(), nodes)
}
@@ -866,7 +912,7 @@ internal class Builder(sourceText: String) {
val nodes =
formatGeneric(node.children) { prev, next ->
when {
next.isTerminal("|") -> SpaceOrLine
next.isTerminal("|") -> spaceOrLine()
prev.isTerminal("|") -> Space
else -> null
}
@@ -878,7 +924,9 @@ internal class Builder(sourceText: String) {
val nodes =
formatGenericWithGen(
node.children,
{ prev, next -> if (prev.isTerminal("(") || next.isTerminal(")")) Line else SpaceOrLine },
{ prev, next ->
if (prev.isTerminal("(") || next.isTerminal(")")) line() else spaceOrLine()
},
) { node, next ->
if (next == null) indent(format(node)) else format(node)
}
@@ -890,13 +938,13 @@ internal class Builder(sourceText: String) {
val groupId = newId()
val nodes =
formatGeneric(node.children) { prev, next ->
if (prev.isTerminal("(") || next.isTerminal(")")) Line else SpaceOrLine
if (prev.isTerminal("(") || next.isTerminal(")")) line() else spaceOrLine()
}
return Group(groupId, nodes)
}
private fun formatParenthesizedTypeElements(node: Node): FormatNode {
return indent(Group(newId(), formatGeneric(node.children, SpaceOrLine)))
return indent(Group(newId(), formatGeneric(node.children, spaceOrLine())))
}
private fun formatTypeAnnotation(node: Node): FormatNode {
@@ -907,7 +955,7 @@ internal class Builder(sourceText: String) {
val nodes = mutableListOf<FormatNode>()
val children = node.children.groupBy { it.type.isAffix }
if (children[true] != null) {
nodes += formatGeneric(children[true]!!, SpaceOrLine)
nodes += formatGeneric(children[true]!!, spaceOrLine())
}
val modifiers = children[false]!!.sortedBy(::modifierPrecedence)
nodes += formatGeneric(modifiers, Space)
@@ -918,8 +966,8 @@ internal class Builder(sourceText: String) {
val nodes = mutableListOf<FormatNode>()
val children = node.children.groupBy { it.type.isAffix }
if (children[true] != null) {
nodes += formatGeneric(children[true]!!, SpaceOrLine)
nodes += ForceLine
nodes += formatGeneric(children[true]!!, spaceOrLine())
nodes += forceLine()
}
val allImports = children[false]!!
@@ -952,27 +1000,27 @@ internal class Builder(sourceText: String) {
if (absolute != null) {
for ((i, imp) in absolute.withIndex()) {
if (i > 0) nodes += ForceLine
if (i > 0) nodes += forceLine()
nodes += format(imp)
}
if (projects != null || relatives != null) nodes += ForceLine
if (projects != null || relatives != null) nodes += forceLine()
shouldNewline = true
}
if (projects != null) {
if (shouldNewline) nodes += ForceLine
if (shouldNewline) nodes += forceLine()
for ((i, imp) in projects.withIndex()) {
if (i > 0) nodes += ForceLine
if (i > 0) nodes += forceLine()
nodes += format(imp)
}
if (relatives != null) nodes += ForceLine
if (relatives != null) nodes += forceLine()
shouldNewline = true
}
if (relatives != null) {
if (shouldNewline) nodes += ForceLine
if (shouldNewline) nodes += forceLine()
for ((i, imp) in relatives.withIndex()) {
if (i > 0) nodes += ForceLine
if (i > 0) nodes += forceLine()
nodes += format(imp)
}
}
@@ -1005,7 +1053,7 @@ internal class Builder(sourceText: String) {
// skip semicolons
val children = children.filter { !it.isSemicolon() }
// short circuit
if (children.isEmpty()) return listOf(SpaceOrLine)
if (children.isEmpty()) return listOf(spaceOrLine())
if (children.size == 1) return listOf(format(children[0]))
val nodes = mutableListOf<FormatNode>()
@@ -1046,19 +1094,19 @@ internal class Builder(sourceText: String) {
val prefixes = children.subList(0, index)
val nodes = children.subList(index, children.size)
val res = mutableListOf<FormatNode>()
res += formatGeneric(prefixes, SpaceOrLine)
res += formatGeneric(prefixes, spaceOrLine())
res += getSeparator(prefixes.last(), nodes.first())
res += groupFn(nodes)
return res
}
private fun getImportUrl(node: Node): String =
node.findChildByType(NodeType.STRING_CONSTANT)!!.text().drop(1).dropLast(1)
node.findChildByType(NodeType.STRING_CHARS)!!.text().drop(1).dropLast(1)
private fun getSeparator(
prev: Node,
next: Node,
separator: FormatNode = SpaceOrLine,
separator: FormatNode = spaceOrLine(),
): FormatNode {
return getSeparator(prev, next) { _, _ -> separator }!!
}
@@ -1073,19 +1121,21 @@ internal class Builder(sourceText: String) {
if (prev.linesBetween(next) > 1) {
TWO_NEWLINES
} else {
ForceLine
mustForceLine()
}
}
hasTrailingAffix(prev, next) -> Space
prev.type == NodeType.DOC_COMMENT || prev.type == NodeType.ANNOTATION -> ForceLine
prev.type == NodeType.DOC_COMMENT -> mustForceLine()
prev.type == NodeType.ANNOTATION -> forceLine()
prev.type in FORCE_LINE_AFFIXES || next.type.isAffix -> {
if (prev.linesBetween(next) > 1) {
TWO_NEWLINES
} else {
ForceLine
mustForceLine()
}
}
prev.type == NodeType.BLOCK_COMMENT -> if (prev.linesBetween(next) > 0) ForceLine else Space
prev.type == NodeType.BLOCK_COMMENT ->
if (prev.linesBetween(next) > 0) forceSpaceyLine() else Space
next.type in EMPTY_SUFFIXES ||
prev.isTerminal("[", "!", "@", "[[") ||
next.isTerminal("]", "?", ",") -> null
@@ -1098,6 +1148,30 @@ internal class Builder(sourceText: String) {
}
}
private fun line(): FormatNode {
return if (noNewlines) Empty else Line
}
private fun spaceOrLine(): FormatNode {
return if (noNewlines) Space else SpaceOrLine
}
private fun mustForceLine(): FormatNode {
return if (noNewlines) throw CannotAvoidNewline() else ForceLine
}
private fun forceLine(): FormatNode {
return if (noNewlines) Empty else ForceLine
}
private fun forceSpaceyLine(): FormatNode {
return if (noNewlines) Space else ForceLine
}
private fun ifWrap(id: Int, ifWrap: FormatNode, ifNotWrap: FormatNode): FormatNode {
return if (noNewlines) ifNotWrap else IfWrap(id, ifWrap, ifNotWrap)
}
private fun hasTrailingAffix(node: Node, next: Node): Boolean {
if (node.span.lineEnd < next.span.lineBegin) return false
var n: Node? = next
@@ -1201,8 +1275,8 @@ internal class Builder(sourceText: String) {
private class ImportComparator(private val source: CharArray) : Comparator<Node> {
override fun compare(o1: Node, o2: Node): Int {
val import1 = o1.findChildByType(NodeType.STRING_CONSTANT)?.text(source)
val import2 = o2.findChildByType(NodeType.STRING_CONSTANT)?.text(source)
val import1 = o1.findChildByType(NodeType.STRING_CHARS)?.text(source)
val import2 = o2.findChildByType(NodeType.STRING_CHARS)?.text(source)
if (import1 == null || import2 == null) {
// should never happen
throw RuntimeException("ImportComparator: not an import")
@@ -1222,7 +1296,7 @@ internal class Builder(sourceText: String) {
// returns true if this node is not an affix or terminal
private fun Node.isProper(): Boolean = !type.isAffix && type != NodeType.TERMINAL
private fun <T> List<T>.splitOn(pred: (T) -> Boolean): Pair<List<T>, List<T>> {
private inline fun <T> List<T>.splitOn(pred: (T) -> Boolean): Pair<List<T>, List<T>> {
val index = indexOfFirst { pred(it) }
return if (index == -1) {
Pair(this, emptyList())
@@ -1243,6 +1317,52 @@ internal class Builder(sourceText: String) {
return false
}
class PeekableIterator<T>(private val iterator: Iterator<T>) : Iterator<T> {
private var peek: T? = null
private var hasPeek = false
override fun next(): T {
return if (hasPeek) {
hasPeek = false
peek!!
} else {
iterator.next()
}
}
override fun hasNext(): Boolean {
return if (hasPeek) true else iterator.hasNext()
}
fun peek(): T {
if (!hasNext()) {
throw NoSuchElementException()
}
if (hasPeek) {
return peek!!
}
peek = iterator.next()
hasPeek = true
return peek!!
}
inline fun takeUntilBefore(predicate: (T) -> Boolean): List<T> {
return buildList {
while (true) {
if (!hasNext() || predicate(peek())) {
return@buildList
}
add(next())
}
}
}
}
private fun <T> Iterator<T>.peekable(): PeekableIterator<T> {
return PeekableIterator(this)
}
companion object {
private val ABSOLUTE_URL_REGEX = Regex("""\w+:.*""")

View File

@@ -17,7 +17,6 @@ package org.pkl.formatter
import org.pkl.formatter.ast.Empty
import org.pkl.formatter.ast.ForceLine
import org.pkl.formatter.ast.ForceWrap
import org.pkl.formatter.ast.FormatNode
import org.pkl.formatter.ast.Group
import org.pkl.formatter.ast.IfWrap
@@ -56,11 +55,6 @@ internal class Generator {
}
node.nodes.forEach { node(it, wrap) }
}
is ForceWrap -> {
wrapped += node.id
val wrap = Wrap.ENABLED
node.nodes.forEach { node(it, wrap) }
}
is IfWrap -> {
if (wrapped.contains(node.id)) {
node(node.ifWrap, Wrap.ENABLED)

View File

@@ -30,14 +30,14 @@ sealed interface FormatNode {
is Nodes -> nodes.sumOf { it.width(wrapped) }
is Group -> nodes.sumOf { it.width(wrapped) }
is Indent -> nodes.sumOf { it.width(wrapped) }
is ForceWrap -> nodes.sumOf { it.width(wrapped + id) }
is IfWrap -> if (id in wrapped) ifWrap.width(wrapped) else ifNotWrap.width(wrapped)
is Text -> text.length
is SpaceOrLine,
is Space -> 1
is ForceLine,
SpaceOrLine,
Space -> 1
ForceLine,
is MultilineStringGroup -> Generator.MAX
else -> 0
Empty -> 0
Line -> 0
}
}
@@ -59,8 +59,6 @@ data class Nodes(val nodes: List<FormatNode>) : FormatNode
data class Group(val id: Int, val nodes: List<FormatNode>) : FormatNode
data class ForceWrap(val id: Int, val nodes: List<FormatNode>) : FormatNode
data class MultilineStringGroup(val endQuoteCol: Int, val nodes: List<FormatNode>) : FormatNode
data class IfWrap(val id: Int, val ifWrap: FormatNode, val ifNotWrap: FormatNode) : FormatNode

View File

@@ -0,0 +1,7 @@
foo1 = "some string"
foo2 = "some string with \( new { x = 1; y = 2 }) interpolation"
foo3 = "some reeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaally long string with \( new { x = 1; y = 2 } ) interpolation"
foo4 = "some reeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaally long string with \( foo.bar.baz() ) qualified access"

View File

@@ -0,0 +1,51 @@
prop1 =
"""
asd \(new { bar = 1 }) asd
"""
prop2 =
"""
\(let (bar = 15) bar + new { qux = 15 }.toString())
"""
prop3 =
"""
\(new {
// some comment
foo = 1
// some comment
bar = 2
})
"""
prop4 =
"""
\(1 + /* block comment */ 2)
"""
prop5 =
"""
\("""
foo
bar
baz
""")
"""
prop6 = "\(// some line comment
/* some block comment */
"\("""
one
two
three
""")"
// some line comment again
)"
prop7 = "\(
5
// trailing line comment
)"
prop8 = "\(new { foo = 1 bar = 2 baz = 3 })"

View File

@@ -1,8 +1,6 @@
foo =
"""
asd \(new {
bar = 1
}) asd
asd \(new { bar = 1 }) asd
"""
bar =

View File

@@ -0,0 +1,9 @@
foo1 = "some string"
foo2 = "some string with \(new { x = 1; y = 2 }) interpolation"
foo3 =
"some reeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaally long string with \(new { x = 1; y = 2 }) interpolation"
foo4 =
"some reeeeaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaally long string with \(foo.bar.baz()) qualified access"

View File

@@ -0,0 +1,52 @@
prop1 =
"""
asd \(new { bar = 1 }) asd
"""
prop2 =
"""
\(let (bar = 15) bar + new { qux = 15 }.toString())
"""
prop3 =
"""
\(new {
// some comment
foo = 1
// some comment
bar = 2
})
"""
prop4 =
"""
\(1 + /* block comment */ 2)
"""
prop5 =
"""
\("""
foo
bar
baz
""")
"""
prop6 =
"\( // some line comment
/* some block comment */
"\("""
one
two
three
""")"
// some line comment again
)"
prop7 =
"\(5
// trailing line comment
)"
prop8 = "\(new { foo = 1; bar = 2; baz = 3 })"

View File

@@ -965,7 +965,7 @@ public class GenericParser {
case STRING_PART -> {
var tk = next();
if (!tk.text(lexer).isEmpty()) {
children.add(make(NodeType.STRING_CONSTANT, tk.span));
children.add(make(NodeType.STRING_CHARS, tk.span));
}
}
case STRING_ESCAPE_NEWLINE,
@@ -1004,7 +1004,7 @@ public class GenericParser {
case STRING_PART -> {
var tk = next();
if (!tk.text(lexer).isEmpty()) {
children.add(make(NodeType.STRING_CONSTANT, tk.span));
children.add(make(NodeType.STRING_CHARS, tk.span));
}
}
case STRING_NEWLINE -> children.add(make(NodeType.STRING_NEWLINE, next().span));
@@ -1383,7 +1383,7 @@ public class GenericParser {
}
}
children.add(makeTerminal(next())); // string end
return new Node(NodeType.STRING_CONSTANT, children);
return new Node(NodeType.STRING_CHARS, children);
}
private FullToken expect(Token type, String errorKey, Object... messageArgs) {

View File

@@ -67,7 +67,7 @@ public enum NodeType {
TYPE_ARGUMENT_LIST_ELEMENTS,
OBJECT_PARAMETER_LIST,
TYPE_PARAMETER,
STRING_CONSTANT,
STRING_CHARS,
OPERATOR,
STRING_NEWLINE,
STRING_ESCAPE,

View File

@@ -130,7 +130,7 @@ class GenericSexpRenderer(code: String) {
}
private fun NodeType.isStringData(): Boolean =
this == NodeType.STRING_CONSTANT || this == NodeType.STRING_ESCAPE
this == NodeType.STRING_CHARS || this == NodeType.STRING_ESCAPE
private fun name(node: Node): String =
when (node.type) {
@@ -142,7 +142,7 @@ class GenericSexpRenderer(code: String) {
NodeType.EXTENDS_CLAUSE,
NodeType.AMENDS_CLAUSE -> "extendsOrAmendsClause"
NodeType.TYPEALIAS -> "typeAlias"
NodeType.STRING_ESCAPE -> "stringConstant"
NodeType.STRING_ESCAPE -> "stringChars"
NodeType.READ_EXPR -> {
val terminal = node.children.find { it.type == NodeType.TERMINAL }!!.text(source)
when (terminal) {

View File

@@ -430,7 +430,7 @@ class SexpRenderer {
renderExpr(part.expr)
} else {
buf.append('\n').append(tab)
buf.append("(stringConstant)")
buf.append("(stringChars)")
}
}
buf.append(')')
@@ -736,7 +736,7 @@ class SexpRenderer {
fun renderStringConstant(str: StringConstant) {
buf.append(tab)
buf.append("(stringConstant)")
buf.append("(stringChars)")
}
fun renderTypeAnnotation(typeAnnotation: TypeAnnotation) {