Skip to content

Commit e91ebae

Browse files
author
Egor Andreevici
committed
Smarter wrapping logic for long parameter lists
1 parent f7aa329 commit e91ebae

7 files changed

Lines changed: 200 additions & 70 deletions

File tree

src/main/java/com/squareup/kotlinpoet/CodeWriter.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ internal class CodeWriter constructor(
275275
}
276276
}
277277

278+
fun openWrappingGroup() = out.openWrappingGroup()
279+
280+
fun closeWrappingGroup() {
281+
trailingNewline = out.closeWrappingGroup()
282+
}
283+
278284
fun emitWrappingSpace() = apply {
279285
out.wrappingSpace(indentLevel + 2)
280286
}

src/main/java/com/squareup/kotlinpoet/FunSpec.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class FunSpec private constructor(builder: Builder) {
116116
codeWriter.emitCode("%L", escapeIfKeyword(name))
117117
}
118118

119-
parameters.emit(codeWriter) { param ->
119+
parameters.emit(codeWriter, wrappable = true) { param ->
120120
param.emit(codeWriter, includeType = name != SETTER)
121121
}
122122

src/main/java/com/squareup/kotlinpoet/LineWrapper.kt

Lines changed: 133 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,71 +26,185 @@ internal class LineWrapper(
2626
) {
2727
private var closed = false
2828

29-
/** Characters written since the last wrapping space that haven't yet been flushed. */
30-
private val buffer = StringBuilder()
31-
3229
/** The number of characters since the most recent newline. Includes both out and the buffer. */
3330
private var column = 0
3431

35-
/** -1 if we have no buffering; otherwise the number of spaces to write after wrapping. */
36-
private var indentLevel = -1
32+
private var helper: BufferedLineWrapperHelper = DefaultLineWrapperHelper()
3733

3834
/** Emit `s`. This may be buffered to permit line wraps to be inserted. */
3935
fun append(s: String) {
4036
check(!closed) { "closed" }
4137

42-
if (indentLevel != -1) {
38+
if (helper.isBuffering) {
4339
val nextNewline = s.indexOf('\n')
4440

4541
// If s doesn't cause the current line to cross the limit, buffer it and return. We'll decide
4642
// whether or not we have to wrap it later.
4743
if (nextNewline == -1 && column + s.length <= columnLimit) {
48-
buffer.append(s)
44+
helper.buffer(s)
4945
column += s.length
5046
return
5147
}
5248

5349
// Wrap if appending s would overflow the current line.
5450
val wrap = nextNewline == -1 || column + nextNewline > columnLimit
55-
flush(wrap)
51+
helper.flush(wrap)
5652
}
5753

58-
out.append(s)
54+
helper.append(s)
5955
val lastNewline = s.lastIndexOf('\n')
6056
column = if (lastNewline != -1)
6157
s.length - lastNewline - 1 else
6258
column + s.length
6359
}
6460

61+
fun openWrappingGroup() {
62+
check(!closed) { "closed" }
63+
64+
helper = GroupLineWrapperHelper()
65+
}
66+
6567
/** Emit either a space or a newline character. */
6668
fun wrappingSpace(indentLevel: Int) {
6769
check(!closed) { "closed" }
6870

69-
if (this.indentLevel != -1) flush(false)
71+
helper.wrappingSpace(indentLevel)
7072
this.column++
71-
this.indentLevel = indentLevel
73+
}
74+
75+
fun closeWrappingGroup(): Boolean {
76+
check(!closed) { "closed" }
77+
78+
val wrapped = helper.close()
79+
helper = DefaultLineWrapperHelper()
80+
return wrapped
7281
}
7382

7483
/** Flush any outstanding text and forbid future writes to this line wrapper. */
7584
fun close() {
76-
if (indentLevel != -1) flush(false)
85+
helper.close()
7786
closed = true
7887
}
7988

8089
/** Write the space followed by any buffered text that follows it. */
81-
private fun flush(wrap: Boolean) {
90+
private fun flush(buffered: String, wrap: Boolean) {
8291
if (wrap) {
8392
out.append('\n')
84-
for (i in 0 until indentLevel) {
93+
for (i in 0 until helper.indentLevel) {
8594
out.append(indent)
8695
}
87-
column = indentLevel * indent.length
88-
column += buffer.length
96+
column = helper.indentLevel * indent.length
97+
column += buffered.length
8998
} else {
9099
out.append(' ')
91100
}
92-
out.append(buffer)
93-
buffer.delete(0, buffer.length)
94-
indentLevel = -1
101+
out.append(buffered)
102+
}
103+
104+
/**
105+
* Contract for helpers that handle buffering, post-processing and flushing of the input.
106+
*/
107+
internal interface BufferedLineWrapperHelper {
108+
109+
val indentLevel: Int
110+
111+
val isBuffering get() = indentLevel != -1
112+
113+
/** Append to out, bypassing the buffer */
114+
fun append(s: String): Appendable
115+
116+
/** Append to buffer */
117+
fun buffer(s: String): Appendable
118+
119+
/**
120+
* Indicates that a new wrapping space occurred in input.
121+
*
122+
* @param indentLevel Indentation level for the new line
123+
*/
124+
fun wrappingSpace(indentLevel: Int)
125+
126+
/**
127+
* Flush any buffered text.
128+
*
129+
* @param wrap `true` if buffer contents should be flushed a on new line
130+
* */
131+
fun flush(wrap: Boolean)
132+
133+
/**
134+
* Flush and clear the buffer.
135+
*
136+
* @return `true` if input wrapped to new line
137+
*/
138+
fun close(): Boolean
139+
}
140+
141+
/** Flushes the buffer each time the wrapping space is encountered */
142+
internal inner class DefaultLineWrapperHelper : BufferedLineWrapperHelper {
143+
144+
private val buffer = StringBuilder()
145+
146+
private var _indentLevel = -1
147+
148+
override val indentLevel get() = _indentLevel
149+
150+
override fun append(s: String): Appendable = out.append(s)
151+
152+
override fun buffer(s: String): Appendable = buffer.append(s)
153+
154+
override fun wrappingSpace(indentLevel: Int) {
155+
if (isBuffering) flush(false)
156+
_indentLevel = indentLevel
157+
}
158+
159+
override fun flush(wrap: Boolean) {
160+
flush(buffer.toString(), wrap)
161+
buffer.delete(0, buffer.length)
162+
_indentLevel = -1
163+
}
164+
165+
override fun close(): Boolean {
166+
if (isBuffering) flush(false)
167+
return false
168+
}
169+
}
170+
171+
/**
172+
* Holds multiple buffers and only flushes when the group is closed. If wrapping happened within
173+
* a group - each buffer will be flushed on a new line.
174+
*/
175+
internal inner class GroupLineWrapperHelper : BufferedLineWrapperHelper {
176+
177+
private val buffer = mutableListOf(StringBuilder())
178+
private var wrapped = false
179+
180+
private var _indentLevel = -1
181+
182+
override val indentLevel get() = _indentLevel
183+
184+
override fun append(s: String): Appendable = buffer.last().append(s)
185+
186+
override fun buffer(s: String): Appendable = buffer.last().append(s)
187+
188+
override fun wrappingSpace(indentLevel: Int) {
189+
_indentLevel = indentLevel
190+
buffer += StringBuilder()
191+
}
192+
193+
override fun flush(wrap: Boolean) {
194+
wrapped = wrap
195+
}
196+
197+
override fun close(): Boolean {
198+
if (wrapped) buffer.last().append('\n')
199+
buffer.forEachIndexed { index, segment ->
200+
if (index == 0 && !wrapped) {
201+
out.append(segment)
202+
} else {
203+
flush(segment.toString(), wrapped)
204+
}
205+
}
206+
_indentLevel = -1
207+
return wrapped
208+
}
95209
}
96210
}

src/main/java/com/squareup/kotlinpoet/ParameterSpec.kt

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -146,28 +146,15 @@ class ParameterSpec private constructor(builder: ParameterSpec.Builder) {
146146

147147
internal fun List<ParameterSpec>.emit(
148148
codeWriter: CodeWriter,
149+
wrappable: Boolean = false,
149150
emitBlock: (ParameterSpec) -> Unit = { it.emit(codeWriter) }
150151
) = with(codeWriter) {
151-
val params = this@emit
152152
emit("(")
153-
when (size) {
154-
0 -> emit("")
155-
1 -> emitBlock(params[0])
156-
2 -> {
157-
emitBlock(params[0])
158-
emit(", ")
159-
emitBlock(params[1])
160-
}
161-
else -> {
162-
emit("\n")
163-
indent(2)
164-
forEachIndexed { index, parameter ->
165-
if (index > 0) emit(",\n")
166-
emitBlock(parameter)
167-
}
168-
unindent(2)
169-
emit("\n")
170-
}
153+
if (wrappable) codeWriter.openWrappingGroup()
154+
forEachIndexed { index, parameter ->
155+
if (index > 0) if (wrappable) emitCode(",%W") else emit(", ")
156+
emitBlock(parameter)
171157
}
158+
if (wrappable) codeWriter.closeWrappingGroup()
172159
emit(")")
173160
}

src/main/java/com/squareup/kotlinpoet/TypeSpec.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ class TypeSpec private constructor(builder: TypeSpec.Builder) {
115115
codeWriter.emit("constructor")
116116
}
117117

118-
it.parameters.emit(codeWriter) { param ->
118+
it.parameters.emit(codeWriter, wrappable = true) { param ->
119119
val property = constructorProperties[param.name]
120120
if (property != null) {
121121
property.emit(codeWriter, setOf(PUBLIC), withInitializer = false, inline = true)

src/test/java/com/squareup/kotlinpoet/KotlinPoetTest.kt

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package com.squareup.kotlinpoet
1717

1818
import com.google.common.truth.Truth.assertThat
1919
import org.junit.Test
20+
import java.io.Serializable
2021

2122
class KotlinPoetTest {
2223
private val tacosPackage = "com.squareup.tacos"
@@ -107,11 +108,7 @@ class KotlinPoetTest {
107108
|import kotlin.Boolean
108109
|import kotlin.String
109110
|
110-
|class Taco(
111-
| val cheese: String,
112-
| var cilantro: String,
113-
| lettuce: String
114-
|) {
111+
|class Taco(val cheese: String, var cilantro: String, lettuce: String) {
115112
| val lettuce: String = lettuce.trim()
116113
|
117114
| val onion: Boolean = true
@@ -365,11 +362,7 @@ class KotlinPoetTest {
365362
|import kotlin.String
366363
|import kotlin.Unit
367364
|
368-
|fun ((
369-
| name: String,
370-
| Int,
371-
| age: Long
372-
|) -> Unit).whatever(): Unit = Unit
365+
|fun ((name: String, Int, age: Long) -> Unit).whatever(): Unit = Unit
373366
|""".trimMargin())
374367
}
375368

@@ -580,4 +573,50 @@ class KotlinPoetTest {
580573
|}
581574
|""".trimMargin())
582575
}
576+
577+
@Test fun longParameterListWrapping() {
578+
val source = FunSpec.builder("sum")
579+
.addParameter(ParameterSpec.builder("a", Int::class).build())
580+
.addParameter(ParameterSpec.builder("b", Int::class).build())
581+
.addParameter(ParameterSpec.builder("c", Int::class).build())
582+
.addParameter(ParameterSpec.builder("d", Int::class).build())
583+
.addParameter(ParameterSpec.builder("e", Int::class).build())
584+
.addParameter(ParameterSpec.builder("f", Int::class).build())
585+
.addParameter(ParameterSpec.builder("g", Int::class).build())
586+
.addStatement("return a + b + c")
587+
.build()
588+
assertThat(source.toString()).isEqualTo("""
589+
|fun sum(
590+
| a: kotlin.Int,
591+
| b: kotlin.Int,
592+
| c: kotlin.Int,
593+
| d: kotlin.Int,
594+
| e: kotlin.Int,
595+
| f: kotlin.Int,
596+
| g: kotlin.Int
597+
|) = a + b + c
598+
|""".trimMargin())
599+
}
600+
601+
@Test fun longLambdaParameterListWrapping() {
602+
val source = FunSpec.builder("veryLongFunctionName")
603+
.addParameter(ParameterSpec.builder(
604+
"veryLongParameterName",
605+
LambdaTypeName.get(
606+
parameters = listOf(
607+
ParameterSpec.unnamed(Serializable::class),
608+
ParameterSpec.unnamed(Appendable::class),
609+
ParameterSpec.unnamed(Cloneable::class)),
610+
returnType = Unit::class.asTypeName()))
611+
.build())
612+
.addParameter("i", Int::class)
613+
.addStatement("return %T", Unit::class)
614+
.build()
615+
assertThat(source.toString()).isEqualTo("""
616+
|fun veryLongFunctionName(
617+
| veryLongParameterName: (java.io.Serializable, java.lang.Appendable, kotlin.Cloneable) -> kotlin.Unit,
618+
| i: kotlin.Int
619+
|) = kotlin.Unit
620+
|""".trimMargin())
621+
}
583622
}

0 commit comments

Comments
 (0)