diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSemicolonInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSemicolonInspection.kt index c79f45ed671..77e850b1dbc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSemicolonInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/RedundantSemicolonInspection.kt @@ -25,9 +25,7 @@ import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiWhiteSpace import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType -import org.jetbrains.kotlin.psi.psiUtil.nextLeaf -import org.jetbrains.kotlin.psi.psiUtil.prevLeaf +import org.jetbrains.kotlin.psi.psiUtil.* class RedundantSemicolonInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { @@ -77,6 +75,22 @@ class RedundantSemicolonInspection : AbstractKotlinInspection(), CleanupLocalIns return false // case with statement starting with '{' and call on the previous line } + if (isRequiredForCompanion(semicolon)) { + return false + } + + return true + } + + private fun isRequiredForCompanion(semicolon: PsiElement): Boolean { + val prev = semicolon.getPrevSiblingIgnoringWhitespaceAndComments() as? KtObjectDeclaration ?: return false + if (!prev.isCompanion()) return false + if (prev.nameIdentifier != null || prev.getChildOfType() != null) return false + + val next = semicolon.getNextSiblingIgnoringWhitespaceAndComments() ?: return false + val firstChildNode = next.firstChild?.node ?: return false + if (KtTokens.KEYWORDS.contains(firstChildNode.elementType)) return false + return true } diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveEmptyClassBodyIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveEmptyClassBodyIntention.kt index 793699ea33a..abb783c46b6 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveEmptyClassBodyIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/RemoveEmptyClassBodyIntention.kt @@ -19,11 +19,12 @@ package org.jetbrains.kotlin.idea.intentions import com.intellij.codeInspection.CleanupLocalInspectionTool import com.intellij.codeInspection.ProblemHighlightType import com.intellij.openapi.editor.Editor +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.editor.fixers.range import org.jetbrains.kotlin.idea.inspections.IntentionBasedInspection -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtClassBody -import org.jetbrains.kotlin.psi.KtObjectDeclaration -import org.jetbrains.kotlin.psi.KtSecondaryConstructor +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType @@ -34,7 +35,25 @@ class RemoveEmptyClassBodyInspection : } class RemoveEmptyClassBodyIntention : SelfTargetingOffsetIndependentIntention(KtClassBody::class.java, "Remove empty class body") { - override fun applyTo(element: KtClassBody, editor: Editor?) = element.delete() + + override fun applyTo(element: KtClassBody, editor: Editor?) { + val parent = element.parent + element.delete() + addSemicolonAfterEmptyCompanion(parent, editor) + } + + private fun addSemicolonAfterEmptyCompanion(element: PsiElement, editor: Editor?) { + if (element !is KtObjectDeclaration) return + if (!element.isCompanion() || element.nameIdentifier != null) return + + val next = element.getNextSiblingIgnoringWhitespaceAndComments() ?: return + if (next.node.elementType == KtTokens.SEMICOLON) return + val firstChildNode = next.firstChild?.node ?: return + if (firstChildNode.elementType in KtTokens.KEYWORDS) return + + element.parent.addAfter(KtPsiFactory(element).createSemicolon(), element) + editor?.caretModel?.moveToOffset(element.endOffset + 1) + } override fun isApplicableTo(element: KtClassBody): Boolean { element.getStrictParentOfType()?.let { diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/.inspection b/idea/testData/inspectionsLocal/redundantSemicolon/.inspection new file mode 100644 index 00000000000..6160b7b2659 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.RedundantSemicolonInspection \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeFun.kt b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeFun.kt new file mode 100644 index 00000000000..e62fbf3c4e4 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeFun.kt @@ -0,0 +1,7 @@ +class Test { + companion object; + + fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeFun.kt.after b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeFun.kt.after new file mode 100644 index 00000000000..48228e06728 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeFun.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object + + fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeInit.kt b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeInit.kt new file mode 100644 index 00000000000..e2c19253fc8 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeInit.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +class Test { + companion object; + + init {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforePrivateFun.kt b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforePrivateFun.kt new file mode 100644 index 00000000000..0ec4b0b2eb6 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforePrivateFun.kt @@ -0,0 +1,8 @@ +// PROBLEM: none +class Test { + companion object; + + private fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeVal.kt b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeVal.kt new file mode 100644 index 00000000000..b2b96b3e629 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeVal.kt @@ -0,0 +1,7 @@ +class Test { + companion object; + + val bar = 1 +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeVal.kt.after b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeVal.kt.after new file mode 100644 index 00000000000..e203ae08b61 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeVal.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object + + val bar = 1 +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionInLast.kt b/idea/testData/inspectionsLocal/redundantSemicolon/companionInLast.kt new file mode 100644 index 00000000000..7b6f14c18fc --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionInLast.kt @@ -0,0 +1,5 @@ +class Test { + companion object; +} + +fun Test.Companion.foo() {} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionInLast.kt.after b/idea/testData/inspectionsLocal/redundantSemicolon/companionInLast.kt.after new file mode 100644 index 00000000000..724f71a4212 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionInLast.kt.after @@ -0,0 +1,5 @@ +class Test { + companion object +} + +fun Test.Companion.foo() {} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionWithBody.kt b/idea/testData/inspectionsLocal/redundantSemicolon/companionWithBody.kt new file mode 100644 index 00000000000..ab36e190c9d --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionWithBody.kt @@ -0,0 +1,7 @@ +class Test { + companion object {}; + + inline fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionWithBody.kt.after b/idea/testData/inspectionsLocal/redundantSemicolon/companionWithBody.kt.after new file mode 100644 index 00000000000..20bf9578cc4 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionWithBody.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object {} + + inline fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionWithName.kt b/idea/testData/inspectionsLocal/redundantSemicolon/companionWithName.kt new file mode 100644 index 00000000000..600f08a6baa --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionWithName.kt @@ -0,0 +1,7 @@ +class Test { + companion object Foo; + + inline fun test() {} +} + +fun Test.Foo.foo() {} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/redundantSemicolon/companionWithName.kt.after b/idea/testData/inspectionsLocal/redundantSemicolon/companionWithName.kt.after new file mode 100644 index 00000000000..cd4ef465ed9 --- /dev/null +++ b/idea/testData/inspectionsLocal/redundantSemicolon/companionWithName.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object Foo + + inline fun test() {} +} + +fun Test.Foo.foo() {} \ No newline at end of file diff --git a/idea/testData/intentions/removeEmptyClassBody/companionBeforeFun.kt b/idea/testData/intentions/removeEmptyClassBody/companionBeforeFun.kt new file mode 100644 index 00000000000..aa03c0ae8fd --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionBeforeFun.kt @@ -0,0 +1,7 @@ +class Test { + companion object {} + + fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionBeforeFun.kt.after b/idea/testData/intentions/removeEmptyClassBody/companionBeforeFun.kt.after new file mode 100644 index 00000000000..48228e06728 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionBeforeFun.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object + + fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionBeforeInit.kt b/idea/testData/intentions/removeEmptyClassBody/companionBeforeInit.kt new file mode 100644 index 00000000000..13d8477b035 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionBeforeInit.kt @@ -0,0 +1,7 @@ +class Test { + companion object {} + + init {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionBeforeInit.kt.after b/idea/testData/intentions/removeEmptyClassBody/companionBeforeInit.kt.after new file mode 100644 index 00000000000..1c7782e458b --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionBeforeInit.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object; + + init {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionBeforePrivateFun.kt b/idea/testData/intentions/removeEmptyClassBody/companionBeforePrivateFun.kt new file mode 100644 index 00000000000..2e2b972046a --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionBeforePrivateFun.kt @@ -0,0 +1,7 @@ +class Test { + companion object {} + + private fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionBeforePrivateFun.kt.after b/idea/testData/intentions/removeEmptyClassBody/companionBeforePrivateFun.kt.after new file mode 100644 index 00000000000..06ed4068066 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionBeforePrivateFun.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object; + + private fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionBeforeVal.kt b/idea/testData/intentions/removeEmptyClassBody/companionBeforeVal.kt new file mode 100644 index 00000000000..a06afd182d4 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionBeforeVal.kt @@ -0,0 +1,7 @@ +class Test { + companion object {} + + val bar = 1 +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionBeforeVal.kt.after b/idea/testData/intentions/removeEmptyClassBody/companionBeforeVal.kt.after new file mode 100644 index 00000000000..e203ae08b61 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionBeforeVal.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object + + val bar = 1 +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionInLast.kt b/idea/testData/intentions/removeEmptyClassBody/companionInLast.kt new file mode 100644 index 00000000000..90a8eeb11b8 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionInLast.kt @@ -0,0 +1,5 @@ +class Test { + companion object {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionInLast.kt.after b/idea/testData/intentions/removeEmptyClassBody/companionInLast.kt.after new file mode 100644 index 00000000000..aba0ea3b354 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionInLast.kt.after @@ -0,0 +1,5 @@ +class Test { + companion object +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionWithName.kt b/idea/testData/intentions/removeEmptyClassBody/companionWithName.kt new file mode 100644 index 00000000000..ff1bfc2cafc --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionWithName.kt @@ -0,0 +1,7 @@ +class Test { + companion object Foo {} + + inline fun test() {} +} + +fun Test.Foo.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionWithName.kt.after b/idea/testData/intentions/removeEmptyClassBody/companionWithName.kt.after new file mode 100644 index 00000000000..7b745a2b6b2 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionWithName.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object Foo + + inline fun test() {} +} + +fun Test.Foo.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionWithSemicolon.kt b/idea/testData/intentions/removeEmptyClassBody/companionWithSemicolon.kt new file mode 100644 index 00000000000..a60150f89e9 --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionWithSemicolon.kt @@ -0,0 +1,7 @@ +class Test { + companion object {}; + + inline fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/testData/intentions/removeEmptyClassBody/companionWithSemicolon.kt.after b/idea/testData/intentions/removeEmptyClassBody/companionWithSemicolon.kt.after new file mode 100644 index 00000000000..9b6322b9e2b --- /dev/null +++ b/idea/testData/intentions/removeEmptyClassBody/companionWithSemicolon.kt.after @@ -0,0 +1,7 @@ +class Test { + companion object; + + inline fun test() {} +} + +fun Test.Companion.foo() {} diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 3edf3197512..6f0e23156b8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -1833,6 +1833,57 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/redundantSemicolon") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedundantSemicolon extends AbstractLocalInspectionTest { + public void testAllFilesPresentInRedundantSemicolon() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/redundantSemicolon"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("companionBeforeFun.kt") + public void testCompanionBeforeFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeFun.kt"); + doTest(fileName); + } + + @TestMetadata("companionBeforeInit.kt") + public void testCompanionBeforeInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeInit.kt"); + doTest(fileName); + } + + @TestMetadata("companionBeforePrivateFun.kt") + public void testCompanionBeforePrivateFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSemicolon/companionBeforePrivateFun.kt"); + doTest(fileName); + } + + @TestMetadata("companionBeforeVal.kt") + public void testCompanionBeforeVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSemicolon/companionBeforeVal.kt"); + doTest(fileName); + } + + @TestMetadata("companionInLast.kt") + public void testCompanionInLast() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSemicolon/companionInLast.kt"); + doTest(fileName); + } + + @TestMetadata("companionWithBody.kt") + public void testCompanionWithBody() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSemicolon/companionWithBody.kt"); + doTest(fileName); + } + + @TestMetadata("companionWithName.kt") + public void testCompanionWithName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/redundantSemicolon/companionWithName.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/redundantSetter") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index 0d01bd40e41..a19157ec334 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -12896,6 +12896,48 @@ public class IntentionTestGenerated extends AbstractIntentionTest { doTest(fileName); } + @TestMetadata("companionBeforeFun.kt") + public void testCompanionBeforeFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeEmptyClassBody/companionBeforeFun.kt"); + doTest(fileName); + } + + @TestMetadata("companionBeforeInit.kt") + public void testCompanionBeforeInit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeEmptyClassBody/companionBeforeInit.kt"); + doTest(fileName); + } + + @TestMetadata("companionBeforePrivateFun.kt") + public void testCompanionBeforePrivateFun() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeEmptyClassBody/companionBeforePrivateFun.kt"); + doTest(fileName); + } + + @TestMetadata("companionBeforeVal.kt") + public void testCompanionBeforeVal() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeEmptyClassBody/companionBeforeVal.kt"); + doTest(fileName); + } + + @TestMetadata("companionInLast.kt") + public void testCompanionInLast() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeEmptyClassBody/companionInLast.kt"); + doTest(fileName); + } + + @TestMetadata("companionWithName.kt") + public void testCompanionWithName() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeEmptyClassBody/companionWithName.kt"); + doTest(fileName); + } + + @TestMetadata("companionWithSemicolon.kt") + public void testCompanionWithSemicolon() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeEmptyClassBody/companionWithSemicolon.kt"); + doTest(fileName); + } + @TestMetadata("emptyClass.kt") public void testEmptyClass() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/removeEmptyClassBody/emptyClass.kt");