diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetClassOrObject.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetClassOrObject.kt index a76aac43c8b..fbac2cb1978 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetClassOrObject.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetClassOrObject.kt @@ -21,9 +21,11 @@ import com.intellij.lang.ASTNode import com.intellij.navigation.ItemPresentation import com.intellij.navigation.ItemPresentationProviders import com.intellij.psi.PsiElement +import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.impl.CheckUtil import com.intellij.psi.impl.source.codeStyle.CodeEditUtil import com.intellij.psi.stubs.IStubElementType +import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.JetNodeTypes import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.lexer.JetTokens @@ -46,6 +48,22 @@ abstract public class JetClassOrObject : JetTypeParameterListOwnerStub()) + return body.addAfter(declaration, anchor) as JetDeclaration + } + + public fun addDeclarationAfter(declaration: JetDeclaration, anchor: PsiElement?): JetDeclaration { + val anchorBefore = anchor ?: getDeclarations().lastOrNull() ?: return addDeclaration(declaration) + return getOrCreateBody().addAfter(declaration, anchorBefore) as JetDeclaration + } + + public fun addDeclarationBefore(declaration: JetDeclaration, anchor: PsiElement?): JetDeclaration { + val anchorAfter = anchor ?: getDeclarations().firstOrNull() ?: return addDeclaration(declaration) + return getOrCreateBody().addBefore(declaration, anchorAfter) as JetDeclaration + } + public fun isTopLevel(): Boolean = getStub()?.isTopLevel() ?: (getParent() is JetFile) public fun isLocal(): Boolean = getStub()?.isLocal() ?: JetPsiUtil.isLocal(this) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetProperty.java b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetProperty.java index 8c7c4c84e97..01b002135da 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/JetProperty.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/JetProperty.java @@ -157,6 +157,11 @@ public class JetProperty extends JetTypeParameterListOwnerStub getAccessors() { return getStubOrPsiChildrenAsList(JetStubElementTypes.PROPERTY_ACCESSOR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt index 8d3c0dbc594..eb4a8d949dc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/psiUtil/jetPsiUtil.kt @@ -134,6 +134,15 @@ public fun JetExpression.getQualifiedExpressionForSelectorOrThis(): JetExpressio return getQualifiedExpressionForSelector() ?: this } +public fun JetExpression.getQualifiedExpressionForReceiver(): JetQualifiedExpression? { + val parent = getParent() + return if (parent is JetQualifiedExpression && parent.getReceiverExpression() == this) parent else null +} + +public fun JetExpression.getQualifiedExpressionForReceiverOrThis(): JetExpression { + return getQualifiedExpressionForReceiver() ?: this +} + public fun JetExpression.isDotReceiver(): Boolean = (getParent() as? JetDotQualifiedExpression)?.getReceiverExpression() == this diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/userDataUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/userDataUtil.kt index 2c52d06db57..1889bd6546b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/userDataUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/userDataUtil.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.psi import kotlin.properties.ReadWriteProperty import com.intellij.openapi.util.Key import com.intellij.openapi.util.UserDataHolder +import com.intellij.psi.PsiElement public class UserDataProperty(val key: Key, val default: T? = null) : ReadWriteProperty { override fun get(thisRef: R, desc: kotlin.PropertyMetadata): T? { @@ -39,3 +40,13 @@ public class NotNullableUserDataProperty(val key: thisRef.putUserData(key, value) } } + +public class CopyableUserDataProperty(val key: Key, val default: T? = null) : ReadWriteProperty { + override fun get(thisRef: R, property: PropertyMetadata): T? { + return thisRef.getCopyableUserData(key) + } + + override fun set(thisRef: R, property: PropertyMetadata, value: T?) { + thisRef.putCopyableUserData(key, value) + } +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/test/util/jetTestUtils.kt b/compiler/tests/org/jetbrains/kotlin/test/util/jetTestUtils.kt index 41871c433fd..470f023980e 100644 --- a/compiler/tests/org/jetbrains/kotlin/test/util/jetTestUtils.kt +++ b/compiler/tests/org/jetbrains/kotlin/test/util/jetTestUtils.kt @@ -16,14 +16,17 @@ package org.jetbrains.kotlin.test.util +import com.intellij.codeInspection.SmartHashMap import com.intellij.testFramework.fixtures.CodeInsightTestFixture import com.intellij.openapi.util.io.FileUtil import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.SmartFMap import org.jetbrains.kotlin.psi.* import java.io.File +import java.util.* public fun String.trimTrailingWhitespacesAndAddNewlineAtEOF(): String = this.split('\n').map { it.trimEnd() }.joinToString(separator = "\n").let { @@ -74,28 +77,29 @@ public fun String.trimIndent(): String { }.joinToString(separator = "\n") } -public fun JetFile.findElementByComment(commentText: String): JetElement? { - var result: JetElement? = null +public fun JetFile.findElementByCommentPrefix(commentText: String): JetElement? = + findElementsByCommentPrefix(commentText).keySet().singleOrNull() + +public fun JetFile.findElementsByCommentPrefix(prefix: String): Map { + var result = SmartFMap.emptyMap() accept( object : JetTreeVisitorVoid() { override fun visitComment(comment: PsiComment) { - if (comment.getText() == commentText) { + val commentText = comment.getText() + if (commentText.startsWith(prefix)) { val parent = comment.getParent() - if (parent is JetDeclaration) { - result = parent - } - else { - result = PsiTreeUtil.skipSiblingsForward( + val elementToAdd = when (parent) { + is JetDeclaration -> parent + else -> PsiTreeUtil.skipSiblingsForward( comment, - javaClass(), - javaClass(), - javaClass() - ) as? JetElement - } + javaClass(), javaClass(), javaClass() + ) + } as? JetElement ?: return + + result = result.plus(elementToAdd, commentText.substring(prefix.length()).trim()) } } } ) - return result } \ No newline at end of file diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index a8002afcd25..1fd974a503d 100644 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -83,6 +83,7 @@ import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixTest import org.jetbrains.kotlin.idea.refactoring.inline.AbstractInlineTest import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractJetExtractionTest import org.jetbrains.kotlin.idea.refactoring.move.AbstractJetMoveTest +import org.jetbrains.kotlin.idea.refactoring.pullUp.AbstractPullUpTest import org.jetbrains.kotlin.idea.refactoring.rename.AbstractRenameTest import org.jetbrains.kotlin.idea.resolve.* import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest @@ -653,6 +654,10 @@ fun main(args: Array) { model("refactoring/introduceJavaParameter", extension = "java", testMethod = "doIntroduceJavaParameterTest") } + testClass(javaClass()) { + model("refactoring/pullUp", extension = "kt", singleClass = true) + } + testClass(javaClass()) { model("debugger/selectExpression", recursive = false) model("debugger/selectExpression/disallowMethodCalls", testMethod = "doTestWoMethodCalls") diff --git a/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt b/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt index 61598d00aad..d21273a3c36 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/util/descriptorUtils.kt @@ -16,11 +16,13 @@ package org.jetbrains.kotlin.util -import org.jetbrains.kotlin.descriptors.CallableDescriptor -import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.resolve.OverridingUtil +import org.jetbrains.kotlin.resolve.OverridingUtil.OverrideCompatibilityInfo.Result.OVERRIDABLE +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.types.TypeConstructor import org.jetbrains.kotlin.types.checker.JetTypeChecker +import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure import org.jetbrains.kotlin.types.typeUtil.equalTypesOrNulls public fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, descriptor2: DeclarationDescriptor?): Boolean { @@ -56,3 +58,12 @@ public fun descriptorsEqualWithSubstitution(descriptor1: DeclarationDescriptor?, return true } +public fun ClassDescriptor.findCallableMemberBySignature(signature: CallableMemberDescriptor): CallableMemberDescriptor? { + val descriptorKind = if (signature is FunctionDescriptor) DescriptorKindFilter.FUNCTIONS else DescriptorKindFilter.VARIABLES + return getDefaultType().getMemberScope() + .getDescriptors(descriptorKind) + .firstOrNull { + it.getContainingDeclaration() == this + && OverridingUtil.DEFAULT.isOverridableBy(it as CallableDescriptor, signature).getResult() == OVERRIDABLE + } as? CallableMemberDescriptor +} diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index ea9fda162d8..afcc052def7 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -527,6 +527,8 @@ language="jet" implementationClass="org.jetbrains.kotlin.idea.search.ideaExtensions.JetTargetElementEvaluator" /> + + org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention Kotlin diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt index c05e7a8c3a9..2b06d2859c2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/KotlinRefactoringSupportProvider.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.Kotlin import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceParameterHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler +import org.jetbrains.kotlin.idea.refactoring.pullUp.KotlinPullUpHandler import org.jetbrains.kotlin.idea.refactoring.safeDelete.* import org.jetbrains.kotlin.psi.* @@ -70,4 +71,6 @@ public class KotlinRefactoringSupportProvider : RefactoringSupportProvider() { } override fun getChangeSignatureHandler() = JetChangeSignatureHandler() + + override fun getPullUpHandler() = KotlinPullUpHandler() } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/JetClassOrObjectCellRenderer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/JetClassOrObjectCellRenderer.kt new file mode 100644 index 00000000000..e05ef9366dc --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/JetClassOrObjectCellRenderer.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2015 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.openapi.util.Iconable +import com.intellij.ui.ListCellRendererWrapper +import org.jetbrains.kotlin.psi.JetClassOrObject +import javax.swing.JList + +public class JetClassOrObjectCellRenderer: ListCellRendererWrapper() { + override fun customize(list: JList, value: JetClassOrObject?, index: Int, selected: Boolean, hasFocus: Boolean) { + if (value == null) return + + setText(value.qualifiedNameForRendering()) + value.getIcon(Iconable.ICON_FLAG_VISIBILITY or Iconable.ICON_FLAG_READ_STATUS)?.let { setIcon(it) } + } +} + +public fun JetClassOrObject.qualifiedNameForRendering(): String = getFqName()?.asString() ?: getName() ?: "[Anonymous]" \ No newline at end of file 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 5356f422296..f483b02ad9b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/memberInfo/KotlinMemberInfo.kt @@ -16,13 +16,16 @@ package org.jetbrains.kotlin.idea.refactoring.memberInfo +import com.intellij.psi.PsiMember import com.intellij.refactoring.classMembers.MemberInfoBase +import com.intellij.refactoring.util.classMembers.MemberInfo +import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod +import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers -import org.jetbrains.kotlin.psi.JetFile -import org.jetbrains.kotlin.psi.JetNamedDeclaration +import org.jetbrains.kotlin.psi.* public class KotlinMemberInfo(member: JetNamedDeclaration) : MemberInfoBase(member) { init { @@ -36,3 +39,17 @@ public class KotlinMemberInfo(member: JetNamedDeclaration) : MemberInfoBase declaration.getRepresentativeLightMethod() + is JetClassOrObject -> declaration.toLightClass() + else -> null + } + if (psiMember == null) return null + + val info = MemberInfo(psiMember) + info.setToAbstract(isToAbstract()) + return info +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/EmptyPullUpHelper.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/EmptyPullUpHelper.kt new file mode 100644 index 00000000000..364f1192dff --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/EmptyPullUpHelper.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2010-2015 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.pullUp + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiMember +import com.intellij.psi.PsiSubstitutor +import com.intellij.refactoring.classMembers.MemberInfoBase +import com.intellij.refactoring.memberPullUp.PullUpData +import com.intellij.refactoring.memberPullUp.PullUpHelper +import com.intellij.refactoring.memberPullUp.PullUpHelperFactory +import org.jetbrains.kotlin.asJava.namedUnwrappedElement +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.psi.JetClassOrObject +import org.jetbrains.kotlin.psi.JetNamedDeclaration +import org.jetbrains.kotlin.types.TypeSubstitutor +import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor +import java.util.LinkedHashSet + +object EmptyPullUpHelper : PullUpHelper> { + override fun postProcessMember(member: PsiMember) { + + } + + override fun moveFieldInitializations(movedFields: LinkedHashSet) { + + } + + override fun encodeContextInfo(info: MemberInfoBase) { + + } + + override fun setCorrectVisibility(info: MemberInfoBase) { + + } + + override fun move(info: MemberInfoBase, substitutor: PsiSubstitutor) { + + } + + override fun updateUsage(element: PsiElement) { + + } +} + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpData.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpData.kt new file mode 100644 index 00000000000..35a609863ba --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpData.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2015 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.pullUp + +import com.intellij.refactoring.memberPullUp.PullUpData +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.core.getResolutionScope +import org.jetbrains.kotlin.psi.JetClass +import org.jetbrains.kotlin.psi.JetClassOrObject +import org.jetbrains.kotlin.psi.JetNamedDeclaration +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter +import org.jetbrains.kotlin.types.* +import org.jetbrains.kotlin.types.substitutions.getTypeSubstitution +import org.jetbrains.kotlin.utils.keysToMap +import java.util.* + +class KotlinPullUpData(val sourceClass: JetClassOrObject, + val targetClass: JetClass, + val membersToMove: Collection) { + val resolutionFacade = sourceClass.getResolutionFacade() + + val sourceClassContext = resolutionFacade.analyzeFullyAndGetResult(listOf(sourceClass)).bindingContext + + val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor + + val memberDescriptors = membersToMove.keysToMap { sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!! } + + val targetClassDescriptor = resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor + + val typeParametersInSourceClassContext by lazy { + sourceClassDescriptor.getTypeConstructor().getParameters() + sourceClass.getResolutionScope(sourceClassContext, resolutionFacade) + .getDescriptors(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS) + .filterIsInstance() + } + + val sourceToTargetClassSubstitutor: TypeSubstitutor by lazy { + val substitution = LinkedHashMap() + + typeParametersInSourceClassContext.forEach { + substitution[it.getTypeConstructor()] = TypeProjectionImpl(it.getUpperBoundsAsType()) + } + + val superClassSubstitution = getTypeSubstitution(targetClassDescriptor.getDefaultType(), sourceClassDescriptor.getDefaultType()) + ?: emptyMap() + for ((typeConstructor, typeProjection) in superClassSubstitution) { + val subClassTypeParameter = typeProjection.getType().getConstructor().getDeclarationDescriptor() as? TypeParameterDescriptor + ?: continue + val superClassTypeParameter = typeConstructor.getDeclarationDescriptor() + ?: continue + substitution[subClassTypeParameter.getTypeConstructor()] = TypeProjectionImpl(superClassTypeParameter.getDefaultType()) + } + + TypeSubstitutor.create(substitution) + } +} \ 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 new file mode 100644 index 00000000000..4eeabbb3e69 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpDialog.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2010-2015 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.pullUp + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiComment +import com.intellij.refactoring.JavaRefactoringSettings +import com.intellij.refactoring.classMembers.AbstractMemberInfoModel +import com.intellij.refactoring.memberPullUp.PullUpDialogBase +import com.intellij.refactoring.memberPullUp.PullUpProcessor +import com.intellij.refactoring.util.DocCommentPolicy +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.idea.refactoring.memberInfo.* +import org.jetbrains.kotlin.psi.* +import java.awt.event.ItemEvent +import javax.swing.JComboBox + +public class KotlinPullUpDialog( + project: Project, + private val classOrObject: JetClassOrObject, + superClasses: List, + memberInfoStorage: KotlinMemberInfoStorage +) : PullUpDialogBase( + project, classOrObject, superClasses, memberInfoStorage, PULL_MEMBERS_UP +) { + init { + init() + } + + private inner class MemberInfoModelImpl : AbstractMemberInfoModel() { + // Abstract members remain abstract + override fun isFixedAbstract(memberInfo: KotlinMemberInfo?) = true + + /* + * Any non-abstract function can change abstractness. + * + * Non-abstract property with initializer or delegate is always made abstract. + * Any other non-abstract property can change abstractness. + * + * Classes do not have abstractness + */ + override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean { + val superClass = getSuperClass() ?: return false + if (!superClass.isInterface()) return true + + val member = memberInfo.getMember() + return member is JetNamedFunction || (member is JetProperty && !member.mustBeAbstractInInterface()) + } + + override fun isAbstractWhenDisabled(memberInfo: KotlinMemberInfo): Boolean { + return memberInfo.getMember() is JetProperty + } + + override fun isMemberEnabled(memberInfo: KotlinMemberInfo): Boolean { + val superClass = getSuperClass() ?: return false + if (memberInfo in memberInfoStorage.getDuplicatedMemberInfos(superClass)) return false + return true + } + } + + protected val memberInfoStorage: KotlinMemberInfoStorage get() = myMemberInfoStorage + + override fun getDimensionServiceKey() = "#" + javaClass.getName() + + override fun getSuperClass() = super.getSuperClass() as? JetClass + + override fun initClassCombo(classCombo: JComboBox) { + classCombo.setRenderer(JetClassOrObjectCellRenderer()) + classCombo.addItemListener { event -> + if (event.getStateChange() == ItemEvent.SELECTED) { + myMemberSelectionPanel?.getTable()?.let { + it.setMemberInfos(myMemberInfos) + it.fireExternalDataChange() + } + } + } + } + + override fun createMemberInfoModel() = MemberInfoModelImpl() + + override fun getPreselection() = mySuperClasses.firstOrNull() + + override fun createMemberSelectionTable(infos: MutableList) = + KotlinMemberSelectionTable(infos, null, "Make abstract") + + override fun doAction() { + invokeRefactoring(createProcessor(myClass, getSuperClass()!!, getSelectedMemberInfos())) + } + + companion object { + fun createProcessor(sourceClass: JetClassOrObject, + targetClass: JetClass, + memberInfos: List): PullUpProcessor { + return PullUpProcessor(sourceClass.toLightClass(), + targetClass.toLightClass(), + memberInfos.map { it.toJavaMemberInfo() }.filterNotNull().toTypedArray(), + DocCommentPolicy(JavaRefactoringSettings.getInstance().PULL_UP_MEMBERS_JAVADOC)) + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHandler.kt new file mode 100644 index 00000000000..f616961f32e --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHandler.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2010-2015 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.pullUp + +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.ScrollType +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.refactoring.HelpID +import com.intellij.refactoring.RefactoringActionHandler +import com.intellij.refactoring.RefactoringBundle +import com.intellij.refactoring.lang.ElementsHandler +import com.intellij.refactoring.util.CommonRefactoringUtil +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor +import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde +import org.jetbrains.kotlin.idea.core.refactoring.canRefactor +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfoStorage +import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedNameForRendering +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.types.typeUtil.supertypes +import java.util.HashSet + +public class KotlinPullUpHandler : RefactoringActionHandler, ElementsHandler { + companion object { + val PULLUP_TEST_HELPER_KEY = "PULLUP_TEST_HELPER_KEY" + } + + interface TestHelper { + fun adjustMembers(members: List): List + fun chooseSuperClass(superClasses: List): JetClass + } + + private fun reportWrongPosition(project: Project, editor: Editor?) { + val message = RefactoringBundle.getCannotRefactorMessage( + RefactoringBundle.message("the.caret.should.be.positioned.inside.a.class.to.pull.members.from") + ) + CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, "refactoring.pullMembersUp") + } + + private fun reportWrongContext(project: Project, editor: Editor?) { + val message = RefactoringBundle.getCannotRefactorMessage( + RefactoringBundle.message("is.not.supported.in.the.current.context", PULL_MEMBERS_UP) + ) + CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, HelpID.MEMBERS_PULL_UP) + } + + private fun reportNoSuperClasses(project: Project, editor: Editor?, classOrObject: JetClassOrObject) { + val message = RefactoringBundle.getCannotRefactorMessage( + RefactoringBundle.message("class.does.not.have.base.classes.interfaces.in.current.project", + classOrObject.qualifiedNameForRendering()) + ) + CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, HelpID.MEMBERS_PULL_UP) + } + + override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { + val offset = editor.getCaretModel().getOffset() + editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE) + + val target = (file.findElementAt(offset) ?: return).parentsWithSelf.firstOrNull { + it is JetClassOrObject || ((it is JetNamedFunction || it is JetProperty) && it.getParent() is JetClassBody) + } + + if (target == null) { + reportWrongPosition(project, editor) + return + } + if (!target.canRefactor()) return + + invoke(project, arrayOf(target), dataContext) + } + + override fun invoke(project: Project, elements: Array, dataContext: DataContext?) { + val element = elements.singleOrNull() ?: return + + val editor = dataContext?.let { CommonDataKeys.EDITOR.getData(it) } + + val (classOrObject, member) = when (element) { + is JetNamedFunction, is JetProperty -> element.getStrictParentOfType() to element as JetNamedDeclaration? + is JetClassOrObject -> element to null + else -> { + reportWrongPosition(project, editor) + return + } + } + + invoke(project, editor, classOrObject, member, dataContext) + } + + private fun invoke(project: Project, + editor: Editor?, + classOrObject: JetClassOrObject?, + member: JetNamedDeclaration?, + dataContext: DataContext?) { + if (classOrObject == null) { + reportWrongContext(project, editor) + return + } + + val classDescriptor = classOrObject.resolveToDescriptor() as ClassDescriptor + val superClasses = classDescriptor.getDefaultType() + .supertypes() + .asSequence() + .map { + val descriptor = it.getConstructor().getDeclarationDescriptor() + val declaration = descriptor?.let { DescriptorToSourceUtilsIde.getAnyDeclaration(project, it) } + if (declaration is JetClass && declaration.canRefactor()) declaration else null + } + .filterNotNull() + .toSortedListBy { it.qualifiedNameForRendering() } + + if (superClasses.isEmpty()) { + val containingClass = classOrObject.getStrictParentOfType() + if (containingClass != null) { + invoke(project, editor, containingClass, classOrObject, dataContext) + } + else { + reportNoSuperClasses(project, editor, classOrObject) + } + return + } + + val memberInfoStorage = KotlinMemberInfoStorage(classOrObject) + val members = memberInfoStorage.getClassMemberInfos(classOrObject) + + if (ApplicationManager.getApplication().isUnitTestMode()) { + val helper = dataContext?.getData(PULLUP_TEST_HELPER_KEY) as TestHelper + KotlinPullUpDialog.createProcessor(classOrObject, + helper.chooseSuperClass(superClasses), + helper.adjustMembers(members)).run() + } + else { + val manager = classOrObject.getManager() + members.filter { manager.areElementsEquivalent(it.getMember(), member) }.forEach { it.setChecked(true) } + + KotlinPullUpDialog(project, classOrObject, superClasses, memberInfoStorage).show() + } + } + + override fun isEnabledOnElements(elements: Array): Boolean { + return elements.mapTo(HashSet()) { + when (it) { + is JetNamedFunction, is JetProperty -> + (it.getParent() as? JetClassBody)?.getParent() as? JetClassOrObject + is JetClassOrObject -> it + else -> null + } ?: return false + }.size() == 1 + } +} + +val PULL_MEMBERS_UP = "Pull Members Up" \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt new file mode 100644 index 00000000000..ddb0dfa5455 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelper.kt @@ -0,0 +1,393 @@ +/* + * Copyright 2010-2015 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.pullUp + +import com.intellij.openapi.util.Key +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiMember +import com.intellij.psi.PsiSubstitutor +import com.intellij.psi.search.LocalSearchScope +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.refactoring.classMembers.MemberInfoBase +import com.intellij.refactoring.memberPullUp.PullUpData +import com.intellij.refactoring.memberPullUp.PullUpHelper +import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod +import org.jetbrains.kotlin.asJava.namedUnwrappedElement +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +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.imports.importableFqName +import org.jetbrains.kotlin.idea.refactoring.safeDelete.removeOverrideModifier +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.ShortenReferences +import org.jetbrains.kotlin.lexer.JetModifierKeywordToken +import org.jetbrains.kotlin.lexer.JetTokens +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.allChildren +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiverOrThis +import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue +import org.jetbrains.kotlin.resolve.source.getPsi +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.typeUtil.isUnit +import org.jetbrains.kotlin.util.findCallableMemberBySignature +import java.util.ArrayList +import java.util.LinkedHashSet + +class KotlinPullUpHelper( + private val javaData: PullUpData, + private val data: KotlinPullUpData +) : PullUpHelper> { + companion object { + private var JetElement.newFqName: FqName? by CopyableUserDataProperty(Key.create("NEW_FQ_NAME")) + private var JetElement.replaceWithTargetThis: Boolean? by CopyableUserDataProperty(Key.create("REPLACE_WITH_TARGET_THIS")) + private var JetElement.newTypeText: String? by CopyableUserDataProperty(Key.create("NEW_TYPE_TEXT")) + } + + private fun willBeUsedInSourceClass(member: PsiElement): Boolean { + return !ReferencesSearch + .search(member, LocalSearchScope(data.sourceClass), false) + .all { it.getElement().parentsWithSelf.any { it in data.membersToMove } } + } + + private fun liftToProtected(declaration: JetNamedDeclaration, ignoreUsages: Boolean = false) { + if (!declaration.hasModifier(JetTokens.PRIVATE_KEYWORD)) return + if (ignoreUsages || willBeUsedInSourceClass(declaration)) declaration.addModifierWithSpace(JetTokens.PROTECTED_KEYWORD) + } + + override fun setCorrectVisibility(info: MemberInfoBase) { + val member = info.getMember().namedUnwrappedElement as? JetNamedDeclaration ?: return + + if (data.targetClass.isInterface()) { + member.removeModifier(JetTokens.PUBLIC_KEYWORD) + return + } + + if (member.hasModifier(JetTokens.PRIVATE_KEYWORD)) { + member.accept( + object: JetVisitorVoid() { + override fun visitNamedDeclaration(declaration: JetNamedDeclaration) { + when (declaration) { + is JetClass -> { + liftToProtected(declaration) + declaration.getDeclarations().forEach { it.accept(this) } + } + is JetNamedFunction, is JetProperty -> { + liftToProtected(declaration, declaration == member && info.isToAbstract()) + } + } + } + } + ) + } + } + + override fun encodeContextInfo(info: MemberInfoBase) { + + } + + private fun getClashingMemberInTargetClass(memberDescriptor: CallableMemberDescriptor): CallableMemberDescriptor? { + val memberInSuper = memberDescriptor.substitute(data.sourceToTargetClassSubstitutor) ?: return null + return data.targetClassDescriptor.findCallableMemberBySignature(memberInSuper as CallableMemberDescriptor) + } + + private fun fixOverrideAndGetClashingSuper(sourceMember: JetCallableDeclaration, + targetMember: JetCallableDeclaration): JetCallableDeclaration? { + val memberDescriptor = data.memberDescriptors[sourceMember] as CallableMemberDescriptor + + if (memberDescriptor.getOverriddenDescriptors().isEmpty()) { + targetMember.removeOverrideModifier() + return null + } + + val clashingSuperDescriptor = getClashingMemberInTargetClass(memberDescriptor) ?: return null + if (clashingSuperDescriptor.getOverriddenDescriptors().isEmpty()) { + targetMember.removeOverrideModifier() + } + return clashingSuperDescriptor.getSource().getPsi() as? JetCallableDeclaration + } + + private fun makeAbstract(sourceMember: JetCallableDeclaration, targetMember: JetCallableDeclaration) { + if (!data.targetClass.isInterface()) { + targetMember.addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD) + } + + if (sourceMember.getTypeReference() == null) { + var type = (data.memberDescriptors[sourceMember] as CallableMemberDescriptor).getReturnType() + if (type == null || type.isError()) { + type = KotlinBuiltIns.getInstance().getNullableAnyType() + } + else { + type = data.sourceToTargetClassSubstitutor.substitute(type, Variance.INVARIANT) + ?: KotlinBuiltIns.getInstance().getNullableAnyType() + } + + if (sourceMember is JetProperty || !type.isUnit()) { + val typeRef = JetPsiFactory(sourceMember).createType(IdeDescriptorRenderers.SOURCE_CODE.renderType(type)) + targetMember.setTypeReference(typeRef) + } + } + + val deleteFrom = when (sourceMember) { + is JetProperty -> { + targetMember as JetProperty + val accessors = targetMember.getAccessors() + targetMember.getEqualsToken() ?: targetMember.getDelegate() ?: accessors.firstOrNull() + } + + is JetNamedFunction -> { + targetMember as JetNamedFunction + targetMember.getEqualsToken() ?: targetMember.getBodyExpression() + } + + else -> null + } + + if (deleteFrom != null) { + targetMember.deleteChildRange(deleteFrom, targetMember.getLastChild()) + } + } + + private fun addMemberToTarget(targetMember: JetNamedDeclaration): JetNamedDeclaration { + val anchor = data.targetClass.getDeclarations().filterIsInstance(targetMember.javaClass).lastOrNull() + val movedMember = when { + anchor == null && targetMember is JetProperty -> data.targetClass.addDeclarationBefore(targetMember, null) + else -> data.targetClass.addDeclarationAfter(targetMember, anchor) + } + return movedMember as JetNamedDeclaration + } + + private fun doAddCallableMember(memberCopy: JetCallableDeclaration, clashingSuper: JetCallableDeclaration?): JetCallableDeclaration { + if (clashingSuper != null && clashingSuper.hasModifier(JetTokens.ABSTRACT_KEYWORD)) { + return clashingSuper.replaced(memberCopy) + } + return addMemberToTarget(memberCopy) as JetCallableDeclaration + } + + private fun markElements(member: JetNamedDeclaration): List { + val affectedElements = ArrayList() + + member.accept( + object: JetVisitorVoid() { + private fun visitSuperOrThis(expression: JetInstanceExpressionWithLabel) { + val referenceTarget = data.sourceClassContext[BindingContext.REFERENCE_TARGET, expression.getInstanceReference()] + if (referenceTarget == data.targetClassDescriptor) { + expression.replaceWithTargetThis = true + affectedElements.add(expression) + } + } + + override fun visitElement(element: PsiElement) { + element.allChildren.forEach { it.accept(this) } + } + + override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) { + val resolvedCall = expression.getResolvedCall(data.sourceClassContext) ?: return + var receiver = resolvedCall.getExplicitReceiverValue() + if (!receiver.exists()) { + receiver = resolvedCall.getExtensionReceiver() + } + if (!receiver.exists()) { + receiver = resolvedCall.getDispatchReceiver() + } + if (!receiver.exists()) return + + val implicitThis = receiver.getType().getConstructor().getDeclarationDescriptor() as? ClassDescriptor ?: return + if (implicitThis.isCompanionObject() + && DescriptorUtils.isAncestor(data.sourceClassDescriptor, implicitThis, true)) { + val qualifierFqName = implicitThis.importableFqName ?: return + + expression.newFqName = FqName("${qualifierFqName.asString()}.${expression.getReferencedName()}") + affectedElements.add(expression) + } + } + + override fun visitThisExpression(expression: JetThisExpression) { + visitSuperOrThis(expression) + } + + override fun visitSuperExpression(expression: JetSuperExpression) { + visitSuperOrThis(expression) + } + + override fun visitTypeReference(typeReference: JetTypeReference) { + val oldType = data.sourceClassContext[BindingContext.TYPE, typeReference] ?: return + val newType = data.sourceToTargetClassSubstitutor.substitute(oldType, Variance.INVARIANT) ?: return + typeReference.newTypeText = IdeDescriptorRenderers.SOURCE_CODE.renderType(newType) + affectedElements.add(typeReference) + } + } + ) + + return affectedElements + } + + private fun processMarkedElements(member: JetNamedDeclaration) { + val psiFactory = JetPsiFactory(member) + val targetThis = psiFactory.createExpression("this@${data.targetClassDescriptor.getName().asString()}") + val shorteningOptionsForThis = ShortenReferences.Options(removeThisLabels = true, removeThis = true) + + member.accept( + object: JetVisitorVoid() { + private fun visitSuperOrThis(expression: JetInstanceExpressionWithLabel) { + expression.replaceWithTargetThis?.let { + expression.replaceWithTargetThis = null + + val newThisExpression = expression.replace(targetThis) as JetExpression + newThisExpression.getQualifiedExpressionForReceiverOrThis().addToShorteningWaitSet(shorteningOptionsForThis) + } + } + + override fun visitElement(element: PsiElement) { + for (it in element.allChildren.toList()) { + it.accept(this) + } + } + + override fun visitSimpleNameExpression(expression: JetSimpleNameExpression) { + expression.newFqName?.let { + expression.newFqName = null + + expression.mainReference.bindToFqName(it) + } + } + + override fun visitThisExpression(expression: JetThisExpression) { + visitSuperOrThis(expression) + } + + override fun visitSuperExpression(expression: JetSuperExpression) { + visitSuperOrThis(expression) + } + + override fun visitTypeReference(typeReference: JetTypeReference) { + typeReference.newTypeText?.let { + typeReference.newTypeText = null + + (typeReference.replace(psiFactory.createType(it)) as JetElement).addToShorteningWaitSet() + } + } + } + ) + } + + private fun clearMarking(markedElements: List) { + markedElements.forEach { + it.newFqName = null + it.newTypeText = null + it.replaceWithTargetThis = null + } + } + + // TODO: Formatting rules don't apply here for some reason + private fun JetNamedDeclaration.addModifierWithSpace(modifier: JetModifierKeywordToken) { + addModifier(modifier) + addAfter(JetPsiFactory(this).createWhiteSpace(), getModifierList()) + } + + override fun move(info: MemberInfoBase, substitutor: PsiSubstitutor) { + val member = info.getMember().namedUnwrappedElement as? JetNamedDeclaration ?: return + val markedElements = markElements(member) + val memberCopy = member.copy() as JetNamedDeclaration + + try { + var movedMember: JetNamedDeclaration + when (member) { + is JetCallableDeclaration -> { + val clashingSuper = fixOverrideAndGetClashingSuper(member, memberCopy as JetCallableDeclaration) + + val originalIsAbstract = member.hasModifier(JetTokens.ABSTRACT_KEYWORD) + val toAbstract = when { + info.isToAbstract() -> true + !data.targetClass.isInterface() -> false + member is JetProperty -> member.mustBeAbstractInInterface() + else -> false + } + if (toAbstract) { + if (!originalIsAbstract) { + makeAbstract(member, memberCopy) + } + + movedMember = doAddCallableMember(memberCopy, clashingSuper) + if (member.getTypeReference() == null) { + movedMember.getTypeReference()?.addToShorteningWaitSet() + } + if (originalIsAbstract) { + member.delete() + } + else if (!member.hasModifier(JetTokens.OVERRIDE_KEYWORD)) { + member.addModifierWithSpace(JetTokens.OVERRIDE_KEYWORD) + } + } + else { + movedMember = doAddCallableMember(memberCopy, clashingSuper) + member.delete() + } + + if (originalIsAbstract && data.targetClass.isInterface()) { + movedMember.removeModifier(JetTokens.ABSTRACT_KEYWORD) + } + + if (!data.targetClass.isInterface() + && !data.targetClass.hasModifier(JetTokens.ABSTRACT_KEYWORD) + && (movedMember.hasModifier(JetTokens.ABSTRACT_KEYWORD))) { + data.targetClass.addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD) + } + + movedMember.getRepresentativeLightMethod()?.let { javaData.getMovedMembers().add(it) } + } + + is JetClassOrObject -> { + if (data.targetClass.isInterface()) { + memberCopy.removeModifier(JetTokens.INNER_KEYWORD) + } + movedMember = addMemberToTarget(memberCopy) as JetClassOrObject + member.delete() + movedMember.toLightClass()?.let { javaData.getMovedMembers().add(it) } + } + + else -> return + } + + processMarkedElements(movedMember) + } + finally { + clearMarking(markedElements) + } + } + + override fun postProcessMember(member: PsiMember) { + + } + + override fun moveFieldInitializations(movedFields: LinkedHashSet) { + + } + + override fun updateUsage(element: PsiElement) { + + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelperFactory.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelperFactory.kt new file mode 100644 index 00000000000..55241dc4015 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/KotlinPullUpHelperFactory.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2015 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.pullUp + +import com.intellij.refactoring.memberPullUp.PullUpData +import com.intellij.refactoring.memberPullUp.PullUpHelper +import com.intellij.refactoring.memberPullUp.PullUpHelperFactory +import org.jetbrains.kotlin.asJava.namedUnwrappedElement +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.psi.JetClass +import org.jetbrains.kotlin.psi.JetClassOrObject +import org.jetbrains.kotlin.psi.JetNamedDeclaration +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +public class KotlinPullUpHelperFactory : PullUpHelperFactory { + private fun PullUpData.toKotlinPullUpData(): KotlinPullUpData? { + if (!getSourceClass().isInheritor(getTargetClass(), true)) return null + val sourceClass = getSourceClass().unwrapped as? JetClassOrObject ?: return null + val targetClass = getTargetClass().unwrapped as? JetClass ?: return null + val membersToMove = getMembersToMove() + .map { it.namedUnwrappedElement as? JetNamedDeclaration } + .filterNotNull() + .sortBy { it.startOffset } + return KotlinPullUpData(sourceClass, targetClass, membersToMove) + } + + override fun createPullUpHelper(data: PullUpData): PullUpHelper<*> { + val kotlinPullUpData = data.toKotlinPullUpData() ?: return EmptyPullUpHelper + return KotlinPullUpHelper(data, kotlinPullUpData) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt new file mode 100644 index 00000000000..1352d1c22bc --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/pullUp/pullUpUtils.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2015 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.pullUp + +import org.jetbrains.kotlin.psi.JetProperty + +fun JetProperty.mustBeAbstractInInterface() = + hasInitializer() || hasDelegate() || (!hasInitializer() && !hasDelegate() && getAccessors().isEmpty()) diff --git a/idea/testData/refactoring/pullUp/fromClassToClass.kt b/idea/testData/refactoring/pullUp/fromClassToClass.kt new file mode 100644 index 00000000000..97b2159441c --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToClass.kt @@ -0,0 +1,27 @@ +// WITH_RUNTIME +open class A + +abstract class B: A() { + // INFO: {"checked": "true"} + val x = 1 + // INFO: {"checked": "true"} + val y: Int get() = 2 + // INFO: {"checked": "true"} + val z: Int by lazy { 3 } + // INFO: {"checked": "true"} + abstract val t: Int + // INFO: {"checked": "true"} + fun foo(n: Int): Boolean = n > 0 + // INFO: {"checked": "true"} + abstract fun bar(s: String) + + // INFO: {"checked": "true"} + inner class X { + + } + + // INFO: {"checked": "true"} + class Y { + + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToClass.kt.after b/idea/testData/refactoring/pullUp/fromClassToClass.kt.after new file mode 100644 index 00000000000..ee08ebb1364 --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToClass.kt.after @@ -0,0 +1,31 @@ +// WITH_RUNTIME +abstract class A { + // INFO: {"checked": "true"} + val x = 1 + // INFO: {"checked": "true"} + val y: Int get() = 2 + // INFO: {"checked": "true"} + val z: Int by lazy { 3 } + // INFO: {"checked": "true"} + abstract val t: Int + + // INFO: {"checked": "true"} + fun foo(n: Int): Boolean = n > 0 + + // INFO: {"checked": "true"} + abstract fun bar(s: String) + + // INFO: {"checked": "true"} + inner class X { + + } + + // INFO: {"checked": "true"} + class Y { + + } +} + +abstract class B: A() { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToClassMakeAbstract.kt b/idea/testData/refactoring/pullUp/fromClassToClassMakeAbstract.kt new file mode 100644 index 00000000000..a3cd3d17361 --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToClassMakeAbstract.kt @@ -0,0 +1,27 @@ +// WITH_RUNTIME +open class A + +abstract class B: A() { + // INFO: {"checked": "true", "toAbstract": "true"} + val x = 1 + // INFO: {"checked": "true", "toAbstract": "true"} + val y: Int get() = 2 + // INFO: {"checked": "true", "toAbstract": "true"} + val z: Int by lazy { 3 } + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val t: Int + // INFO: {"checked": "true", "toAbstract": "true"} + fun foo(n: Int): Boolean = n > 0 + // INFO: {"checked": "true", "toAbstract": "true"} + abstract fun bar(s: String) + + // INFO: {"checked": "true", "toAbstract": "true"} + inner class X { + + } + + // INFO: {"checked": "true", "toAbstract": "true"} + class Y { + + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToClassMakeAbstract.kt.after b/idea/testData/refactoring/pullUp/fromClassToClassMakeAbstract.kt.after new file mode 100644 index 00000000000..00a2fd23ffc --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToClassMakeAbstract.kt.after @@ -0,0 +1,40 @@ +// WITH_RUNTIME +abstract class A { + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val x: Int + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val y: Int + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val z: Int + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val t: Int + + // INFO: {"checked": "true", "toAbstract": "true"} + abstract fun foo(n: Int): Boolean + + // INFO: {"checked": "true", "toAbstract": "true"} + abstract fun bar(s: String) + + // INFO: {"checked": "true", "toAbstract": "true"} + inner class X { + + } + + // INFO: {"checked": "true", "toAbstract": "true"} + class Y { + + } +} + +abstract class B: A() { + // INFO: {"checked": "true", "toAbstract": "true"} + override val x = 1 + // INFO: {"checked": "true", "toAbstract": "true"} + override val y: Int get() = 2 + // INFO: {"checked": "true", "toAbstract": "true"} + override val z: Int by lazy { 3 } + + // INFO: {"checked": "true", "toAbstract": "true"} + override fun foo(n: Int): Boolean = n > 0 + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToClassWithGenerics.kt b/idea/testData/refactoring/pullUp/fromClassToClassWithGenerics.kt new file mode 100644 index 00000000000..8891cdc0960 --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToClassWithGenerics.kt @@ -0,0 +1,36 @@ +// WITH_RUNTIME +interface I + +interface Z + +open class A + +class C { + inner class B(x: X, y: Y): A>() { + // INFO: {"checked": "true"} + fun foo(x1: X, x2: Z, y1: Y, y2: Z, w1: W, w2: Z, s1: S, s2: Z) { + + } + // INFO: {"checked": "true"} + inner class Foo(x1: X, x2: Z, y1: Y, y2: Z, w1: W, w2: Z, s1: S, s2: Z): A>(), Z { + + } + // INFO: {"checked": "true"} + val foo1: X + // INFO: {"checked": "true"} + val foo2: Z + // INFO: {"checked": "true"} + val foo3: Y + // INFO: {"checked": "true"} + val foo4: Z + + // INFO: {"checked": "true", "toAbstract": "true"} + val foo5 = x + // INFO: {"checked": "true", "toAbstract": "true"} + val foo6 = object: Z {} + // INFO: {"checked": "true", "toAbstract": "true"} + val foo7 = y + // INFO: {"checked": "true", "toAbstract": "true"} + val foo8 = object: Z {} + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToClassWithGenerics.kt.after b/idea/testData/refactoring/pullUp/fromClassToClassWithGenerics.kt.after new file mode 100644 index 00000000000..2436eb3cc94 --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToClassWithGenerics.kt.after @@ -0,0 +1,47 @@ +// WITH_RUNTIME +interface I + +interface Z + +abstract class A { + // INFO: {"checked": "true"} + val foo1: T + // INFO: {"checked": "true"} + val foo2: Z + // INFO: {"checked": "true"} + val foo3: Any? + // INFO: {"checked": "true"} + val foo4: Z + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val foo5: T + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val foo6: Z + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val foo7: Any? + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val foo8: Z + + // INFO: {"checked": "true"} + fun foo(x1: T, x2: Z, y1: Any?, y2: Z, w1: I, w2: Z, s1: S, s2: Z) { + + } + + // INFO: {"checked": "true"} + inner class Foo(x1: T, x2: Z, y1: Any?, y2: Z, w1: I, w2: Z, s1: S, s2: Z): A>(), Z { + + } +} + +class C { + inner class B(x: X, y: Y): A>() { + + // INFO: {"checked": "true", "toAbstract": "true"} + override val foo5 = x + // INFO: {"checked": "true", "toAbstract": "true"} + override val foo6 = object: Z {} + // INFO: {"checked": "true", "toAbstract": "true"} + override val foo7 = y + // INFO: {"checked": "true", "toAbstract": "true"} + override val foo8 = object: Z {} + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToInterface.kt b/idea/testData/refactoring/pullUp/fromClassToInterface.kt new file mode 100644 index 00000000000..32462a483d8 --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToInterface.kt @@ -0,0 +1,27 @@ +// WITH_RUNTIME +interface T + +abstract class B: T { + // INFO: {"checked": "true"} + val x = 1 + // INFO: {"checked": "true"} + val y: Int get() = 2 + // INFO: {"checked": "true"} + val z: Int by lazy { 3 } + // INFO: {"checked": "true"} + abstract val t: Int + // INFO: {"checked": "true"} + fun foo(n: Int): Boolean = n > 0 + // INFO: {"checked": "true"} + abstract fun bar(s: String) + + // INFO: {"checked": "true"} + inner class X { + + } + + // INFO: {"checked": "true"} + class Y { + + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToInterface.kt.after b/idea/testData/refactoring/pullUp/fromClassToInterface.kt.after new file mode 100644 index 00000000000..81ac7da7cc0 --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToInterface.kt.after @@ -0,0 +1,35 @@ +// WITH_RUNTIME +interface T { + // INFO: {"checked": "true"} + val x: Int + // INFO: {"checked": "true"} + val y: Int get() = 2 + // INFO: {"checked": "true"} + val z: Int + // INFO: {"checked": "true"} + val t: Int + + // INFO: {"checked": "true"} + fun foo(n: Int): Boolean = n > 0 + + // INFO: {"checked": "true"} + fun bar(s: String) + + // INFO: {"checked": "true"} + class X { + + } + + // INFO: {"checked": "true"} + class Y { + + } +} + +abstract class B: T { + // INFO: {"checked": "true"} + override val x = 1 + // INFO: {"checked": "true"} + override val z: Int by lazy { 3 } + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToInterfaceMakeAbstract.kt b/idea/testData/refactoring/pullUp/fromClassToInterfaceMakeAbstract.kt new file mode 100644 index 00000000000..05dbdfbb4a6 --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToInterfaceMakeAbstract.kt @@ -0,0 +1,27 @@ +// WITH_RUNTIME +interface T + +abstract class B: T { + // INFO: {"checked": "true", "toAbstract": "true"} + val x = 1 + // INFO: {"checked": "true", "toAbstract": "true"} + val y: Int get() = 2 + // INFO: {"checked": "true", "toAbstract": "true"} + val z: Int by lazy { 3 } + // INFO: {"checked": "true", "toAbstract": "true"} + abstract val t: Int + // INFO: {"checked": "true", "toAbstract": "true"} + fun foo(n: Int): Boolean = n > 0 + // INFO: {"checked": "true", "toAbstract": "true"} + abstract fun bar(s: String) + + // INFO: {"checked": "true", "toAbstract": "true"} + inner class X { + + } + + // INFO: {"checked": "true", "toAbstract": "true"} + class Y { + + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/fromClassToInterfaceMakeAbstract.kt.after b/idea/testData/refactoring/pullUp/fromClassToInterfaceMakeAbstract.kt.after new file mode 100644 index 00000000000..4a5d8fcbcd2 --- /dev/null +++ b/idea/testData/refactoring/pullUp/fromClassToInterfaceMakeAbstract.kt.after @@ -0,0 +1,40 @@ +// WITH_RUNTIME +interface T { + // INFO: {"checked": "true", "toAbstract": "true"} + val x: Int + // INFO: {"checked": "true", "toAbstract": "true"} + val y: Int + // INFO: {"checked": "true", "toAbstract": "true"} + val z: Int + // INFO: {"checked": "true", "toAbstract": "true"} + val t: Int + + // INFO: {"checked": "true", "toAbstract": "true"} + fun foo(n: Int): Boolean + + // INFO: {"checked": "true", "toAbstract": "true"} + fun bar(s: String) + + // INFO: {"checked": "true", "toAbstract": "true"} + class X { + + } + + // INFO: {"checked": "true", "toAbstract": "true"} + class Y { + + } +} + +abstract class B: T { + // INFO: {"checked": "true", "toAbstract": "true"} + override val x = 1 + // INFO: {"checked": "true", "toAbstract": "true"} + override val y: Int get() = 2 + // INFO: {"checked": "true", "toAbstract": "true"} + override val z: Int by lazy { 3 } + + // INFO: {"checked": "true", "toAbstract": "true"} + override fun foo(n: Int): Boolean = n > 0 + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/implicitCompanionUsages.kt b/idea/testData/refactoring/pullUp/implicitCompanionUsages.kt new file mode 100644 index 00000000000..38e7f2e4785 --- /dev/null +++ b/idea/testData/refactoring/pullUp/implicitCompanionUsages.kt @@ -0,0 +1,12 @@ +open class A + +class X { + class B: A() { + // INFO: {"checked": "true"} + fun foo(): Int = bar() + B.bar() + X.B.bar() + + companion object { + fun bar() = 1 + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/implicitCompanionUsages.kt.after b/idea/testData/refactoring/pullUp/implicitCompanionUsages.kt.after new file mode 100644 index 00000000000..99e5ac7b2de --- /dev/null +++ b/idea/testData/refactoring/pullUp/implicitCompanionUsages.kt.after @@ -0,0 +1,13 @@ +open class A { + // INFO: {"checked": "true"} + fun foo(): Int = X.B.bar() + X.B.bar() + X.B.bar() +} + +class X { + class B: A() { + + companion object { + fun bar() = 1 + } + } +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/noCaret.kt b/idea/testData/refactoring/pullUp/noCaret.kt new file mode 100644 index 00000000000..4982e2ddd21 --- /dev/null +++ b/idea/testData/refactoring/pullUp/noCaret.kt @@ -0,0 +1,4 @@ + +class A { + val x: Int = 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/noCaret.kt.messages b/idea/testData/refactoring/pullUp/noCaret.kt.messages new file mode 100644 index 00000000000..cd4f2d0bbab --- /dev/null +++ b/idea/testData/refactoring/pullUp/noCaret.kt.messages @@ -0,0 +1,2 @@ +Cannot perform refactoring. +Caret should be positioned inside a class to pull members from \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/noSuperClass.kt b/idea/testData/refactoring/pullUp/noSuperClass.kt new file mode 100644 index 00000000000..4bcc05e13f9 --- /dev/null +++ b/idea/testData/refactoring/pullUp/noSuperClass.kt @@ -0,0 +1,3 @@ +class A { + val x: Int = 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/noSuperClass.kt.messages b/idea/testData/refactoring/pullUp/noSuperClass.kt.messages new file mode 100644 index 00000000000..76ddbe2ffcb --- /dev/null +++ b/idea/testData/refactoring/pullUp/noSuperClass.kt.messages @@ -0,0 +1,2 @@ +Cannot perform refactoring. +A does not have base classes/interfaces in current project \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/outsideOfClass.kt b/idea/testData/refactoring/pullUp/outsideOfClass.kt new file mode 100644 index 00000000000..7b729c2546f --- /dev/null +++ b/idea/testData/refactoring/pullUp/outsideOfClass.kt @@ -0,0 +1,3 @@ +fun test() { + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/outsideOfClass.kt.messages b/idea/testData/refactoring/pullUp/outsideOfClass.kt.messages new file mode 100644 index 00000000000..cd4f2d0bbab --- /dev/null +++ b/idea/testData/refactoring/pullUp/outsideOfClass.kt.messages @@ -0,0 +1,2 @@ +Cannot perform refactoring. +Caret should be positioned inside a class to pull members from \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/publicToInterface.kt b/idea/testData/refactoring/pullUp/publicToInterface.kt new file mode 100644 index 00000000000..06a1e69208e --- /dev/null +++ b/idea/testData/refactoring/pullUp/publicToInterface.kt @@ -0,0 +1,6 @@ +interface I + +class A: I { + // INFO: {"checked": "true"} + public fun foo(): Int = 1 +} diff --git a/idea/testData/refactoring/pullUp/publicToInterface.kt.after b/idea/testData/refactoring/pullUp/publicToInterface.kt.after new file mode 100644 index 00000000000..6ef9168db8a --- /dev/null +++ b/idea/testData/refactoring/pullUp/publicToInterface.kt.after @@ -0,0 +1,7 @@ +interface I { + // INFO: {"checked": "true"} + fun foo(): Int = 1 +} + +class A: I { +} diff --git a/idea/testData/refactoring/pullUp/superToThis.kt b/idea/testData/refactoring/pullUp/superToThis.kt new file mode 100644 index 00000000000..3cc3a346261 --- /dev/null +++ b/idea/testData/refactoring/pullUp/superToThis.kt @@ -0,0 +1,10 @@ +open class A { + open fun bar() = 1 +} + +class B: A() { + override fun bar() = 1 + + // INFO: {"checked": "true"} + fun foo() = super.bar() +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/superToThis.kt.after b/idea/testData/refactoring/pullUp/superToThis.kt.after new file mode 100644 index 00000000000..97d2b191c5b --- /dev/null +++ b/idea/testData/refactoring/pullUp/superToThis.kt.after @@ -0,0 +1,10 @@ +open class A { + open fun bar() = 1 + // INFO: {"checked": "true"} + fun foo() = bar() +} + +class B: A() { + override fun bar() = 1 + +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/toIndirectSuperClass.kt b/idea/testData/refactoring/pullUp/toIndirectSuperClass.kt new file mode 100644 index 00000000000..50e65437b5d --- /dev/null +++ b/idea/testData/refactoring/pullUp/toIndirectSuperClass.kt @@ -0,0 +1,13 @@ +// TARGET_CLASS: A +open class A { + +} + +open class B: A() { + +} + +class C: B() { + // INFO: {"checked": "true"} + val x: Int +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/toIndirectSuperClass.kt.after b/idea/testData/refactoring/pullUp/toIndirectSuperClass.kt.after new file mode 100644 index 00000000000..066e5d50459 --- /dev/null +++ b/idea/testData/refactoring/pullUp/toIndirectSuperClass.kt.after @@ -0,0 +1,13 @@ +// TARGET_CLASS: A +open class A { + // INFO: {"checked": "true"} + val x: Int + +} + +open class B: A() { + +} + +class C: B() { +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/usedPrivateToClass.kt b/idea/testData/refactoring/pullUp/usedPrivateToClass.kt new file mode 100644 index 00000000000..6b3c6070ed8 --- /dev/null +++ b/idea/testData/refactoring/pullUp/usedPrivateToClass.kt @@ -0,0 +1,8 @@ +open class A + +class B: A() { + // INFO: {"checked": "true"} + private fun foo(): Int = 1 + + fun bar() = foo() + 1 +} \ No newline at end of file diff --git a/idea/testData/refactoring/pullUp/usedPrivateToClass.kt.after b/idea/testData/refactoring/pullUp/usedPrivateToClass.kt.after new file mode 100644 index 00000000000..ba26b142103 --- /dev/null +++ b/idea/testData/refactoring/pullUp/usedPrivateToClass.kt.after @@ -0,0 +1,9 @@ +open class A { + // INFO: {"checked": "true"} + protected fun foo(): Int = 1 +} + +class B: A() { + + fun bar() = foo() + 1 +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt index eb30ba0a2e1..7354339270f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractJetExtractionTest.kt @@ -52,7 +52,7 @@ import org.jetbrains.kotlin.psi.JetNamedDeclaration import org.jetbrains.kotlin.renderer.DescriptorRenderer import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.JetTestUtils -import org.jetbrains.kotlin.test.util.findElementByComment +import org.jetbrains.kotlin.test.util.findElementByCommentPrefix import org.jetbrains.kotlin.utils.emptyOrSingletonList import java.io.File import java.util.Collections @@ -106,7 +106,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe KotlinIntroduceParameterHandler(HelperImpl()) } with (handler) { - val target = (file as JetFile).findElementByComment("// TARGET:") as? JetNamedDeclaration + val target = (file as JetFile).findElementByCommentPrefix("// TARGET:") as? JetNamedDeclaration if (target != null) { JetRefactoringUtil.selectExpression(fixture.getEditor(), file, true) { expression -> invoke(fixture.getProject(), fixture.getEditor(), expression!!, target) @@ -208,7 +208,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe val extractionTarget = propertyTargets.single { it.name == InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// EXTRACTION_TARGET: ") } - val explicitPreviousSibling = file.findElementByComment("// SIBLING:") + val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:") val helper = object : ExtractionEngineHelper(INTRODUCE_PROPERTY) { override fun configureAndRun( project: Project, @@ -237,7 +237,7 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe doTest(path) { file -> file as JetFile - val explicitPreviousSibling = file.findElementByComment("// SIBLING:") + val explicitPreviousSibling = file.findElementByCommentPrefix("// SIBLING:") val fileText = file.getText() ?: "" val expectedNames = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_NAMES: ") val expectedReturnTypes = InTextDirectivesUtils.findListWithPrefixes(fileText, "// SUGGESTED_RETURN_TYPES: ") diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/AbstractPullUpTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/AbstractPullUpTest.kt new file mode 100644 index 00000000000..2cfe4dd1a7a --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/AbstractPullUpTest.kt @@ -0,0 +1,115 @@ +/* + * Copyright 2010-2015 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.pullUp + +import com.google.gson.JsonParser +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.util.Key +import com.intellij.openapi.util.io.FileUtil +import com.intellij.refactoring.BaseRefactoringProcessor +import com.intellij.refactoring.util.CommonRefactoringUtil +import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo +import org.jetbrains.kotlin.idea.stubindex.JetFullClassNameIndex +import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil +import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinMultiFileTestCase +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.jetbrains.kotlin.test.JetTestUtils +import org.jetbrains.kotlin.test.util.findElementByCommentPrefix +import org.jetbrains.kotlin.test.util.findElementsByCommentPrefix +import java.io.File + +public abstract class AbstractPullUpTest : JetLightCodeInsightFixtureTestCase() { + private data class ElementInfo(val checked: Boolean, val toAbstract: Boolean) + + companion object { + private var JetElement.elementInfo: ElementInfo + by NotNullableUserDataProperty(Key.create("ELEMENT_INFO"), ElementInfo(false, false)) + } + + override fun getProjectDescriptor() = LightCodeInsightFixtureTestCase.JAVA_LATEST + + val fixture: JavaCodeInsightTestFixture get() = myFixture + + protected override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase() + + protected fun doTest(path: String) { + val mainFile = File(path) + val afterFile = File("$path.after") + val conflictFile = File("$path.messages") + + fixture.setTestDataPath("${JetTestUtils.getHomeDirectory()}/${mainFile.getParent()}") + + val mainFileName = mainFile.getName() + val file = fixture.configureByFile(mainFileName) as JetFile + + val addKotlinRuntime = InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// WITH_RUNTIME") != null + if (addKotlinRuntime) { + ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk()) + } + + try { + for ((element, info) in file.findElementsByCommentPrefix("// INFO: ")) { + val parsedInfo = JsonParser().parse(info).getAsJsonObject() + element.elementInfo = ElementInfo(parsedInfo["checked"]?.getAsBoolean() ?: false, + parsedInfo["toAbstract"]?.getAsBoolean() ?: false) + } + val targetClassName = InTextDirectivesUtils.findStringWithPrefixes(file.getText(), "// TARGET_CLASS: ") + + val helper = object: KotlinPullUpHandler.TestHelper { + override fun adjustMembers(members: List): List { + members.forEach { + val info = it.getMember().elementInfo + it.setChecked(info.checked) + it.setToAbstract(info.toAbstract) + } + return members.filter { it.isChecked() } + } + + override fun chooseSuperClass(superClasses: List): JetClass { + if (targetClassName != null) { + return superClasses.single { it.getFqName()?.asString() ?: it.getName() == targetClassName } + } + return superClasses.first() + } + } + KotlinPullUpHandler().invoke(getProject(), getEditor(), file) { + if (it == KotlinPullUpHandler.PULLUP_TEST_HELPER_KEY) helper else null + } + + assert(!conflictFile.exists()) { "Conflict file $conflictFile should not exist" } + JetTestUtils.assertEqualsToFile(afterFile, file.getText()!!) + } + catch(e: Exception) { + val message = when (e) { + is BaseRefactoringProcessor.ConflictsInTestsException -> e.getMessages().sort().joinToString("\n") + is CommonRefactoringUtil.RefactoringErrorHintException -> e.getMessage()!! + else -> throw e + } + JetTestUtils.assertEqualsToFile(conflictFile, message) + } + finally { + if (addKotlinRuntime) { + ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk()) + } + } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/PullUpTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/PullUpTestGenerated.java new file mode 100644 index 00000000000..0a21f4248dd --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/pullUp/PullUpTestGenerated.java @@ -0,0 +1,115 @@ +/* + * Copyright 2010-2015 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.pullUp; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.JetTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/testData/refactoring/pullUp") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class PullUpTestGenerated extends AbstractPullUpTest { + public void testAllFilesPresentInPullUp() throws Exception { + JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/pullUp"), Pattern.compile("^(.+)\\.kt$")); + } + + @TestMetadata("fromClassToClass.kt") + public void testFromClassToClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToClass.kt"); + doTest(fileName); + } + + @TestMetadata("fromClassToClassMakeAbstract.kt") + public void testFromClassToClassMakeAbstract() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToClassMakeAbstract.kt"); + doTest(fileName); + } + + @TestMetadata("fromClassToClassWithGenerics.kt") + public void testFromClassToClassWithGenerics() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToClassWithGenerics.kt"); + doTest(fileName); + } + + @TestMetadata("fromClassToInterface.kt") + public void testFromClassToInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToInterface.kt"); + doTest(fileName); + } + + @TestMetadata("fromClassToInterfaceMakeAbstract.kt") + public void testFromClassToInterfaceMakeAbstract() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/fromClassToInterfaceMakeAbstract.kt"); + doTest(fileName); + } + + @TestMetadata("implicitCompanionUsages.kt") + public void testImplicitCompanionUsages() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/implicitCompanionUsages.kt"); + doTest(fileName); + } + + @TestMetadata("noCaret.kt") + public void testNoCaret() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noCaret.kt"); + doTest(fileName); + } + + @TestMetadata("noSuperClass.kt") + public void testNoSuperClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/noSuperClass.kt"); + doTest(fileName); + } + + @TestMetadata("outsideOfClass.kt") + public void testOutsideOfClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/outsideOfClass.kt"); + doTest(fileName); + } + + @TestMetadata("publicToInterface.kt") + public void testPublicToInterface() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/publicToInterface.kt"); + doTest(fileName); + } + + @TestMetadata("superToThis.kt") + public void testSuperToThis() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/superToThis.kt"); + doTest(fileName); + } + + @TestMetadata("toIndirectSuperClass.kt") + public void testToIndirectSuperClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/toIndirectSuperClass.kt"); + doTest(fileName); + } + + @TestMetadata("usedPrivateToClass.kt") + public void testUsedPrivateToClass() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pullUp/usedPrivateToClass.kt"); + doTest(fileName); + } +}