Skip to content

Commit

Permalink
Add file annotations rule (#714)
Browse files Browse the repository at this point in the history
* Add file annotations rule

* Fix error message

* Fix offset
  • Loading branch information
t-kameyama authored May 26, 2020
1 parent bbb7ac6 commit 257885e
Show file tree
Hide file tree
Showing 5 changed files with 331 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ fun ASTNode.isPartOf(klass: KClass<out PsiElement>): Boolean {
fun ASTNode.isPartOfString() =
parent(STRING_TEMPLATE, strict = false) != null

fun ASTNode?.isWhiteSpace() =
this != null && elementType == WHITE_SPACE
fun ASTNode?.isWhiteSpaceWithNewline() =
this != null && elementType == WHITE_SPACE && textContains('\n')
fun ASTNode?.isWhiteSpaceWithoutNewline() =
Expand Down Expand Up @@ -228,3 +230,6 @@ fun ASTNode.visit(enter: (node: ASTNode) -> Unit, exit: (node: ASTNode) -> Unit)
this.getChildren(null).forEach { it.visit(enter, exit) }
exit(this)
}

fun ASTNode.lineNumber(): Int? =
this.psi.containingFile?.viewProvider?.document?.getLineNumber(this.startOffset)?.let { it + 1 }
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT
import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER
import com.pinterest.ktlint.core.ast.children
import com.pinterest.ktlint.core.ast.isPartOf
import com.pinterest.ktlint.core.ast.isPartOfComment
import com.pinterest.ktlint.core.ast.isWhiteSpace
import com.pinterest.ktlint.core.ast.lineNumber
import com.pinterest.ktlint.core.ast.nextSibling
import com.pinterest.ktlint.core.ast.prevSibling
import com.pinterest.ktlint.core.ast.upsertWhitespaceBeforeMe
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.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
import org.jetbrains.kotlin.psi.psiUtil.nextLeaf

/**
Expand All @@ -30,6 +37,8 @@ class AnnotationRule : Rule("annotation") {
"Multiple annotations should not be placed on the same line as the annotated construct"
const val annotationsWithParametersAreNotOnSeparateLinesErrorMessage =
"Annotations with parameters should all be placed on separate lines prior to the annotated construct"
const val fileAnnotationsShouldBeSeparated =
"File annotations should be separated from file contents with a blank line"
}

override fun visit(
Expand Down Expand Up @@ -62,7 +71,8 @@ class AnnotationRule : Rule("annotation") {
.take(annotations.size)
.toList()

val noWhiteSpaceAfterAnnotation = whiteSpaces.isEmpty() || whiteSpaces.last().nextSibling is KtAnnotationEntry
val noWhiteSpaceAfterAnnotation = node.elementType != FILE_ANNOTATION_LIST &&
(whiteSpaces.isEmpty() || whiteSpaces.last().nextSibling is KtAnnotationEntry)
if (noWhiteSpaceAfterAnnotation) {
emit(
annotations.last().endOffset - 1,
Expand Down Expand Up @@ -106,6 +116,30 @@ class AnnotationRule : Rule("annotation") {
}
}
}

if (node.elementType == FILE_ANNOTATION_LIST) {
val lineNumber = node.lineNumber()
val next = node.nextSibling {
!it.isWhiteSpace() && it.textLength > 0 && !(it.isPartOfComment() && it.lineNumber() == lineNumber)
}
val nextLineNumber = next?.lineNumber()
if (lineNumber != null && nextLineNumber != null) {
val diff = nextLineNumber - lineNumber
if (diff < 2) {
val psi = node.psi
emit(psi.endOffset - 1, fileAnnotationsShouldBeSeparated, true)
if (autoCorrect) {
if (diff == 0) {
psi.getNextSiblingIgnoringWhitespaceAndComments(withItself = false)?.node
?.prevSibling { it.isWhiteSpace() }
?.let { (it as? LeafPsiElement)?.delete() }
next.treeParent.addChild(PsiWhiteSpaceImpl("\n"), next)
}
next.treeParent.addChild(PsiWhiteSpaceImpl("\n"), next)
}
}
}
}
}

private fun getNewlineWithIndent(modifierListRoot: ASTNode): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,7 @@ class AnnotationRuleTest {
).isEqualTo(
"""
@file:JvmName("FooClass")
package foo.bar
""".trimIndent()
)
Expand Down Expand Up @@ -631,4 +632,292 @@ class AnnotationRuleTest {
""".trimIndent()
assertThat(AnnotationRule().lint(code)).isEmpty()
}

@Test
fun `format file annotations should be separated with a blank line 1`() {
assertThat(
AnnotationRule().format(
"""
@file:JvmName package foo.bar
""".trimIndent()
)
).isEqualTo(
"""
@file:JvmName
package foo.bar
""".trimIndent()
)
}

@Test
fun `format file annotations should be separated with a blank line 2`() {
assertThat(
AnnotationRule().format(
"""
/*
* Copyright 2000-2020 XXX
*/
@file:JvmName
package foo.bar
""".trimIndent()
)
).isEqualTo(
"""
/*
* Copyright 2000-2020 XXX
*/
@file:JvmName
package foo.bar
""".trimIndent()
)
}

@Test
fun `format file annotations should be separated with a blank line 3`() {
assertThat(
AnnotationRule().format(
"""
@file:JvmName
fun foo() {}
""".trimIndent()
)
).isEqualTo(
"""
@file:JvmName
fun foo() {}
""".trimIndent()
)
}

@Test
fun `format file annotations should be separated with a blank line 4`() {
assertThat(
AnnotationRule().format(
"""
@file:JvmName // comment
package foo.bar
""".trimIndent()
)
).isEqualTo(
"""
@file:JvmName // comment
package foo.bar
""".trimIndent()
)
}

@Test
fun `format file annotations should be separated with a blank line 5`() {
assertThat(
AnnotationRule().format(
"""
@file:JvmName /* comment */ package foo.bar
""".trimIndent()
)
).isEqualTo(
"""
@file:JvmName /* comment */
package foo.bar
""".trimIndent()
)
}

@Test
fun `format file annotations should be separated with a blank line 6`() {
assertThat(
AnnotationRule().format(
"""
@file:JvmName
// comment
package foo.bar
""".trimIndent()
)
).isEqualTo(
"""
@file:JvmName
// comment
package foo.bar
""".trimIndent()
)
}

@Test
fun `lint file annotations should be separated with a blank line 1`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName package foo.bar
""".trimIndent()
)
).isEqualTo(
listOf(
LintError(1, 13, "annotation", AnnotationRule.fileAnnotationsShouldBeSeparated)
)
)
}

