Repository: square/kotlinpoet
Issue: KDoc formatting for types with properties is off

KDoc formatting for constructor parameter/properties is off.

Example:

```kotlin
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.INT
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec

fun main(args: Array<String>) {
  val typeSpec = TypeSpec.classBuilder("MyType")
      .addKdoc("This is a thing for stuff.")
      .addProperty(
          PropertySpec.builder("first", INT)
              .initializer("first")
              .build()
      )
      .addProperty(
          PropertySpec.builder("second", INT)
              .initializer("second")
              .build()
      )
      .addProperty(PropertySpec.builder("third", INT)
          .initializer("third")
          .build()
      )
      .primaryConstructor(
          FunSpec.constructorBuilder()
              .addKdoc("Construct a thing!")
              .addParameter(ParameterSpec.builder("first", INT).addKdoc("the first thing").build())
              .addParameter(ParameterSpec.builder("second", INT).addKdoc("the second thing").build())
              .addParameter(ParameterSpec.builder("third", INT).addKdoc("the third thing").build())
              .build()
      ).build()

  FileSpec.get("", typeSpec).writeTo(System.out)
}


```

Output

```kotlin
import kotlin.Int

/**
 * This is a thing for stuff.@param first the first thing@param second the second thing@param third
        the third thing */
class MyType(
    val first: Int,
    val second: Int,
    val third: Int
)
```

Expected - something like:

```kotlin
import kotlin.Int

/**
  * This is a thing for stuff.
  *
  * @param first the first thing
  * @param second the second thing
  * @param third the third thing 
  */
class MyType(
    val first: Int,
    val second: Int,
    val third: Int
)
```

Changed files:
src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt, src/main/java/com/squareup/kotlinpoet/CodeBlock.kt, src/main/java/com/squareup/kotlinpoet/TypeSpec.kt, src/main/java/com/squareup/kotlinpoet/Util.kt

diff --git a/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt b/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt
index f3f1eecd..7541e431 100644
--- a/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt
+++ b/src/main/java/com/squareup/kotlinpoet/CodeBlock.kt
@@ -436,6 +436,9 @@ class CodeBlock private constructor(
 
     internal fun isMultiCharNoArgPlaceholder(c: Char) = c.isOneOf('%', 'W')
     internal fun isSingleCharNoArgPlaceholder(c: Char) = c.isOneOf('⇥', '⇤', '«', '»')
+    internal fun isPlaceholder(s: String) =
+        (s.length == 1 && isSingleCharNoArgPlaceholder(s.first())) ||
+            (s.length == 2 && isMultiCharNoArgPlaceholder(s.first()))
     internal fun nextPotentialPlaceholderPosition(s: String, startIndex: Int) = s.indexOfAny(
         charArrayOf('%', '«', '»', '⇥', '⇤'), startIndex)
   }
diff --git a/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt b/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
index 175821f9..f6439917 100644
--- a/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
+++ b/src/main/java/com/squareup/kotlinpoet/TypeSpec.kt
@@ -301,12 +301,13 @@ class TypeSpec private constructor(builder: TypeSpec.Builder) {
 
     val constructorProperties = constructorProperties()
 
-    return with(kdoc.toBuilder()) {
-      for (parameterSpec in primaryConstructor.parameters) {
+    return with(kdoc.ensureEndsWithNewLine().toBuilder()) {
+      primaryConstructor.parameters.forEachIndexed { index, parameterSpec ->
         val kdoc = parameterSpec.kdoc.takeUnless { it.isEmpty() }
                 ?: constructorProperties[parameterSpec.name]?.kdoc?.takeUnless { it.isEmpty() }
         if (kdoc != null) {
-          add("@param %L %L", parameterSpec.name, kdoc)
+          if (isNotEmpty() && index == 0) add("\\n")
+          add("@param %L %L", parameterSpec.name, kdoc.ensureEndsWithNewLine())
         }
       }
       build()
diff --git a/src/main/java/com/squareup/kotlinpoet/Util.kt b/src/main/java/com/squareup/kotlinpoet/Util.kt
index 16776eb0..ae8ce252 100644
--- a/src/main/java/com/squareup/kotlinpoet/Util.kt
+++ b/src/main/java/com/squareup/kotlinpoet/Util.kt
@@ -156,6 +156,19 @@ internal val String.isKeyword get() = this in KEYWORDS
 
 internal val String.isName get() = split("\\\\.").none { it.isKeyword }
 
+internal fun CodeBlock.ensureEndsWithNewLine() = if (isEmpty()) this else with(toBuilder()) {
+  val lastFormatPart = formatParts.last()
+  if (CodeBlock.isPlaceholder(lastFormatPart)) {
+    val lastArg = args.last()
+    if (lastArg is String) {
+      args[args.size - 1] = lastArg.trimEnd('\\n') + '\\n'
+    }
+  } else {
+    formatParts[formatParts.size - 1] = lastFormatPart.trimEnd('\\n') + '\\n'
+  }
+  return@with build()
+}
+
 private val IDENTIFIER_REGEX
     = ("((\\\\p{gc=Lu}+|\\\\p{gc=Ll}+|\\\\p{gc=Lt}+|\\\\p{gc=Lm}+|\\\\p{gc=Lo}+|\\\\p{gc=Nl}+)+" +
     "\\\\d*" +
diff --git a/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt b/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
index d7683488..cc633295 100644
--- a/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
+++ b/src/test/java/com/squareup/kotlinpoet/TypeSpecTest.kt
@@ -1193,6 +1193,7 @@ class TypeSpecTest {
         | * A hard or soft tortilla, loosely folded and filled with whatever
         | * [random][java.util.Random] tex-mex stuff we could find in the pantry
         | * and some [kotlin.String] cheese.
+        | *
         | * @param temperature Taco temperature. Can be as cold as the famous ice tacos from
         | * the Andes, or hot with lava-like cheese from the depths of
         | * the Ninth Circle.
@@ -3568,6 +3569,48 @@ class TypeSpecTest {
       |""".trimMargin())
   }
 
+  // https://github.com/square/kotlinpoet/issues/563
+  @Test fun kdocFormatting() {
+    val typeSpec = TypeSpec.classBuilder("MyType")
+        .addKdoc("This is a thing for stuff.")
+        .addProperty(PropertySpec.builder("first", INT)
+            .initializer("first")
+            .build())
+        .addProperty(PropertySpec.builder("second", INT)
+            .initializer("second")
+            .build())
+        .addProperty(PropertySpec.builder("third", INT)
+            .initializer("third")
+            .build())
+        .primaryConstructor(FunSpec.constructorBuilder()
+            .addKdoc("Construct a thing!")
+            .addParameter(ParameterSpec.builder("first", INT)
+                .addKdoc("the first thing")
+                .build())
+            .addParameter(ParameterSpec.builder("second", INT)
+                .addKdoc("the second thing")
+                .build())
+            .addParameter(ParameterSpec.builder("third", INT)
+                .addKdoc("the third thing")
+                .build())
+            .build())
+        .build()
+    assertThat(typeSpec.toString()).isEqualTo("""
+      |/**
+      | * This is a thing for stuff.
+      | *
+      | * @param first the first thing
+      | * @param second the second thing
+      | * @param third the third thing
+      | */
+      |class MyType(
+      |    val first: kotlin.Int,
+      |    val second: kotlin.Int,
+      |    val third: kotlin.Int
+      |)
+      |""".trimMargin())
+  }
+
   companion object {
     private val donutsPackage = "com.squareup.donuts"
   }