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.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
|
||||
|
||||
@@ -324,6 +324,10 @@
|
||||
id="kotlinAwareJavaMoveClassesOrPackagesHandler"
|
||||
implementation="org.jetbrains.kotlin.idea.refactoring.move.moveClassesOrPackages.KotlinAwareJavaMoveClassesOrPackagesHandler"
|
||||
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.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.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<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");
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
Reference in New Issue
Block a user