diff --git a/CHANGELOG.md b/CHANGELOG.md index ea4b034ebb..4ca0e743ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). ### Removed ### Fixed +* Fix with array-syntax annotations on the same line as other annotations `annotation` ([#1765](https://github.com/pinterest/ktlint/issues/1765)) * Do not enable the experimental rules by default when `.editorconfig` properties `disabled_rules` or `ktlint_disabled_rules` are set. ([#1771](https://github.com/pinterest/ktlint/issues/1771)) * A function signature not having any parameters which exceeds the `max-line-length` should be ignored by rule `function-signature` ([#1773](https://github.com/pinterest/ktlint/issues/1773)) -* ### Changed diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/AnnotationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/AnnotationRule.kt index 7fdfdd8891..0d942fde35 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/AnnotationRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/AnnotationRule.kt @@ -20,6 +20,7 @@ import com.pinterest.ktlint.core.ast.lastChildLeafOrSelf import com.pinterest.ktlint.core.ast.nextCodeLeaf import com.pinterest.ktlint.core.ast.nextLeaf import com.pinterest.ktlint.core.ast.prevLeaf +import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace @@ -49,6 +50,9 @@ public class AnnotationRule : Rule("annotation") { FILE_ANNOTATION_LIST -> { visitFileAnnotationList(node, emit, autoCorrect) } + ANNOTATION -> { + visitAnnotation(node, emit, autoCorrect) + } ANNOTATION_ENTRY -> visitAnnotationEntry(node, emit, autoCorrect) } @@ -259,6 +263,31 @@ public class AnnotationRule : Rule("annotation") { } } + private fun visitAnnotation( + node: ASTNode, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit, + autoCorrect: Boolean, + ) { + require(node.elementType == ANNOTATION) + + if ((node.isFollowedByOtherAnnotationEntry() && node.isOnSameLineAsNextAnnotationEntry()) || + (node.isPrecededByOtherAnnotationEntry() && node.isOnSameLineAsPreviousAnnotationEntry()) + ) { + emit( + node.startOffset, + "@[...] style annotations should be on a separate line from other annotations.", + true, + ) + if (autoCorrect) { + if (node.isFollowedByOtherAnnotationEntry()) { + node.upsertWhitespaceAfterMe(getNewlineWithIndent(node.treeParent)) + } else if (node.isPrecededByOtherAnnotationEntry()) { + node.upsertWhitespaceBeforeMe(getNewlineWithIndent(node.treeParent)) + } + } + } + } + private fun getNewlineWithIndent(modifierListRoot: ASTNode): String { val nodeBeforeAnnotations = modifierListRoot.treeParent.treePrev as? PsiWhiteSpace // If there is no whitespace before the annotation, the annotation is the first diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/AnnotationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/AnnotationRuleTest.kt index e1f4dc9ae8..af14897739 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/AnnotationRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/AnnotationRuleTest.kt @@ -548,4 +548,46 @@ class AnnotationRuleTest { """.trimIndent() annotationRuleAssertThat(code).hasNoLintViolations() } + + @Nested inner class `Array syntax annotations, Issue #1765` { + @Test + fun `annotation preceded by array syntax annotation`() { + val code = + """ + class Main { + @[Foo1 Foo2] @Foo3 + fun foo() {} + } + """.trimIndent() + val formattedCode = + """ + class Main { + @[Foo1 Foo2] + @Foo3 + fun foo() {} + } + """.trimIndent() + annotationRuleAssertThat(code) + .hasLintViolation(2, 5, "@[...] style annotations should be on a separate line from other annotations.") + .isFormattedAs(formattedCode) + } + + @Test + fun `annotation followed by array syntax annotation`() { + val code = + """ + @Foo3 @[Foo1 Foo2] + fun foo() {} + """.trimIndent() + val formattedCode = + """ + @Foo3 + @[Foo1 Foo2] + fun foo() {} + """.trimIndent() + annotationRuleAssertThat(code) + .hasLintViolation(1, 7, "@[...] style annotations should be on a separate line from other annotations.") + .isFormattedAs(formattedCode) + } + } }