Move: Support method moving
This commit is contained in:
committed by
Igor Yakovlev
parent
a5368e443c
commit
5515465760
@@ -829,6 +829,8 @@ label.text.receiver.type=Receiver &type:\u0020
|
|||||||
label.text.target.file.name=Target file name:
|
label.text.target.file.name=Target file name:
|
||||||
label.text.to.file=To &file:
|
label.text.to.file=To &file:
|
||||||
label.text.to.package=To p&ackage:
|
label.text.to.package=To p&ackage:
|
||||||
|
label.text.to.class=To class:
|
||||||
|
label.text.to.object=To object:
|
||||||
class.name.prompt=Class &name:
|
class.name.prompt=Class &name:
|
||||||
search.for.text.occurrences=Search for &text occurrences
|
search.for.text.occurrences=Search for &text occurrences
|
||||||
search.in.comments.and.strings=Search in &comments and strings
|
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.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.declaration.proceed.move.without.mpp.counterparts.title=MPP declarations does not supported by this refactoring.
|
||||||
text.move.declarations=Move declarations
|
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.file.0=Move {0}
|
||||||
text.move.refactoring.not.available.during.indexing=Move refactoring is not available while indexing is in progress
|
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
|
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.inline.function=Inline Function
|
||||||
title.introduce.parameter.to.declaration=Introduce parameter to declaration
|
title.introduce.parameter.to.declaration=Introduce parameter to declaration
|
||||||
title.move.nested.classes.to.upper.level=Move Nested Classes to Upper Level
|
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
|
title.select.target.code.block=Select target code block
|
||||||
unsupported.usage.0=Unsupported usage: {0}
|
unsupported.usage.0=Unsupported usage: {0}
|
||||||
|
|
||||||
@@ -1925,6 +1936,7 @@ title.parameters=Parameters
|
|||||||
signature.preview=Signature Preview
|
signature.preview=Signature Preview
|
||||||
move.members.from=Move members from:
|
move.members.from=Move members from:
|
||||||
open.moved.members.in.editor=Open moved members in editor
|
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):
|
to.fully.qualified.name=To (fully qualified name):
|
||||||
toggle.library.to.source.dependency.support=Toggle library to source dependency support
|
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
|
enable.components.for.library.to.source.analysis.in.kotlin=Enable components for analyzing libraries depending on project source files in Kotlin
|
||||||
|
|||||||
@@ -324,6 +324,10 @@
|
|||||||
id="kotlinAwareJavaMoveClassesOrPackagesHandler"
|
id="kotlinAwareJavaMoveClassesOrPackagesHandler"
|
||||||
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareJavaMoveClassesOrPackagesHandler"
|
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareJavaMoveClassesOrPackagesHandler"
|
||||||
order="first" />
|
order="first" />
|
||||||
|
<refactoring.moveHandler
|
||||||
|
id="kotlin.moveMethod"
|
||||||
|
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveMethod.MoveKotlinMethodHandler"
|
||||||
|
order="first,before kotlin.moveTopLevelDeclarations" />
|
||||||
<moveFileHandler implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinFileHandler" />
|
<moveFileHandler implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.MoveKotlinFileHandler" />
|
||||||
<moveFileHandler
|
<moveFileHandler
|
||||||
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.FqNameFixingMoveJavaFileHandler"
|
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveFilesOrDirectories.FqNameFixingMoveJavaFileHandler"
|
||||||
|
|||||||
+290
@@ -0,0 +1,290 @@
|
|||||||
|
package org.jetbrains.kotlin.idea.refactoring.move.moveMethod
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.lookup.LookupElement
|
||||||
|
import com.intellij.ide.util.TreeClassChooser
|
||||||
|
import com.intellij.ide.util.TreeJavaClassChooserDialog
|
||||||
|
import com.intellij.openapi.ui.VerticalFlowLayout
|
||||||
|
import com.intellij.psi.PsiClass
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.refactoring.ui.RefactoringDialog
|
||||||
|
import com.intellij.ui.EditorTextField
|
||||||
|
import com.intellij.ui.ScrollPaneFactory
|
||||||
|
import com.intellij.ui.TitledSeparator
|
||||||
|
import com.intellij.ui.components.JBList
|
||||||
|
import com.intellij.util.ui.JBUI
|
||||||
|
import com.jetbrains.rd.util.firstOrNull
|
||||||
|
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
|
||||||
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinIconProvider.Companion.getBaseIcon
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
|
import org.jetbrains.kotlin.idea.completion.extraCompletionFilter
|
||||||
|
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester.suggestNamesByType
|
||||||
|
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
|
||||||
|
import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject
|
||||||
|
import org.jetbrains.kotlin.idea.core.completion.PackageLookupObject
|
||||||
|
import org.jetbrains.kotlin.idea.projectView.KtClassOrObjectTreeNode
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.canRefactor
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.ui.KotlinTypeReferenceEditorComboWithBrowseButton
|
||||||
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
|
import org.jetbrains.kotlin.name.Name.isValidIdentifier
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
|
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy
|
||||||
|
import java.awt.Component
|
||||||
|
import java.awt.Dimension
|
||||||
|
import java.awt.GridBagConstraints
|
||||||
|
import java.awt.GridBagLayout
|
||||||
|
import java.awt.event.ActionListener
|
||||||
|
import javax.swing.*
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode
|
||||||
|
|
||||||
|
|
||||||
|
class MoveKotlinMethodDialog(
|
||||||
|
private val method: KtNamedFunction,
|
||||||
|
private val variableToClassMap: Map<KtNamedDeclaration, KtClass>,
|
||||||
|
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<KtClass, EditorTextField>()
|
||||||
|
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<KtClass, String>()
|
||||||
|
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<KtNamedDeclaration> {
|
||||||
|
val listModel = object : AbstractListModel<KtNamedDeclaration>() {
|
||||||
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
+118
@@ -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<KtNamedDeclaration, KtClass> {
|
||||||
|
val sourceClassOrObject = method.containingClassOrObject ?: return emptyMap()
|
||||||
|
val allVariables = mutableListOf<KtNamedDeclaration>()
|
||||||
|
|
||||||
|
allVariables.addAll(method.valueParameters)
|
||||||
|
allVariables.addAll(sourceClassOrObject.declarations.filterIsInstance<KtProperty>())
|
||||||
|
allVariables.addAll(sourceClassOrObject.primaryConstructorParameters.filter { parameter -> parameter.hasValOrVar() })
|
||||||
|
|
||||||
|
val variableToClassMap = LinkedHashMap<KtNamedDeclaration, KtClass>()
|
||||||
|
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<PsiElement?>, 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<out PsiElement>): Boolean {
|
||||||
|
return psiElement is KtClassOrObject && !psiElement.hasModifier(KtTokens.ANNOTATION_KEYWORD)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun doMove(project: Project, elements: Array<out PsiElement>, 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<KtTypeParameter>()
|
||||||
|
while (ktClassOrObject != null) {
|
||||||
|
typeParameters.addAll(ktClassOrObject.typeParameters)
|
||||||
|
ktClassOrObject = if (ktClassOrObject.hasModifier(KtTokens.INNER_KEYWORD)) ktClassOrObject.containingClassOrObject else null
|
||||||
|
}
|
||||||
|
collectDescendantsOfType<KtUserType>().forEach { userType ->
|
||||||
|
if (userType.referenceExpression?.mainReference?.resolve() in typeParameters) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getActionName(elements: Array<out PsiElement>): String = "${KotlinBundle.message("text.move.method")}.."
|
||||||
|
}
|
||||||
+334
@@ -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<KtClass, String>,
|
||||||
|
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<PsiElement, String>()
|
||||||
|
|
||||||
|
override fun createUsageViewDescriptor(usages: Array<out UsageInfo>): UsageViewDescriptor {
|
||||||
|
return MoveMultipleElementsViewDescriptor(
|
||||||
|
arrayOf(method), (targetClassOrObject.fqName ?: UsageViewBundle.message("default.package.presentable.name")).toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun preprocessUsages(refUsages: Ref<Array<UsageInfo>>): Boolean {
|
||||||
|
return showConflicts(conflicts, refUsages.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findUsages(): Array<UsageInfo> {
|
||||||
|
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<UsageInfo>()
|
||||||
|
val methodCallUsages = mutableSetOf<UsageInfo>()
|
||||||
|
|
||||||
|
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<out UsageInfo>) {
|
||||||
|
val usagesToProcess = mutableListOf<UsageInfo>()
|
||||||
|
|
||||||
|
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<UsageInfo>()
|
||||||
|
val oldInternalUsages = mutableListOf<UsageInfo>()
|
||||||
|
|
||||||
|
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<PsiElement, PsiElement>(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<KtClass, MutableSet<KtNamedDeclaration>> {
|
||||||
|
val context = method.analyzeWithContent()
|
||||||
|
val containingClassOrObject = method.containingClassOrObject ?: return LinkedHashMap()
|
||||||
|
val descriptor = containingClassOrObject.unsafeResolveToDescriptor()
|
||||||
|
|
||||||
|
fun getClassOrObjectAndMemberReferencedBy(reference: KtExpression): Pair<DeclarationDescriptor?, CallableDescriptor?> {
|
||||||
|
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<KtClass, MutableSet<KtNamedDeclaration>>()
|
||||||
|
method.bodyExpression?.forEachDescendantOfType<KtExpression> { 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)
|
||||||
Vendored
+14
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+13
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package usages;
|
||||||
|
|
||||||
|
import a.A;
|
||||||
|
import b.B;
|
||||||
|
|
||||||
|
public class JavaUsage {
|
||||||
|
void javaUsage(A a, B b) {
|
||||||
|
b.foo();
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import a.A
|
||||||
|
import b.B
|
||||||
|
|
||||||
|
fun kotlinUsage(a: A, b: B) {
|
||||||
|
b.foo()
|
||||||
|
}
|
||||||
Vendored
+17
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package b
|
||||||
|
|
||||||
|
import a.A
|
||||||
|
|
||||||
|
class B (val a: A) {
|
||||||
|
fun baz(b: B) {
|
||||||
|
a.foo(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package usages;
|
||||||
|
|
||||||
|
import a.A;
|
||||||
|
import b.B;
|
||||||
|
|
||||||
|
public class JavaUsage {
|
||||||
|
void javaUsage(A a, B b) {
|
||||||
|
a.foo(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import a.A
|
||||||
|
import b.B
|
||||||
|
|
||||||
|
fun kotlinUsage(a: A, b: B) {
|
||||||
|
a.foo(b)
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"mainFile": "a/A.kt",
|
||||||
|
"type": "MOVE_KOTLIN_METHOD",
|
||||||
|
"methodToMove": "foo",
|
||||||
|
"methodParameter": "b"
|
||||||
|
}
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
import b.B
|
||||||
|
|
||||||
|
class A(val b: B) {
|
||||||
|
|
||||||
|
fun bar() {}
|
||||||
|
}
|
||||||
Vendored
+14
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package usages;
|
||||||
|
|
||||||
|
import a.A;
|
||||||
|
|
||||||
|
public class JavaUsage {
|
||||||
|
void javaUsage(A a) {
|
||||||
|
a.getB().foo(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import a.A
|
||||||
|
|
||||||
|
fun kotlinUsage(a: A) {
|
||||||
|
a.b.foo(a)
|
||||||
|
}
|
||||||
Vendored
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
import b.B
|
||||||
|
|
||||||
|
class A(val b: B) {
|
||||||
|
fun foo() {
|
||||||
|
val a1 = b.a
|
||||||
|
bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar() {}
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package b
|
||||||
|
|
||||||
|
import a.A
|
||||||
|
|
||||||
|
class B (val a: A) {
|
||||||
|
fun baz() {
|
||||||
|
a.foo()
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package usages;
|
||||||
|
|
||||||
|
import a.A;
|
||||||
|
|
||||||
|
public class JavaUsage {
|
||||||
|
void javaUsage(A a) {
|
||||||
|
a.foo();
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import a.A
|
||||||
|
|
||||||
|
fun kotlinUsage(a: A) {
|
||||||
|
a.foo()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"mainFile": "a/A.kt",
|
||||||
|
"type": "MOVE_KOTLIN_METHOD",
|
||||||
|
"methodToMove": "foo",
|
||||||
|
"sourceProperty": "b",
|
||||||
|
"outerInstanceParameter": "a"
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
import b.B
|
||||||
|
|
||||||
|
class A(val b: B) {
|
||||||
|
|
||||||
|
fun bar() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package b
|
||||||
|
|
||||||
|
import a.A
|
||||||
|
|
||||||
|
object B {
|
||||||
|
fun baz(a: A) {
|
||||||
|
B.foo(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun foo(a: A) {
|
||||||
|
a.bar()
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package usages;
|
||||||
|
|
||||||
|
import a.A;
|
||||||
|
import b.B;
|
||||||
|
|
||||||
|
public class JavaUsage {
|
||||||
|
void javaUsage(A a) {
|
||||||
|
B.INSTANCE.foo(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import a.A
|
||||||
|
import b.B
|
||||||
|
|
||||||
|
fun kotlinUsage(a: A) {
|
||||||
|
B.foo(a)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package a
|
||||||
|
|
||||||
|
import b.B
|
||||||
|
|
||||||
|
class A(val b: B) {
|
||||||
|
fun foo() {
|
||||||
|
bar()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bar() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package b
|
||||||
|
|
||||||
|
import a.A
|
||||||
|
|
||||||
|
object B {
|
||||||
|
fun baz(a: A) {
|
||||||
|
a.foo()
|
||||||
|
}
|
||||||
|
}
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
package usages;
|
||||||
|
|
||||||
|
import a.A;
|
||||||
|
import b.B;
|
||||||
|
|
||||||
|
public class JavaUsage {
|
||||||
|
void javaUsage(A a) {
|
||||||
|
a.foo();
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
import a.A
|
||||||
|
|
||||||
|
fun kotlinUsage(a: A) {
|
||||||
|
a.foo()
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"mainFile": "a/A.kt",
|
||||||
|
"type": "MOVE_KOTLIN_METHOD",
|
||||||
|
"methodToMove": "foo",
|
||||||
|
"targetObject": "b.B",
|
||||||
|
"outerInstanceParameter": "a"
|
||||||
|
}
|
||||||
@@ -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.changePackage.KotlinChangePackageRefactoring
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareDelegatingMoveDestination
|
import org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareDelegatingMoveDestination
|
||||||
import org.jetbrains.kotlin.idea.refactoring.move.moveDeclarations.*
|
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.refactoring.runRefactoringTest
|
||||||
import org.jetbrains.kotlin.idea.search.allScope
|
import org.jetbrains.kotlin.idea.search.allScope
|
||||||
import org.jetbrains.kotlin.idea.search.projectScope
|
import org.jetbrains.kotlin.idea.search.projectScope
|
||||||
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
|
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.name.FqName
|
||||||
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||||
|
|
||||||
abstract class AbstractMoveTest : AbstractMultifileRefactoringTest() {
|
abstract class AbstractMoveTest : AbstractMultifileRefactoringTest() {
|
||||||
@@ -283,5 +288,29 @@ enum class MoveAction : AbstractMultifileRefactoringTest.RefactoringAction {
|
|||||||
val descriptor = MoveDeclarationsDescriptor(project, MoveSource(elementToMove), moveTarget, delegate)
|
val descriptor = MoveDeclarationsDescriptor(project, MoveSource(elementToMove), moveTarget, delegate)
|
||||||
MoveKotlinDeclarationsProcessor(descriptor).run()
|
MoveKotlinDeclarationsProcessor(descriptor).run()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
MOVE_KOTLIN_METHOD {
|
||||||
|
override fun runRefactoring(rootDir: VirtualFile, mainFile: PsiFile, elementsAtCaret: List<PsiElement>, 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<KtClass, String>()
|
||||||
|
val outerInstanceParameter = config.getNullableString("outerInstanceParameter")
|
||||||
|
if (outerInstanceParameter != null) {
|
||||||
|
oldClassParameterNames[method.containingClassOrObject as KtClass] = outerInstanceParameter
|
||||||
|
}
|
||||||
|
MoveKotlinMethodProcessor(method, targetVariable, oldClassParameterNames).run()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+15
@@ -323,6 +323,21 @@ public class MoveTestGenerated extends AbstractMoveTest {
|
|||||||
runTest("idea/testData/refactoring/move/kotlin/moveFile/typeRefWithArguments/typeRefWithArguments.test");
|
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")
|
@TestMetadata("kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test")
|
||||||
public void testKotlin_moveNestedClass_callableReferences_nestedToAnotherClass_NestedToAnotherClass() throws Exception {
|
public void testKotlin_moveNestedClass_callableReferences_nestedToAnotherClass_NestedToAnotherClass() throws Exception {
|
||||||
runTest("idea/testData/refactoring/move/kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test");
|
runTest("idea/testData/refactoring/move/kotlin/moveNestedClass/callableReferences/nestedToAnotherClass/nestedToAnotherClass.test");
|
||||||
|
|||||||
Reference in New Issue
Block a user