From 22271f0af0d39a78f0844060ab1db790785fd8d2 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 20 Sep 2016 20:48:07 +0300 Subject: [PATCH] Pull Up/Push Down/Extract Super: Show member dependencies in the refactoring dialog --- ChangeLog.md | 1 + .../jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt | 4 +- idea/src/META-INF/plugin.xml | 4 ++ .../ui/KotlinExtractInterfaceDialog.kt | 7 +- .../ui/KotlinExtractSuperDialogBase.kt | 8 ++- .../ui/KotlinExtractSuperclassDialog.kt | 7 +- .../KotlinClassMembersRefactoringSupport.kt | 72 +++++++++++++++++++ ...otlinInterfaceDependencyMemberInfoModel.kt | 44 ++++++++++++ .../KotlinInterfaceMemberDependencyGraph.kt | 51 +++++++++++++ .../memberInfo/KotlinMemberInfo.kt | 14 ++-- ...sAndInterfacesDependencyMemberInfoModel.kt | 52 ++++++++++++++ .../KotlinUsesDependencyMemberInfoModel.kt | 43 +++++++++++ .../refactoring/pullUp/KotlinPullUpDialog.kt | 16 ++++- .../idea/refactoring/pullUp/pullUpUtils.kt | 16 +++++ .../pushDown/KotlinPushDownDialog.kt | 13 ++-- 15 files changed, 335 insertions(+), 17 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinClassMembersRefactoringSupport.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinInterfaceDependencyMemberInfoModel.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinInterfaceMemberDependencyGraph.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinUsesAndInterfacesDependencyMemberInfoModel.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinUsesDependencyMemberInfoModel.kt diff --git a/ChangeLog.md b/ChangeLog.md index 67b02b04d30..edbaa776be7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -150,6 +150,7 @@ These artifacts include extensions for the types available in the latter JDKs, s - [`KT-11017`](https://youtrack.jetbrains.com/issue/KT-11017) Implement "Extract Interface" refactoring Pull Up: Support properties declared in the primary constructor Pull Up: Support members declared in the companion object of the original class +Pull Up: Show member dependencies in the refactoring dialog #### Android Lint diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt index a244ddbaf0c..1db8639ce3f 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/ktPsiUtil.kt @@ -471,4 +471,6 @@ fun KtClassOrObject.findPropertyByName(name: String): KtNamedDeclaration? { fun isTypeConstructorReference(e: PsiElement): Boolean { val parent = e.parent return parent is KtUserType && parent.referenceExpression == e -} \ No newline at end of file +} + +fun KtParameter.isPropertyParameter() = ownerFunction is KtPrimaryConstructor && hasValOrVar() \ No newline at end of file diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 5a3edac338d..4e38c170230 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -707,6 +707,10 @@ order="first" implementationClass="org.jetbrains.kotlin.idea.refactoring.pullUp.JavaToKotlinPullUpHelperFactory"/> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt index 4e896430b4d..4b930d273a8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractInterfaceDialog.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuper import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractInterfaceHandler import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers +import org.jetbrains.kotlin.idea.refactoring.pullUp.getInterfaceContainmentVerifier import org.jetbrains.kotlin.idea.refactoring.pullUp.mustBeAbstractInInterface import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* @@ -48,7 +49,11 @@ class KotlinExtractInterfaceDialog( member is KtClass && member.hasModifier(KtTokens.INNER_KEYWORD) } extractableMemberInfos.forEach { it.isToAbstract = true } - return object : MemberInfoModelBase(extractableMemberInfos) { + return object : MemberInfoModelBase( + originalClass, + extractableMemberInfos, + getInterfaceContainmentVerifier { selectedMembers } + ) { override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean { val member = memberInfo.member return member is KtNamedFunction || (member is KtProperty && !member.mustBeAbstractInInterface()) || member is KtParameter diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperDialogBase.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperDialogBase.kt index 23114aec48e..dd958bacda4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperDialogBase.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperDialogBase.kt @@ -20,7 +20,6 @@ import com.intellij.psi.PsiComment import com.intellij.psi.PsiDirectory import com.intellij.psi.PsiElement import com.intellij.refactoring.RefactoringBundle -import com.intellij.refactoring.classMembers.AbstractMemberInfoModel import com.intellij.refactoring.classMembers.MemberInfoChange import com.intellij.refactoring.extractSuperclass.JavaExtractSuperBaseDialog import com.intellij.refactoring.util.DocCommentPolicy @@ -34,6 +33,7 @@ import org.jetbrains.kotlin.idea.core.unquote import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuperInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinUsesAndInterfacesDependencyMemberInfoModel import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtNamedDeclaration import java.awt.BorderLayout @@ -58,8 +58,10 @@ abstract class KotlinExtractSuperDialogBase( private val fileNameField = JTextField() open class MemberInfoModelBase( - val memberInfos: List - ) : AbstractMemberInfoModel() { + originalClass: KtClassOrObject, + val memberInfos: List, + interfaceContainmentVerifier: (KtNamedDeclaration) -> Boolean + ) : KotlinUsesAndInterfacesDependencyMemberInfoModel(originalClass, null, false, interfaceContainmentVerifier) { override fun isFixedAbstract(memberInfo: KotlinMemberInfo?) = true } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperclassDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperclassDialog.kt index 62d391cf4c6..fad8f3cb047 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperclassDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/extractClass/ui/KotlinExtractSuperclassDialog.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.ExtractSuper import org.jetbrains.kotlin.idea.refactoring.introduce.extractClass.KotlinExtractSuperclassHandler import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.extractClassMembers +import org.jetbrains.kotlin.idea.refactoring.pullUp.getInterfaceContainmentVerifier import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtParameter @@ -44,7 +45,11 @@ class KotlinExtractSuperclassDialog( } override fun createMemberInfoModel(): MemberInfoModelBase { - return object : MemberInfoModelBase(extractClassMembers(originalClass)) { + return object : MemberInfoModelBase( + originalClass, + extractClassMembers(originalClass), + getInterfaceContainmentVerifier { selectedMembers } + ) { override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean { val member = memberInfo.member return member is KtNamedFunction || member is KtProperty || member is KtParameter diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinClassMembersRefactoringSupport.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinClassMembersRefactoringSupport.kt new file mode 100644 index 00000000000..ebcbf295ded --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinClassMembersRefactoringSupport.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.memberInfo + +import com.intellij.psi.PsiNamedElement +import com.intellij.refactoring.classMembers.ClassMembersRefactoringSupport +import com.intellij.refactoring.classMembers.DependentMembersCollectorBase +import com.intellij.refactoring.classMembers.MemberInfoBase +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.refactoring.pullUp.KotlinPullUpData +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject +import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter +import org.jetbrains.kotlin.util.findCallableMemberBySignature + +class KotlinClassMembersRefactoringSupport : ClassMembersRefactoringSupport { + override fun isProperMember(memberInfo: MemberInfoBase<*>): Boolean { + val member = memberInfo.member + return member is KtNamedFunction + || member is KtProperty + || (member is KtParameter && member.isPropertyParameter()) + || (member is KtClassOrObject && memberInfo.overrides == null) + } + + override fun createDependentMembersCollector(clazz: Any, superClass: Any?): DependentMembersCollectorBase<*, *> { + return object : DependentMembersCollectorBase( + clazz as KtClassOrObject, + superClass as PsiNamedElement? + ) { + override fun collect(member: KtNamedDeclaration) { + member.accept( + object : KtTreeVisitorVoid() { + private val pullUpData = superClass?.let { KotlinPullUpData(clazz as KtClassOrObject, it as PsiNamedElement, emptyList()) } + + private val possibleContainingClasses = + listOf(clazz) + if (clazz is KtClass) clazz.getCompanionObjects() else emptyList() + + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + val referencedMember = expression.mainReference.resolve() as? KtNamedDeclaration ?: return + val containingClassOrObject = referencedMember.containingClassOrObject ?: return + if (containingClassOrObject !in possibleContainingClasses) return + + if (pullUpData != null) { + val memberDescriptor = referencedMember.resolveToDescriptor() as? CallableMemberDescriptor ?: return + val memberInSuper = memberDescriptor.substitute(pullUpData.sourceToTargetClassSubstitutor) ?: return + if (pullUpData.targetClassDescriptor.findCallableMemberBySignature(memberInSuper as CallableMemberDescriptor) != null) return + } + + myCollection.add(referencedMember) + } + } + ) + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinInterfaceDependencyMemberInfoModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinInterfaceDependencyMemberInfoModel.kt new file mode 100644 index 00000000000..9aeac6076b3 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinInterfaceDependencyMemberInfoModel.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.memberInfo + +import com.intellij.refactoring.RefactoringBundle +import com.intellij.refactoring.classMembers.DependencyMemberInfoModel +import com.intellij.refactoring.classMembers.MemberInfoBase +import com.intellij.refactoring.classMembers.MemberInfoModel +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.utils.ifEmpty + +class KotlinInterfaceDependencyMemberInfoModel>( + aClass: KtClassOrObject +) : DependencyMemberInfoModel(KotlinInterfaceMemberDependencyGraph(aClass), MemberInfoModel.WARNING) { + init { + setTooltipProvider { memberInfo -> + val dependencies = myMemberDependencyGraph.getDependenciesOf(memberInfo.member).ifEmpty { return@setTooltipProvider null } + buildString { + append(RefactoringBundle.message("interface.member.dependency.required.by.interfaces", dependencies.size)) + append(" ") + dependencies.joinTo(this) { it.name ?: "" } + } + } + } + + override fun isCheckedWhenDisabled(member: M) = false + + override fun isFixedAbstract(member: M) = null +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinInterfaceMemberDependencyGraph.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinInterfaceMemberDependencyGraph.kt new file mode 100644 index 00000000000..9f6e5b09099 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinInterfaceMemberDependencyGraph.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.memberInfo + +import com.intellij.psi.PsiMember +import com.intellij.refactoring.classMembers.MemberDependencyGraph +import com.intellij.refactoring.classMembers.MemberInfoBase +import com.intellij.refactoring.util.classMembers.InterfaceMemberDependencyGraph +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import java.util.* + +class KotlinInterfaceMemberDependencyGraph>( + klass: KtClassOrObject +) : MemberDependencyGraph { + private val delegateGraph = InterfaceMemberDependencyGraph>(klass.toLightClass()) + + override fun memberChanged(memberInfo: M) { + delegateGraph.memberChanged(memberInfo.toJavaMemberInfo()!!) + } + + @Suppress("UNCHECKED_CAST") + override fun getDependent() = delegateGraph.dependent + .mapNotNull { it.unwrapped } + .filterIsInstanceTo(LinkedHashSet()) as Set + + @Suppress("UNCHECKED_CAST") + override fun getDependenciesOf(member: T): Set { + val psiMember = lightElementForMemberInfo(member) ?: return emptySet() + val psiMemberDependencies = delegateGraph.getDependenciesOf(psiMember) ?: return emptySet() + return psiMemberDependencies + .mapNotNull { it.unwrapped } + .filterIsInstanceTo(LinkedHashSet()) as Set + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt index 0dcb2fc8a8e..53632df3fd8 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt @@ -16,6 +16,7 @@ package org.jetbrains.kotlin.idea.refactoring.memberInfo +import com.intellij.psi.PsiClass import com.intellij.psi.PsiField import com.intellij.psi.PsiMember import com.intellij.psi.PsiMethod @@ -78,9 +79,8 @@ class KotlinMemberInfo @JvmOverloads constructor( } } -fun KotlinMemberInfo.toJavaMemberInfo(): MemberInfo? { - val declaration = member - val psiMember: PsiMember? = when (declaration) { +fun lightElementForMemberInfo(declaration: KtNamedDeclaration?): PsiMember? { + return when (declaration) { is KtNamedFunction -> declaration.getRepresentativeLightMethod() is KtProperty, is KtParameter -> declaration.toLightElements().let { it.firstIsInstanceOrNull() ?: it.firstIsInstanceOrNull() @@ -88,8 +88,14 @@ fun KotlinMemberInfo.toJavaMemberInfo(): MemberInfo? { is KtClassOrObject -> declaration.toLightClass() else -> null } - val info = MemberInfo(psiMember ?: return null, isSuperClass, null) +} + +fun MemberInfoBase.toJavaMemberInfo(): MemberInfo? { + val declaration = member + val psiMember: PsiMember? = lightElementForMemberInfo(declaration) + val info = MemberInfo(psiMember ?: return null, psiMember is PsiClass && overrides != null, null) info.isToAbstract = isToAbstract + info.isChecked = isChecked return info } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinUsesAndInterfacesDependencyMemberInfoModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinUsesAndInterfacesDependencyMemberInfoModel.kt new file mode 100644 index 00000000000..2d49fc1a59f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinUsesAndInterfacesDependencyMemberInfoModel.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.memberInfo + +import com.intellij.psi.PsiNamedElement +import com.intellij.refactoring.classMembers.ANDCombinedMemberInfoModel +import com.intellij.refactoring.classMembers.DelegatingMemberInfoModel +import com.intellij.refactoring.classMembers.MemberInfoBase +import com.intellij.refactoring.classMembers.MemberInfoModel +import com.intellij.refactoring.util.classMembers.UsesDependencyMemberInfoModel +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtNamedDeclaration + +open class KotlinUsesAndInterfacesDependencyMemberInfoModel>( + klass: KtClassOrObject, + superClass: PsiNamedElement?, + recursive: Boolean, + interfaceContainmentVerifier: (T) -> Boolean = { false } +) : DelegatingMemberInfoModel( + ANDCombinedMemberInfoModel( + object : KotlinUsesDependencyMemberInfoModel(klass, superClass, recursive) { + override fun checkForProblems(memberInfo: M): Int { + val problem = super.checkForProblems(memberInfo) + if (problem == MemberInfoModel.OK) return MemberInfoModel.OK + + val member = memberInfo.member + if (interfaceContainmentVerifier(member)) return MemberInfoModel.OK + + return problem + } + }, + KotlinInterfaceDependencyMemberInfoModel(klass)) +) { + @Suppress("UNCHECKED_CAST") + fun setSuperClass(superClass: PsiNamedElement) { + ((delegatingTarget as ANDCombinedMemberInfoModel).model1 as UsesDependencyMemberInfoModel).setSuperClass(superClass) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinUsesDependencyMemberInfoModel.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinUsesDependencyMemberInfoModel.kt new file mode 100644 index 00000000000..dae4f9f9a8c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinUsesDependencyMemberInfoModel.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.memberInfo + +import com.intellij.psi.PsiNamedElement +import com.intellij.refactoring.classMembers.MemberInfoBase +import com.intellij.refactoring.classMembers.MemberInfoModel +import com.intellij.refactoring.util.classMembers.UsesDependencyMemberInfoModel +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtObjectDeclaration +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject + +open class KotlinUsesDependencyMemberInfoModel>( + klass : KtClassOrObject, + superClass: PsiNamedElement?, + recursive: Boolean +) : UsesDependencyMemberInfoModel(klass, superClass, recursive) { + override fun doCheck(memberInfo: M, problem: Int): Int { + val member = memberInfo.member + val container = member.containingClassOrObject + if (problem == MemberInfoModel.ERROR + && container is KtObjectDeclaration + && container.isCompanion() + && container.containingClassOrObject == myClass) return MemberInfoModel.WARNING + + return problem + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt index 2a3911714be..1d041f8c26d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt @@ -22,7 +22,6 @@ import com.intellij.psi.PsiClass import com.intellij.psi.PsiComment import com.intellij.psi.PsiNamedElement import com.intellij.refactoring.JavaRefactoringSettings -import com.intellij.refactoring.classMembers.AbstractMemberInfoModel import com.intellij.refactoring.classMembers.MemberInfoChange import com.intellij.refactoring.classMembers.MemberInfoModel import com.intellij.refactoring.memberPullUp.PullUpProcessor @@ -48,7 +47,16 @@ class KotlinPullUpDialog( init() } - private inner class MemberInfoModelImpl : AbstractMemberInfoModel() { + private inner class MemberInfoModelImpl( + originalClass: KtClassOrObject, + superClass: PsiNamedElement?, + interfaceContainmentVerifier: (KtNamedDeclaration) -> Boolean + ) : KotlinUsesAndInterfacesDependencyMemberInfoModel( + originalClass, + superClass, + false, + interfaceContainmentVerifier + ) { private var lastSuperClass: PsiNamedElement? = null // Abstract members remain abstract @@ -96,11 +104,13 @@ class KotlinPullUpDialog( } override fun memberInfoChanged(event: MemberInfoChange) { + super.memberInfoChanged(event) val superClass = superClass ?: return if (superClass != lastSuperClass) { lastSuperClass = superClass val isInterface = superClass is KtClass && superClass.isInterface() event.changedMembers.forEach { it.isToAbstract = isInterface } + setSuperClass(superClass) } } } @@ -114,7 +124,7 @@ class KotlinPullUpDialog( override fun getSuperClass() = super.getSuperClass() override fun createMemberInfoModel(): MemberInfoModel = - MemberInfoModelImpl() + MemberInfoModelImpl(sourceClass, preselection, getInterfaceContainmentVerifier { selectedMemberInfos }) override fun getPreselection() = mySuperClasses.firstOrNull { !it.isInterfaceClass() } ?: mySuperClasses.firstOrNull() diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt index e3e2ed216b1..9d73448139d 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt @@ -17,11 +17,15 @@ package org.jetbrains.kotlin.idea.refactoring.pullUp import com.intellij.psi.PsiClass +import com.intellij.psi.PsiMethod +import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.intentions.setType +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo +import org.jetbrains.kotlin.idea.refactoring.memberInfo.lightElementForMemberInfo import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.anonymousObjectSuperTypeOrNull import org.jetbrains.kotlin.lexer.KtTokens @@ -166,4 +170,16 @@ fun addSuperTypeEntry( val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(typeInTargetClass) val newSpecifier = KtPsiFactory(targetClass).createSuperTypeEntry(renderedType) targetClass.addSuperTypeListEntry(newSpecifier).addToShorteningWaitSet() +} + +fun getInterfaceContainmentVerifier(getMemberInfos: () -> List): (KtNamedDeclaration) -> Boolean { + return result@ { member -> + val psiMethodToCheck = lightElementForMemberInfo(member) as? PsiMethod ?: return@result false + getMemberInfos().any { + if (!it.isSuperClass || it.overrides != false) return@any false + + val psiSuperInterface = (it.member as? KtClass)?.toLightClass() + psiSuperInterface?.findMethodBySignature(psiMethodToCheck, true) != null + } + } } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/KotlinPushDownDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/KotlinPushDownDialog.kt index f1ff8655716..242d4b60018 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/KotlinPushDownDialog.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pushDown/KotlinPushDownDialog.kt @@ -17,13 +17,14 @@ package org.jetbrains.kotlin.idea.refactoring.pushDown import com.intellij.openapi.project.Project +import com.intellij.psi.PsiNamedElement import com.intellij.refactoring.JavaRefactoringSettings import com.intellij.refactoring.RefactoringBundle -import com.intellij.refactoring.classMembers.AbstractMemberInfoModel -import com.intellij.refactoring.classMembers.MemberInfoChange +import com.intellij.refactoring.classMembers.* import com.intellij.refactoring.ui.RefactoringDialog import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberSelectionPanel +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinUsesDependencyMemberInfoModel import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedClassNameForRendering import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtNamedDeclaration @@ -47,7 +48,7 @@ class KotlinPushDownDialog( init() } - private var memberInfoModel: AbstractMemberInfoModel? = null + private var memberInfoModel: MemberInfoModel? = null val selectedMemberInfos: List get() = memberInfos.filter { it.isChecked && memberInfoModel?.isMemberEnabled(it) ?: false } @@ -79,7 +80,11 @@ class KotlinPushDownDialog( RefactoringBundle.message("keep.abstract.column.header")) panel.add(memberSelectionPanel, BorderLayout.CENTER) - memberInfoModel = object : AbstractMemberInfoModel() { + memberInfoModel = object : DelegatingMemberInfoModel( + ANDCombinedMemberInfoModel( + KotlinUsesDependencyMemberInfoModel(sourceClass, null, false), + UsedByDependencyMemberInfoModel(sourceClass)) + ) { override fun isFixedAbstract(member: KotlinMemberInfo?) = null override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean {