From a608b205bc71474aed8687f6ba7d3272b64220f5 Mon Sep 17 00:00:00 2001 From: Paul Dingemans Date: Wed, 9 Feb 2022 19:57:31 +0100 Subject: [PATCH] Add rule to report discouraged comment locations Autocorrect is not implemented. This is an error which only happens rarely. Its value is that it makes implementation of some rules way easier when comment do not have to be taken into account. Initial implementation just contains one single discouraged location. More will follow when implementing the rule to rewrite the function signature automatically as is described in #1341. --- .../DiscouragedCommentLocationRule.kt | 51 ++++++++++ .../ExperimentalRuleSetProvider.kt | 3 +- .../DiscouragedCommentLocationTest.kt | 92 +++++++++++++++++++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/DiscouragedCommentLocationRule.kt create mode 100644 ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/DiscouragedCommentLocationTest.kt diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/DiscouragedCommentLocationRule.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/DiscouragedCommentLocationRule.kt new file mode 100644 index 0000000000..6979a11a3d --- /dev/null +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/DiscouragedCommentLocationRule.kt @@ -0,0 +1,51 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.TYPE_PARAMETER_LIST +import com.pinterest.ktlint.core.ast.isPartOfComment +import com.pinterest.ktlint.core.ast.prevCodeSibling +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +/** + * The AST allows comments to be placed anywhere. This however can lead to code which is unnecessarily hard to read. + * Disallowing comments at certain positions in the AST makes development of a rule easier as they have not to be taken + * into account in that rule. + * + * In examples below, a comment is placed between a type parameter list and the function name. Such comments are badly + * handled by default IntelliJ IDEA code formatter. We should put no effort in making and keeping ktlint in sync with + * such bad code formatting. + * + * ``` + * fun + * // some comment + * foo(t: T) = "some-result" + * + * fun + * /* some comment + * * + * */ + * foo(t: T) = "some-result" + * ``` + */ +public class DiscouragedCommentLocationRule : Rule("discouraged-comment-location") { + override fun visit( + node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit + ) { + node + .takeIf { it.isPartOfComment() } + ?.prevCodeSibling() + ?.let { codeSiblingBeforeComment -> + // Be restrictive when adding new locations at which comments are discouraged. Always run against major + // open source projects first to verify whether valid cases are found to comment at this location. + if (codeSiblingBeforeComment.elementType == TYPE_PARAMETER_LIST) { + emit( + node.startOffset, + "No comment expected at this location", + false + ) + } + } + } +} diff --git a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt index fa3f9aedd5..7708bbc370 100644 --- a/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt +++ b/ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/ExperimentalRuleSetProvider.kt @@ -21,6 +21,7 @@ public class ExperimentalRuleSetProvider : RuleSetProvider { SpacingAroundAngleBracketsRule(), SpacingAroundUnaryOperatorRule(), AnnotationSpacingRule(), - UnnecessaryParenthesesBeforeTrailingLambdaRule() + UnnecessaryParenthesesBeforeTrailingLambdaRule(), + DiscouragedCommentLocationRule() ) } diff --git a/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/DiscouragedCommentLocationTest.kt b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/DiscouragedCommentLocationTest.kt new file mode 100644 index 0000000000..cc06194a69 --- /dev/null +++ b/ktlint-ruleset-experimental/src/test/kotlin/com/pinterest/ktlint/ruleset/experimental/DiscouragedCommentLocationTest.kt @@ -0,0 +1,92 @@ +package com.pinterest.ktlint.ruleset.experimental + +import com.pinterest.ktlint.core.LintError +import com.pinterest.ktlint.test.format +import com.pinterest.ktlint.test.lint +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +class DiscouragedCommentLocationTest { + @Test + fun `Given an EOL comment after a type parameter then report a discouraged comment location`() { + val code = + """ + fun // some comment + foo(t: T) = "some-result" + """.trimIndent() + assertThat(DiscouragedCommentLocationRule().lint(code)).containsExactly( + LintError(1, 9, "discouraged-comment-location", "No comment expected at this location") + ) + assertThat(DiscouragedCommentLocationRule().format(code)).isEqualTo(code) + } + + @Test + fun `Given an EOL comment on a newline after a type parameter then report a discouraged comment location`() { + val code = + """ + fun + // some comment + foo(t: T) = "some-result" + """.trimIndent() + assertThat(DiscouragedCommentLocationRule().lint(code)).containsExactly( + LintError(2, 1, "discouraged-comment-location", "No comment expected at this location") + ) + assertThat(DiscouragedCommentLocationRule().format(code)).isEqualTo(code) + } + + @Test + fun `Given a block comment after a type parameter then report a discouraged comment location`() { + val code = + """ + fun /* some comment */ + foo(t: T) = "some-result" + """.trimIndent() + assertThat(DiscouragedCommentLocationRule().lint(code)).containsExactly( + LintError(1, 9, "discouraged-comment-location", "No comment expected at this location") + ) + assertThat(DiscouragedCommentLocationRule().format(code)).isEqualTo(code) + } + + @Test + fun `Given a block comment on a newline after a type parameter then report a discouraged comment location`() { + val code = + """ + fun + /* some comment */ + foo(t: T) = "some-result" + """.trimIndent() + assertThat(DiscouragedCommentLocationRule().lint(code)).containsExactly( + LintError(2, 1, "discouraged-comment-location", "No comment expected at this location") + ) + assertThat(DiscouragedCommentLocationRule().format(code)).isEqualTo(code) + } + + @Test + fun `Given a KDOC comment after a type parameter then report a discouraged comment location`() { + val code = + """ + fun /** some comment */ + foo(t: T) = "some-result" + """.trimIndent() + assertThat(DiscouragedCommentLocationRule().lint(code)).containsExactly( + LintError(1, 9, "discouraged-comment-location", "No comment expected at this location") + ) + assertThat(DiscouragedCommentLocationRule().format(code)).isEqualTo(code) + } + + @Test + fun `Given a KDOC comment on a newline after a type parameter then report a discouraged comment location`() { + val code = + """ + fun + /** + * some comment + */ + foo(t: T) = "some-result" + """.trimIndent() + assertThat(DiscouragedCommentLocationRule().lint(code)).containsExactly( + LintError(2, 1, "discouraged-comment-location", "No comment expected at this location") + ) + assertThat(DiscouragedCommentLocationRule().format(code)).isEqualTo(code) + } +}