Push Down: Initial Support

This commit is contained in:
Alexey Sedunov
2015-07-28 21:28:39 +03:00
parent 5168ccc159
commit a66ef47887
42 changed files with 1638 additions and 356 deletions
@@ -84,6 +84,7 @@ import org.jetbrains.kotlin.idea.refactoring.inline.AbstractInlineTest
import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractJetExtractionTest import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractJetExtractionTest
import org.jetbrains.kotlin.idea.refactoring.move.AbstractJetMoveTest import org.jetbrains.kotlin.idea.refactoring.move.AbstractJetMoveTest
import org.jetbrains.kotlin.idea.refactoring.pullUp.AbstractPullUpTest import org.jetbrains.kotlin.idea.refactoring.pullUp.AbstractPullUpTest
import org.jetbrains.kotlin.idea.refactoring.pushDown.AbstractPushDownTest
import org.jetbrains.kotlin.idea.refactoring.rename.AbstractRenameTest import org.jetbrains.kotlin.idea.refactoring.rename.AbstractRenameTest
import org.jetbrains.kotlin.idea.resolve.* import org.jetbrains.kotlin.idea.resolve.*
import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest import org.jetbrains.kotlin.idea.structureView.AbstractKotlinFileStructureTest
@@ -664,6 +665,10 @@ fun main(args: Array<String>) {
model("refactoring/pullUp/j2k", extension = "java", singleClass = true, testClassName = "J2K", testMethod = "doJavaTest") model("refactoring/pullUp/j2k", extension = "java", singleClass = true, testClassName = "J2K", testMethod = "doJavaTest")
} }
testClass(javaClass<AbstractPushDownTest>()) {
model("refactoring/pushDown", extension = "kt", singleClass = true)
}
testClass(javaClass<AbstractSelectExpressionForDebuggerTest>()) { testClass(javaClass<AbstractSelectExpressionForDebuggerTest>()) {
model("debugger/selectExpression", recursive = false) model("debugger/selectExpression", recursive = false)
model("debugger/selectExpression/disallowMethodCalls", testMethod = "doTestWoMethodCalls") model("debugger/selectExpression/disallowMethodCalls", testMethod = "doTestWoMethodCalls")
@@ -62,6 +62,7 @@ public fun ClassDescriptor.findCallableMemberBySignature(signature: CallableMemb
val descriptorKind = if (signature is FunctionDescriptor) DescriptorKindFilter.FUNCTIONS else DescriptorKindFilter.VARIABLES val descriptorKind = if (signature is FunctionDescriptor) DescriptorKindFilter.FUNCTIONS else DescriptorKindFilter.VARIABLES
return getDefaultType().getMemberScope() return getDefaultType().getMemberScope()
.getDescriptors(descriptorKind) .getDescriptors(descriptorKind)
.filterIsInstance<CallableMemberDescriptor>()
.firstOrNull { .firstOrNull {
it.getContainingDeclaration() == this it.getContainingDeclaration() == this
&& OverridingUtil.DEFAULT.isOverridableBy(it as CallableDescriptor, signature).getResult() == OVERRIDABLE && OverridingUtil.DEFAULT.isOverridableBy(it as CallableDescriptor, signature).getResult() == OVERRIDABLE
@@ -26,7 +26,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.search.searches.ClassInheritorsSearch import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.util.EmptyQuery import com.intellij.util.EmptyQuery
public fun HierarchySearchRequest<PsiElement>.searchInheritors(): Query<PsiClass> { public fun HierarchySearchRequest<*>.searchInheritors(): Query<PsiClass> {
val psiClass: PsiClass? = when (originalElement) { val psiClass: PsiClass? = when (originalElement) {
is JetClassOrObject -> LightClassUtil.getPsiClass(originalElement) is JetClassOrObject -> LightClassUtil.getPsiClass(originalElement)
is PsiClass -> originalElement is PsiClass -> originalElement
@@ -0,0 +1,103 @@
/*
* 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
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
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.RefactoringActionHandler
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.lang.ElementsHandler
import com.intellij.refactoring.util.CommonRefactoringUtil
import org.jetbrains.kotlin.idea.core.refactoring.canRefactor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import java.util.HashSet
public abstract class AbstractPullPushMembersHandler(
private val refactoringName: String,
private val helpId: String,
private val wrongPositionMessage: String
) : RefactoringActionHandler, ElementsHandler {
private fun reportWrongPosition(project: Project, editor: Editor?) {
val message = RefactoringBundle.getCannotRefactorMessage(wrongPositionMessage)
CommonRefactoringUtil.showErrorHint(project, editor, message, refactoringName, helpId)
}
protected fun reportWrongContext(project: Project, editor: Editor?) {
val message = RefactoringBundle.getCannotRefactorMessage(
RefactoringBundle.message("is.not.supported.in.the.current.context", refactoringName)
)
CommonRefactoringUtil.showErrorHint(project, editor, message, refactoringName, helpId)
}
protected abstract fun invoke(project: Project,
editor: Editor?,
classOrObject: JetClassOrObject?,
member: JetNamedDeclaration?,
dataContext: DataContext?)
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) {
val offset = editor.caretModel.offset
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
val target = (file.findElementAt(offset) ?: return).parentsWithSelf.firstOrNull {
it is JetClassOrObject || ((it is JetNamedFunction || it is JetProperty) && it.parent 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<out PsiElement>, 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<JetClassOrObject>() to element as JetNamedDeclaration?
is JetClassOrObject -> element to null
else -> {
reportWrongPosition(project, editor)
return
}
}
invoke(project, editor, classOrObject, member, dataContext)
}
override fun isEnabledOnElements(elements: Array<out PsiElement>): Boolean {
return elements.mapTo(HashSet<PsiElement>()) {
when (it) {
is JetNamedFunction, is JetProperty ->
(it.parent as? JetClassBody)?.parent as? JetClassOrObject
is JetClassOrObject -> it
else -> null
} ?: return false
}.size() == 1
}
}
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.Kotlin
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
import org.jetbrains.kotlin.idea.refactoring.pullUp.KotlinPullUpHandler import org.jetbrains.kotlin.idea.refactoring.pullUp.KotlinPullUpHandler
import org.jetbrains.kotlin.idea.refactoring.pushDown.KotlinPushDownHandler
import org.jetbrains.kotlin.idea.refactoring.safeDelete.* import org.jetbrains.kotlin.idea.refactoring.safeDelete.*
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
@@ -73,4 +74,6 @@ public class KotlinRefactoringSupportProvider : RefactoringSupportProvider() {
override fun getChangeSignatureHandler() = JetChangeSignatureHandler() override fun getChangeSignatureHandler() = JetChangeSignatureHandler()
override fun getPullUpHandler() = KotlinPullUpHandler() override fun getPullUpHandler() = KotlinPullUpHandler()
override fun getPushDownHandler() = KotlinPushDownHandler()
} }
@@ -16,37 +16,36 @@
package org.jetbrains.kotlin.idea.refactoring.pullUp package org.jetbrains.kotlin.idea.refactoring.pullUp
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNamedElement import com.intellij.psi.PsiNamedElement
import com.intellij.refactoring.HelpID import com.intellij.refactoring.HelpID
import com.intellij.refactoring.RefactoringActionHandler
import com.intellij.refactoring.RefactoringBundle import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.lang.ElementsHandler
import com.intellij.refactoring.util.CommonRefactoringUtil import com.intellij.refactoring.util.CommonRefactoringUtil
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.refactoring.canRefactor import org.jetbrains.kotlin.idea.core.refactoring.canRefactor
import org.jetbrains.kotlin.idea.refactoring.AbstractPullPushMembersHandler
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfoStorage import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfoStorage
import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedClassNameForRendering import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedClassNameForRendering
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.types.typeUtil.supertypes
import java.util.HashSet
public class KotlinPullUpHandler : RefactoringActionHandler, ElementsHandler { public class KotlinPullUpHandler : AbstractPullPushMembersHandler(
refactoringName = PULL_MEMBERS_UP,
helpId = HelpID.MEMBERS_PULL_UP,
wrongPositionMessage = RefactoringBundle.message("the.caret.should.be.positioned.inside.a.class.to.pull.members.from")
) {
companion object { companion object {
val PULLUP_TEST_HELPER_KEY = "PULLUP_TEST_HELPER_KEY" val PULL_UP_TEST_HELPER_KEY = "PULL_UP_TEST_HELPER_KEY"
} }
interface TestHelper { interface TestHelper {
@@ -54,20 +53,6 @@ public class KotlinPullUpHandler : RefactoringActionHandler, ElementsHandler {
fun chooseSuperClass(superClasses: List<PsiNamedElement>): PsiNamedElement fun chooseSuperClass(superClasses: List<PsiNamedElement>): PsiNamedElement
} }
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) { private fun reportNoSuperClasses(project: Project, editor: Editor?, classOrObject: JetClassOrObject) {
val message = RefactoringBundle.getCannotRefactorMessage( val message = RefactoringBundle.getCannotRefactorMessage(
RefactoringBundle.message("class.does.not.have.base.classes.interfaces.in.current.project", RefactoringBundle.message("class.does.not.have.base.classes.interfaces.in.current.project",
@@ -76,41 +61,7 @@ public class KotlinPullUpHandler : RefactoringActionHandler, ElementsHandler {
CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, HelpID.MEMBERS_PULL_UP) CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, HelpID.MEMBERS_PULL_UP)
} }
override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { override fun invoke(project: Project,
val offset = editor.caretModel.offset
editor.scrollingModel.scrollToCaret(ScrollType.MAKE_VISIBLE)
val target = (file.findElementAt(offset) ?: return).parentsWithSelf.firstOrNull {
it is JetClassOrObject || ((it is JetNamedFunction || it is JetProperty) && it.parent 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<out PsiElement>, 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<JetClassOrObject>() 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?, editor: Editor?,
classOrObject: JetClassOrObject?, classOrObject: JetClassOrObject?,
member: JetNamedDeclaration?, member: JetNamedDeclaration?,
@@ -148,7 +99,7 @@ public class KotlinPullUpHandler : RefactoringActionHandler, ElementsHandler {
val members = memberInfoStorage.getClassMemberInfos(classOrObject) val members = memberInfoStorage.getClassMemberInfos(classOrObject)
if (ApplicationManager.getApplication().isUnitTestMode) { if (ApplicationManager.getApplication().isUnitTestMode) {
val helper = dataContext?.getData(PULLUP_TEST_HELPER_KEY) as TestHelper val helper = dataContext?.getData(PULL_UP_TEST_HELPER_KEY) as TestHelper
val selectedMembers = helper.adjustMembers(members) val selectedMembers = helper.adjustMembers(members)
val targetClass = helper.chooseSuperClass(superClasses) val targetClass = helper.chooseSuperClass(superClasses)
checkConflicts(project, classOrObject, targetClass, selectedMembers) { checkConflicts(project, classOrObject, targetClass, selectedMembers) {
@@ -162,17 +113,6 @@ public class KotlinPullUpHandler : RefactoringActionHandler, ElementsHandler {
KotlinPullUpDialog(project, classOrObject, superClasses, memberInfoStorage).show() KotlinPullUpDialog(project, classOrObject, superClasses, memberInfoStorage).show()
} }
} }
override fun isEnabledOnElements(elements: Array<out PsiElement>): Boolean {
return elements.mapTo(HashSet<PsiElement>()) {
when (it) {
is JetNamedFunction, is JetProperty ->
(it.parent as? JetClassBody)?.parent as? JetClassOrObject
is JetClassOrObject -> it
else -> null
} ?: return false
}.size() == 1
}
} }
val PULL_MEMBERS_UP = "Pull Members Up" val PULL_MEMBERS_UP = "Pull Members Up"
@@ -16,7 +16,6 @@
package org.jetbrains.kotlin.idea.refactoring.pullUp package org.jetbrains.kotlin.idea.refactoring.pullUp
import com.intellij.openapi.util.Key
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.codeStyle.JavaCodeStyleManager import com.intellij.psi.codeStyle.JavaCodeStyleManager
import com.intellij.psi.impl.light.LightField import com.intellij.psi.impl.light.LightField
@@ -31,30 +30,25 @@ import org.jetbrains.kotlin.asJava.getRepresentativeLightMethod
import org.jetbrains.kotlin.asJava.namedUnwrappedElement import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightClass import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.asJava.unwrapped import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.JetLanguage import org.jetbrains.kotlin.idea.JetLanguage
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.core.refactoring.createJavaField import org.jetbrains.kotlin.idea.core.refactoring.createJavaField
import org.jetbrains.kotlin.idea.core.refactoring.createJavaMethod import org.jetbrains.kotlin.idea.core.refactoring.createJavaMethod
import org.jetbrains.kotlin.idea.imports.importableFqName
import org.jetbrains.kotlin.idea.refactoring.safeDelete.removeOverrideModifier 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.idea.util.psi.patternMatching.JetPsiUnifier import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiUnifier
import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.* import org.jetbrains.kotlin.psi.psiUtil.allChildren
import org.jetbrains.kotlin.psi.psiUtil.asAssignment
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.isUnit
import java.util.ArrayList import java.util.ArrayList
import java.util.Comparator import java.util.Comparator
import java.util.LinkedHashMap import java.util.LinkedHashMap
@@ -64,12 +58,6 @@ class KotlinPullUpHelper(
private val javaData: PullUpData, private val javaData: PullUpData,
private val data: KotlinPullUpData private val data: KotlinPullUpData
) : PullUpHelper<MemberInfoBase<PsiMember>> { ) : PullUpHelper<MemberInfoBase<PsiMember>> {
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 JetExpression.isMovable(): Boolean { private fun JetExpression.isMovable(): Boolean {
return accept( return accept(
object: JetVisitor<Boolean, Nothing?>() { object: JetVisitor<Boolean, Nothing?>() {
@@ -334,184 +322,13 @@ class KotlinPullUpHelper(
return clashingSuperDescriptor.source.getPsi() as? JetCallableDeclaration return clashingSuperDescriptor.source.getPsi() as? JetCallableDeclaration
} }
private fun makeAbstract(sourceMember: JetCallableDeclaration, targetMember: JetCallableDeclaration) {
if (!data.isInterfaceTarget) {
targetMember.addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
}
if (sourceMember.typeReference == null) {
var type = (data.memberDescriptors[sourceMember] as CallableMemberDescriptor).returnType
if (type == null || type.isError) {
type = KotlinBuiltIns.getInstance().nullableAnyType
}
else {
type = data.sourceToTargetClassSubstitutor.substitute(type, Variance.INVARIANT)
?: KotlinBuiltIns.getInstance().nullableAnyType
}
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.accessors
targetMember.equalsToken ?: targetMember.delegate ?: accessors.firstOrNull()
}
is JetNamedFunction -> {
targetMember as JetNamedFunction
targetMember.equalsToken ?: targetMember.bodyExpression
}
else -> null
}
if (deleteFrom != null) {
targetMember.deleteChildRange(deleteFrom, targetMember.lastChild)
}
}
private fun markElements(member: JetNamedDeclaration): List<JetElement> {
val affectedElements = ArrayList<JetElement>()
member.accept(
object: JetVisitorVoid() {
private fun visitSuperOrThis(expression: JetInstanceExpressionWithLabel) {
val referenceTarget = data.sourceClassContext[BindingContext.REFERENCE_TARGET, expression.instanceReference]
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.extensionReceiver
}
if (!receiver.exists()) {
receiver = resolvedCall.dispatchReceiver
}
if (!receiver.exists()) return
val implicitThis = receiver.type.constructor.declarationDescriptor 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.name.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<JetElement>) {
markedElements.forEach {
it.newFqName = null
it.newTypeText = null
it.replaceWithTargetThis = null
}
}
private fun moveSuperInterface(member: JetClass, substitutor: PsiSubstitutor) { private fun moveSuperInterface(member: JetClass, substitutor: PsiSubstitutor) {
val classDescriptor = data.memberDescriptors[member] as? ClassDescriptor ?: return val classDescriptor = data.memberDescriptors[member] as? ClassDescriptor ?: return
val currentSpecifier = val currentSpecifier = data.sourceClass.getDelegatorToSuperClassByDescriptor(classDescriptor, data.sourceClassContext) ?: return
data.sourceClass.getDelegationSpecifiers()
.filterIsInstance<JetDelegatorToSuperClass>()
.firstOrNull {
val referencedType = data.sourceClassContext[BindingContext.TYPE, it.typeReference]
referencedType?.constructor?.declarationDescriptor == classDescriptor
} ?: return
when (data.targetClass) { when (data.targetClass) {
is JetClass -> { is JetClass -> {
data.sourceClass.removeDelegationSpecifier(currentSpecifier) data.sourceClass.removeDelegationSpecifier(currentSpecifier)
addDelegatorToSuperClass(currentSpecifier, data.targetClass, data.targetClassDescriptor, data.sourceClassContext, data.sourceToTargetClassSubstitutor)
if (DescriptorUtils.isSubclass(data.targetClassDescriptor, classDescriptor)) return
val referencedType = data.sourceClassContext[BindingContext.TYPE, currentSpecifier.typeReference]!!
val typeInTargetClass = data.sourceToTargetClassSubstitutor.substitute(referencedType, Variance.INVARIANT)
if (!(typeInTargetClass != null && !typeInTargetClass.isError)) return
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(typeInTargetClass)
val newSpecifier = JetPsiFactory(member).createDelegatorToSuperClass(renderedType)
data.targetClass.addDelegationSpecifier(newSpecifier).addToShorteningWaitSet()
} }
is PsiClass -> { is PsiClass -> {
@@ -601,7 +418,7 @@ class KotlinPullUpHelper(
return return
} }
val markedElements = markElements(member) val markedElements = markElements(member, data.sourceClassContext, data.sourceClassDescriptor, data.targetClassDescriptor)
val memberCopy = member.copy() as JetNamedDeclaration val memberCopy = member.copy() as JetNamedDeclaration
fun moveClassOrObject(member: JetClassOrObject, memberCopy: JetClassOrObject): JetClassOrObject { fun moveClassOrObject(member: JetClassOrObject, memberCopy: JetClassOrObject): JetClassOrObject {
@@ -629,7 +446,7 @@ class KotlinPullUpHelper(
} }
if (toAbstract) { if (toAbstract) {
if (!originalIsAbstract) { if (!originalIsAbstract) {
makeAbstract(member, memberCopy) makeAbstract(memberCopy, data.memberDescriptors[member] as CallableMemberDescriptor, data.sourceToTargetClassSubstitutor, data.targetClass)
} }
movedMember = doAddCallableMember(memberCopy, clashingSuper, data.targetClass) movedMember = doAddCallableMember(memberCopy, clashingSuper, data.targetClass)
@@ -661,7 +478,7 @@ class KotlinPullUpHelper(
else -> return else -> return
} }
processMarkedElements(movedMember) applyMarking(movedMember, data.sourceToTargetClassSubstitutor, data.targetClassDescriptor)
addMovedMember(movedMember) addMovedMember(movedMember)
} }
finally { finally {
@@ -0,0 +1,167 @@
/*
* 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 org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.imports.importableFqName
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.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.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.types.TypeSubstitutor
import org.jetbrains.kotlin.types.Variance
import java.util.*
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: ((TypeSubstitutor) -> String?)? by CopyableUserDataProperty(Key.create("NEW_TYPE_TEXT"))
fun markElements(
declaration: JetNamedDeclaration,
context: BindingContext,
sourceClassDescriptor: ClassDescriptor, targetClassDescriptor: ClassDescriptor?
): List<JetElement> {
val affectedElements = ArrayList<JetElement>()
declaration.accept(
object : JetVisitorVoid() {
private fun visitSuperOrThis(expression: JetInstanceExpressionWithLabel) {
if (targetClassDescriptor == null) return
val referenceTarget = context[BindingContext.REFERENCE_TARGET, expression.instanceReference]
if (referenceTarget == 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(context) ?: return
var receiver = resolvedCall.getExplicitReceiverValue()
if (!receiver.exists()) {
receiver = resolvedCall.extensionReceiver
}
if (!receiver.exists()) {
receiver = resolvedCall.dispatchReceiver
}
if (!receiver.exists()) return
val implicitThis = receiver.type.constructor.declarationDescriptor as? ClassDescriptor ?: return
if (implicitThis.isCompanionObject
&& DescriptorUtils.isAncestor(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 = context[BindingContext.TYPE, typeReference] ?: return
typeReference.newTypeText = f@ { substitutor ->
substitutor.substitute(oldType, Variance.INVARIANT)?.let { IdeDescriptorRenderers.SOURCE_CODE.renderType(it) }
}
affectedElements.add(typeReference)
}
}
)
return affectedElements
}
fun applyMarking(
declaration: JetNamedDeclaration,
substitutor: TypeSubstitutor, targetClassDescriptor: ClassDescriptor
) {
val psiFactory = JetPsiFactory(declaration)
val targetThis = psiFactory.createExpression("this@${targetClassDescriptor.name.asString()}")
val shorteningOptionsForThis = ShortenReferences.Options(removeThisLabels = true, removeThis = true)
declaration.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) {
this.visitSuperOrThis(expression)
}
override fun visitSuperExpression(expression: JetSuperExpression) {
this.visitSuperOrThis(expression)
}
override fun visitTypeReference(typeReference: JetTypeReference) {
typeReference.newTypeText?.let f@ {
typeReference.newTypeText = null
val newTypeText = it(substitutor) ?: return@f
(typeReference.replace(psiFactory.createType(newTypeText)) as JetElement).addToShorteningWaitSet()
}
}
}
)
}
fun clearMarking(markedElements: List<JetElement>) {
markedElements.forEach {
it.newFqName = null
it.newTypeText = null
it.replaceWithTargetThis = null
}
}
@@ -17,10 +17,20 @@
package org.jetbrains.kotlin.idea.refactoring.pullUp package org.jetbrains.kotlin.idea.refactoring.pullUp
import com.intellij.psi.PsiClass import com.intellij.psi.PsiClass
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.core.replaced
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.lexer.JetModifierKeywordToken import org.jetbrains.kotlin.lexer.JetModifierKeywordToken
import org.jetbrains.kotlin.lexer.JetTokens import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.types.typeUtil.isUnit
fun JetProperty.mustBeAbstractInInterface() = fun JetProperty.mustBeAbstractInInterface() =
hasInitializer() || hasDelegate() || (!hasInitializer() && !hasDelegate() && accessors.isEmpty()) hasInitializer() || hasDelegate() || (!hasInitializer() && !hasDelegate() && accessors.isEmpty())
@@ -75,3 +85,75 @@ fun JetClass.makeAbstract() {
addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD) addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
} }
} }
fun JetClassOrObject.getDelegatorToSuperClassByDescriptor(
descriptor: ClassDescriptor,
context: BindingContext
): JetDelegatorToSuperClass? {
return getDelegationSpecifiers()
.filterIsInstance<JetDelegatorToSuperClass>()
.firstOrNull {
val referencedType = context[BindingContext.TYPE, it.typeReference]
referencedType?.constructor?.declarationDescriptor == descriptor
}
}
fun makeAbstract(member: JetCallableDeclaration,
originalMemberDescriptor: CallableMemberDescriptor,
substitutor: TypeSubstitutor,
targetClass: JetClass) {
if (!targetClass.isInterface()) {
member.addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
}
if (member.typeReference == null) {
var type = originalMemberDescriptor.returnType
if (type == null || type.isError) {
type = KotlinBuiltIns.getInstance().nullableAnyType
}
else {
type = substitutor.substitute(type, Variance.INVARIANT) ?: KotlinBuiltIns.getInstance().nullableAnyType
}
if (member is JetProperty || !type.isUnit()) {
val typeRef = JetPsiFactory(targetClass).createType(IdeDescriptorRenderers.SOURCE_CODE.renderType(type))
member.setTypeReference(typeRef)
}
}
val deleteFrom = when (member) {
is JetProperty -> {
member.equalsToken ?: member.delegate ?: member.accessors.firstOrNull()
}
is JetNamedFunction -> {
member.equalsToken ?: member.bodyExpression
}
else -> null
}
if (deleteFrom != null) {
member.deleteChildRange(deleteFrom, member.lastChild)
}
}
fun addDelegatorToSuperClass(
delegator: JetDelegatorToSuperClass,
targetClass: JetClassOrObject,
targetClassDescriptor: ClassDescriptor,
context: BindingContext,
substitutor: TypeSubstitutor
) {
val referencedType = context[BindingContext.TYPE, delegator.typeReference]!!
val referencedClass = referencedType.constructor.declarationDescriptor as? ClassDescriptor ?: return
if (targetClassDescriptor == referencedClass || DescriptorUtils.isDirectSubclass(targetClassDescriptor, referencedClass)) return
val typeInTargetClass = substitutor.substitute(referencedType, Variance.INVARIANT)
if (!(typeInTargetClass != null && !typeInTargetClass.isError)) return
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(typeInTargetClass)
val newSpecifier = JetPsiFactory(targetClass).createDelegatorToSuperClass(renderedType)
targetClass.addDelegationSpecifier(newSpecifier).addToShorteningWaitSet()
}
@@ -0,0 +1,104 @@
/*
* 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.pushDown
import com.intellij.openapi.project.Project
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.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.qualifiedClassNameForRendering
import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.JetNamedFunction
import org.jetbrains.kotlin.psi.JetProperty
import java.awt.BorderLayout
import java.awt.GridBagConstraints
import java.awt.GridBagLayout
import java.awt.Insets
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JPanel
public class KotlinPushDownDialog(
project: Project,
private val memberInfos: List<KotlinMemberInfo>,
private val sourceClass: JetClass
) : RefactoringDialog(project, true) {
init {
title = PUSH_MEMBERS_DOWN
init()
}
private var memberInfoModel: AbstractMemberInfoModel<JetNamedDeclaration, KotlinMemberInfo>? = null
public val selectedMemberInfos: List<KotlinMemberInfo>
get() = memberInfos.filter { it.isChecked && memberInfoModel?.isMemberEnabled(it) ?: false }
override fun getDimensionServiceKey() = "#" + javaClass.name
override fun createNorthPanel(): JComponent? {
val gbConstraints = GridBagConstraints()
val panel = JPanel(GridBagLayout())
gbConstraints.insets = Insets(4, 0, 10, 8)
gbConstraints.weighty = 1.0
gbConstraints.weightx = 1.0
gbConstraints.gridy = 0
gbConstraints.gridwidth = GridBagConstraints.REMAINDER
gbConstraints.fill = GridBagConstraints.BOTH
gbConstraints.anchor = GridBagConstraints.WEST
panel.add(JLabel(RefactoringBundle.message("push.members.from.0.down.label",
sourceClass.qualifiedClassNameForRendering())), gbConstraints)
return panel
}
override fun createCenterPanel(): JComponent? {
val panel = JPanel(BorderLayout())
val memberSelectionPanel = KotlinMemberSelectionPanel(
RefactoringBundle.message("members.to.be.pushed.down.panel.title"),
memberInfos,
RefactoringBundle.message("keep.abstract.column.header"))
panel.add(memberSelectionPanel, BorderLayout.CENTER)
memberInfoModel = object : AbstractMemberInfoModel<JetNamedDeclaration, KotlinMemberInfo>() {
override fun isFixedAbstract(member: KotlinMemberInfo?) = null
override fun isAbstractEnabled(memberInfo: KotlinMemberInfo): Boolean {
val member = memberInfo.member
return member is JetNamedFunction || member is JetProperty
}
}
memberInfoModel!!.memberInfoChanged(MemberInfoChange(memberInfos))
memberSelectionPanel.table.memberInfoModel = memberInfoModel
memberSelectionPanel.table.addMemberInfoChangeListener(memberInfoModel)
return panel
}
override fun doAction() {
if (!isOKActionEnabled()) return
JavaRefactoringSettings.getInstance().PUSH_DOWN_PREVIEW_USAGES = isPreviewUsages
invokeRefactoring(KotlinPushDownProcessor(getProject(), sourceClass, selectedMemberInfos))
}
}
@@ -0,0 +1,89 @@
/*
* 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.pushDown
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiManager
import com.intellij.refactoring.HelpID
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.refactoring.util.RefactoringUIUtil
import org.jetbrains.kotlin.idea.refactoring.AbstractPullPushMembersHandler
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfoStorage
import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedClassNameForRendering
import org.jetbrains.kotlin.idea.refactoring.pullUp.PULL_MEMBERS_UP
import org.jetbrains.kotlin.psi.JetClass
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.JetNamedDeclaration
import org.jetbrains.kotlin.psi.JetObjectDeclaration
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.isInheritable
val PUSH_MEMBERS_DOWN = "Push Members Down"
public class KotlinPushDownHandler : AbstractPullPushMembersHandler(
refactoringName = PUSH_MEMBERS_DOWN,
helpId = HelpID.MEMBERS_PUSH_DOWN,
wrongPositionMessage = RefactoringBundle.message("the.caret.should.be.positioned.inside.a.class.to.push.members.from")
) {
companion object {
val PUSH_DOWN_TEST_HELPER_KEY = "PUSH_DOWN_TEST_HELPER_KEY"
}
interface TestHelper {
fun adjustMembers(members: List<KotlinMemberInfo>): List<KotlinMemberInfo>
}
private fun reportFinalClassOrObject(project: Project, editor: Editor?, classOrObject: JetClassOrObject) {
val message = RefactoringBundle.getCannotRefactorMessage(
"${RefactoringUIUtil.getDescription(classOrObject, false)} is final".capitalize()
)
CommonRefactoringUtil.showErrorHint(project, editor, message, PULL_MEMBERS_UP, HelpID.MEMBERS_PULL_UP)
}
override fun invoke(project: Project,
editor: Editor?,
classOrObject: JetClassOrObject?,
member: JetNamedDeclaration?,
dataContext: DataContext?) {
if (classOrObject == null) {
reportWrongContext(project, editor)
return
}
if (!(classOrObject is JetClass && classOrObject.isInheritable())) {
reportFinalClassOrObject(project, editor, classOrObject)
return
}
val members = KotlinMemberInfoStorage(classOrObject).getClassMemberInfos(classOrObject)
if (ApplicationManager.getApplication().isUnitTestMode) {
val helper = dataContext?.getData(PUSH_DOWN_TEST_HELPER_KEY) as TestHelper
val selectedMembers = helper.adjustMembers(members)
KotlinPushDownProcessor(project, classOrObject, selectedMembers).run()
}
else {
val manager = PsiManager.getInstance(project)
members.filter { manager.areElementsEquivalent(it.member, member) }.forEach { it.isChecked = true }
KotlinPushDownDialog(project, members, classOrObject).show()
}
}
}
@@ -0,0 +1,202 @@
/*
* 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.pushDown
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.listeners.RefactoringEventData
import com.intellij.usageView.UsageInfo
import com.intellij.usageView.UsageViewBundle
import com.intellij.usageView.UsageViewDescriptor
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.shorten.addToShorteningWaitSet
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.refactoring.pullUp.*
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
import org.jetbrains.kotlin.lexer.JetTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.types.TypeSubstitutor
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
import org.jetbrains.kotlin.util.findCallableMemberBySignature
import org.jetbrains.kotlin.utils.keysToMap
import java.util.ArrayList
public class KotlinPushDownProcessor(
project: Project,
private val sourceClass: JetClass,
private val membersToMove: List<KotlinMemberInfo>
) : BaseRefactoringProcessor(project) {
private val resolutionFacade = sourceClass.getResolutionFacade()
private val sourceClassContext = resolutionFacade.analyzeFullyAndGetResult(listOf(sourceClass)).bindingContext
private val sourceClassDescriptor = sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, sourceClass] as ClassDescriptor
private val memberDescriptors = membersToMove
.map { it.member }
.keysToMap { sourceClassContext[BindingContext.DECLARATION_TO_DESCRIPTOR, it]!! }
inner class UsageViewDescriptorImpl : UsageViewDescriptor {
override fun getProcessedElementsHeader() = RefactoringBundle.message("push.down.members.elements.header")
override fun getElements() = arrayOf(sourceClass)
override fun getCodeReferencesText(usagesCount: Int, filesCount: Int) =
RefactoringBundle.message("classes.to.push.down.members.to", UsageViewBundle.getReferencesString(usagesCount, filesCount))
override fun getCommentReferencesText(usagesCount: Int, filesCount: Int) = null
}
class SubclassUsage(element: PsiElement) : UsageInfo(element)
override fun getCommandName() = PUSH_MEMBERS_DOWN
override fun createUsageViewDescriptor(usages: Array<out UsageInfo>) = UsageViewDescriptorImpl()
override fun getBeforeData() = RefactoringEventData().apply {
addElement(sourceClass)
addElements(membersToMove.map { it.member }.toTypedArray())
}
override fun getAfterData(usages: Array<out UsageInfo>) = RefactoringEventData().apply {
addElements(usages.map { it.element as? JetClassOrObject }.filterNotNull())
}
override fun findUsages(): Array<out UsageInfo> {
return HierarchySearchRequest(sourceClass, sourceClass.useScope, false)
.searchInheritors()
.map { it.unwrapped }
.filterNotNull()
.map { SubclassUsage(it) }
.toTypedArray()
}
private fun pushDownToClass(targetClass: JetClassOrObject) {
val targetClassDescriptor = resolutionFacade.resolveToDescriptor(targetClass) as ClassDescriptor
val substitutor = getTypeSubstitutor(sourceClassDescriptor.defaultType, targetClassDescriptor.defaultType)
?: TypeSubstitutor.EMPTY
members@ for (memberInfo in membersToMove) {
val member = memberInfo.member
val memberDescriptor = memberDescriptors[member] ?: continue
val movedMember = when (member) {
is JetProperty, is JetNamedFunction -> {
memberDescriptor as CallableMemberDescriptor
val targetMemberDescriptor = memberDescriptor.substitute(substitutor)?.let {
targetClassDescriptor.findCallableMemberBySignature(it as CallableMemberDescriptor)
}
val targetMember = targetMemberDescriptor?.source?.getPsi() as? JetCallableDeclaration
targetMember?.apply {
if (memberDescriptor.modality != Modality.ABSTRACT && memberInfo.isToAbstract) {
addModifierWithSpace(JetTokens.OVERRIDE_KEYWORD)
}
else if (memberDescriptor.overriddenDescriptors.isEmpty()) {
removeModifier(JetTokens.OVERRIDE_KEYWORD)
}
else {
addModifierWithSpace(JetTokens.OVERRIDE_KEYWORD)
}
} ?: addMemberToTarget(member, targetClass).apply {
if (sourceClassDescriptor.kind == ClassKind.INTERFACE) {
if (targetClassDescriptor.kind != ClassKind.INTERFACE && memberDescriptor.modality == Modality.ABSTRACT) {
addModifierWithSpace(JetTokens.ABSTRACT_KEYWORD)
}
}
if (memberDescriptor.modality != Modality.ABSTRACT && memberInfo.isToAbstract) {
if (hasModifier(JetTokens.PRIVATE_KEYWORD)) {
addModifierWithSpace(JetTokens.PROTECTED_KEYWORD)
}
addModifierWithSpace(JetTokens.OVERRIDE_KEYWORD)
}
}
}
is JetClassOrObject -> {
if (memberInfo.overrides != null) {
sourceClass.getDelegatorToSuperClassByDescriptor(memberDescriptor as ClassDescriptor, sourceClassContext)?.let {
addDelegatorToSuperClass(it, targetClass, targetClassDescriptor, sourceClassContext, substitutor)
}
continue@members
}
else {
addMemberToTarget(member, targetClass)
}
}
else -> continue@members
}
applyMarking(movedMember, substitutor, targetClassDescriptor)
}
}
private fun removeOriginalMembers() {
for (memberInfo in membersToMove) {
val member = memberInfo.member
val memberDescriptor = memberDescriptors[member] ?: continue
when (member) {
is JetProperty, is JetNamedFunction -> {
member as JetCallableDeclaration
memberDescriptor as CallableMemberDescriptor
if (memberDescriptor.modality != Modality.ABSTRACT && memberInfo.isToAbstract) {
if (member.hasModifier(JetTokens.PRIVATE_KEYWORD)) {
member.addModifierWithSpace(JetTokens.PROTECTED_KEYWORD)
}
makeAbstract(member, memberDescriptor, TypeSubstitutor.EMPTY, sourceClass)
member.typeReference?.addToShorteningWaitSet()
}
else {
member.delete()
}
}
is JetClassOrObject -> {
if (memberInfo.overrides != null) {
sourceClass.getDelegatorToSuperClassByDescriptor(memberDescriptor as ClassDescriptor, sourceClassContext)?.let {
sourceClass.removeDelegationSpecifier(it)
}
}
else {
member.delete()
}
}
}
}
}
override fun performRefactoring(usages: Array<out UsageInfo>) {
val markedElements = ArrayList<JetElement>()
try {
membersToMove.forEach { markedElements += markElements(it.member, sourceClassContext, sourceClassDescriptor, null) }
usages.forEach { (it.element as? JetClassOrObject)?.let { pushDownToClass(it) } }
removeOriginalMembers()
}
finally {
clearMarking(markedElements)
}
}
}
+8
View File
@@ -0,0 +1,8 @@
class <caret>A {
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
fun foo() {
}
}
@@ -0,0 +1,2 @@
Cannot perform refactoring.
Class <b><code>A</code></b> is final
@@ -0,0 +1,12 @@
class X {
open class <caret>A {
// INFO: {"checked": "true"}
fun foo(): Int = bar() + A.bar() + X.A.bar()
companion object {
fun bar() = 1
}
}
}
class B : X.A
@@ -0,0 +1,13 @@
class X {
open class A {
companion object {
fun bar() = 1
}
}
}
class B : X.A {
// INFO: {"checked": "true"}
fun foo(): Int = X.A.bar() + X.A.bar() + X.A.bar()
}
+12
View File
@@ -0,0 +1,12 @@
open class <caret>A {
// INFO: {"checked": "true", "toAbstract": "true"}
private fun foo() {
}
}
class B : A {
fun foo() {
}
}
+10
View File
@@ -0,0 +1,10 @@
open class A {
// INFO: {"checked": "true", "toAbstract": "true"}
protected abstract fun foo()
}
class B : A {
override fun foo() {
}
}
+4
View File
@@ -0,0 +1,4 @@
class A {
val x: Int = 1
}
@@ -0,0 +1,2 @@
Cannot perform refactoring.
Caret should be positioned inside a class to push members from
@@ -0,0 +1,8 @@
object <caret>O {
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
fun foo() {
}
}
@@ -0,0 +1,2 @@
Cannot perform refactoring.
Object <b><code>O</code></b> is final
+3
View File
@@ -0,0 +1,3 @@
fun <caret>test() {
}
@@ -0,0 +1,2 @@
Cannot perform refactoring.
Caret should be positioned inside a class to push members from
+34
View File
@@ -0,0 +1,34 @@
abstract class <caret>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() {
}
class C : A() {
override val t = 1
override fun bar(s: String) = s.length()
}
@@ -0,0 +1,55 @@
abstract 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 {
}
}
class C : A() {
val t = 1
// INFO: {"checked": "true"}
val x = 1
// INFO: {"checked": "true"}
val y: Int get() = 2
// INFO: {"checked": "true"}
val z: Int by lazy { 3 }
fun bar(s: String) = s.length()
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
inner class X {
}
// INFO: {"checked": "true"}
class Y {
}
}
@@ -0,0 +1,34 @@
abstract class <caret>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 {
}
}
abstract class B : A() {
}
class C : A() {
override val t = 1
override fun bar(s: String) = s.length()
}
@@ -0,0 +1,64 @@
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 fun foo(n: Int): Boolean
}
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"}
abstract val t: Int
// INFO: {"checked": "true", "toAbstract": "true"}
override 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 {
}
}
class C : A() {
val t = 1
// 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 }
fun bar(s: String) = s.length()
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true", "toAbstract": "true"}
inner class X {
}
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
@@ -0,0 +1,29 @@
interface Z<T>
open class <caret>A<T> {
// INFO: {"checked": "true"}
val t1: T
// INFO: {"checked": "true"}
val t2: Z<T>
// INFO: {"checked": "true"}
fun foo<S>(t1: T, t2: Z<T>, s1: S, s2: Z<S>): Boolean = true
// INFO: {"checked": "true"}
inner class X : Z<T> {
}
// INFO: {"checked": "true"}
class Y<U> : Z<U> {
}
}
class B<S> : A<Z<S>>() {
}
class C<K> : A<B<K>>() {
}
@@ -0,0 +1,47 @@
interface Z<T>
open class A<T> {
}
class B<S> : A<Z<S>>() {
// INFO: {"checked": "true"}
val t1: Z<S>
// INFO: {"checked": "true"}
val t2: Z<Z<S>>
// INFO: {"checked": "true"}
fun foo<S>(t1: Z<S>, t2: Z<Z<S>>, s1: S, s2: Z<S>): Boolean = true
// INFO: {"checked": "true"}
inner class X : Z<Z<S>> {
}
// INFO: {"checked": "true"}
class Y<U> : Z<U> {
}
}
class C<K> : A<B<K>>() {
// INFO: {"checked": "true"}
val t1: B<K>
// INFO: {"checked": "true"}
val t2: Z<B<K>>
// INFO: {"checked": "true"}
fun foo<S>(t1: B<K>, t2: Z<B<K>>, s1: S, s2: Z<S>): Boolean = true
// INFO: {"checked": "true"}
inner class X : Z<B<K>> {
}
// INFO: {"checked": "true"}
class Y<U> : Z<U> {
}
}
@@ -0,0 +1,31 @@
interface <caret>I {
// INFO: {"checked": "true"}
val x: Int get() = 2
// INFO: {"checked": "true"}
val y: Int
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
fun bar(s: String)
// INFO: {"checked": "true"}
class Y {
}
}
abstract class A : I
class B : I {
override val y = 1
override fun bar(s: String) = s.length()
}
interface J : I
interface K : I {
override val y: Int get() = 1
override fun bar(s: String) = s.length()
}
@@ -0,0 +1,69 @@
interface I {
}
abstract class A : I {
// INFO: {"checked": "true"}
val x: Int get() = 2
// INFO: {"checked": "true"}
abstract val y: Int
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
abstract fun bar(s: String)
// INFO: {"checked": "true"}
class Y {
}
}
class B : I {
val y = 1
// INFO: {"checked": "true"}
val x: Int get() = 2
fun bar(s: String) = s.length()
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
class Y {
}
}
interface J : I {
// INFO: {"checked": "true"}
val x: Int get() = 2
// INFO: {"checked": "true"}
val y: Int
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
fun bar(s: String)
// INFO: {"checked": "true"}
class Y {
}
}
interface K : I {
val y: Int get() = 1
// INFO: {"checked": "true"}
val x: Int get() = 2
fun bar(s: String) = s.length()
// INFO: {"checked": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true"}
class Y {
}
}
@@ -0,0 +1,31 @@
interface <caret>I {
// INFO: {"checked": "true", "toAbstract": "true"}
val x: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
val y: Int
// INFO: {"checked": "true", "toAbstract": "true"}
fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true", "toAbstract": "true"}
fun bar(s: String)
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
abstract class A : I
class B : I {
override val y = 1
override fun bar(s: String) = s.length()
}
interface J : I
interface K : I {
override val y: Int get() = 1
override fun bar(s: String) = s.length()
}
@@ -0,0 +1,74 @@
interface I {
// INFO: {"checked": "true", "toAbstract": "true"}
val x: Int
// INFO: {"checked": "true", "toAbstract": "true"}
fun foo(n: Int): Boolean
}
abstract class A : I {
// INFO: {"checked": "true", "toAbstract": "true"}
override val x: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
abstract val y: Int
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true", "toAbstract": "true"}
abstract fun bar(s: String)
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
class B : I {
val y = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val x: Int get() = 2
fun bar(s: String) = s.length()
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
interface J : I {
// INFO: {"checked": "true", "toAbstract": "true"}
override val x: Int get() = 2
// INFO: {"checked": "true", "toAbstract": "true"}
val y: Int
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true", "toAbstract": "true"}
fun bar(s: String)
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
interface K : I {
val y: Int get() = 1
// INFO: {"checked": "true", "toAbstract": "true"}
override val x: Int get() = 2
fun bar(s: String) = s.length()
// INFO: {"checked": "true", "toAbstract": "true"}
override fun foo(n: Int): Boolean = n > 0
// INFO: {"checked": "true", "toAbstract": "true"}
class Y {
}
}
@@ -0,0 +1,12 @@
// INFO: {"checked": "true"}
interface X
// INFO: {"checked": "true"}
interface Y
// INFO: {"checked": "true"}
interface Z
open class <caret>A : X, Y, Z
class B : A(), Z
@@ -0,0 +1,12 @@
// INFO: {"checked": "true"}
interface X
// INFO: {"checked": "true"}
interface Y
// INFO: {"checked": "true"}
interface Z
open class A
class B : A(), Z, X, Y
@@ -0,0 +1,14 @@
interface I
// INFO: {"checked": "true"}
interface X<T>
// INFO: {"checked": "true"}
interface Y<T>
// INFO: {"checked": "true"}
interface Z<T>
open class <caret>A<T, U> : X<T>, Y<X<I>>, Z<Y<U>>
class B<S> : A<X<S>, Y<S>>(), Z<I>
@@ -0,0 +1,14 @@
interface I
// INFO: {"checked": "true"}
interface X<T>
// INFO: {"checked": "true"}
interface Y<T>
// INFO: {"checked": "true"}
interface Z<T>
open class A<T, U>
class B<S> : A<X<S>, Y<S>>(), Z<I>, X<X<S>>, Y<X<I>>
@@ -0,0 +1,110 @@
/*
* 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
import com.google.gson.JsonParser
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.classMembers.MemberInfoBase
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.JetTestUtils
import org.jetbrains.kotlin.test.util.findElementsByCommentPrefix
import java.io.File
public abstract class AbstractMemberPullPushTest : JetLightCodeInsightFixtureTestCase() {
private data class ElementInfo(val checked: Boolean, val toAbstract: Boolean)
companion object {
private var PsiElement.elementInfo: ElementInfo
by NotNullableUserDataProperty(Key.create("ELEMENT_INFO"), ElementInfo(false, false))
}
override fun getProjectDescriptor() = JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
val fixture: JavaCodeInsightTestFixture get() = myFixture
protected override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase()
protected fun doTest(path: String, action: (mainFile: PsiFile) -> Unit) {
val mainFile = File(path)
val afterFile = File("$path.after")
val conflictFile = File("$path.messages")
fixture.testDataPath = "${JetTestUtils.getHomeDirectory()}/${mainFile.getParent()}"
val mainFileName = mainFile.getName()
val mainFileBaseName = FileUtil.getNameWithoutExtension(mainFileName)
val extraFiles = mainFile.parentFile.listFiles { file, name ->
name != mainFileName && name.startsWith("$mainFileBaseName.") && (name.endsWith(".kt") || name.endsWith(".java"))
}
val extraFilesToPsi = extraFiles.toMap { fixture.configureByFile(it.getName()) }
val file = fixture.configureByFile(mainFileName)
val addKotlinRuntime = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// WITH_RUNTIME") != null
if (addKotlinRuntime) {
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
try {
for ((element, info) in file.findElementsByCommentPrefix("// INFO: ")) {
val parsedInfo = JsonParser().parse(info).asJsonObject
element.elementInfo = ElementInfo(parsedInfo["checked"]?.asBoolean ?: false,
parsedInfo["toAbstract"]?.asBoolean ?: false)
}
action(file)
assert(!conflictFile.exists()) { "Conflict file $conflictFile should not exist" }
JetTestUtils.assertEqualsToFile(afterFile, file.text!!)
for ((extraPsiFile, extraFile) in extraFilesToPsi) {
JetTestUtils.assertEqualsToFile(File("${extraFile.getPath()}.after"), extraPsiFile.text)
}
}
catch(e: Exception) {
val message = when (e) {
is BaseRefactoringProcessor.ConflictsInTestsException -> e.messages.sort().joinToString("\n")
is CommonRefactoringUtil.RefactoringErrorHintException -> e.getMessage()!!
else -> throw e
}
JetTestUtils.assertEqualsToFile(conflictFile, message)
}
finally {
if (addKotlinRuntime) {
ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
}
}
protected fun chooseMembers<T : MemberInfoBase<*>>(members: List<T>): List<T> {
members.forEach {
val info = it.member.elementInfo
it.isChecked = info.checked
it.isToAbstract = info.toAbstract
}
return members.filter { it.isChecked }
}
}
@@ -16,116 +16,29 @@
package org.jetbrains.kotlin.idea.refactoring.pullUp package org.jetbrains.kotlin.idea.refactoring.pullUp
import com.google.gson.JsonParser
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.* import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.refactoring.BaseRefactoringProcessor import com.intellij.refactoring.BaseRefactoringProcessor
import com.intellij.refactoring.classMembers.MemberInfoBase
import com.intellij.refactoring.memberPullUp.PullUpConflictsUtil import com.intellij.refactoring.memberPullUp.PullUpConflictsUtil
import com.intellij.refactoring.memberPullUp.PullUpProcessor import com.intellij.refactoring.memberPullUp.PullUpProcessor
import com.intellij.refactoring.util.CommonRefactoringUtil
import com.intellij.refactoring.util.DocCommentPolicy import com.intellij.refactoring.util.DocCommentPolicy
import com.intellij.refactoring.util.RefactoringHierarchyUtil import com.intellij.refactoring.util.RefactoringHierarchyUtil
import com.intellij.refactoring.util.classMembers.MemberInfoStorage import com.intellij.refactoring.util.classMembers.MemberInfoStorage
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.core.getPackage import org.jetbrains.kotlin.idea.core.getPackage
import org.jetbrains.kotlin.idea.refactoring.AbstractMemberPullPushTest
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedClassNameForRendering import org.jetbrains.kotlin.idea.refactoring.memberInfo.qualifiedClassNameForRendering
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.psi.NotNullableUserDataProperty
import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.JetTestUtils
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 PsiElement.elementInfo: ElementInfo
by NotNullableUserDataProperty(Key.create("ELEMENT_INFO"), ElementInfo(false, false))
}
override fun getProjectDescriptor() = JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
val fixture: JavaCodeInsightTestFixture get() = myFixture
protected override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase()
protected fun doTest(path: String, action: (mainFile: PsiFile) -> Unit) {
val mainFile = File(path)
val afterFile = File("$path.after")
val conflictFile = File("$path.messages")
fixture.testDataPath = "${JetTestUtils.getHomeDirectory()}/${mainFile.getParent()}"
val mainFileName = mainFile.getName()
val mainFileBaseName = FileUtil.getNameWithoutExtension(mainFileName)
val extraFiles = mainFile.parentFile.listFiles { file, name ->
name != mainFileName && name.startsWith("$mainFileBaseName.") && (name.endsWith(".kt") || name.endsWith(".java"))
}
val extraFilesToPsi = extraFiles.toMap { fixture.configureByFile(it.getName()) }
val file = fixture.configureByFile(mainFileName)
val addKotlinRuntime = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// WITH_RUNTIME") != null
if (addKotlinRuntime) {
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
try {
for ((element, info) in file.findElementsByCommentPrefix("// INFO: ")) {
val parsedInfo = JsonParser().parse(info).asJsonObject
element.elementInfo = ElementInfo(parsedInfo["checked"]?.asBoolean ?: false,
parsedInfo["toAbstract"]?.asBoolean ?: false)
}
action(file)
assert(!conflictFile.exists()) { "Conflict file $conflictFile should not exist" }
JetTestUtils.assertEqualsToFile(afterFile, file.text!!)
for ((extraPsiFile, extraFile) in extraFilesToPsi) {
JetTestUtils.assertEqualsToFile(File("${extraFile.getPath()}.after"), extraPsiFile.text)
}
}
catch(e: Exception) {
val message = when (e) {
is BaseRefactoringProcessor.ConflictsInTestsException -> e.messages.sort().joinToString("\n")
is CommonRefactoringUtil.RefactoringErrorHintException -> e.getMessage()!!
else -> throw e
}
JetTestUtils.assertEqualsToFile(conflictFile, message)
}
finally {
if (addKotlinRuntime) {
ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
}
}
public abstract class AbstractPullUpTest : AbstractMemberPullPushTest() {
private fun getTargetClassName(file: PsiFile) = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// TARGET_CLASS: ") private fun getTargetClassName(file: PsiFile) = InTextDirectivesUtils.findStringWithPrefixes(file.text, "// TARGET_CLASS: ")
private fun chooseMembers<T : MemberInfoBase<*>>(members: List<T>): List<T> {
members.forEach {
val info = it.member.elementInfo
it.isChecked = info.checked
it.isToAbstract = info.toAbstract
}
return members.filter { it.isChecked }
}
protected fun doKotlinTest(path: String) { protected fun doKotlinTest(path: String) {
doTest(path) { file -> doTest(path) { file ->
val targetClassName = getTargetClassName(file) val targetClassName = getTargetClassName(file)
val helper = object: KotlinPullUpHandler.TestHelper { val helper = object: KotlinPullUpHandler.TestHelper {
override fun adjustMembers(members: List<KotlinMemberInfo>): List<KotlinMemberInfo> { override fun adjustMembers(members: List<KotlinMemberInfo>) = chooseMembers(members)
return chooseMembers(members)
}
override fun chooseSuperClass(superClasses: List<PsiNamedElement>): PsiNamedElement { override fun chooseSuperClass(superClasses: List<PsiNamedElement>): PsiNamedElement {
if (targetClassName != null) { if (targetClassName != null) {
@@ -135,7 +48,7 @@ public abstract class AbstractPullUpTest : JetLightCodeInsightFixtureTestCase()
} }
} }
KotlinPullUpHandler().invoke(getProject(), getEditor(), file) { KotlinPullUpHandler().invoke(getProject(), getEditor(), file) {
if (it == KotlinPullUpHandler.PULLUP_TEST_HELPER_KEY) helper else null if (it == KotlinPullUpHandler.PULL_UP_TEST_HELPER_KEY) helper else null
} }
} }
} }
@@ -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.pushDown
import org.jetbrains.kotlin.idea.refactoring.AbstractMemberPullPushTest
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KotlinMemberInfo
public abstract class AbstractPushDownTest : AbstractMemberPullPushTest() {
protected fun doTest(path: String) {
doTest(path) { file ->
val helper = object: KotlinPushDownHandler.TestHelper {
override fun adjustMembers(members: List<KotlinMemberInfo>) = chooseMembers(members)
}
KotlinPushDownHandler().invoke(getProject(), getEditor(), file) {
if (it == KotlinPushDownHandler.PUSH_DOWN_TEST_HELPER_KEY) helper else null
}
}
}
}
@@ -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.pushDown;
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/pushDown")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class PushDownTestGenerated extends AbstractPushDownTest {
public void testAllFilesPresentInPushDown() throws Exception {
JetTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/refactoring/pushDown"), Pattern.compile("^(.+)\\.kt$"));
}
@TestMetadata("finalClass.kt")
public void testFinalClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/finalClass.kt");
doTest(fileName);
}
@TestMetadata("implicitCompanionUsages.kt")
public void testImplicitCompanionUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/implicitCompanionUsages.kt");
doTest(fileName);
}
@TestMetadata("liftPrivate.kt")
public void testLiftPrivate() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/liftPrivate.kt");
doTest(fileName);
}
@TestMetadata("noCaret.kt")
public void testNoCaret() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/noCaret.kt");
doTest(fileName);
}
@TestMetadata("objectDeclaration.kt")
public void testObjectDeclaration() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/objectDeclaration.kt");
doTest(fileName);
}
@TestMetadata("outsideOfClass.kt")
public void testOutsideOfClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/outsideOfClass.kt");
doTest(fileName);
}
@TestMetadata("pushClassMembers.kt")
public void testPushClassMembers() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushClassMembers.kt");
doTest(fileName);
}
@TestMetadata("pushClassMembersAndMakeAbstract.kt")
public void testPushClassMembersAndMakeAbstract() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushClassMembersAndMakeAbstract.kt");
doTest(fileName);
}
@TestMetadata("pushClassMembersWithGenerics.kt")
public void testPushClassMembersWithGenerics() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushClassMembersWithGenerics.kt");
doTest(fileName);
}
@TestMetadata("pushInterfaceMembers.kt")
public void testPushInterfaceMembers() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushInterfaceMembers.kt");
doTest(fileName);
}
@TestMetadata("pushInterfaceMembersAndMakeAbstract.kt")
public void testPushInterfaceMembersAndMakeAbstract() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushInterfaceMembersAndMakeAbstract.kt");
doTest(fileName);
}
@TestMetadata("pushSuperInterfaces.kt")
public void testPushSuperInterfaces() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushSuperInterfaces.kt");
doTest(fileName);
}
@TestMetadata("pushSuperInterfacesWithGenerics.kt")
public void testPushSuperInterfacesWithGenerics() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/pushDown/pushSuperInterfacesWithGenerics.kt");
doTest(fileName);
}
}