Skip to content

Commit

Permalink
An enumeration class having a primary constructor and in which the li…
Browse files Browse the repository at this point in the history
…st of enum entries is followed by a semicolon then do not remove the semicolon in case it is followed by code element

Closes pinterest#1733
  • Loading branch information
paul-dingemans committed Dec 17, 2022
1 parent 20d9a88 commit f5fba68
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 14 deletions.
Expand Up @@ -156,6 +156,7 @@ public fun ASTNode.parent(elementType: IElementType, strict: Boolean = true): AS
return null
}

// TODO in ktlint 0.49 deprecate and replace with "ASTNode.parent(strict: Boolean = true, p: (ASTNode) -> Boolean): ASTNode?"
public fun ASTNode.parent(p: (ASTNode) -> Boolean, strict: Boolean = true): ASTNode? {
var n: ASTNode? = if (strict) this.treeParent else this
while (n != null) {
Expand Down
@@ -1,11 +1,14 @@
package com.pinterest.ktlint.ruleset.standard

import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY
import com.pinterest.ktlint.core.ast.ElementType.ENUM_ENTRY
import com.pinterest.ktlint.core.ast.ElementType.OBJECT_KEYWORD
import com.pinterest.ktlint.core.ast.ElementType.SEMICOLON
import com.pinterest.ktlint.core.ast.isWhiteSpace
import com.pinterest.ktlint.core.ast.nextCodeLeaf
import com.pinterest.ktlint.core.ast.lastChildLeafOrSelf
import com.pinterest.ktlint.core.ast.nextLeaf
import com.pinterest.ktlint.core.ast.parent
import com.pinterest.ktlint.core.ast.prevCodeLeaf
import com.pinterest.ktlint.core.ast.prevLeaf
import com.pinterest.ktlint.core.ast.upsertWhitespaceAfterMe
Expand All @@ -16,7 +19,6 @@ import org.jetbrains.kotlin.kdoc.psi.api.KDoc
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtAnnotationEntry
import org.jetbrains.kotlin.psi.KtDoWhileExpression
import org.jetbrains.kotlin.psi.KtEnumEntry
import org.jetbrains.kotlin.psi.KtIfExpression
import org.jetbrains.kotlin.psi.KtLoopExpression
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
Expand All @@ -32,7 +34,7 @@ public class NoSemicolonsRule : Rule("no-semi") {
}
val nextLeaf = node.nextLeaf()
val prevCodeLeaf = node.prevCodeLeaf()
if (nextLeaf.doesNotRequirePreSemi() && prevCodeLeaf.doesNotRequirePostSemi()) {
if (nextLeaf.doesNotRequirePreSemi() && isNoSemicolonRequiredAfter(node)) {
emit(node.startOffset, "Unnecessary semicolon", true)
if (autoCorrect) {
val prevLeaf = node.prevLeaf(true)
Expand Down Expand Up @@ -72,27 +74,38 @@ public class NoSemicolonsRule : Rule("no-semi") {
return false
}

private fun ASTNode?.doesNotRequirePostSemi(): Boolean {
if (this == null) {
return true
}
if (this.elementType == OBJECT_KEYWORD) {
private fun isNoSemicolonRequiredAfter(node: ASTNode): Boolean {
val prevCodeLeaf =
node.prevCodeLeaf()
?: return true
if (prevCodeLeaf.elementType == OBJECT_KEYWORD) {
// https://github.com/pinterest/ktlint/issues/281
return false
}
val parent = this.treeParent?.psi

val parent = prevCodeLeaf.treeParent?.psi
if (parent is KtLoopExpression && parent !is KtDoWhileExpression && parent.body == null) {
// https://github.com/pinterest/ktlint/issues/955
return false
}
if (parent is KtIfExpression && parent.then == null) {
return false
}
if (parent is KtEnumEntry) {
return this.nextCodeLeaf()?.nextCodeLeaf() ==
parent.parent.lastChild
// In case of an enum entry the semicolon (e.g. the node) is a direct child node of enum entry
if (node.treeParent.elementType == ENUM_ENTRY) {
return node.isLastCodeLeafBeforeClosingOfClassBody()
}

return true
}

private fun ASTNode?.isLastCodeLeafBeforeClosingOfClassBody() =
getLastCodeLeafBeforeClosingOfClassBody() == this

private fun ASTNode?.getLastCodeLeafBeforeClosingOfClassBody() =
this
?.parent(CLASS_BODY)
?.lastChildLeafOrSelf()
?.prevCodeLeaf()

}
Expand Up @@ -293,6 +293,37 @@ class NoSemicolonsRuleTest {
ONE, TWO;
val foo = "foo"
}
enum class E3 {
ONE, TWO;
private companion object {
val foo = "foo"
}
}
""".trimIndent()
noSemicolonsRuleAssertThat(code).hasNoLintViolations()
}

@Test
fun `Issue 1733 - Given an enumeration with primary constructor and the list of values is followed by a code element then do not return a lint error`() {
val code =
"""
enum class E1(bar: String) {
ONE("one"),
TWO("two");
fun fn() {}
}
enum class E2(bar: String) {
ONE("one"),
TWO("two");
val foo = "foo"
}
enum class E3(bar: String) {
ONE("one"),
TWO("two");
private companion object {
val foo = "foo"
}
}
""".trimIndent()
noSemicolonsRuleAssertThat(code).hasNoLintViolations()
}
Expand All @@ -306,17 +337,28 @@ class NoSemicolonsRuleTest {
// comment
;
}
enum class Foo(bar: String) {
ONE("one")
// comment
;
}
""".trimIndent()
val formattedCode =
"""
enum class Test {
ONE
// comment
}
enum class Foo(bar: String) {
ONE("one")
// comment
}
""".trimIndent()
noSemicolonsRuleAssertThat(code)
.hasLintViolation(4, 5, "Unnecessary semicolon")
.isFormattedAs(formattedCode)
.hasLintViolations(
LintViolation(4, 5, "Unnecessary semicolon"),
LintViolation(9, 5, "Unnecessary semicolon"),
).isFormattedAs(formattedCode)
}

@Test
Expand Down Expand Up @@ -348,6 +390,36 @@ class NoSemicolonsRuleTest {
LintViolation(15, 5, "Unnecessary semicolon"),
)
}

@Test
fun `Given an enumeration with a primary constructor and the list of values is closed with a semicolon not followed by statements then do return a lint error`() {
val code =
"""
enum class Foo1(bar: String) {
A(1),
B(2);
}
enum class Foo2(bar: String) {
A(1),
B(2) // comment
;
}
enum class Foo3(bar: String) {
;
}
enum class Foo4(bar: String) {
// comment
;
}
""".trimIndent()
noSemicolonsRuleAssertThat(code)
.hasLintViolations(
LintViolation(3, 9, "Unnecessary semicolon"),
LintViolation(8, 5, "Unnecessary semicolon"),
LintViolation(11, 5, "Unnecessary semicolon"),
LintViolation(15, 5, "Unnecessary semicolon"),
)
}
}

@Test
Expand Down

0 comments on commit f5fba68

Please sign in to comment.