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
- [`KT-9490`](https://youtrack.jetbrains.com/issue/KT-9490) Convert receiver to parameter: use template instead of the dialog
##### New features
- [`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
} ?: return null
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
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.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinChangeSignatureConfiguration
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinMethodDescriptor
import org.jetbrains.kotlin.idea.refactoring.changeSignature.modify
import org.jetbrains.kotlin.idea.refactoring.changeSignature.runChangeSignature
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.getOrCreateValueParameterList
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPsiFactory
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 {
override fun isApplicableTo(element: KtTypeReference): Boolean {
return (element.parent as? KtNamedFunction)?.receiverTypeReference == element
}
private fun configureChangeSignature(): KotlinChangeSignatureConfiguration {
private fun configureChangeSignature(newName: String? = null): KotlinChangeSignatureConfiguration {
return object : KotlinChangeSignatureConfiguration {
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?) {
val function = element.parent as? KtNamedFunction ?: return
val context = function.analyze()
val descriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, function] as? FunctionDescriptor ?: return
runChangeSignature(element.project, descriptor, configureChangeSignature(), element, text)
val descriptor = function.resolveToDescriptor() as FunctionDescriptor
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.descriptors.*
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.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.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.KtClass
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.utils.findVariable
import java.util.*
class KotlinChangeSignatureData(
@@ -80,16 +72,8 @@ class KotlinChangeSignatureData(
}
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 receiverName = KotlinNameSuggester.suggestNamesByType(receiverType, validator, "receiver").first()
val receiverName = suggestReceiverNames(baseDeclaration.project, baseDescriptor).first()
val receiverTypeText = (baseDeclaration as? KtCallableDeclaration)?.receiverTypeReference?.text
?: IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(receiverType)
return KotlinParameterInfo(callableDescriptor = baseDescriptor,
@@ -16,19 +16,30 @@
package org.jetbrains.kotlin.idea.refactoring.changeSignature
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.refactoring.changeSignature.CallerUsageInfo
import com.intellij.refactoring.changeSignature.OverriderUsageInfo
import com.intellij.usageView.UsageInfo
import org.jetbrains.kotlin.descriptors.CallableDescriptor
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.JavaMethodKotlinUsageWithDelegate
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallableDefinitionUsage
import org.jetbrains.kotlin.idea.refactoring.changeSignature.usages.KotlinCallerUsage
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.name.Name
import org.jetbrains.kotlin.psi.*
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.substitutions.getCallableSubstitutor
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
@@ -95,3 +106,16 @@ private object ForceTypeCopySubstitution : TypeSubstitution() {
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")
}