diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/AbstractClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/AbstractClassesRule.kt index 66b9fcc8c6..b409636397 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/AbstractClassesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/AbstractClassesRule.kt @@ -13,8 +13,9 @@ import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY import com.pinterest.ktlint.core.ast.ElementType.FUN import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER import com.pinterest.ktlint.core.ast.ElementType.MODIFIER_LIST -import com.pinterest.ktlint.core.ast.isWhiteSpace +import com.pinterest.ktlint.core.ast.ElementType.OPEN_KEYWORD import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement /** * Checks if abstract class has any abstract method. If not, warns that class should not be abstract @@ -45,25 +46,9 @@ class AbstractClassesRule(configRules: List) : DiktatRule( if (functions.isNotEmpty() && functions.none { hasAbstractModifier(it) }) { CLASS_SHOULD_NOT_BE_ABSTRACT.warnAndFix(configRules, emitWarn, isFixMode, identifier, node.startOffset, node) { val modList = classNode.getFirstChildWithType(MODIFIER_LIST)!! - if (modList.getChildren(null).size > 1) { - val abstractKeyword = modList.getFirstChildWithType(ABSTRACT_KEYWORD)!! - - // we are deleting one keyword, so we need to delete extra space - val spaceInModifiers = if (abstractKeyword == modList.firstChildNode) { - abstractKeyword.treeNext - } else { - abstractKeyword.treePrev - } - modList.removeChild(abstractKeyword) - if (spaceInModifiers != null && spaceInModifiers.isWhiteSpace()) { - modList.removeChild(spaceInModifiers) - } - } else { - if (modList.treeNext.isWhiteSpace()) { - classNode.removeChild(modList.treeNext) - } - classNode.removeChild(modList) - } + val abstractKeyword = modList.getFirstChildWithType(ABSTRACT_KEYWORD)!! + val newOpenKeyword = LeafPsiElement(OPEN_KEYWORD, "open") + modList.replaceChild(abstractKeyword, newOpenKeyword) } } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesFixTest.kt index b5a222d4fe..ccbf59a0af 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesFixTest.kt @@ -11,6 +11,6 @@ class AbstractClassesFixTest : FixTestBase("test/chapter6/abstract_classes", ::A @Test @Tag(CLASS_SHOULD_NOT_BE_ABSTRACT) fun `fix abstract class`() { - fixAndCompare("ShouldRemoveAbstractKeywordExpected.kt", "ShouldRemoveAbstractKeywordTest.kt") + fixAndCompare("ShouldReplaceAbstractKeywordExpected.kt", "ShouldReplaceAbstractKeywordTest.kt") } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesWarnTest.kt index 4212c29cb4..76d2fc9511 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/AbstractClassesWarnTest.kt @@ -15,7 +15,7 @@ class AbstractClassesWarnTest : LintTestBase(::AbstractClassesRule) { @Test @Tag(CLASS_SHOULD_NOT_BE_ABSTRACT) - fun `should not remove abstract`() { + fun `should not replace abstract with open`() { lintMethod( """ |abstract class Some(val a: Int = 5) { @@ -29,7 +29,7 @@ class AbstractClassesWarnTest : LintTestBase(::AbstractClassesRule) { @Test @Tag(CLASS_SHOULD_NOT_BE_ABSTRACT) - fun `should remove abstract`() { + fun `should replace abstract on open`() { lintMethod( """ |abstract class Some(val a: Int = 5) { @@ -42,7 +42,7 @@ class AbstractClassesWarnTest : LintTestBase(::AbstractClassesRule) { @Test @Tag(CLASS_SHOULD_NOT_BE_ABSTRACT) - fun `should remove abstract with inner`() { + fun `should replace abstract on open with inner`() { lintMethod( """ |class Some(val a: Int = 5) { @@ -56,4 +56,34 @@ class AbstractClassesWarnTest : LintTestBase(::AbstractClassesRule) { LintError(4, 32, ruleId, "${Warnings.CLASS_SHOULD_NOT_BE_ABSTRACT.warnText()} Inner", true) ) } + + @Test + @Tag(CLASS_SHOULD_NOT_BE_ABSTRACT) + fun `should replace abstract on open in actual or expect classes`() { + lintMethod( + """ + |actual abstract class CoroutineTest actual constructor() { + | /** + | * Test rule + | */ + | @get:Rule + | var coroutineTestRule = CoroutineTestRule() + | + | /** + | * Run test + | * + | * @param T + | * @param block + | * @receiver a Coroutine Scope + | */ + | actual fun runTest(block: suspend CoroutineScope.() -> T) { + | runBlocking { + | block() + | } + | } + |} + """.trimMargin(), + LintError(1, 58, ruleId, "${Warnings.CLASS_SHOULD_NOT_BE_ABSTRACT.warnText()} CoroutineTest", true) + ) + } } diff --git a/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldRemoveAbstractKeywordExpected.kt b/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldRemoveAbstractKeywordExpected.kt deleted file mode 100644 index e1b6d5167b..0000000000 --- a/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldRemoveAbstractKeywordExpected.kt +++ /dev/null @@ -1,21 +0,0 @@ -package test.paragraph6.abstract_classes - -class Some() { - fun some(){} - - fun another(){} - - @SomeAnnotation @Another inner class Any { - fun func(){} - } - - inner class Second { - fun someFunc(){} - } -} - -abstract class Another { - abstract fun absFunc() - - fun someFunc(){} -} diff --git a/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldReplaceAbstractKeywordExpected.kt b/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldReplaceAbstractKeywordExpected.kt new file mode 100644 index 0000000000..ba222e0ef9 --- /dev/null +++ b/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldReplaceAbstractKeywordExpected.kt @@ -0,0 +1,29 @@ +package test.paragraph6.abstract_classes + +actual open class CoroutineTest actual constructor() { + actual fun runTest(block: suspend CoroutineScope.() -> T) { + runBlocking { + block() + } + } +} + +open class Some() { + fun some(){} + + fun another(){} + + @SomeAnnotation @Another open inner class Any { + fun func(){} + } + + inner open class Second { + fun someFunc(){} + } +} + +abstract class Another { + abstract fun absFunc() + + fun someFunc(){} +} diff --git a/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldRemoveAbstractKeywordTest.kt b/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldReplaceAbstractKeywordTest.kt similarity index 63% rename from diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldRemoveAbstractKeywordTest.kt rename to diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldReplaceAbstractKeywordTest.kt index 6784219efb..0e26c4a8d5 100644 --- a/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldRemoveAbstractKeywordTest.kt +++ b/diktat-rules/src/test/resources/test/chapter6/abstract_classes/ShouldReplaceAbstractKeywordTest.kt @@ -1,5 +1,13 @@ package test.paragraph6.abstract_classes +actual abstract class CoroutineTest actual constructor() { + actual fun runTest(block: suspend CoroutineScope.() -> T) { + runBlocking { + block() + } + } +} + abstract class Some() { fun some(){} diff --git a/info/guide/guide-chapter-6.md b/info/guide/guide-chapter-6.md index ff6c9bb6c9..7f9a1bd939 100644 --- a/info/guide/guide-chapter-6.md +++ b/info/guide/guide-chapter-6.md @@ -184,7 +184,7 @@ class Square() : Rectangle() { #### 6.1.6 Abstract class should have at least one abstract method Abstract classes are used to force a developer to implement some of its parts in their inheritors. -When the abstract class has no abstract methods, it was set `abstract` incorrectly and can be converted to a regular class. +When the abstract class has no abstract methods, it was set `abstract` incorrectly and can be converted to open class. **Invalid example**: ```kotlin @@ -204,11 +204,18 @@ abstract class NotAbstract { } // OR -class NotAbstract { +open class NotAbstract { fun foo() {} fun test() {} } + +// OR +class NotAbstract { + fun foo() {} + + fun test() {} +} ```