Add / preserve semicolon after empty companion object #KT-21179 Fixed

This commit is contained in:
Toshiaki Kameyama
2017-11-15 15:03:32 +03:00
committed by Mikhail Glukhikh
parent 45e5cc190f
commit 0eec3ef1f8
31 changed files with 311 additions and 8 deletions
@@ -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<KtClassBody>() != 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
}
@@ -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>(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<KtObjectDeclaration>()?.let {
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.RedundantSemicolonInspection
@@ -0,0 +1,7 @@
class Test {
companion object<caret>;
fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object
fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,8 @@
// PROBLEM: none
class Test {
companion object<caret>;
init {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,8 @@
// PROBLEM: none
class Test {
companion object<caret>;
private fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object<caret>;
val bar = 1
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object
val bar = 1
}
fun Test.Companion.foo() {}
@@ -0,0 +1,5 @@
class Test {
companion object<caret>;
}
fun Test.Companion.foo() {}
@@ -0,0 +1,5 @@
class Test {
companion object
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object {}<caret>;
inline fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object {}
inline fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object Foo<caret>;
inline fun test() {}
}
fun Test.Foo.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object Foo
inline fun test() {}
}
fun Test.Foo.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object <caret>{}
fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object
fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object <caret>{}
init {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object;<caret>
init {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object <caret>{}
private fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object;<caret>
private fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object <caret>{}
val bar = 1
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object
val bar = 1
}
fun Test.Companion.foo() {}
@@ -0,0 +1,5 @@
class Test {
companion object <caret>{}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,5 @@
class Test {
companion object
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object Foo <caret>{}
inline fun test() {}
}
fun Test.Foo.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object Foo
inline fun test() {}
}
fun Test.Foo.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object <caret>{};
inline fun test() {}
}
fun Test.Companion.foo() {}
@@ -0,0 +1,7 @@
class Test {
companion object;
inline fun test() {}
}
fun Test.Companion.foo() {}
@@ -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)
@@ -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");