Convert Receiver to Parameter: Use template instead of the dialog

#KT-9490 Fixed
This commit is contained in:
Alexey Sedunov
2016-08-24 17:29:07 +03:00
parent 63092f6985
commit 639186ddae
5 changed files with 111 additions and 28 deletions
+2
View File
@@ -103,6 +103,8 @@ These artifacts include extensions for the types available in the latter JDKs, s
#### Intention actions, inspections and quickfixes #### Intention actions, inspections and quickfixes
- [`KT-9490`](https://youtrack.jetbrains.com/issue/KT-9490) Convert receiver to parameter: use template instead of the dialog
##### New features ##### New features
- [`KT-11525`](https://youtrack.jetbrains.com/issue/KT-11525) Implement "Create type parameter" quickfix - [`KT-11525`](https://youtrack.jetbrains.com/issue/KT-11525) Implement "Create type parameter" quickfix
@@ -285,4 +285,11 @@ fun KtTypeParameterListOwner.addTypeParameter(typeParameter: KtTypeParameter): K
else -> null else -> null
} ?: return null } ?: return null
return (addAfter(list, leftAnchor) as KtTypeParameterList).parameters.first() return (addAfter(list, leftAnchor) as KtTypeParameterList).parameters.first()
}
fun KtNamedFunction.getOrCreateValueParameterList(): KtParameterList {
valueParameterList?.let { return it }
val parameterList = KtPsiFactory(this).createParameterList("()")
val anchor = nameIdentifier ?: funKeyword!!
return addAfter(parameterList, anchor) as KtParameterList
} }
@@ -17,27 +17,43 @@
package org.jetbrains.kotlin.idea.intentions package org.jetbrains.kotlin.idea.intentions
import com.intellij.codeInsight.intention.LowPriorityAction import com.intellij.codeInsight.intention.LowPriorityAction
import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateBuilderImpl
import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureConfiguration import org.jetbrains.kotlin.idea.core.getOrCreateValueParameterList
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinMethodDescriptor import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
import org.jetbrains.kotlin.idea.refactoring.changeSignature.modify import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.refactoring.changeSignature.runChangeSignature import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.psi.typeRefHelpers.setReceiverTypeReference
class ConvertReceiverToParameterIntention : SelfTargetingOffsetIndependentIntention<KtTypeReference>(KtTypeReference::class.java, "Convert receiver to parameter"), LowPriorityAction { class ConvertReceiverToParameterIntention : SelfTargetingOffsetIndependentIntention<KtTypeReference>(KtTypeReference::class.java, "Convert receiver to parameter"), LowPriorityAction {
override fun isApplicableTo(element: KtTypeReference): Boolean { override fun isApplicableTo(element: KtTypeReference): Boolean {
return (element.parent as? KtNamedFunction)?.receiverTypeReference == element return (element.parent as? KtNamedFunction)?.receiverTypeReference == element
} }
private fun configureChangeSignature(): KotlinChangeSignatureConfiguration { private fun configureChangeSignature(newName: String? = null): KotlinChangeSignatureConfiguration {
return object : KotlinChangeSignatureConfiguration { return object : KotlinChangeSignatureConfiguration {
override fun configure(originalDescriptor: KotlinMethodDescriptor): KotlinMethodDescriptor { override fun configure(originalDescriptor: KotlinMethodDescriptor): KotlinMethodDescriptor {
return originalDescriptor.modify { it.receiver = null } return originalDescriptor.modify {
it.receiver = null
if (newName != null) {
it.parameters.last().name = newName
}
}
} }
override fun performSilently(affectedFunctions: Collection<PsiElement>) = true
} }
} }
@@ -45,8 +61,58 @@ class ConvertReceiverToParameterIntention : SelfTargetingOffsetIndependentIntent
override fun applyTo(element: KtTypeReference, editor: Editor?) { override fun applyTo(element: KtTypeReference, editor: Editor?) {
val function = element.parent as? KtNamedFunction ?: return val function = element.parent as? KtNamedFunction ?: return
val context = function.analyze() val descriptor = function.resolveToDescriptor() as FunctionDescriptor
val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, function] as? FunctionDescriptor ?: return
runChangeSignature(element.project, descriptor, configureChangeSignature(), element, text) val project = function.project
if (editor != null && !ApplicationManager.getApplication().isUnitTestMode) {
val receiverNames = suggestReceiverNames(project, descriptor)
val defaultReceiverName = receiverNames.first()
val receiverTypeRef = function.receiverTypeReference!!
val psiFactory = KtPsiFactory(element)
val newParameter = psiFactory.createParameter("$defaultReceiverName: Dummy").apply { typeReference!!.replace(receiverTypeRef) }
project.executeWriteCommand(text) {
function.setReceiverTypeReference(null)
val addedParameter = function.getOrCreateValueParameterList().addParameter(newParameter)
with(PsiDocumentManager.getInstance(project)) {
commitDocument(editor.document)
doPostponedOperationsAndUnblockDocument(editor.document)
}
editor.caretModel.moveToOffset(function.startOffset)
val templateBuilder = TemplateBuilderImpl(function)
templateBuilder.replaceElement(addedParameter.nameIdentifier!!, ChooseStringExpression(receiverNames))
TemplateManager.getInstance(project).startTemplate(
editor,
templateBuilder.buildInlineTemplate(),
object: TemplateEditingAdapter() {
private fun revertChanges() {
runWriteAction {
function.setReceiverTypeReference(addedParameter.typeReference)
function.valueParameterList!!.removeParameter(addedParameter)
}
}
override fun templateFinished(template: Template?, brokenOff: Boolean) {
val newName = addedParameter.name
revertChanges()
if (!brokenOff) {
runChangeSignature(element.project, descriptor, configureChangeSignature(newName), function.receiverTypeReference!!, text)
}
}
override fun templateCancelled(template: Template?) {
revertChanges()
}
}
)
}
}
else {
runChangeSignature(element.project, descriptor, configureChangeSignature(), element, text)
}
} }
} }
@@ -26,23 +26,15 @@ import org.jetbrains.kotlin.asJava.namedUnwrappedElement
import org.jetbrains.kotlin.asJava.toLightMethods import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtCallableDeclaration import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
import java.util.* import java.util.*
class KotlinChangeSignatureData( class KotlinChangeSignatureData(
@@ -80,16 +72,8 @@ class KotlinChangeSignatureData(
} }
private fun createReceiverInfoIfNeeded(): KotlinParameterInfo? { private fun createReceiverInfoIfNeeded(): KotlinParameterInfo? {
val callable = baseDeclaration as? KtCallableDeclaration ?: return null
val bodyScope = (callable as? KtFunction)?.bodyExpression?.let { it.getResolutionScope(it.analyze(), it.getResolutionFacade()) }
val paramNames = baseDescriptor.valueParameters.map { it.name.asString() }
val validator = bodyScope?.let { bodyScope ->
CollectingNameValidator(paramNames) {
bodyScope.findVariable(Name.identifier(it), NoLookupLocation.FROM_IDE) == null
}
} ?: CollectingNameValidator(paramNames)
val receiverType = baseDescriptor.extensionReceiverParameter?.type ?: return null val receiverType = baseDescriptor.extensionReceiverParameter?.type ?: return null
val receiverName = KotlinNameSuggester.suggestNamesByType(receiverType, validator, "receiver").first() val receiverName = suggestReceiverNames(baseDeclaration.project, baseDescriptor).first()
val receiverTypeText = (baseDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.text val receiverTypeText = (baseDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.text
?: IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType) ?: IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType)
return KotlinParameterInfo(callableDescriptor = baseDescriptor, return KotlinParameterInfo(callableDescriptor = baseDescriptor,
@@ -16,19 +16,30 @@
package org.jetbrains.kotlin.idea.refactoring.changeSignature package org.jetbrains.kotlin.idea.refactoring.changeSignature
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.refactoring.changeSignature.CallerUsageInfo import com.intellij.refactoring.changeSignature.CallerUsageInfo
import com.intellij.refactoring.changeSignature.OverriderUsageInfo import com.intellij.refactoring.changeSignature.OverriderUsageInfo
import com.intellij.usageView.UsageInfo import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.core.CollectingNameValidator
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.DeferredJavaMethodKotlinCallerUsage import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.DeferredJavaMethodKotlinCallerUsage
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.JavaMethodKotlinUsageWithDelegate import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.JavaMethodKotlinUsageWithDelegate
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallerUsage import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallerUsage
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
import org.jetbrains.kotlin.types.* import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.substitutions.getCallableSubstitutor import org.jetbrains.kotlin.types.substitutions.getCallableSubstitutor
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
@@ -95,3 +106,16 @@ private object ForceTypeCopySubstitution : TypeSubstitution() {
override fun isEmpty() = false override fun isEmpty() = false
} }
fun suggestReceiverNames(project: Project, descriptor: CallableDescriptor): List<String> {
val callable = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor) as? KtCallableDeclaration ?: return emptyList()
val bodyScope = (callable as? KtFunction)?.bodyExpression?.let { it.getResolutionScope(it.analyze(), it.getResolutionFacade()) }
val paramNames = descriptor.valueParameters.map { it.name.asString() }
val validator = bodyScope?.let { bodyScope ->
CollectingNameValidator(paramNames) {
bodyScope.findVariable(Name.identifier(it), NoLookupLocation.FROM_IDE) == null
}
} ?: CollectingNameValidator(paramNames)
val receiverType = descriptor.extensionReceiverParameter?.type ?: return emptyList()
return KotlinNameSuggester.suggestNamesByType(receiverType, validator, "receiver")
}