Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An enumeration class having a primary constructor and in which the li… #1736

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,8 @@ This project adheres to [Semantic Versioning](https://semver.org/).

### Fixed

* An enumeration class having a primary constructor and in which the list of enum entries is followed by a semicolon then do not remove the semicolon in case it is followed by code element `no-semi` ([#1733](https://github.com/pinterest/ktlint/issues/1733))

### Changed

## [0.48.0] - 2022-10-15
Expand Down
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,37 @@ 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