From 1d52f00562f22fe7c82db5311c5b41b36d804841 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Sun, 31 Oct 2021 12:36:13 +0100 Subject: [PATCH] Fix KDOC indentation for tab indentation style Closes #850 --- .../ruleset/standard/IndentationRule.kt | 29 ++++++++++-- .../ruleset/standard/IndentationRuleTest.kt | 47 +++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt index 4f62f7952a..b50dbf294d 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRule.kt @@ -27,6 +27,8 @@ import com.pinterest.ktlint.core.ast.ElementType.FUN import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_LITERAL import com.pinterest.ktlint.core.ast.ElementType.GT import com.pinterest.ktlint.core.ast.ElementType.KDOC +import com.pinterest.ktlint.core.ast.ElementType.KDOC_END +import com.pinterest.ktlint.core.ast.ElementType.KDOC_LEADING_ASTERISK import com.pinterest.ktlint.core.ast.ElementType.KDOC_START import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.LBRACE @@ -44,7 +46,6 @@ import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.RBRACKET import com.pinterest.ktlint.core.ast.ElementType.REGULAR_STRING_PART import com.pinterest.ktlint.core.ast.ElementType.RPAR -import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.SHORT_STRING_TEMPLATE_ENTRY import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE @@ -989,7 +990,14 @@ class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast { } } IndentStyle.TAB -> { - if (' ' in nodeIndent) { + val isKdocIndent = node.isKDocIndent() + val indentWithoutKdocIndent = + if (isKdocIndent) { + nodeIndent.removeSuffix(" ") + } else { + nodeIndent + } + if (' ' in indentWithoutKdocIndent) { emit( node.startOffset + text.length - nodeIndent.length, "Unexpected space character(s)", @@ -998,7 +1006,13 @@ class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast { // First normalize the indent to spaces using the tab width. val asSpaces = nodeIndent.replace("\t", " ".repeat(editorConfig.tabWidth)) // Then divide that space-based indent into tabs. - "\t".repeat(asSpaces.length / editorConfig.tabWidth) + "\t".repeat(asSpaces.length / editorConfig.tabWidth) + + // Re-add the kdoc indent when it was present before + if (isKdocIndent) { + " " + } else { + "" + } } else { nodeIndent } @@ -1117,6 +1131,15 @@ class IndentationRule : Rule("indent"), Rule.Modifier.RestrictToRootLast { } } +private fun ASTNode.isKDocIndent() = + if (text.lastOrNull() == ' ') { + // The indentation of a KDoc comment contains a space as the last character regardless of the indentation style + // (tabs or spaces) except for the starting line of the KDoc comment + nextLeaf()?.elementType == KDOC_LEADING_ASTERISK || nextLeaf()?.elementType == KDOC_END + } else { + false + } + private fun ASTNode.isIndentBeforeClosingQuote() = elementType == CLOSING_QUOTE || (text.isBlank() && nextCodeSibling()?.elementType == CLOSING_QUOTE) diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt index 499472818a..d42d1036ca 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/IndentationRuleTest.kt @@ -1296,6 +1296,53 @@ internal class IndentationRuleTest { assertThat(IndentationRule().format(code)).isEqualTo(code) } + @Test + fun `format kdoc`() { + @Suppress("RemoveCurlyBracesFromTemplate") + val code = + """ + /** + * some function1 + */ + fun someFunction1() { + return Unit + } + + class SomeClass { + /** + * some function2 + */ + fun someFunction2() { + return Unit + } + } + """.trimIndent() + @Suppress("RemoveCurlyBracesFromTemplate") + val codeTabs = + """ + /** + * some function1 + */ + fun someFunction1() { + ${TAB}return Unit + } + + class SomeClass { + ${TAB}/** + ${TAB} * some function2 + ${TAB} */ + ${TAB}fun someFunction2() { + ${TAB}${TAB}return Unit + ${TAB}} + } + """.trimIndent() + assertThat(IndentationRule().lint(code)).isEmpty() + assertThat(IndentationRule().format(code)).isEqualTo(code) + + assertThat(IndentationRule().lint(codeTabs, INDENT_STYLE_TABS)).isEmpty() + assertThat(IndentationRule().format(codeTabs, INDENT_STYLE_TABS)).isEqualTo(codeTabs) + } + private companion object { const val MULTILINE_STRING_QUOTE = "${'"'}${'"'}${'"'}" const val TAB = "${'\t'}"