Skip to content

Commit 33b22a2

Browse files
committed
Add file annotations rule
1 parent a86d1c7 commit 33b22a2

File tree

5 files changed

+325
-12
lines changed
  • ktlint/src/main/kotlin/com/pinterest/ktlint
  • ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/ast
  • ktlint-ruleset-experimental/src
  • ktlint-test/src/main/kotlin/com/pinterest/ktlint/test

5 files changed

+325
-12
lines changed

ktlint-core/src/main/kotlin/com/pinterest/ktlint/core/ast/package.kt

+5
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ fun ASTNode.isPartOf(klass: KClass<out PsiElement>): Boolean {
184184
fun ASTNode.isPartOfString() =
185185
parent(STRING_TEMPLATE, strict = false) != null
186186

187+
fun ASTNode?.isWhiteSpace() =
188+
this != null && elementType == WHITE_SPACE
187189
fun ASTNode?.isWhiteSpaceWithNewline() =
188190
this != null && elementType == WHITE_SPACE && textContains('\n')
189191
fun ASTNode?.isWhiteSpaceWithoutNewline() =
@@ -228,3 +230,6 @@ fun ASTNode.visit(enter: (node: ASTNode) -> Unit, exit: (node: ASTNode) -> Unit)
228230
this.getChildren(null).forEach { it.visit(enter, exit) }
229231
exit(this)
230232
}
233+
234+
fun ASTNode.lineNumber(): Int? =
235+
this.psi.containingFile?.viewProvider?.document?.getLineNumber(this.startOffset)?.let { it + 1 }

ktlint-ruleset-experimental/src/main/kotlin/com/pinterest/ktlint/ruleset/experimental/AnnotationRule.kt

+33-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,20 @@ import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT
88
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER
99
import com.pinterest.ktlint.core.ast.children
1010
import com.pinterest.ktlint.core.ast.isPartOf
11+
import com.pinterest.ktlint.core.ast.isPartOfComment
12+
import com.pinterest.ktlint.core.ast.isWhiteSpace
13+
import com.pinterest.ktlint.core.ast.lineNumber
14+
import com.pinterest.ktlint.core.ast.nextSibling
15+
import com.pinterest.ktlint.core.ast.prevSibling
1116
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe
1217
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
1318
import org.jetbrains.kotlin.com.intellij.psi.PsiComment
1419
import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
1520
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
21+
import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
1622
import org.jetbrains.kotlin.psi.KtAnnotationEntry
1723
import org.jetbrains.kotlin.psi.psiUtil.endOffset
24+
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
1825
import org.jetbrains.kotlin.psi.psiUtil.nextLeaf
1926

2027
/**
@@ -30,6 +37,7 @@ class AnnotationRule : Rule("annotation") {
3037
"Multiple annotations should not be placed on the same line as the annotated construct"
3138
const val annotationsWithParametersAreNotOnSeparateLinesErrorMessage =
3239
"Annotations with parameters should all be placed on separate lines prior to the annotated construct"
40+
const val fileAnnotationsShouldBeSeparated = "File annotations should be separated from packages with a blank line"
3341
}
3442

3543
override fun visit(
@@ -62,7 +70,8 @@ class AnnotationRule : Rule("annotation") {
6270
.take(annotations.size)
6371
.toList()
6472

65-
val noWhiteSpaceAfterAnnotation = whiteSpaces.isEmpty() || whiteSpaces.last().nextSibling is KtAnnotationEntry
73+
val noWhiteSpaceAfterAnnotation = node.elementType != FILE_ANNOTATION_LIST &&
74+
(whiteSpaces.isEmpty() || whiteSpaces.last().nextSibling is KtAnnotationEntry)
6675
if (noWhiteSpaceAfterAnnotation) {
6776
emit(
6877
annotations.last().endOffset - 1,
@@ -106,6 +115,29 @@ class AnnotationRule : Rule("annotation") {
106115
}
107116
}
108117
}
118+
119+
if (node.elementType == FILE_ANNOTATION_LIST) {
120+
val lineNumber = node.lineNumber()
121+
val next = node.nextSibling {
122+
!it.isWhiteSpace() && it.textLength > 0 && !(it.isPartOfComment() && it.lineNumber() == lineNumber)
123+
}
124+
val nextLineNumber = next?.lineNumber()
125+
if (lineNumber != null && nextLineNumber != null) {
126+
val diff = nextLineNumber - lineNumber
127+
if (diff < 2) {
128+
emit(0, fileAnnotationsShouldBeSeparated, true)
129+
if (autoCorrect) {
130+
if (diff == 0) {
131+
node.psi.getNextSiblingIgnoringWhitespaceAndComments(withItself = false)?.node
132+
?.prevSibling { it.isWhiteSpace() }
133+
?.let { (it as? LeafPsiElement)?.delete() }
134+
next.treeParent.addChild(PsiWhiteSpaceImpl("\n"), next)
135+
}
136+
next.treeParent.addChild(PsiWhiteSpaceImpl("\n"), next)
137+
}
138+
}
139+
}
140+
}
109141
}
110142

111143
private fun getNewlineWithIndent(modifierListRoot: ASTNode): String {

0 commit comments

Comments
 (0)