From 4e1403e40c2e63a68b20300c2842b83ca761c5ad Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Sun, 29 Nov 2020 06:48:05 +0900 Subject: [PATCH] NoSemicolonsRule: don't report semicolon for control flow with empty body (#976) * NoSemicolonsRule: don't report semicolon for control flow with empty body * Drop a comment * Add comments pointing to issues Co-authored-by: Roman Zavarnitsyn --- .../ruleset/standard/NoSemicolonsRule.kt | 33 +++++++++-- .../ruleset/standard/NoSemicolonsRuleTest.kt | 56 +++++++++++++++++++ 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoSemicolonsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoSemicolonsRule.kt index 21ea5418f6..e063b53c11 100644 --- a/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoSemicolonsRule.kt +++ b/ktlint-ruleset-standard/src/main/kotlin/com/pinterest/ktlint/ruleset/standard/NoSemicolonsRule.kt @@ -3,6 +3,7 @@ package com.pinterest.ktlint.ruleset.standard import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType.KDOC_TEXT import com.pinterest.ktlint.core.ast.ElementType.OBJECT_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.SEMICOLON import com.pinterest.ktlint.core.ast.isPartOf import com.pinterest.ktlint.core.ast.isPartOfComment import com.pinterest.ktlint.core.ast.isPartOfString @@ -20,7 +21,10 @@ import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtAnnotationEntry import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClassBody +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 class NoSemicolonsRule : Rule("no-semi") { @@ -33,13 +37,14 @@ class NoSemicolonsRule : Rule("no-semi") { if (node.elementType == KDOC_TEXT) { return } - if (node is LeafPsiElement && node.textMatches(";") && !node.isPartOfString() && !node.isPartOfEnumEntry()) { + if (node is LeafPsiElement && + node.elementType == SEMICOLON && + !node.isPartOfString() && + !node.isPartOfEnumEntry() + ) { val nextLeaf = node.nextLeaf() - if (doesNotRequirePreSemi(nextLeaf)) { - if (node.prevCodeLeaf()?.elementType == OBJECT_KEYWORD) { - // https://github.com/shyiko/ktlint/issues/281 - return - } + val prevCodeLeaf = node.prevCodeLeaf() + if (doesNotRequirePreSemi(nextLeaf) && doesNotRequirePostSemi(prevCodeLeaf)) { emit(node.startOffset, "Unnecessary semicolon", true) if (autoCorrect) { node.treeParent.removeChild(node) @@ -73,6 +78,22 @@ class NoSemicolonsRule : Rule("no-semi") { return nextLeaf == null /* eof */ } + private fun doesNotRequirePostSemi(prevLeaf: ASTNode?): Boolean { + if (prevLeaf?.elementType == OBJECT_KEYWORD) { + // https://github.com/pinterest/ktlint/issues/281 + return false + } + val parent = prevLeaf?.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 + } + return true + } + private fun ASTNode.isPartOfEnumEntry(): Boolean { if (isPartOf(KtEnumEntry::class)) return true val lBrace = prevLeaf { !it.isWhiteSpace() && !it.isPartOfComment() } diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/NoSemicolonsRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/NoSemicolonsRuleTest.kt index df74dcb151..8927825d3d 100644 --- a/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/NoSemicolonsRuleTest.kt +++ b/ktlint-ruleset-standard/src/test/kotlin/com/pinterest/ktlint/ruleset/standard/NoSemicolonsRuleTest.kt @@ -230,4 +230,60 @@ class NoSemicolonsRuleTest { ) ) } + + @Test + fun testForWithEmptyBody() { + assertThat( + NoSemicolonsRule().lint( + """ + fun test(list: List) { + for (i in list); + } + """.trimIndent() + ) + ).isEmpty() + } + + @Test + fun testWhileWithEmptyBody() { + assertThat( + NoSemicolonsRule().lint( + """ + fun test() { + while (true); + } + """.trimIndent() + ) + ).isEmpty() + } + + @Test + fun testDoWhileWithEmptyBody() { + assertThat( + NoSemicolonsRule().lint( + """ + fun test() { + do while (true); + } + """.trimIndent() + ) + ).isEqualTo( + listOf( + LintError(2, 20, "no-semi", "Unnecessary semicolon") + ) + ) + } + + @Test + fun testIfWithEmptyBranch() { + assertThat( + NoSemicolonsRule().lint( + """ + fun testIf() { + if (true); + } + """.trimIndent() + ) + ).isEmpty() + } }