diff --git a/CHANGELOG.md b/CHANGELOG.md index 7139e7ca4d..609f815d8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Fixed - Fix false positive in rule spacing-between-declarations-with-annotations ([#1281](https://github.com/pinterest/ktlint/issues/1281)) - Fix NoSuchElementException for property accessor (`trailing-comma`) ([#1280](https://github.com/pinterest/ktlint/issues/1280)) +- Fix ClassCastException using ktlintFormat on class with KDoc (`no-trailing-spaces`) ([#1270](https://github.com/pinterest/ktlint/issues/1270) ### Changed - Update Kotlin version to `1.6.0` release diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoTrailingSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoTrailingSpacesRule.kt index 229c10d402..0ace64c335 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoTrailingSpacesRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoTrailingSpacesRule.kt @@ -1,11 +1,14 @@ package com.pinterest.ktlint.ruleset.standard import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE +import com.pinterest.ktlint.core.ast.isPartOfComment import com.pinterest.ktlint.core.ast.nextLeaf +import com.pinterest.ktlint.core.ast.parent import org.jetbrains.kotlin.com.intellij.lang.ASTNode -import org.jetbrains.kotlin.com.intellij.psi.PsiComment import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement +import org.jetbrains.kotlin.kdoc.psi.api.KDoc class NoTrailingSpacesRule : Rule("no-trailing-spaces") { @@ -14,7 +17,14 @@ class NoTrailingSpacesRule : Rule("no-trailing-spaces") { autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit ) { - if (node is PsiWhiteSpace || node is PsiComment) { + if (node.isPartOfKDoc()) { + if (node.elementType == WHITE_SPACE && node.hasTrailingSpacesBeforeNewline()) { + emit(node.startOffset, "Trailing space(s)", true) + if (autoCorrect) { + node.removeTrailingSpacesBeforeNewline() + } + } + } else if (node.elementType == WHITE_SPACE || node.isPartOfComment()) { val lines = node.text.split("\n") var violated = false var violationOffset = node.startOffset @@ -22,7 +32,8 @@ class NoTrailingSpacesRule : Rule("no-trailing-spaces") { .head() .forEach { line -> if (line.hasTrailingSpace()) { - emit(violationOffset, "Trailing space(s)", true) + val firstTrailingSpaceOffset = violationOffset + line.trimEnd().length + emit(firstTrailingSpaceOffset, "Trailing space(s)", true) violated = true } violationOffset += line.length + 1 @@ -32,7 +43,8 @@ class NoTrailingSpacesRule : Rule("no-trailing-spaces") { // Ignore the last line as it contains the indentation of the next element Unit lines.last().hasTrailingSpace() -> { - emit(violationOffset, "Trailing space(s)", true) + val firstTrailingSpaceOffset = violationOffset + lines.last().trimEnd().length + emit(firstTrailingSpaceOffset, "Trailing space(s)", true) violated = true } } @@ -43,6 +55,21 @@ class NoTrailingSpacesRule : Rule("no-trailing-spaces") { } } + private fun ASTNode.isPartOfKDoc() = parent({ it.psi is KDoc }, strict = false) != null + + private fun ASTNode.hasTrailingSpacesBeforeNewline() = + text.contains( + regex = Regex("\\s+\\n") + ) + + private fun ASTNode.removeTrailingSpacesBeforeNewline() { + val newText = text.replace( + regex = Regex("\\s+\\n"), + replacement = "\n" + ) + (this as LeafPsiElement).replaceWithText(newText) + } + private fun String.hasTrailingSpace() = takeLast(1) == " " } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/NoTrailingSpacesRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/NoTrailingSpacesRuleTest.kt index 07d8837a53..b30573776d 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/NoTrailingSpacesRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/NoTrailingSpacesRuleTest.kt @@ -26,23 +26,41 @@ class NoTrailingSpacesRuleTest { } @Test - fun `lint trailing spaces inside block comments`() { + fun `trailing spaces inside line comments`() { val code = """ - /*${" "} - * Some comment${" "} - */ + //${" "} + // Some comment${" "} + class Foo { + //${" "}${" "} + // Some comment${" "}${" "} + fun bar() = "foobar" + } + """.trimIndent() + val expectedCode = + """ + // + // Some comment + class Foo { + // + // Some comment + fun bar() = "foobar" + } """.trimIndent() - val actual = NoTrailingSpacesRule().lint(code) - - assertThat(actual) - .isEqualTo( - listOf( - LintError(1, 1, "no-trailing-spaces", "Trailing space(s)"), - LintError(2, 1, "no-trailing-spaces", "Trailing space(s)") - ) + assertThat( + NoTrailingSpacesRule().format(code) + ).isEqualTo(expectedCode) + assertThat( + NoTrailingSpacesRule().lint(code) + ).isEqualTo( + listOf( + LintError(1, 3, "no-trailing-spaces", "Trailing space(s)"), + LintError(2, 16, "no-trailing-spaces", "Trailing space(s)"), + LintError(4, 7, "no-trailing-spaces", "Trailing space(s)"), + LintError(5, 20, "no-trailing-spaces", "Trailing space(s)") ) + ) } @Test @@ -52,17 +70,86 @@ class NoTrailingSpacesRuleTest { /*${" "} * Some comment${" "} */ + class Foo { + /*${" "}${" "} + * Some comment${" "}${" "} + */ + fun bar() = "foobar" + } + """.trimIndent() + val expectedCode = + """ + /* + * Some comment + */ + class Foo { + /* + * Some comment + */ + fun bar() = "foobar" + } """.trimIndent() - val actual = NoTrailingSpacesRule().format(code) + assertThat( + NoTrailingSpacesRule().format(code) + ).isEqualTo(expectedCode) + assertThat( + NoTrailingSpacesRule().lint(code) + ).isEqualTo( + listOf( + LintError(1, 3, "no-trailing-spaces", "Trailing space(s)"), + LintError(2, 16, "no-trailing-spaces", "Trailing space(s)"), + LintError(5, 7, "no-trailing-spaces", "Trailing space(s)"), + LintError(6, 20, "no-trailing-spaces", "Trailing space(s)") + ) + ) + } - assertThat(actual) - .isEqualTo( - """ - /* + @Test + fun `trailing spaces inside KDoc`() { + val code = + """ + /**${" "} + * Some comment${" "} + *${" "} + */ + class Foo { + /**${" "}${" "} + * Some comment${" "}${" "} + *${" "}${" "} + */ + fun bar() = "foobar" + } + """.trimIndent() + val codeExpected = + """ + /** + * Some comment + * + */ + class Foo { + /** * Some comment + * */ - """.trimIndent() + fun bar() = "foobar" + } + """.trimIndent() + + assertThat( + NoTrailingSpacesRule().format(code) + ).isEqualTo(codeExpected) + assertThat( + NoTrailingSpacesRule().lint(code) + ).isEqualTo( + listOf( + LintError(1, 4, "no-trailing-spaces", "Trailing space(s)"), + LintError(2, 16, "no-trailing-spaces", "Trailing space(s)"), + LintError(3, 3, "no-trailing-spaces", "Trailing space(s)"), + LintError(6, 8, "no-trailing-spaces", "Trailing space(s)"), + LintError(7, 20, "no-trailing-spaces", "Trailing space(s)"), + LintError(8, 7, "no-trailing-spaces", "Trailing space(s)"), ) + ) } }