Convert Receiver to Parameter: Use template instead of the dialog
#KT-9490 Fixed
This commit is contained in:
@@ -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
|
||||
}
|
||||
+77
-11
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-17
@@ -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,
|
||||
|
||||
+24
@@ -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")
|
||||
}
|
||||
Reference in New Issue
Block a user