From a3c3ab08fdc85b370659a8a6e1c876c0263be91d Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Fri, 5 Apr 2019 16:52:35 +0900 Subject: [PATCH] Override/Implement members: place members in the same order as super class members --- .../kotlin/idea/core/generateUtil.kt | 5 +- .../OverrideImplementMembersHandler.kt | 82 +++++++++++++++++-- .../implementFromClassName.kt | 8 ++ .../implementFromClassName.kt.after | 12 +++ .../implementFromClassName2.kt | 18 ++++ .../implementFromClassName2.kt.after | 42 ++++++++++ .../implementFromClassName3.kt | 18 ++++ .../implementFromClassName3.kt.after | 42 ++++++++++ .../implementFromClassName4.kt | 21 +++++ .../implementFromClassName4.kt.after | 41 ++++++++++ .../implementFromClassName5.kt | 27 ++++++ .../implementFromClassName5.kt.after | 51 ++++++++++++ .../implementFromClassName6.kt | 27 ++++++ .../implementFromClassName6.kt.after | 59 +++++++++++++ .../overrideFromClassName.kt.after | 14 ++-- .../enumClassWithSemicolonAndMembers.kt.after | 7 +- .../enumClassWithSemicolonAndMembers.kt.after | 5 +- .../idea/codeInsight/OverrideImplementTest.kt | 24 ++++++ 18 files changed, 480 insertions(+), 23 deletions(-) create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName.kt create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName.kt.after create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName2.kt create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName2.kt.after create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName3.kt create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName3.kt.after create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName4.kt create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName4.kt.after create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName5.kt create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName5.kt.after create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName6.kt create mode 100644 idea/testData/codeInsight/overrideImplement/implementFromClassName6.kt.after diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/generateUtil.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/generateUtil.kt index cb568ff83b3..46cf0b643d5 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/generateUtil.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/generateUtil.kt @@ -218,7 +218,8 @@ fun insertMembersAfter( editor: Editor?, classOrObject: KtClassOrObject, members: Collection, - anchor: PsiElement? = null + anchor: PsiElement? = null, + getAnchor: (KtDeclaration) -> PsiElement? = { null } ): List { members.ifEmpty { return emptyList() } @@ -239,6 +240,8 @@ fun insertMembersAfter( var afterAnchor = anchor ?: findInsertAfterAnchor(editor, body) ?: return@runWriteAction emptyList() otherMembers.mapTo(insertedMembers) { + afterAnchor = getAnchor(it) ?: afterAnchor + if (classOrObject is KtClass && classOrObject.isEnum()) { val enumEntries = classOrObject.declarations.filterIsInstance() val bound = (enumEntries.lastOrNull() ?: classOrObject.allChildren.firstOrNull { element -> diff --git a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMembersHandler.kt b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMembersHandler.kt index df4a03bf6e6..674c7410050 100644 --- a/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMembersHandler.kt +++ b/idea/idea-core/src/org/jetbrains/kotlin/idea/core/overrideImplement/OverrideImplementMembersHandler.kt @@ -25,13 +25,24 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.ui.DialogWrapper import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.insertMembersAfter +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.prevSiblingOfSameType +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.util.findCallableMemberBySignature +import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult abstract class OverrideImplementMembersHandler : LanguageCodeInsightActionHandler { @@ -42,7 +53,10 @@ abstract class OverrideImplementMembersHandler : LanguageCodeInsightActionHandle protected abstract fun collectMembersToGenerate(descriptor: ClassDescriptor, project: Project): Collection - private fun showOverrideImplementChooser(project: Project, members: Array): MemberChooser? { + private fun showOverrideImplementChooser( + project: Project, + members: Array + ): MemberChooser? { val chooser = MemberChooser(members, true, true, project) chooser.title = getChooserTitle() chooser.show() @@ -80,8 +94,7 @@ abstract class OverrideImplementMembersHandler : LanguageCodeInsightActionHandle if (implementAll) { selectedElements = members copyDoc = false - } - else { + } else { val chooser = showOverrideImplementChooser(project, members.toTypedArray()) ?: return selectedElements = chooser.selectedElements ?: return copyDoc = chooser.isCopyJavadoc @@ -101,12 +114,65 @@ abstract class OverrideImplementMembersHandler : LanguageCodeInsightActionHandle companion object { fun generateMembers( - editor: Editor?, - classOrObject: KtClassOrObject, - selectedElements: Collection, - copyDoc: Boolean + editor: Editor?, + classOrObject: KtClassOrObject, + selectedElements: Collection, + copyDoc: Boolean ) { - insertMembersAfter(editor, classOrObject, selectedElements.map { it.generateMember(classOrObject, copyDoc) }) + val selectedMemberDescriptors = selectedElements.associate { it.generateMember(classOrObject, copyDoc) to it.descriptor } + + val classBody = classOrObject.body + if (classBody == null) { + insertMembersAfter(editor, classOrObject, selectedMemberDescriptors.keys) + return + } + val offset = editor?.caretModel?.offset ?: classBody.startOffset + val offsetCursorElement = PsiTreeUtil.findFirstParent(classBody.containingFile.findElementAt(offset)) { + it.parent == classBody + } + if (offsetCursorElement != null && offsetCursorElement != classBody.rBrace) { + insertMembersAfter(editor, classOrObject, selectedMemberDescriptors.keys) + return + } + val classLeftBrace = classBody.lBrace + + val allSuperMemberDescriptors = selectedMemberDescriptors.values + .mapNotNull { it.containingDeclaration as? ClassDescriptor } + .associateWith { + DescriptorUtils.getAllDescriptors(it.unsubstitutedMemberScope).filterIsInstance() + } + + val implementedElements = mutableMapOf() + + fun ClassDescriptor.findElement(memberDescriptor: CallableMemberDescriptor): KtDeclaration? { + return implementedElements[memberDescriptor] + ?: (findCallableMemberBySignature(memberDescriptor)?.source?.getPsi() as? KtDeclaration)?.also { + implementedElements[memberDescriptor] = it + } + } + + fun getAnchor(selectedElement: KtDeclaration): PsiElement? { + val lastElement = classOrObject.declarations.lastOrNull() + val selectedMemberDescriptor = selectedMemberDescriptors[selectedElement] ?: return lastElement + val superMemberDescriptors = allSuperMemberDescriptors[selectedMemberDescriptor.containingDeclaration] ?: return lastElement + val index = superMemberDescriptors.indexOf(selectedMemberDescriptor) + if (index == -1) return lastElement + val classDescriptor = classOrObject.descriptor as? ClassDescriptor ?: return lastElement + + val upperElement = ((index - 1) downTo 0).firstNotNullResult { + classDescriptor.findElement(superMemberDescriptors[it]) + } + if (upperElement != null) return upperElement + + val lowerElement = ((index + 1) until superMemberDescriptors.size).firstNotNullResult { + classDescriptor.findElement(superMemberDescriptors[it]) + } + if (lowerElement != null) return lowerElement.prevSiblingOfSameType() ?: classLeftBrace + + return lastElement + } + + insertMembersAfter(editor, classOrObject, selectedMemberDescriptors.keys) { getAnchor(it) } } } } diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName.kt b/idea/testData/codeInsight/overrideImplement/implementFromClassName.kt new file mode 100644 index 00000000000..d9055c5e702 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName.kt @@ -0,0 +1,8 @@ +interface Runnable { + fun run() +} + +class A : Runnable { + fun foo() { + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName.kt.after b/idea/testData/codeInsight/overrideImplement/implementFromClassName.kt.after new file mode 100644 index 00000000000..229b4a08bf5 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName.kt.after @@ -0,0 +1,12 @@ +interface Runnable { + fun run() +} + +class A : Runnable { + fun foo() { + } + + override fun run() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName2.kt b/idea/testData/codeInsight/overrideImplement/implementFromClassName2.kt new file mode 100644 index 00000000000..77232b4cd0d --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName2.kt @@ -0,0 +1,18 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +class Test : A { + override fun a3() { + } + + override fun a6() { + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName2.kt.after b/idea/testData/codeInsight/overrideImplement/implementFromClassName2.kt.after new file mode 100644 index 00000000000..5655c90c4fd --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName2.kt.after @@ -0,0 +1,42 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +class Test : A { + override fun a1() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a2() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a3() { + } + + override fun a4() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a5() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a6() { + } + + override fun a7() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a8() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName3.kt b/idea/testData/codeInsight/overrideImplement/implementFromClassName3.kt new file mode 100644 index 00000000000..8b9c3198ff0 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName3.kt @@ -0,0 +1,18 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +class Test : A { + override fun a6() { + } + + override fun a3() { + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName3.kt.after b/idea/testData/codeInsight/overrideImplement/implementFromClassName3.kt.after new file mode 100644 index 00000000000..92f234c8ad1 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName3.kt.after @@ -0,0 +1,42 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +class Test : A { + override fun a6() { + } + + override fun a7() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a8() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a1() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a2() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a3() { + } + + override fun a4() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a5() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName4.kt b/idea/testData/codeInsight/overrideImplement/implementFromClassName4.kt new file mode 100644 index 00000000000..85f914c9c16 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName4.kt @@ -0,0 +1,21 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +class Test : A { + override fun a4() { + } + + override fun a6() { + } + + override fun a2() { + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName4.kt.after b/idea/testData/codeInsight/overrideImplement/implementFromClassName4.kt.after new file mode 100644 index 00000000000..97ae86ffb5d --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName4.kt.after @@ -0,0 +1,41 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +class Test : A { + override fun a4() { + } + + override fun a5() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a6() { + } + + override fun a7() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a8() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a1() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a2() { + } + + override fun a3() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName5.kt b/idea/testData/codeInsight/overrideImplement/implementFromClassName5.kt new file mode 100644 index 00000000000..460a1b07790 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName5.kt @@ -0,0 +1,27 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +class Test : A { + fun foo() { + } + + override fun a3() { + } + + fun bar() { + } + + override fun a6() { + } + + fun baz() { + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName5.kt.after b/idea/testData/codeInsight/overrideImplement/implementFromClassName5.kt.after new file mode 100644 index 00000000000..e4d773fa837 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName5.kt.after @@ -0,0 +1,51 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +class Test : A { + fun foo() { + } + + override fun a1() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a2() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a3() { + } + + override fun a4() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a5() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + fun bar() { + } + + override fun a6() { + } + + override fun a7() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a8() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + fun baz() { + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName6.kt b/idea/testData/codeInsight/overrideImplement/implementFromClassName6.kt new file mode 100644 index 00000000000..1dd8e8bdaee --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName6.kt @@ -0,0 +1,27 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +interface B { + fun b3() + fun b2() + fun b1() +} + +class Test : A, B { + override fun a3() { + } + + override fun b2() { + } + + override fun a6() { + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/implementFromClassName6.kt.after b/idea/testData/codeInsight/overrideImplement/implementFromClassName6.kt.after new file mode 100644 index 00000000000..33fe7c0e510 --- /dev/null +++ b/idea/testData/codeInsight/overrideImplement/implementFromClassName6.kt.after @@ -0,0 +1,59 @@ +interface A { + fun a1() + fun a2() + fun a3() + fun a4() + fun a5() + fun a6() + fun a7() + fun a8() +} + +interface B { + fun b3() + fun b2() + fun b1() +} + +class Test : A, B { + override fun a1() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a2() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a3() { + } + + override fun a4() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a5() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun b3() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun b2() { + } + + override fun b1() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a6() { + } + + override fun a7() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } + + override fun a8() { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/overrideImplement/overrideFromClassName.kt.after b/idea/testData/codeInsight/overrideImplement/overrideFromClassName.kt.after index d5c1c5cb39d..dd7cedd05f3 100644 --- a/idea/testData/codeInsight/overrideImplement/overrideFromClassName.kt.after +++ b/idea/testData/codeInsight/overrideImplement/overrideFromClassName.kt.after @@ -3,6 +3,13 @@ interface Some { } class Other { + fun test() { + val a = 1 + } + fun otherTest() { + + } + override fun equals(other: Any?): Boolean { return super.equals(other) } @@ -14,12 +21,5 @@ class Other { override fun toString(): String { return super.toString() } - - fun test() { - val a = 1 - } - fun otherTest() { - - } } diff --git a/idea/testData/intentions/implementAbstractMember/function/enumClassWithSemicolonAndMembers.kt.after b/idea/testData/intentions/implementAbstractMember/function/enumClassWithSemicolonAndMembers.kt.after index 0bd527783f4..749cf0c03e5 100644 --- a/idea/testData/intentions/implementAbstractMember/function/enumClassWithSemicolonAndMembers.kt.after +++ b/idea/testData/intentions/implementAbstractMember/function/enumClassWithSemicolonAndMembers.kt.after @@ -7,11 +7,10 @@ interface T { enum class E : T { A, B, C; - override fun foo(x: Int): Int { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } - val bar = 1 fun baz() = 2 + override fun foo(x: Int): Int { + TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + } } \ No newline at end of file diff --git a/idea/testData/intentions/implementAbstractMember/property/enumClassWithSemicolonAndMembers.kt.after b/idea/testData/intentions/implementAbstractMember/property/enumClassWithSemicolonAndMembers.kt.after index 4e1103f5700..a16a276dce1 100644 --- a/idea/testData/intentions/implementAbstractMember/property/enumClassWithSemicolonAndMembers.kt.after +++ b/idea/testData/intentions/implementAbstractMember/property/enumClassWithSemicolonAndMembers.kt.after @@ -7,10 +7,9 @@ interface T { enum class E : T { A, B, C; - override val foo: Int - get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. - val bar = 1 fun baz() = 2 + override val foo: Int + get() = TODO("not implemented") //To change initializer of created properties use File | Settings | File Templates. } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/OverrideImplementTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/OverrideImplementTest.kt index d8ff8feed1b..ecc7ef24700 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/OverrideImplementTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/OverrideImplementTest.kt @@ -289,4 +289,28 @@ class OverrideImplementTest : AbstractOverrideImplementTest() { fun testUnresolvedType() { doOverrideFileTest() } + + fun testImplementFromClassName() { + doMultiImplementFileTest() + } + + fun testImplementFromClassName2() { + doMultiImplementFileTest() + } + + fun testImplementFromClassName3() { + doMultiImplementFileTest() + } + + fun testImplementFromClassName4() { + doMultiImplementFileTest() + } + + fun testImplementFromClassName5() { + doMultiImplementFileTest() + } + + fun testImplementFromClassName6() { + doMultiImplementFileTest() + } }