@Test
fun `lint file annotations should be separated with a blank line 2`() {
assertThat(
AnnotationRule().lint(
"""
/*
* Copyright 2000-2020 XXX
*/
@file:JvmName
package foo.bar
""".trimIndent()
)
).isEqualTo(
listOf(
LintError(5, 13, "annotation", AnnotationRule.fileAnnotationsShouldBeSeparated)
)
)
}

@Test
fun `lint file annotations should be separated with a blank line 3`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName
fun foo() {}
""".trimIndent()
)
).isEqualTo(
listOf(
LintError(1, 13, "annotation", AnnotationRule.fileAnnotationsShouldBeSeparated)
)
)
}

@Test
fun `lint file annotations should be separated with a blank line 4`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName // comment
package foo.bar
""".trimIndent()
)
).isEqualTo(
listOf(
LintError(1, 13, "annotation", AnnotationRule.fileAnnotationsShouldBeSeparated)
)
)
}

@Test
fun `lint file annotations should be separated with a blank line 5`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName /* comment */ package foo.bar
""".trimIndent()
)
).isEqualTo(
listOf(
LintError(1, 13, "annotation", AnnotationRule.fileAnnotationsShouldBeSeparated)
)
)
}

@Test
fun `lint file annotations should be separated with a blank line 6`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName
// comment
package foo.bar
""".trimIndent()
)
).isEqualTo(
listOf(
LintError(1, 13, "annotation", AnnotationRule.fileAnnotationsShouldBeSeparated)
)
)
}

@Test
fun `lint file annotations should be separated with a blank line 7`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName
""".trimIndent()
)
).isEmpty()
}

@Test
fun `lint file annotations should be separated with a blank line 8`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName
package foo.bar
""".trimIndent()
)
).isEmpty()
}

@Test
fun `lint file annotations should be separated with a blank line 9`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName
package foo.bar
""".trimIndent()
)
).isEmpty()
}

@Test
fun `lint file annotations should be separated with a blank line 10`() {
assertThat(
AnnotationRule().lint(
"""
@file:JvmName
fun foo() {}
""".trimIndent()
)
).isEmpty()
}
}
Loading

0 comments on commit 257885e

Please sign in to comment.