From aeefdffaab65f93451903df6a03f286878591274 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Thu, 24 Dec 2015 17:13:53 +0300 Subject: [PATCH] Implement Abstract Member Intention: Support primary constructor parameters #KT-8427 Fixed --- .../src/org/jetbrains/kotlin/psi/KtClass.kt | 16 ++++ .../kotlin/idea/quickfix/generateUtil.kt | 53 ++++++++----- .../after.kt.template | 7 ++ .../before.kt.template | 7 ++ .../description.html | 5 ++ idea/src/META-INF/plugin.xml | 5 ++ .../ImplementAbstractMemberIntention.kt | 65 +++++++++++++--- .../idea/quickfix/SuperClassNotInitialized.kt | 1 - .../usages/KotlinCallableDefinitionUsage.kt | 5 +- .../usages/KotlinCallerUsages.kt | 1 - .../KotlinInplaceParameterIntroducer.kt | 1 - .../idea/refactoring/jetRefactoringUtil.kt | 16 ---- .../refactoring/pullUp/KotlinPullUpHelper.kt | 1 - .../.intention | 1 + .../enumClass.kt | 9 +++ .../enumClass.kt.after | 9 +++ .../function.kt | 9 +++ .../implementAll.kt | 22 ++++++ .../implementAll.kt.after | 22 ++++++ .../inEnumClass.kt | 7 ++ .../inFinalClass.kt | 5 ++ .../inObject.kt | 5 ++ .../noDirectOverridesNeeded.kt | 17 +++++ .../noInheritors.kt | 4 + .../notAbstractInClass.kt | 4 + .../notAbstractNoBodyInClass.kt | 5 ++ .../notAbstractWithGetterInInterface.kt | 5 ++ .../after/source/inheritors.java | 20 +++++ .../implementValInJava/after/source/test.kt | 5 ++ .../before/source/inheritors.java | 20 +++++ .../implementValInJava/before/source/test.kt | 5 ++ .../implementAllInJava.test | 6 ++ .../intentions/IntentionTestGenerated.java | 75 +++++++++++++++++++ .../MultiFileIntentionTestGenerated.java | 6 ++ 34 files changed, 389 insertions(+), 55 deletions(-) create mode 100644 idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/after.kt.template create mode 100644 idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/before.kt.template create mode 100644 idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/description.html create mode 100644 idea/testData/intentions/implementAsConstructorParameter/.intention create mode 100644 idea/testData/intentions/implementAsConstructorParameter/enumClass.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/enumClass.kt.after create mode 100644 idea/testData/intentions/implementAsConstructorParameter/function.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/implementAll.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/implementAll.kt.after create mode 100644 idea/testData/intentions/implementAsConstructorParameter/inEnumClass.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/inFinalClass.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/inObject.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/noDirectOverridesNeeded.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/noInheritors.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/notAbstractInClass.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/notAbstractNoBodyInClass.kt create mode 100644 idea/testData/intentions/implementAsConstructorParameter/notAbstractWithGetterInInterface.kt create mode 100644 idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/after/source/inheritors.java create mode 100644 idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/after/source/test.kt create mode 100644 idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/before/source/inheritors.java create mode 100644 idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/before/source/test.kt create mode 100644 idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/implementAllInJava.test diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtClass.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtClass.kt index f2e895c87a3..78196fd2b30 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtClass.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtClass.kt @@ -88,3 +88,19 @@ public open class KtClass : KtClassOrObject { public fun getClassOrInterfaceKeyword(): PsiElement? = findChildByType(TokenSet.create(KtTokens.CLASS_KEYWORD, KtTokens.INTERFACE_KEYWORD)) } + +public fun KtClass.createPrimaryConstructorIfAbsent(): KtPrimaryConstructor { + val constructor = getPrimaryConstructor() + if (constructor != null) return constructor + var anchor: PsiElement? = typeParameterList + if (anchor == null) anchor = nameIdentifier + if (anchor == null) anchor = lastChild + return addAfter(KtPsiFactory(project).createPrimaryConstructor(), anchor) as KtPrimaryConstructor +} + +public fun KtClass.createPrimaryConstructorParameterListIfAbsent(): KtParameterList { + val constructor = createPrimaryConstructorIfAbsent() + val parameterList = constructor.valueParameterList + if (parameterList != null) return parameterList + return constructor.add(KtPsiFactory(project).createParameterList("()")) as KtParameterList +} \ No newline at end of file diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt index 7b871cc9515..3b45ef1f11e 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/quickfix/generateUtil.kt @@ -153,29 +153,42 @@ public fun insertMembersAfter( ): List { members.ifEmpty { return emptyList() } - return runWriteAction> { - val body = classOrObject.getOrCreateBody() + return runWriteAction { + val insertedMembers = SmartList() - var afterAnchor = anchor ?: findInsertAfterAnchor(editor, body) ?: return@runWriteAction emptyList() - val insertedMembers = members.mapTo(SmartList()) { - if (classOrObject is KtClass && classOrObject.isEnum()) { - val enumEntries = classOrObject.declarations.filterIsInstance() - val bound = (enumEntries.lastOrNull() ?: classOrObject.allChildren.firstOrNull { it.node.elementType == KtTokens.SEMICOLON }) - if (it !is KtEnumEntry) { - if (bound != null && afterAnchor.startOffset <= bound.startOffset) { - afterAnchor = bound - } - } - else if (bound == null && body.declarations.isNotEmpty()) { - afterAnchor = body.lBrace!! - } - else if (bound != null && afterAnchor.startOffset >= bound.startOffset) { - afterAnchor = bound.prevSibling!! - } - } + val (parameters, otherMembers) = members.partition { it is KtParameter } + + parameters.mapNotNullTo(insertedMembers) { + if (classOrObject !is KtClass) return@mapNotNullTo null @Suppress("UNCHECKED_CAST") - (body.addAfter(it, afterAnchor) as T).apply { afterAnchor = this } + (classOrObject.createPrimaryConstructorParameterListIfAbsent().addParameter(it as KtParameter) as T) + } + + if (otherMembers.isNotEmpty()) { + val body = classOrObject.getOrCreateBody() + + var afterAnchor = anchor ?: findInsertAfterAnchor(editor, body) ?: return@runWriteAction emptyList() + otherMembers.mapNotNullTo(insertedMembers) { + if (classOrObject is KtClass && classOrObject.isEnum()) { + val enumEntries = classOrObject.declarations.filterIsInstance() + val bound = (enumEntries.lastOrNull() ?: classOrObject.allChildren.firstOrNull { it.node.elementType == KtTokens.SEMICOLON }) + if (it !is KtEnumEntry) { + if (bound != null && afterAnchor.startOffset <= bound.startOffset) { + afterAnchor = bound + } + } + else if (bound == null && body.declarations.isNotEmpty()) { + afterAnchor = body.lBrace!! + } + else if (bound != null && afterAnchor.startOffset >= bound.startOffset) { + afterAnchor = bound.prevSibling!! + } + } + + @Suppress("UNCHECKED_CAST") + (body.addAfter(it, afterAnchor) as T).apply { afterAnchor = this } + } } ShortenReferences.DEFAULT.process(insertedMembers) diff --git a/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/after.kt.template b/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/after.kt.template new file mode 100644 index 00000000000..fba9fb7899f --- /dev/null +++ b/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/after.kt.template @@ -0,0 +1,7 @@ +interface A { + val foo: Int +} + +class B(override val foo: Int) : A { + +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/before.kt.template b/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/before.kt.template new file mode 100644 index 00000000000..3a5422271da --- /dev/null +++ b/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/before.kt.template @@ -0,0 +1,7 @@ +interface A { + val foo: Int +} + +class B : A { + +} \ No newline at end of file diff --git a/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/description.html b/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/description.html new file mode 100644 index 00000000000..9d823ce8046 --- /dev/null +++ b/idea/resources/intentionDescriptions/ImplementAbstractMemberAsConstructorParameterIntention/description.html @@ -0,0 +1,5 @@ + + +This intention searches for all classes that can implement selected abstract property, and creates corresponding property parameter there. + + \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 37002cf4961..7401485cc76 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -1115,6 +1115,11 @@ Kotlin + + org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberAsConstructorParameterIntention + Kotlin + + org.jetbrains.kotlin.idea.intentions.AddValVarToConstructorParameterAction$Intention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt index a51729e569d..ba93f0b844f 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ImplementAbstractMemberIntention.kt @@ -24,7 +24,6 @@ import com.intellij.ide.util.PsiElementListCellRenderer import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor -import com.intellij.openapi.project.Project import com.intellij.openapi.ui.popup.PopupChooserBuilder import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiClass @@ -40,7 +39,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.getJavaClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMembersHandler import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject -import org.jetbrains.kotlin.idea.refactoring.canRefactor import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass import org.jetbrains.kotlin.idea.refactoring.runSynchronouslyWithProgress import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest @@ -48,6 +46,7 @@ import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.types.TypeSubstitutor @@ -57,10 +56,10 @@ import org.jetbrains.kotlin.utils.addToStdlib.singletonList import java.util.* import javax.swing.ListSelectionModel -class ImplementAbstractMemberIntention : +abstract class ImplementAbstractMemberIntentionBase : SelfTargetingRangeIntention(KtNamedDeclaration::class.java, "", "Implement abstract member") { companion object { - private val LOG = Logger.getInstance("#${ImplementAbstractMemberIntention::class.java.canonicalName}") + private val LOG = Logger.getInstance("#${ImplementAbstractMemberIntentionBase::class.java.canonicalName}") } private fun isAbstract(element: KtNamedDeclaration): Boolean { @@ -73,7 +72,7 @@ class ImplementAbstractMemberIntention : } } - private fun findExistingImplementation( + protected fun findExistingImplementation( subClass: ClassDescriptor, superMember: CallableMemberDescriptor ): CallableMemberDescriptor? { @@ -83,6 +82,8 @@ class ImplementAbstractMemberIntention : if (subMember?.kind?.isReal ?: false) return subMember else return null } + protected abstract fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean + private fun findClassesToProcess(member: KtNamedDeclaration): Sequence { val baseClass = member.containingClassOrObject as? KtClass ?: return emptySequence() val memberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return emptySequence() @@ -94,7 +95,7 @@ class ImplementAbstractMemberIntention : is PsiClass -> subClass.getJavaClassDescriptor() else -> null } as? ClassDescriptor ?: return false - return classDescriptor.kind != ClassKind.INTERFACE && findExistingImplementation(classDescriptor, memberDescriptor) == null + return acceptSubClass(classDescriptor, memberDescriptor) } if (baseClass.isEnum()) { @@ -110,20 +111,20 @@ class ImplementAbstractMemberIntention : .filter(::acceptSubClass) } + protected abstract fun computeText(element: KtNamedDeclaration): String? + override fun applicabilityRange(element: KtNamedDeclaration): TextRange? { if (!isAbstract(element)) return null - text = when(element) { - is KtProperty -> "Implement abstract property" - is KtNamedFunction -> "Implement abstract function" - else -> return null - } + text = computeText(element) ?: return null if (!findClassesToProcess(element).any()) return null return element.nameIdentifier?.textRange } + protected abstract val preferConstructorParameters: Boolean + private fun implementInKotlinClass(member: KtNamedDeclaration, targetClass: KtClassOrObject) { val subClassDescriptor = targetClass.resolveToDescriptorIfAny() as? ClassDescriptor ?: return val superMemberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return @@ -134,7 +135,8 @@ class ImplementAbstractMemberIntention : val chooserObject = OverrideMemberChooserObject.create(member.project, descriptorToImplement, descriptorToImplement, - OverrideMemberChooserObject.BodyType.EMPTY) + OverrideMemberChooserObject.BodyType.EMPTY, + preferConstructorParameters) OverrideImplementMembersHandler.generateMembers(null, targetClass, chooserObject.singletonList()) } @@ -229,4 +231,43 @@ class ImplementAbstractMemberIntention : .createPopup() .showInBestPositionFor(editor) } +} + +class ImplementAbstractMemberIntention : ImplementAbstractMemberIntentionBase() { + override fun computeText(element: KtNamedDeclaration): String? { + return when(element) { + is KtProperty -> "Implement abstract property" + is KtNamedFunction -> "Implement abstract function" + else -> null + } + } + + override fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean { + return subClassDescriptor.kind != ClassKind.INTERFACE && findExistingImplementation(subClassDescriptor, memberDescriptor) == null + } + + override val preferConstructorParameters: Boolean + get() = false +} + +class ImplementAbstractMemberAsConstructorParameterIntention : ImplementAbstractMemberIntentionBase() { + override fun computeText(element: KtNamedDeclaration): String? { + if (element !is KtProperty) return null + return "Implement as constructor parameter" + } + + override fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean { + val kind = subClassDescriptor.kind + return (kind == ClassKind.CLASS || kind == ClassKind.ENUM_CLASS) + && subClassDescriptor !is JavaClassDescriptor + && findExistingImplementation(subClassDescriptor, memberDescriptor) == null + } + + override val preferConstructorParameters: Boolean + get() = true + + override fun applicabilityRange(element: KtNamedDeclaration): TextRange? { + if (element !is KtProperty) return null + return super.applicabilityRange(element) + } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/SuperClassNotInitialized.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/SuperClassNotInitialized.kt index e5c8fa49e05..c8faabfb045 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/SuperClassNotInitialized.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/SuperClassNotInitialized.kt @@ -30,7 +30,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.core.isVisible -import org.jetbrains.kotlin.idea.refactoring.createPrimaryConstructorParameterListIfAbsent import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.ShortenReferences diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinCallableDefinitionUsage.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinCallableDefinitionUsage.kt index 97062aed1fc..d72c2106b54 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinCallableDefinitionUsage.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinCallableDefinitionUsage.kt @@ -26,15 +26,14 @@ import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.getJavaMethodDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet -import org.jetbrains.kotlin.idea.refactoring.createPrimaryConstructorIfAbsent -import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary -import org.jetbrains.kotlin.idea.refactoring.replaceListPsiAndKeepDelimiters import org.jetbrains.kotlin.idea.core.setVisibility import org.jetbrains.kotlin.idea.core.toKeywordToken import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeInfo import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinParameterInfo import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar import org.jetbrains.kotlin.idea.refactoring.changeSignature.getCallableSubstitutor +import org.jetbrains.kotlin.idea.refactoring.dropOverrideKeywordIfNecessary +import org.jetbrains.kotlin.idea.refactoring.replaceListPsiAndKeepDelimiters import org.jetbrains.kotlin.idea.util.ShortenReferences import org.jetbrains.kotlin.idea.util.ShortenReferences.Options import org.jetbrains.kotlin.psi.* diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinCallerUsages.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinCallerUsages.kt index 50c80c2690f..34a5d92102d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinCallerUsages.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/usages/KotlinCallerUsages.kt @@ -18,7 +18,6 @@ package org.jetbrains.kotlin.idea.refactoring.changeSignature.usages import com.intellij.usageView.UsageInfo import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet -import org.jetbrains.kotlin.idea.refactoring.createPrimaryConstructorParameterListIfAbsent import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeInfo import org.jetbrains.kotlin.idea.refactoring.changeSignature.getAffectedCallables import org.jetbrains.kotlin.idea.refactoring.changeSignature.isInsideOfCallerBody diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt index e1b678f6f67..cf9a7de5f5d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt @@ -30,7 +30,6 @@ import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.ui.JBColor import com.intellij.ui.NonFocusableCheckBox -import org.jetbrains.kotlin.idea.refactoring.createPrimaryConstructorParameterListIfAbsent import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractKotlinInplaceIntroducer import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinInplaceVariableIntroducer diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt index efeddc75216..8f2ca248587 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/jetRefactoringUtil.kt @@ -701,22 +701,6 @@ public fun String.quoteIfNeeded(): String = if (KotlinNameSuggester.isIdentifier public fun FqNameUnsafe.hasIdentifiersOnly(): Boolean = pathSegments().all { KotlinNameSuggester.isIdentifier(it.asString()) } -public fun KtClass.createPrimaryConstructorIfAbsent(): KtPrimaryConstructor { - val constructor = getPrimaryConstructor() - if (constructor != null) return constructor - var anchor: PsiElement? = typeParameterList - if (anchor == null) anchor = nameIdentifier - if (anchor == null) anchor = lastChild - return addAfter(KtPsiFactory(project).createPrimaryConstructor(), anchor) as KtPrimaryConstructor -} - -public fun KtClass.createPrimaryConstructorParameterListIfAbsent(): KtParameterList { - val constructor = createPrimaryConstructorIfAbsent() - val parameterList = constructor.valueParameterList - if (parameterList != null) return parameterList - return constructor.add(KtPsiFactory(project).createParameterList("()")) as KtParameterList -} - fun PsiNamedElement.isInterfaceClass(): Boolean = this is KtClass && isInterface() || this is PsiClass && isInterface fun replaceListPsiAndKeepDelimiters( diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt index 14bbf865ca5..1a07b2dcde2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt @@ -36,7 +36,6 @@ import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.core.dropDefaultValue import org.jetbrains.kotlin.idea.intentions.setType import org.jetbrains.kotlin.idea.refactoring.createJavaField -import org.jetbrains.kotlin.idea.refactoring.createPrimaryConstructorIfAbsent import org.jetbrains.kotlin.idea.refactoring.safeDelete.removeOverrideModifier import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier diff --git a/idea/testData/intentions/implementAsConstructorParameter/.intention b/idea/testData/intentions/implementAsConstructorParameter/.intention new file mode 100644 index 00000000000..fc049ee6f2b --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/.intention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberAsConstructorParameterIntention diff --git a/idea/testData/intentions/implementAsConstructorParameter/enumClass.kt b/idea/testData/intentions/implementAsConstructorParameter/enumClass.kt new file mode 100644 index 00000000000..5b6934a5560 --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/enumClass.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// DISABLE-ERRORS +interface T { + val foo: X +} + +enum class E : T { + A, B, C +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/enumClass.kt.after b/idea/testData/intentions/implementAsConstructorParameter/enumClass.kt.after new file mode 100644 index 00000000000..6c37032513c --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/enumClass.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME +// DISABLE-ERRORS +interface T { + val foo: X +} + +enum class E(override val foo: Int) : T { + A, B, C +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/function.kt b/idea/testData/intentions/implementAsConstructorParameter/function.kt new file mode 100644 index 00000000000..16e6a58cffc --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/function.kt @@ -0,0 +1,9 @@ +// IS_APPLICABLE: false +// DISABLE-ERRORS +interface A { + abstract fun foo(): Int +} + +class B : A { + +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/implementAll.kt b/idea/testData/intentions/implementAsConstructorParameter/implementAll.kt new file mode 100644 index 00000000000..5cb2d1c9cef --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/implementAll.kt @@ -0,0 +1,22 @@ +// WITH_RUNTIME +// DISABLE-ERRORS +interface T { + val foo: X +} + +class U : T { + +} + +class V : T { + +} + +class Z : T by V() { + +} + +class W : T { + override val foo: Boolean + get() = throw UnsupportedOperationException() +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/implementAll.kt.after b/idea/testData/intentions/implementAsConstructorParameter/implementAll.kt.after new file mode 100644 index 00000000000..c7b69511666 --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/implementAll.kt.after @@ -0,0 +1,22 @@ +// WITH_RUNTIME +// DISABLE-ERRORS +interface T { + val foo: X +} + +class U(override val foo: String) : T { + +} + +class V(override val foo: Int) : T { + +} + +class Z : T by V() { + +} + +class W : T { + override val foo: Boolean + get() = throw UnsupportedOperationException() +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/inEnumClass.kt b/idea/testData/intentions/implementAsConstructorParameter/inEnumClass.kt new file mode 100644 index 00000000000..a8d295108ad --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/inEnumClass.kt @@ -0,0 +1,7 @@ +// IS_APPLICABLE: false +// DISABLE-ERRORS +enum class E { + A, B, C; + + abstract val foo: Int +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/inFinalClass.kt b/idea/testData/intentions/implementAsConstructorParameter/inFinalClass.kt new file mode 100644 index 00000000000..50cf88e5e58 --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/inFinalClass.kt @@ -0,0 +1,5 @@ +// IS_APPLICABLE: false +// ERROR: Abstract property 'foo' in non-abstract class 'A' +class A { + abstract val foo: Int +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/inObject.kt b/idea/testData/intentions/implementAsConstructorParameter/inObject.kt new file mode 100644 index 00000000000..ea8c3b061fb --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/inObject.kt @@ -0,0 +1,5 @@ +// IS_APPLICABLE: false +// ERROR: Abstract property 'foo' in non-abstract class 'A' +object A { + abstract val foo: Int +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/noDirectOverridesNeeded.kt b/idea/testData/intentions/implementAsConstructorParameter/noDirectOverridesNeeded.kt new file mode 100644 index 00000000000..5572887325f --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/noDirectOverridesNeeded.kt @@ -0,0 +1,17 @@ +// IS_APPLICABLE: false +// ERROR: Class 'C' must be declared abstract or implement abstract base class member public abstract val foo: kotlin.Int defined in B +interface A { + val foo: Int +} + +class X : A { + override val foo = 1 +} + +abstract class B : A { + abstract override val foo: Int +} + +class C: B() { + +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/noInheritors.kt b/idea/testData/intentions/implementAsConstructorParameter/noInheritors.kt new file mode 100644 index 00000000000..327c42b24e0 --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/noInheritors.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +interface A { + val foo: Int +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/notAbstractInClass.kt b/idea/testData/intentions/implementAsConstructorParameter/notAbstractInClass.kt new file mode 100644 index 00000000000..1f777f9e8e8 --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/notAbstractInClass.kt @@ -0,0 +1,4 @@ +// IS_APPLICABLE: false +open class A { + val foo = 1 +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/notAbstractNoBodyInClass.kt b/idea/testData/intentions/implementAsConstructorParameter/notAbstractNoBodyInClass.kt new file mode 100644 index 00000000000..a022ffd6c7e --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/notAbstractNoBodyInClass.kt @@ -0,0 +1,5 @@ +// IS_APPLICABLE: false +// ERROR: Property must be initialized or be abstract +open class A { + val foo: Int +} \ No newline at end of file diff --git a/idea/testData/intentions/implementAsConstructorParameter/notAbstractWithGetterInInterface.kt b/idea/testData/intentions/implementAsConstructorParameter/notAbstractWithGetterInInterface.kt new file mode 100644 index 00000000000..71669d65eaa --- /dev/null +++ b/idea/testData/intentions/implementAsConstructorParameter/notAbstractWithGetterInInterface.kt @@ -0,0 +1,5 @@ +// IS_APPLICABLE: false +interface A { + val foo: Int + get() = 1 +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/after/source/inheritors.java b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/after/source/inheritors.java new file mode 100644 index 00000000000..87617c8cd1b --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/after/source/inheritors.java @@ -0,0 +1,20 @@ +package source; + +abstract class X implements T { + +} + +class Y implements T { + +} + +class Z implements T { + @Override + public Boolean getFoo() { + return null; + } +} + +interface U extends T { + +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/after/source/test.kt b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/after/source/test.kt new file mode 100644 index 00000000000..314ad1d906e --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/after/source/test.kt @@ -0,0 +1,5 @@ +package source + +interface T { + val foo: X +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/before/source/inheritors.java b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/before/source/inheritors.java new file mode 100644 index 00000000000..87617c8cd1b --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/before/source/inheritors.java @@ -0,0 +1,20 @@ +package source; + +abstract class X implements T { + +} + +class Y implements T { + +} + +class Z implements T { + @Override + public Boolean getFoo() { + return null; + } +} + +interface U extends T { + +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/before/source/test.kt b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/before/source/test.kt new file mode 100644 index 00000000000..3ab67874ecd --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/before/source/test.kt @@ -0,0 +1,5 @@ +package source + +interface T { + val foo: X +} \ No newline at end of file diff --git a/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/implementAllInJava.test b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/implementAllInJava.test new file mode 100644 index 00000000000..054e43abc5c --- /dev/null +++ b/idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/implementAllInJava.test @@ -0,0 +1,6 @@ +{ + "mainFile": "source/test.kt", + "intentionClass": "org.jetbrains.kotlin.idea.intentions.ImplementAbstractMemberAsConstructorParameterIntention", + "isApplicable": "false", + "withRuntime": "true" +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java index fbcf5e0f511..c7046face96 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/IntentionTestGenerated.java @@ -5464,6 +5464,81 @@ public class IntentionTestGenerated extends AbstractIntentionTest { } } + @TestMetadata("idea/testData/intentions/implementAsConstructorParameter") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ImplementAsConstructorParameter extends AbstractIntentionTest { + public void testAllFilesPresentInImplementAsConstructorParameter() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/intentions/implementAsConstructorParameter"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("enumClass.kt") + public void testEnumClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/enumClass.kt"); + doTest(fileName); + } + + @TestMetadata("function.kt") + public void testFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/function.kt"); + doTest(fileName); + } + + @TestMetadata("implementAll.kt") + public void testImplementAll() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/implementAll.kt"); + doTest(fileName); + } + + @TestMetadata("inEnumClass.kt") + public void testInEnumClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/inEnumClass.kt"); + doTest(fileName); + } + + @TestMetadata("inFinalClass.kt") + public void testInFinalClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/inFinalClass.kt"); + doTest(fileName); + } + + @TestMetadata("inObject.kt") + public void testInObject() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/inObject.kt"); + doTest(fileName); + } + + @TestMetadata("noDirectOverridesNeeded.kt") + public void testNoDirectOverridesNeeded() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/noDirectOverridesNeeded.kt"); + doTest(fileName); + } + + @TestMetadata("noInheritors.kt") + public void testNoInheritors() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/noInheritors.kt"); + doTest(fileName); + } + + @TestMetadata("notAbstractInClass.kt") + public void testNotAbstractInClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/notAbstractInClass.kt"); + doTest(fileName); + } + + @TestMetadata("notAbstractNoBodyInClass.kt") + public void testNotAbstractNoBodyInClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/notAbstractNoBodyInClass.kt"); + doTest(fileName); + } + + @TestMetadata("notAbstractWithGetterInInterface.kt") + public void testNotAbstractWithGetterInInterface() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/intentions/implementAsConstructorParameter/notAbstractWithGetterInInterface.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/intentions/importAllMembers") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java index dcd5f72be3e..2796b16d3fa 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/MultiFileIntentionTestGenerated.java @@ -53,6 +53,12 @@ public class MultiFileIntentionTestGenerated extends AbstractMultiFileIntentionT doTest(fileName); } + @TestMetadata("implementAsConstructorParameter/implementValInJava/implementAllInJava.test") + public void testImplementAsConstructorParameter_implementValInJava_ImplementAllInJava() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/implementAsConstructorParameter/implementValInJava/implementAllInJava.test"); + doTest(fileName); + } + @TestMetadata("moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test") public void testMoveDeclarationToSeparateFile_moveClassToExistingFile_MoveClassToExistingFile() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/multiFileIntentions/moveDeclarationToSeparateFile/moveClassToExistingFile/moveClassToExistingFile.test");