diff --git a/core/src/main/java/com/facebook/ktfmt/kdoc/ParagraphListBuilder.kt b/core/src/main/java/com/facebook/ktfmt/kdoc/ParagraphListBuilder.kt index 3d26e8d4..592e207b 100644 --- a/core/src/main/java/com/facebook/ktfmt/kdoc/ParagraphListBuilder.kt +++ b/core/src/main/java/com/facebook/ktfmt/kdoc/ParagraphListBuilder.kt @@ -565,10 +565,11 @@ class ParagraphListBuilder( return false } - private fun docTagRank(tag: String): Int { + private fun docTagRank(tag: String, isPriority: Boolean): Int { // Canonical kdoc order -- https://kotlinlang.org/docs/kotlin-doc.html#block-tags // Full list in Dokka's sources: plugins/base/src/main/kotlin/parsers/Parser.kt return when { + isPriority -> -1 tag.startsWith("@param") -> 0 tag.startsWith("@return") -> 1 tag.startsWith("@constructor") -> 2 @@ -586,6 +587,21 @@ class ParagraphListBuilder( } } + /** + * Tags that are "priority" are placed before other tags, with their order unchanged. + * + * Note that if a priority tag comes after a regular tag (before formatting), it doesn't get + * treated as priority. + * + * See: https://github.com/facebook/ktfmt/issues/406 + */ + private fun docTagIsPriority(tag: String): Boolean { + return when { + tag.startsWith("@sample") -> true + else -> false + } + } + /** * Make a pass over the paragraphs and make sure that we (for example) place blank lines around * preformatted text. @@ -605,6 +621,7 @@ class ParagraphListBuilder( private fun sortDocTags() { if (options.orderDocTags && paragraphs.any { it.doc }) { val order = paragraphs.mapIndexed { index, paragraph -> paragraph to index }.toMap() + val firstNonPriorityDocTag = paragraphs.indexOfFirst { it.doc && !docTagIsPriority(it.text) } val comparator = object : Comparator> { override fun compare(l1: List, l2: List): Int { @@ -612,6 +629,8 @@ class ParagraphListBuilder( val p2 = l2.first() val o1 = order[p1]!! val o2 = order[p2]!! + val isPriority1 = p1.doc && docTagIsPriority(p1.text) && o1 < firstNonPriorityDocTag + val isPriority2 = p2.doc && docTagIsPriority(p2.text) && o2 < firstNonPriorityDocTag // Sort TODOs to the end if (p1.text.isTodo() != p2.text.isTodo()) { @@ -621,8 +640,8 @@ class ParagraphListBuilder( if (p1.doc == p2.doc) { if (p1.doc) { // Sort @return after @param etc - val r1 = docTagRank(p1.text) - val r2 = docTagRank(p2.text) + val r1 = docTagRank(p1.text, isPriority1) + val r2 = docTagRank(p2.text, isPriority2) if (r1 != r2) { return r1 - r2 } diff --git a/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt b/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt index 4e5e0a78..0930e92b 100644 --- a/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt +++ b/core/src/test/java/com/facebook/ktfmt/kdoc/KDocFormatterTest.kt @@ -2031,6 +2031,60 @@ class KDocFormatterTest { ) } + @Test + fun testNoReorderSample() { + val source = + """ + /** + * Constructs a new location range for the given file, from start to + * end. If the length of the range is not known, end may be null. + * + * @sample abc + * + * You might want to see another sample. + * + * @sample xyz + * + * Makes sense? + * @return Something + * @see more + * @sample foo + * + * Note that samples after another tag don't get special treatment. + */ + """ + .trimIndent() + checkFormatter( + FormattingTask( + KDocFormattingOptions(72), + source, + " ", + orderedParameterNames = listOf("file", "start", "end")), + """ + /** + * Constructs a new location range for the given file, from start to + * end. If the length of the range is not known, end may be null. + * + * @sample abc + * + * You might want to see another sample. + * + * @sample xyz + * + * Makes sense? + * + * @return Something + * @sample foo + * + * Note that samples after another tag don't get special treatment. + * + * @see more + */ + """ + .trimIndent(), + ) + } + @Test fun testKDocOrdering() { // From AndroidX'