diff --git a/idea/resources-en/messages/KotlinBundle.properties b/idea/resources-en/messages/KotlinBundle.properties
index 566ce14db1b..714caa46638 100644
--- a/idea/resources-en/messages/KotlinBundle.properties
+++ b/idea/resources-en/messages/KotlinBundle.properties
@@ -829,6 +829,8 @@ label.text.receiver.type=Receiver &type:\u0020
label.text.target.file.name=Target file name:
label.text.to.file=To &file:
label.text.to.package=To p&ackage:
+label.text.to.class=To class:
+label.text.to.object=To object:
class.name.prompt=Class &name:
search.for.text.occurrences=Search for &text occurrences
search.in.comments.and.strings=Search in &comments and strings
@@ -952,6 +954,13 @@ text.move.declaration.supports.only.top.levels.and.nested.classes=Move declarati
text.move.declaration.proceed.move.without.mpp.counterparts.text=This refactoring will move selected declaration without it's expect/actual counterparts that may lead to compilation errors.\nDo you wish to proceed?
text.move.declaration.proceed.move.without.mpp.counterparts.title=MPP declarations does not supported by this refactoring.
text.move.declarations=Move declarations
+text.no.destination.object.specified=No destination object specified
+text.select.a.name.for.this.parameter=Select a name for ''this@{0}'' parameter
+text.move.method.is.not.supported.for.non.project.methods=Move method is not supported for non-project methods
+text.move.method.is.not.supported.for.generic.classes=Move method is not supported for generic classes
+text.move.method.is.not.supported.when.method.is.a.part.of.inheritance.hierarchy=Move method is not supported when method is a part of inheritance hierarchy
+text.references.to.outer.classes.have.to.be.added.manually=References to outer classes will have to be added manually after move
+text.move.method=Move method
text.move.file.0=Move {0}
text.move.refactoring.not.available.during.indexing=Move refactoring is not available while indexing is in progress
text.moving.multiple.nested.classes.to.top.level.not.supported=Moving multiple nested classes to top-level is not supported
@@ -1020,6 +1029,8 @@ title.change.signature=Change Signature
title.inline.function=Inline Function
title.introduce.parameter.to.declaration=Introduce parameter to declaration
title.move.nested.classes.to.upper.level=Move Nested Classes to Upper Level
+title.move.method=Move Method
+title.choose.destination.object=Choose Destination Object
title.select.target.code.block=Select target code block
unsupported.usage.0=Unsupported usage: {0}
@@ -1925,6 +1936,7 @@ title.parameters=Parameters
signature.preview=Signature Preview
move.members.from=Move members from:
open.moved.members.in.editor=Open moved members in editor
+open.moved.method.in.editor=Open moved method in editor
to.fully.qualified.name=To (fully qualified name):
toggle.library.to.source.dependency.support=Toggle library to source dependency support
enable.components.for.library.to.source.analysis.in.kotlin=Enable components for analyzing libraries depending on project source files in Kotlin
diff --git a/idea/resources/META-INF/plugin-common.xml b/idea/resources/META-INF/plugin-common.xml
index c2d044dffb8..e84f5118026 100644
--- a/idea/resources/META-INF/plugin-common.xml
+++ b/idea/resources/META-INF/plugin-common.xml
@@ -324,6 +324,10 @@
id="kotlinAwareJavaMoveClassesOrPackagesHandler"
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareJavaMoveClassesOrPackagesHandler"
order="first" />
+
,
+ private val targetContainer: KtClassOrObject?
+) : RefactoringDialog(method.project, true) {
+ private val variables = variableToClassMap.keys.toList()
+ private val targetVariableList = createTargetVariableChooser()
+ private val thisClassesToMembers = getThisClassesToMembers(method)
+ private val oldClassParameterNameFields = LinkedHashMap()
+ private val targetObjectChooser = createTargetObjectChooser()
+ private val toClassRadioButton = JRadioButton(KotlinBundle.message("label.text.to.class"))
+ private val toObjectRadioButton = JRadioButton(KotlinBundle.message("label.text.to.object"))
+ private var selectedTarget: KtNamedDeclaration? = null
+ private val openInEditorCheckBox = JCheckBox(KotlinBundle.message("open.moved.method.in.editor"), true)
+
+ init {
+ super.init()
+ title = KotlinBundle.message("title.move.method")
+ }
+
+ override fun doAction() {
+ if (toClassRadioButton.isSelected) selectedTarget = targetVariableList.selectedValue
+
+ if (toObjectRadioButton.isSelected && selectedTarget == null) {
+ setErrorText(KotlinBundle.message("text.no.destination.object.specified"))
+ return
+ }
+
+ val oldClassParameterNames = LinkedHashMap()
+ for ((ktClass, field) in oldClassParameterNameFields) {
+ if (!isValidIdentifier(field.text)) {
+ setErrorText(KotlinBundle.message("parameter.name.is.invalid", field.text))
+ return
+ }
+ if (field.isEnabled) {
+ oldClassParameterNames[ktClass] = field.text
+ }
+ }
+
+ val processor = MoveKotlinMethodProcessor(method, selectedTarget!!, oldClassParameterNames, openInEditorCheckBox.isSelected)
+ invokeRefactoring(processor)
+ }
+
+ override fun createCenterPanel(): JComponent? {
+ val mainPanel = JPanel(GridBagLayout())
+ val buttonGroup = ButtonGroup()
+
+ toClassRadioButton.addActionListener { enableTargetChooser() }
+ toObjectRadioButton.addActionListener { enableTargetChooser() }
+ buttonGroup.add(toClassRadioButton)
+ buttonGroup.add(toObjectRadioButton)
+
+ targetVariableList.addListSelectionListener { enableTextFields() }
+ val scrollPane = ScrollPaneFactory.createScrollPane(targetVariableList)
+ mainPanel.add(
+ scrollPane, GridBagConstraints(
+ 1, 0, 1, 1, 1.0, 1.0,
+ GridBagConstraints.WEST, GridBagConstraints.BOTH, JBUI.emptyInsets(), 0, 0
+ )
+ )
+
+ mainPanel.add(
+ targetObjectChooser, GridBagConstraints(
+ 1, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0,
+ GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, JBUI.emptyInsets(), 0, 0
+ )
+ )
+ mainPanel.add(
+ toClassRadioButton, GridBagConstraints(
+ 0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0,
+ GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, JBUI.emptyInsets(), 0, 0
+ )
+ )
+ mainPanel.add(
+ toObjectRadioButton, GridBagConstraints(
+ 0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0,
+ GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, JBUI.emptyInsets(), 0, 0
+ )
+ )
+
+ val parametersPanel: JPanel? = createParametersPanel()
+ if (parametersPanel != null) {
+ mainPanel.add(
+ parametersPanel, GridBagConstraints(
+ 0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0,
+ GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, JBUI.emptyInsets(), 0, 0
+ )
+ )
+ }
+ mainPanel.add(
+ openInEditorCheckBox, GridBagConstraints(
+ 0, GridBagConstraints.RELATIVE, 2, 1, 0.0, 0.0,
+ GridBagConstraints.WEST, GridBagConstraints.NONE, JBUI.emptyInsets(), 0, 0
+ )
+ )
+
+ if (variables.isNotEmpty() && targetContainer !is KtObjectDeclaration) {
+ toClassRadioButton.isSelected = true
+ } else {
+ toObjectRadioButton.isSelected = true
+ if (variables.isEmpty()) {
+ toClassRadioButton.isEnabled = false
+ }
+ }
+
+ enableTextFields()
+ enableTargetChooser()
+ updateOnChanged(targetVariableList)
+ return mainPanel
+ }
+
+ private fun createTargetVariableChooser(): JList {
+ val listModel = object : AbstractListModel() {
+ override fun getElementAt(index: Int): KtNamedDeclaration = variables[index]
+ override fun getSize(): Int = variables.size
+ }
+ val list = JBList(listModel)
+ val listCellRenderer = object : DefaultListCellRenderer() {
+ private val renderer = IdeDescriptorRenderers.SOURCE_CODE_TYPES.withOptions {
+ classifierNamePolicy = ClassifierNamePolicy.SHORT
+ }
+
+ override fun getListCellRendererComponent(
+ list: JList<*>?, value: Any?, index: Int, isSelected: Boolean, cellHasFocus: Boolean
+ ): Component {
+ super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
+ if (value is KtNamedDeclaration) {
+ icon = value.getBaseIcon()
+ text = value.nameAsSafeName.identifier
+ (value.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType?.let { type ->
+ text = "$text: ${renderer.renderType(type)}"
+ }
+ }
+ return this
+ }
+ }
+
+ list.cellRenderer = listCellRenderer
+ list.selectionMode = ListSelectionModel.SINGLE_SELECTION
+ val defaultVariableIndex = variables.indexOf(variableToClassMap.filter { it.value == targetContainer }.firstOrNull()?.key)
+ list.selectedIndex = if (defaultVariableIndex != -1) defaultVariableIndex else 0
+ list.selectionModel.addListSelectionListener { updateOnChanged(list) }
+ return list
+ }
+
+ private fun createTargetObjectChooser(): KotlinTypeReferenceEditorComboWithBrowseButton {
+ val targetObjectChooser = KotlinTypeReferenceEditorComboWithBrowseButton(
+ ActionListener {
+ val chooser: TreeClassChooser = object : TreeJavaClassChooserDialog(
+ KotlinBundle.message("title.choose.destination.object"),
+ project,
+ GlobalSearchScope.projectScope(project),
+ { psiClass ->
+ if (psiClass !is KtLightClassForSourceDeclaration) {
+ false
+ } else {
+ val ktClassOrObject = psiClass.kotlinOrigin
+ ktClassOrObject is KtObjectDeclaration && !ktClassOrObject.isObjectLiteral()
+ }
+ },
+ null,
+ null,
+ true
+ ) {
+ override fun getSelectedFromTreeUserObject(node: DefaultMutableTreeNode): PsiClass? {
+ val psiClass = super.getSelectedFromTreeUserObject(node)
+ if (psiClass != null) return psiClass
+ val userObject = node.userObject
+ return if (userObject !is KtClassOrObjectTreeNode) null else userObject.value.toLightClass()
+ }
+ }
+ chooser.selectDirectory(method.containingFile.containingDirectory)
+ chooser.showDialog()
+ val psiClass = chooser.selected
+ if (psiClass is KtLightClassForSourceDeclaration) {
+ selectedTarget = psiClass.kotlinOrigin
+ targetObjectChooser.text = psiClass.kotlinOrigin.fqName.toString()
+ }
+ },
+ (targetContainer ?: method.containingClassOrObject as? KtObjectDeclaration)?.fqName?.asString(),
+ targetContainer ?: method.containingClassOrObject!!,
+ RECENTS_KEY
+ )
+ targetObjectChooser.codeFragment?.let { codeFragment ->
+ codeFragment.extraCompletionFilter = { lookupElement: LookupElement ->
+ val lookupObject = lookupElement.getObject() as? DeclarationLookupObject
+ val psiElement = lookupObject?.psiElement
+ lookupObject is PackageLookupObject || psiElement is KtObjectDeclaration && psiElement.canRefactor()
+ }
+ }
+ return targetObjectChooser
+ }
+
+ private fun updateOnChanged(list: JList<*>) {
+ okAction.isEnabled = !list.selectionModel.isSelectionEmpty
+ }
+
+ private fun enableTextFields() {
+ for (textField in oldClassParameterNameFields.values) {
+ textField.isEnabled = true
+ }
+ if (toClassRadioButton.isSelected) {
+ val variable = variables[targetVariableList.selectedIndex]
+ val containingClass = variable.containingClassOrObject as? KtClass ?: return
+ if (variable !is KtParameter || variable.hasValOrVar()) {
+ if (thisClassesToMembers[containingClass]?.size == 1
+ && thisClassesToMembers[containingClass]?.contains(variable) == true
+ ) {
+ oldClassParameterNameFields[containingClass]?.isEnabled = false
+ }
+ }
+ }
+ }
+
+ private fun enableTargetChooser() {
+ if (toClassRadioButton.isSelected) {
+ targetVariableList.isEnabled = true
+ targetObjectChooser.isEnabled = false
+ } else {
+ targetVariableList.isEnabled = false
+ targetObjectChooser.isEnabled = true
+ }
+ enableTextFields()
+ }
+
+ private fun createParametersPanel(): JPanel? {
+ if (thisClassesToMembers.isEmpty()) return null
+ val panel = JPanel(VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, true))
+ val validator = NewDeclarationNameValidator(method, null, NewDeclarationNameValidator.Target.VARIABLES)
+ for (ktClass in thisClassesToMembers.keys) {
+ val text = KotlinBundle.message("text.select.a.name.for.this.parameter", ktClass.nameAsSafeName.identifier)
+ panel.add(TitledSeparator(text, null))
+ val suggestedName = suggestNamesByType(ktClass.resolveToDescriptorIfAny()!!.defaultType, validator).firstOrNull() ?: "parameter"
+ val field = EditorTextField(suggestedName, project, KotlinFileType.INSTANCE)
+ field.minimumSize = Dimension(field.preferredSize)
+ oldClassParameterNameFields[ktClass] = field
+ panel.add(field)
+ }
+ panel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
+ return panel
+ }
+
+ companion object {
+ val RECENTS_KEY = "${MoveKotlinMethodDialog::class.java.name}.RECENTS_KEY"
+ }
+}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodHandler.kt
new file mode 100644
index 00000000000..903e04be004
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodHandler.kt
@@ -0,0 +1,118 @@
+package org.jetbrains.kotlin.idea.refactoring.move.moveMethod
+
+import com.intellij.openapi.actionSystem.CommonDataKeys
+import com.intellij.openapi.actionSystem.DataContext
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiReference
+import com.intellij.refactoring.move.MoveCallback
+import com.intellij.refactoring.move.MoveHandlerDelegate
+import com.intellij.refactoring.util.CommonRefactoringUtil
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.idea.KotlinBundle
+import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
+import org.jetbrains.kotlin.idea.references.mainReference
+import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
+import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
+import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
+
+class MoveKotlinMethodHandler : MoveHandlerDelegate() {
+ private fun showErrorHint(project: Project, dataContext: DataContext?, message: String) {
+ val editor = if (dataContext == null) null else CommonDataKeys.EDITOR.getData(dataContext)
+ CommonRefactoringUtil.showErrorHint(project, editor, message, KotlinBundle.message("text.move.method"), null)
+ }
+
+ private fun invokeMoveMethodRefactoring(
+ project: Project, method: KtNamedFunction, targetContainer: KtClassOrObject?, dataContext: DataContext?
+ ) {
+ var message: String? = null
+ if (method.containingClassOrObject == null) return
+ if (!method.manager.isInProject(method)) {
+ message = KotlinBundle.message("text.move.method.is.not.supported.for.non.project.methods")
+ } else if (method.mentionsTypeParameters()) {
+ message = KotlinBundle.message("text.move.method.is.not.supported.for.generic.classes")
+ } else if (method.hasModifier(KtTokens.OVERRIDE_KEYWORD) || method.hasModifier(KtTokens.OPEN_KEYWORD)) {
+ message = KotlinBundle.message("text.move.method.is.not.supported.when.method.is.a.part.of.inheritance.hierarchy")
+ }
+ message?.let {
+ showErrorHint(project, dataContext, message)
+ return
+ }
+ MoveKotlinMethodDialog(method, collectSuitableVariables(method), targetContainer).show()
+ }
+
+ private fun collectSuitableVariables(method: KtNamedFunction): Map {
+ val sourceClassOrObject = method.containingClassOrObject ?: return emptyMap()
+ val allVariables = mutableListOf()
+
+ allVariables.addAll(method.valueParameters)
+ allVariables.addAll(sourceClassOrObject.declarations.filterIsInstance())
+ allVariables.addAll(sourceClassOrObject.primaryConstructorParameters.filter { parameter -> parameter.hasValOrVar() })
+
+ val variableToClassMap = LinkedHashMap()
+ for (variable in allVariables) {
+ (variable.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType?.let { type ->
+ if (type.arguments.isEmpty()) {
+ val ktClass = type.constructor.declarationDescriptor?.findPsi() as? KtClass
+ if (ktClass != null && method.manager.isInProject(ktClass)) {
+ variableToClassMap[variable] = ktClass
+ }
+ }
+ }
+ }
+ return variableToClassMap
+ }
+
+ override fun canMove(elements: Array, targetContainer: PsiElement?, reference: PsiReference?): Boolean {
+ if (elements.size != 1) return false
+ val method = elements[0] as? KtNamedFunction ?: return false
+ val sourceContainer = method.containingClassOrObject
+ return (targetContainer == null || super.canMove(elements, targetContainer, reference))
+ && sourceContainer != null && !sourceContainer.isObjectLiteral()
+ }
+
+ override fun isValidTarget(psiElement: PsiElement?, sources: Array): Boolean {
+ return psiElement is KtClassOrObject && !psiElement.hasModifier(KtTokens.ANNOTATION_KEYWORD)
+ }
+
+ override fun doMove(project: Project, elements: Array, targetContainer: PsiElement?, callback: MoveCallback?) {
+ if (elements.size != 1) return
+ val method = elements[0] as? KtNamedFunction ?: return
+ val sourceContainer = method.containingClassOrObject
+ if (sourceContainer == null || sourceContainer.isObjectLiteral()) return
+ invokeMoveMethodRefactoring(project, elements[0] as KtNamedFunction, targetContainer as? KtClassOrObject, null)
+ }
+
+ override fun tryToMove(
+ element: PsiElement, project: Project, dataContext: DataContext?, reference: PsiReference?, editor: Editor?
+ ): Boolean {
+ if (element is KtNamedFunction) {
+ element.containingClassOrObject?.let { sourceContainer ->
+ if (!sourceContainer.isObjectLiteral()) {
+ invokeMoveMethodRefactoring(project, element, null, dataContext)
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ private fun KtNamedFunction.mentionsTypeParameters(): Boolean {
+ var ktClassOrObject = containingClassOrObject
+ val typeParameters = mutableListOf()
+ while (ktClassOrObject != null) {
+ typeParameters.addAll(ktClassOrObject.typeParameters)
+ ktClassOrObject = if (ktClassOrObject.hasModifier(KtTokens.INNER_KEYWORD)) ktClassOrObject.containingClassOrObject else null
+ }
+ collectDescendantsOfType().forEach { userType ->
+ if (userType.referenceExpression?.mainReference?.resolve() in typeParameters) return true
+ }
+ return false
+ }
+
+ override fun getActionName(elements: Array): String = "${KotlinBundle.message("text.move.method")}.."
+}
diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodProcessor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodProcessor.kt
new file mode 100644
index 00000000000..448153338a6
--- /dev/null
+++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/move/moveMethod/MoveKotlinMethodProcessor.kt
@@ -0,0 +1,334 @@
+package org.jetbrains.kotlin.idea.refactoring.move.moveMethod
+
+import com.intellij.ide.util.EditorHelper
+import com.intellij.openapi.util.Ref
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiMethodCallExpression
+import com.intellij.psi.PsiReferenceExpression
+import com.intellij.psi.search.searches.ReferencesSearch
+import com.intellij.refactoring.BaseRefactoringProcessor
+import com.intellij.refactoring.move.MoveMultipleElementsViewDescriptor
+import com.intellij.refactoring.util.MoveRenameUsageInfo
+import com.intellij.usageView.UsageInfo
+import com.intellij.usageView.UsageViewBundle
+import com.intellij.usageView.UsageViewDescriptor
+import com.intellij.util.IncorrectOperationException
+import com.intellij.util.containers.MultiMap
+import org.jetbrains.kotlin.descriptors.CallableDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
+import org.jetbrains.kotlin.descriptors.Visibilities
+import org.jetbrains.kotlin.idea.KotlinBundle
+import org.jetbrains.kotlin.idea.caches.resolve.*
+import org.jetbrains.kotlin.idea.core.setVisibility
+import org.jetbrains.kotlin.idea.refactoring.move.*
+import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.KotlinMoveTargetForExistingElement
+import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.MoveConflictChecker
+import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.Mover
+import org.jetbrains.kotlin.idea.references.mainReference
+import org.jetbrains.kotlin.idea.search.projectScope
+import org.jetbrains.kotlin.idea.util.getFactoryForImplicitReceiverWithSubtypeOf
+import org.jetbrains.kotlin.idea.util.getResolutionScope
+import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.name.tail
+import org.jetbrains.kotlin.psi.*
+import org.jetbrains.kotlin.psi.psiUtil.*
+import org.jetbrains.kotlin.resolve.BindingContext
+import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
+import org.jetbrains.kotlin.resolve.descriptorUtil.isAncestorOf
+import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
+import org.jetbrains.kotlin.resolve.scopes.utils.getImplicitReceiversHierarchy
+import org.jetbrains.kotlin.util.containingNonLocalDeclaration
+
+class MoveKotlinMethodProcessor(
+ private val method: KtNamedFunction,
+ private val targetVariable: KtNamedDeclaration,
+ private val oldClassParameterNames: Map,
+ private val openInEditor: Boolean = false
+) : BaseRefactoringProcessor(method.project) {
+ private val targetClassOrObject: KtClassOrObject = if (targetVariable is KtObjectDeclaration) targetVariable else
+ (targetVariable.resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType?.constructor?.declarationDescriptor?.findPsi() as KtClass
+ private val factory = KtPsiFactory(myProject)
+ private val conflicts = MultiMap()
+
+ override fun createUsageViewDescriptor(usages: Array): UsageViewDescriptor {
+ return MoveMultipleElementsViewDescriptor(
+ arrayOf(method), (targetClassOrObject.fqName ?: UsageViewBundle.message("default.package.presentable.name")).toString()
+ )
+ }
+
+ override fun preprocessUsages(refUsages: Ref>): Boolean {
+ return showConflicts(conflicts, refUsages.get())
+ }
+
+ override fun findUsages(): Array {
+ val changeInfo = ContainerChangeInfo(
+ ContainerInfo.Class(method.containingClassOrObject!!.fqName!!),
+ ContainerInfo.Class(targetClassOrObject.fqName!!)
+ )
+ val conflictChecker =
+ MoveConflictChecker(myProject, listOf(method), KotlinMoveTargetForExistingElement(targetClassOrObject), method)
+ val searchScope = myProject.projectScope()
+ val internalUsages = mutableSetOf()
+ val methodCallUsages = mutableSetOf()
+
+ methodCallUsages += ReferencesSearch.search(method, searchScope).mapNotNull { ref ->
+ createMoveUsageInfoIfPossible(ref, method, addImportToOriginalFile = true, isInternal = method.isAncestor(ref.element))
+ }
+
+ if (targetVariableIsMethodParameter()) {
+ internalUsages += ReferencesSearch.search(targetVariable, searchScope).mapNotNull { ref ->
+ createMoveUsageInfoIfPossible(ref, targetVariable, addImportToOriginalFile = false, isInternal = true)
+ }
+ }
+
+ internalUsages += method.getInternalReferencesToUpdateOnPackageNameChange(changeInfo)
+ traverseOuterInstanceReferences(method) { internalUsages += it }
+
+ conflictChecker.checkAllConflicts(
+ methodCallUsages.filter { it is KotlinMoveUsage && !it.isInternal }.toMutableSet(), internalUsages, conflicts
+ )
+
+ if (oldClassParameterNames.size > 1) {
+ for (usage in methodCallUsages.filter { it.element is KtNameReferenceExpression || it.element is PsiReferenceExpression }) {
+ conflicts.putValue(usage.element, KotlinBundle.message("text.references.to.outer.classes.have.to.be.added.manually"))
+ }
+ }
+
+ return (internalUsages + methodCallUsages).toTypedArray()
+ }
+
+ override fun performRefactoring(usages: Array) {
+ val usagesToProcess = mutableListOf()
+
+ fun changeMethodSignature() {
+ if (targetVariableIsMethodParameter()) {
+ val parameterIndex = method.valueParameters.indexOf(targetVariable as KtParameter)
+ method.valueParameterList?.removeParameter(parameterIndex)
+ }
+ for ((ktClass, parameterName) in oldClassParameterNames) {
+ method.valueParameterList?.addParameterBefore(
+ factory.createParameter("$parameterName: ${ktClass.nameAsSafeName.identifier}"),
+ method.valueParameters.firstOrNull()
+ )
+ }
+ }
+
+ fun KtNameReferenceExpression.getImplicitReceiver(): KtExpression? {
+ val scope = getResolutionScope(this.analyze()) ?: return null
+ val descriptor = this.resolveToCall()?.resultingDescriptor ?: return null
+ val receiverDescriptor = descriptor.extensionReceiverParameter
+ ?: descriptor.dispatchReceiverParameter
+ ?: return null
+ val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverDescriptor.type)
+ ?: return null
+ val receiverText = if (expressionFactory.isImmediate) "this" else expressionFactory.expressionText
+ return factory.createExpression(receiverText)
+ }
+
+ fun escalateTargetVariableVisibilityIfNeeded(where: DeclarationDescriptor?) {
+ if (where == null || targetVariableIsMethodParameter()) return
+ val targetDescriptor = targetVariable.resolveToDescriptorIfAny() as? DeclarationDescriptorWithVisibility
+ ?: return
+ if (!Visibilities.isVisibleIgnoringReceiver(targetDescriptor, where) && method.manager.isInProject(targetVariable)) {
+ targetVariable.setVisibility(KtTokens.PUBLIC_KEYWORD)
+ }
+ }
+
+ fun correctMethodCall(expression: PsiElement) {
+ when (expression) {
+ is KtNameReferenceExpression -> {
+ val callExpression = expression.parent as? KtCallExpression ?: return
+ escalateTargetVariableVisibilityIfNeeded(callExpression.containingNonLocalDeclaration()?.resolveToDescriptorIfAny())
+
+ val oldReceiver = callExpression.getQualifiedExpressionForSelector()?.receiverExpression
+ ?: expression.getImplicitReceiver()
+ ?: return
+
+ val newReceiver = if (targetVariable is KtObjectDeclaration) {
+ factory.createExpression(targetVariable.nameAsSafeName.identifier)
+ } else if (targetVariableIsMethodParameter()) {
+ val parameterIndex = method.valueParameters.indexOf(targetVariable as KtParameter)
+ if (parameterIndex in callExpression.valueArguments.indices) {
+ val argumentExpression = callExpression.valueArguments[parameterIndex].getArgumentExpression()
+ ?: return
+ callExpression.valueArgumentList?.removeArgument(parameterIndex)
+ argumentExpression
+ } else targetVariable.defaultValue
+ } else {
+ factory.createExpression("${oldReceiver.text}.${targetVariable.nameAsSafeName.identifier}")
+ } ?: return
+
+ if (method.containingClassOrObject in oldClassParameterNames) {
+ callExpression.valueArgumentList?.addArgumentBefore(
+ factory.createArgument(oldReceiver),
+ callExpression.valueArguments.firstOrNull()
+ )
+ }
+
+ val resultingExpression = callExpression.getQualifiedExpressionForSelectorOrThis()
+ .replace(factory.createExpressionByPattern("$0.$1", newReceiver.text, callExpression))
+
+ if (targetVariable is KtObjectDeclaration) {
+ val ref = (resultingExpression as? KtQualifiedExpression)?.receiverExpression?.mainReference ?: return
+ createMoveUsageInfoIfPossible(
+ ref, targetClassOrObject, addImportToOriginalFile = true,
+ isInternal = targetClassOrObject.isAncestor(ref.element)
+ )?.let { usagesToProcess += it }
+ }
+ }
+ is PsiReferenceExpression -> {
+ val callExpression = expression.parent as? PsiMethodCallExpression ?: return
+ val oldReceiver = callExpression.methodExpression.qualifierExpression ?: return
+
+ val newReceiver = if (targetVariable is KtObjectDeclaration) {
+ // todo: fix usage of target object (import might be needed)
+ val targetObjectName = targetVariable.fqName?.tail(targetVariable.containingKtFile.packageFqName)?.toString()
+ ?: targetVariable.nameAsSafeName.identifier
+ JavaPsiFacade.getElementFactory(myProject).createExpressionFromText("$targetObjectName.INSTANCE", null)
+ } else if (targetVariableIsMethodParameter()) {
+ val parameterIndex = method.valueParameters.indexOf(targetVariable as KtParameter)
+ val arguments = callExpression.argumentList.expressions
+ if (parameterIndex in arguments.indices) {
+ val argumentExpression = arguments[parameterIndex].copy() ?: return
+ arguments[parameterIndex].delete()
+ argumentExpression
+ } else return
+ } else {
+ val getterName = "get${targetVariable.nameAsSafeName.identifier.capitalize()}"
+ JavaPsiFacade.getElementFactory(myProject).createExpressionFromText("${oldReceiver.text}.$getterName()", null)
+ }
+
+ if (method.containingClassOrObject in oldClassParameterNames) {
+ callExpression.argumentList.addBefore(oldReceiver, callExpression.argumentList.expressions.firstOrNull())
+ }
+
+ oldReceiver.replace(newReceiver)
+ }
+ }
+ }
+
+ fun replaceReferenceToTargetWithThis(element: KtExpression) {
+ val scope = element.getResolutionScope(element.analyze()) ?: return
+ val receivers = scope.getImplicitReceiversHierarchy()
+ val receiverText =
+ if (receivers.isEmpty() || receivers[0].containingDeclaration == method.containingClassOrObject?.resolveToDescriptorIfAny())
+ "this" else "this@${targetClassOrObject.nameAsSafeName.identifier}"
+ element.replace(factory.createExpression(receiverText))
+ }
+
+ val (methodCallUsages, internalUsages) = usages.partition { it is MoveRenameUsageInfo && it.referencedElement == method }
+ val newInternalUsages = mutableListOf()
+ val oldInternalUsages = mutableListOf()
+
+ try {
+ for (usage in methodCallUsages) {
+ usage.element?.let { element ->
+ correctMethodCall(element)
+ }
+ }
+
+ for (usage in internalUsages) {
+ val element = usage.element ?: continue
+
+ if (usage is MoveRenameUsageInfo) {
+ if (usage.referencedElement == targetVariable && element is KtNameReferenceExpression) {
+ replaceReferenceToTargetWithThis(element)
+ } else {
+ oldInternalUsages += usage
+ }
+ }
+
+ if (usage is SourceInstanceReferenceUsageInfo) {
+ if (usage.member == targetVariable && element is KtNameReferenceExpression) {
+ replaceReferenceToTargetWithThis(
+ (element as? KtThisExpression)?.getQualifiedExpressionForReceiver() ?: element
+ )
+ } else {
+ val receiverText = oldClassParameterNames[usage.sourceOrOuter] ?: continue
+ when (element) {
+ is KtThisExpression -> element.replace(factory.createExpression(receiverText))
+ is KtNameReferenceExpression -> {
+ val elementToReplace = (element.parent as? KtCallExpression) ?: element
+ elementToReplace.replace(factory.createExpressionByPattern("$0.$1", receiverText, elementToReplace))
+ }
+ }
+ }
+ }
+ }
+
+ changeMethodSignature()
+ markInternalUsages(oldInternalUsages)
+
+ val movedMethod = Mover.Default(method, targetClassOrObject)
+ val oldToNewMethodMap = mapOf(method to movedMethod)
+
+ newInternalUsages += restoreInternalUsages(movedMethod, oldToNewMethodMap)
+ usagesToProcess += newInternalUsages
+
+ postProcessMoveUsages(usagesToProcess, oldToNewMethodMap)
+ if (openInEditor) EditorHelper.openInEditor(movedMethod)
+ } catch (e: IncorrectOperationException) {
+ } finally {
+ cleanUpInternalUsages(oldInternalUsages + newInternalUsages)
+ }
+ }
+
+ private fun targetVariableIsMethodParameter(): Boolean = targetVariable is KtParameter && !targetVariable.hasValOrVar()
+
+ override fun getCommandName(): String = KotlinBundle.message("text.move.method")
+}
+
+internal fun getThisClassesToMembers(method: KtNamedFunction) = traverseOuterInstanceReferences(method)
+
+private fun traverseOuterInstanceReferences(
+ method: KtNamedFunction,
+ body: (SourceInstanceReferenceUsageInfo) -> Unit = {}
+): LinkedHashMap> {
+ val context = method.analyzeWithContent()
+ val containingClassOrObject = method.containingClassOrObject ?: return LinkedHashMap()
+ val descriptor = containingClassOrObject.unsafeResolveToDescriptor()
+
+ fun getClassOrObjectAndMemberReferencedBy(reference: KtExpression): Pair {
+ var classOrObjectDescriptor: DeclarationDescriptor? = null
+ var memberDescriptor: CallableDescriptor? = null
+ if (reference is KtThisExpression) {
+ classOrObjectDescriptor = context[BindingContext.REFERENCE_TARGET, reference.instanceReference]
+ if (classOrObjectDescriptor?.isAncestorOf(descriptor, false) == true) {
+ memberDescriptor =
+ reference.getQualifiedExpressionForReceiver()?.selectorExpression?.getResolvedCall(context)?.resultingDescriptor
+ }
+ }
+ if (reference is KtNameReferenceExpression) {
+ val dispatchReceiver = reference.getResolvedCall(context)?.dispatchReceiver as? ImplicitReceiver
+ classOrObjectDescriptor = dispatchReceiver?.declarationDescriptor
+ if (classOrObjectDescriptor?.isAncestorOf(descriptor, false) == true) {
+ memberDescriptor = reference.getResolvedCall(context)?.resultingDescriptor
+ }
+ }
+ return classOrObjectDescriptor to memberDescriptor
+ }
+
+ val thisClassesToMembers = LinkedHashMap>()
+ method.bodyExpression?.forEachDescendantOfType { reference ->
+ val (classOrObjectDescriptor, memberDescriptor) = getClassOrObjectAndMemberReferencedBy(reference)
+ (classOrObjectDescriptor?.findPsi() as? KtClassOrObject)?.let { resolvedClassOrObject ->
+ val resolvedMember = memberDescriptor?.findPsi() as? KtNamedDeclaration
+ if (resolvedClassOrObject is KtClass) {
+ if (resolvedClassOrObject in thisClassesToMembers) thisClassesToMembers[resolvedClassOrObject]?.add(
+ resolvedMember
+ ?: resolvedClassOrObject
+ )
+ else thisClassesToMembers[resolvedClassOrObject] = mutableSetOf(resolvedMember ?: resolvedClassOrObject)
+ }
+ body(SourceInstanceReferenceUsageInfo(reference, resolvedClassOrObject, resolvedMember))
+ }
+ }
+ return thisClassesToMembers
+}
+
+internal class SourceInstanceReferenceUsageInfo(
+ reference: KtExpression, val sourceOrOuter: KtClassOrObject, val member: KtNamedDeclaration?
+) : UsageInfo(reference)
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/a/A.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/a/A.kt
new file mode 100644
index 00000000000..3f9bb2f1202
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/a/A.kt
@@ -0,0 +1,14 @@
+package a
+
+import b.B
+
+class A {
+
+ fun bar(b: B) {
+ b.foo()
+ val a = A().apply {
+ b.foo()
+ b.foo()
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/b/B.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/b/B.kt
new file mode 100644
index 00000000000..ae6cb29f391
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/b/B.kt
@@ -0,0 +1,13 @@
+package b
+
+import a.A
+
+class B (val a: A) {
+ fun baz(b: B) {
+ b.foo()
+ }
+
+ fun foo() {
+ val a1 = this.a
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/usages/JavaUsage.java b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/usages/JavaUsage.java
new file mode 100644
index 00000000000..aa3aec867fb
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/usages/JavaUsage.java
@@ -0,0 +1,10 @@
+package usages;
+
+import a.A;
+import b.B;
+
+public class JavaUsage {
+ void javaUsage(A a, B b) {
+ b.foo();
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/usages/kotlinUsage.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/usages/kotlinUsage.kt
new file mode 100644
index 00000000000..88db436b677
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/after/usages/kotlinUsage.kt
@@ -0,0 +1,6 @@
+import a.A
+import b.B
+
+fun kotlinUsage(a: A, b: B) {
+ b.foo()
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/a/A.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/a/A.kt
new file mode 100644
index 00000000000..f66e26250f4
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/a/A.kt
@@ -0,0 +1,17 @@
+package a
+
+import b.B
+
+class A {
+ fun foo(b: B) {
+ val a1 = b.a
+ }
+
+ fun bar(b: B) {
+ foo(b)
+ val a = A().apply {
+ foo(b)
+ this@A.foo(b)
+ }
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/b/B.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/b/B.kt
new file mode 100644
index 00000000000..68d91b180ab
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/b/B.kt
@@ -0,0 +1,9 @@
+package b
+
+import a.A
+
+class B (val a: A) {
+ fun baz(b: B) {
+ a.foo(b)
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/usages/JavaUsage.java b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/usages/JavaUsage.java
new file mode 100644
index 00000000000..9c486e9ae98
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/usages/JavaUsage.java
@@ -0,0 +1,10 @@
+package usages;
+
+import a.A;
+import b.B;
+
+public class JavaUsage {
+ void javaUsage(A a, B b) {
+ a.foo(b);
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/usages/kotlinUsage.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/usages/kotlinUsage.kt
new file mode 100644
index 00000000000..92f9ca146f5
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/before/usages/kotlinUsage.kt
@@ -0,0 +1,6 @@
+import a.A
+import b.B
+
+fun kotlinUsage(a: A, b: B) {
+ a.foo(b)
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/parameterAsReference.test b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/parameterAsReference.test
new file mode 100644
index 00000000000..8ecfc019f75
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/parameterAsReference.test
@@ -0,0 +1,6 @@
+{
+ "mainFile": "a/A.kt",
+ "type": "MOVE_KOTLIN_METHOD",
+ "methodToMove": "foo",
+ "methodParameter": "b"
+}
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/a/A.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/a/A.kt
new file mode 100644
index 00000000000..49c624d5b6a
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/a/A.kt
@@ -0,0 +1,8 @@
+package a
+
+import b.B
+
+class A(val b: B) {
+
+ fun bar() {}
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/b/B.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/b/B.kt
new file mode 100644
index 00000000000..8bc32110ef1
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/b/B.kt
@@ -0,0 +1,14 @@
+package b
+
+import a.A
+
+class B (val a: A) {
+ fun baz() {
+ a.b.foo(a)
+ }
+
+ fun foo(a: A) {
+ val a1 = this.a
+ a.bar()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/usages/JavaUsage.java b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/usages/JavaUsage.java
new file mode 100644
index 00000000000..547894d12cb
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/usages/JavaUsage.java
@@ -0,0 +1,9 @@
+package usages;
+
+import a.A;
+
+public class JavaUsage {
+ void javaUsage(A a) {
+ a.getB().foo(a);
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/usages/kotlinUsage.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/usages/kotlinUsage.kt
new file mode 100644
index 00000000000..74b8a9db988
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/after/usages/kotlinUsage.kt
@@ -0,0 +1,5 @@
+import a.A
+
+fun kotlinUsage(a: A) {
+ a.b.foo(a)
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/a/A.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/a/A.kt
new file mode 100644
index 00000000000..e5f57eb6096
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/a/A.kt
@@ -0,0 +1,12 @@
+package a
+
+import b.B
+
+class A(val b: B) {
+ fun foo() {
+ val a1 = b.a
+ bar()
+ }
+
+ fun bar() {}
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/b/B.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/b/B.kt
new file mode 100644
index 00000000000..a85c627a6e7
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/b/B.kt
@@ -0,0 +1,9 @@
+package b
+
+import a.A
+
+class B (val a: A) {
+ fun baz() {
+ a.foo()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/usages/JavaUsage.java b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/usages/JavaUsage.java
new file mode 100644
index 00000000000..6ebf27052a5
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/usages/JavaUsage.java
@@ -0,0 +1,9 @@
+package usages;
+
+import a.A;
+
+public class JavaUsage {
+ void javaUsage(A a) {
+ a.foo();
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/usages/kotlinUsage.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/usages/kotlinUsage.kt
new file mode 100644
index 00000000000..1ab7afb6c83
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/before/usages/kotlinUsage.kt
@@ -0,0 +1,5 @@
+import a.A
+
+fun kotlinUsage(a: A) {
+ a.foo()
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/propertyAsReference.test b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/propertyAsReference.test
new file mode 100644
index 00000000000..ad468d5c78c
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/propertyAsReference.test
@@ -0,0 +1,7 @@
+{
+ "mainFile": "a/A.kt",
+ "type": "MOVE_KOTLIN_METHOD",
+ "methodToMove": "foo",
+ "sourceProperty": "b",
+ "outerInstanceParameter": "a"
+}
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/a/A.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/a/A.kt
new file mode 100644
index 00000000000..49c624d5b6a
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/a/A.kt
@@ -0,0 +1,8 @@
+package a
+
+import b.B
+
+class A(val b: B) {
+
+ fun bar() {}
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/b/B.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/b/B.kt
new file mode 100644
index 00000000000..1c28f1806da
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/b/B.kt
@@ -0,0 +1,13 @@
+package b
+
+import a.A
+
+object B {
+ fun baz(a: A) {
+ B.foo(a)
+ }
+
+ fun foo(a: A) {
+ a.bar()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/usages/JavaUsage.java b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/usages/JavaUsage.java
new file mode 100644
index 00000000000..93cb3853f94
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/usages/JavaUsage.java
@@ -0,0 +1,10 @@
+package usages;
+
+import a.A;
+import b.B;
+
+public class JavaUsage {
+ void javaUsage(A a) {
+ B.INSTANCE.foo(a);
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/usages/kotlinUsage.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/usages/kotlinUsage.kt
new file mode 100644
index 00000000000..4f38dd0c5ad
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/after/usages/kotlinUsage.kt
@@ -0,0 +1,6 @@
+import a.A
+import b.B
+
+fun kotlinUsage(a: A) {
+ B.foo(a)
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/a/A.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/a/A.kt
new file mode 100644
index 00000000000..ff9428b8bdf
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/a/A.kt
@@ -0,0 +1,11 @@
+package a
+
+import b.B
+
+class A(val b: B) {
+ fun foo() {
+ bar()
+ }
+
+ fun bar() {}
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/b/B.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/b/B.kt
new file mode 100644
index 00000000000..98f05de681e
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/b/B.kt
@@ -0,0 +1,9 @@
+package b
+
+import a.A
+
+object B {
+ fun baz(a: A) {
+ a.foo()
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/usages/JavaUsage.java b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/usages/JavaUsage.java
new file mode 100644
index 00000000000..d4d380bc282
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/usages/JavaUsage.java
@@ -0,0 +1,10 @@
+package usages;
+
+import a.A;
+import b.B;
+
+public class JavaUsage {
+ void javaUsage(A a) {
+ a.foo();
+ }
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/usages/kotlinUsage.kt b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/usages/kotlinUsage.kt
new file mode 100644
index 00000000000..1ab7afb6c83
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/before/usages/kotlinUsage.kt
@@ -0,0 +1,5 @@
+import a.A
+
+fun kotlinUsage(a: A) {
+ a.foo()
+}
\ No newline at end of file
diff --git a/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/moveToObject.test b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/moveToObject.test
new file mode 100644
index 00000000000..5448f08f611
--- /dev/null
+++ b/idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/moveToObject.test
@@ -0,0 +1,7 @@
+{
+ "mainFile": "a/A.kt",
+ "type": "MOVE_KOTLIN_METHOD",
+ "methodToMove": "foo",
+ "targetObject": "b.B",
+ "outerInstanceParameter": "a"
+}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt
index dd3e1c3e33e..7b1295df63d 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt
+++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/AbstractMoveTest.kt
@@ -29,14 +29,19 @@ import org.jetbrains.kotlin.idea.refactoring.createKotlinFile
import org.jetbrains.kotlin.idea.refactoring.move.changePackage.KotlinChangePackageRefactoring
import org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareDelegatingMoveDestination
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
+import org.jetbrains.kotlin.idea.refactoring.move.moveMethod.MoveKotlinMethodProcessor
import org.jetbrains.kotlin.idea.refactoring.runRefactoringTest
import org.jetbrains.kotlin.idea.search.allScope
import org.jetbrains.kotlin.idea.search.projectScope
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
+import org.jetbrains.kotlin.idea.stubindex.KotlinFunctionShortNameIndex
+import org.jetbrains.kotlin.idea.stubindex.KotlinPropertyShortNameIndex
import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedDeclaration
+import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
abstract class AbstractMoveTest : AbstractMultifileRefactoringTest() {
@@ -283,5 +288,29 @@ enum class MoveAction : AbstractMultifileRefactoringTest.RefactoringAction {
val descriptor = MoveDeclarationsDescriptor(project, MoveSource(elementToMove), moveTarget, delegate)
MoveKotlinDeclarationsProcessor(descriptor).run()
}
+ },
+
+ MOVE_KOTLIN_METHOD {
+ override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List, config: JsonObject) {
+ val project = mainFile.project
+ val method =
+ KotlinFunctionShortNameIndex.getInstance().get(config.getString("methodToMove"), project, project.projectScope()).first()
+ val methodParameterName = config.getNullableString("methodParameter")
+ val sourcePropertyName = config.getNullableString("sourceProperty")
+ val targetObjectName = config.getNullableString("targetObject")
+ val targetVariable = when {
+ methodParameterName != null -> method.valueParameters.find { it.name == methodParameterName }!!
+ sourcePropertyName != null -> KotlinPropertyShortNameIndex.getInstance()
+ .get(sourcePropertyName, project, project.projectScope()).first()
+ else -> KotlinFullClassNameIndex.getInstance().get(targetObjectName!!, project, project.projectScope()).first()
+
+ }
+ val oldClassParameterNames = mutableMapOf()
+ val outerInstanceParameter = config.getNullableString("outerInstanceParameter")
+ if (outerInstanceParameter != null) {
+ oldClassParameterNames[method.containingClassOrObject as KtClass] = outerInstanceParameter
+ }
+ MoveKotlinMethodProcessor(method, targetVariable, oldClassParameterNames).run()
+ }
};
}
diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java
index a040e7b76e7..2c7c841155a 100644
--- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java
+++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/move/MoveTestGenerated.java
@@ -323,6 +323,21 @@ public class MoveTestGenerated extends AbstractMoveTest {
runTest("idea/testData/refactoring/move/kotlin/moveFile/typeRefWithArguments/typeRefWithArguments.test");
}
+ @TestMetadata("kotlin/moveMethod/moveToClass/parameterAsReference/parameterAsReference.test")
+ public void testKotlin_moveMethod_moveToClass_parameterAsReference_ParameterAsReference() throws Exception {
+ runTest("idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/parameterAsReference/parameterAsReference.test");
+ }
+
+ @TestMetadata("kotlin/moveMethod/moveToClass/propertyAsReference/propertyAsReference.test")
+ public void testKotlin_moveMethod_moveToClass_propertyAsReference_PropertyAsReference() throws Exception {
+ runTest("idea/testData/refactoring/move/kotlin/moveMethod/moveToClass/propertyAsReference/propertyAsReference.test");
+ }
+
+ @TestMetadata("kotlin/moveMethod/moveToObject/moveToObject.test")
+ public void testKotlin_moveMethod_moveToObject_MoveToObject() throws Exception {
+ runTest("idea/testData/refactoring/move/kotlin/moveMethod/moveToObject/moveToObject.test");
+ }
+
@TestMetadata("kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test")
public void testKotlin_moveNestedClass_callableReferences_nestedToAnotherClass_NestedToAnotherClass() throws Exception {
runTest("idea/testData/refactoring/move/kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test");