Suggested Rename and Change Signature implementation for Kotlin
This commit is contained in:
@@ -88,5 +88,6 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
|
||||
<fileTypeUsageSchemaDescriptor schema="Gradle Script" implementationClass="org.jetbrains.kotlin.idea.core.script.KotlinGradleScriptFileTypeSchemaDetector"/>
|
||||
|
||||
<completion.ml.model implementation="org.jetbrains.kotlin.idea.completion.ml.KotlinMLRankingProvider"/>
|
||||
<suggestedRefactoringSupport language="kotlin" implementationClass="org.jetbrains.kotlin.idea.refactoring.suggested.KotlinSuggestedRefactoringSupport"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport
|
||||
import com.intellij.refactoring.suggested.SignatureChangePresentationModel.Effect
|
||||
import com.intellij.refactoring.suggested.SignatureChangePresentationModel.TextFragment.Leaf
|
||||
import com.intellij.refactoring.suggested.SignaturePresentationBuilder
|
||||
|
||||
internal class KotlinSignaturePresentationBuilder(
|
||||
signature: SuggestedRefactoringSupport.Signature,
|
||||
otherSignature: SuggestedRefactoringSupport.Signature,
|
||||
isOldSignature: Boolean
|
||||
) : SignaturePresentationBuilder(signature, otherSignature, isOldSignature)
|
||||
{
|
||||
override fun buildPresentation() {
|
||||
val declarationType = (signature.additionalData as KotlinSignatureAdditionalData).declarationType
|
||||
val keyword = declarationType.prefixKeyword
|
||||
if (keyword != null) {
|
||||
fragments += Leaf("$keyword ")
|
||||
}
|
||||
|
||||
signature.receiverType?.let {
|
||||
when (val effect = effect(it, otherSignature.receiverType)) {
|
||||
Effect.Modified -> {
|
||||
fragments += Leaf(it, effect)
|
||||
fragments += Leaf(".")
|
||||
}
|
||||
|
||||
else -> {
|
||||
fragments += Leaf("$it.", effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val name = if (declarationType == DeclarationType.SECONDARY_CONSTRUCTOR) "constructor" else signature.name
|
||||
fragments += Leaf(name, effect(signature.name, otherSignature.name))
|
||||
|
||||
if (declarationType.isFunction) {
|
||||
buildParameterList { fragments, parameter, correspondingParameter ->
|
||||
if (parameter.modifiers.isNotEmpty()) {
|
||||
fragments += leaf(parameter.modifiers, correspondingParameter?.modifiers ?: parameter.modifiers)
|
||||
fragments += Leaf(" ")
|
||||
}
|
||||
|
||||
fragments += leaf(parameter.name, correspondingParameter?.name ?: parameter.name)
|
||||
|
||||
fragments += Leaf(": ")
|
||||
|
||||
fragments += leaf(parameter.type, correspondingParameter?.type ?: parameter.type)
|
||||
|
||||
val defaultValue = parameter.defaultValue
|
||||
if (defaultValue != null) {
|
||||
val defaultValueEffect = if (correspondingParameter != null)
|
||||
effect(defaultValue, correspondingParameter.defaultValue)
|
||||
else
|
||||
Effect.None
|
||||
fragments += Leaf(" = ", defaultValueEffect.takeIf { it != Effect.Modified } ?: Effect.None)
|
||||
fragments += Leaf(defaultValue, defaultValueEffect)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
require(signature.parameters.isEmpty())
|
||||
}
|
||||
|
||||
signature.type?.let { type ->
|
||||
when (val effect = effect(type, otherSignature.type)) {
|
||||
Effect.Added, Effect.Removed, Effect.None -> {
|
||||
fragments += Leaf(": ${signature.type}", effect)
|
||||
}
|
||||
|
||||
Effect.Modified -> {
|
||||
fragments += Leaf(": ")
|
||||
fragments += Leaf(type, effect)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.refactoring.suggested.*
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Parameter
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.project.forcedModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.project.getModuleInfo
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.refactoring.isInterfaceClass
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.hasBody
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
|
||||
class KotlinSuggestedRefactoringAvailability(refactoringSupport: SuggestedRefactoringSupport) :
|
||||
SuggestedRefactoringAvailability(refactoringSupport)
|
||||
{
|
||||
override fun refineSignaturesWithResolve(state: SuggestedRefactoringState): SuggestedRefactoringState {
|
||||
val newDeclaration = state.declaration as? KtCallableDeclaration ?: return state
|
||||
val oldDeclaration = state.createRestoredDeclarationCopy(refactoringSupport) as KtCallableDeclaration
|
||||
oldDeclaration.containingKtFile.forcedModuleInfo = newDeclaration.getModuleInfo()
|
||||
|
||||
val descriptorWithOldSignature = oldDeclaration.resolveToDescriptorIfAny() as CallableDescriptor
|
||||
val descriptorWithNewSignature = newDeclaration.resolveToDescriptorIfAny() as CallableDescriptor
|
||||
|
||||
val oldSignature = state.oldSignature
|
||||
val newSignature = state.newSignature
|
||||
|
||||
val (oldReturnType, newReturnType) = refineType(
|
||||
oldSignature.type,
|
||||
newSignature.type,
|
||||
descriptorWithOldSignature.returnType,
|
||||
descriptorWithNewSignature.returnType
|
||||
)
|
||||
|
||||
val improvedOldParameterTypesById = mutableMapOf<Any, String>()
|
||||
val improvedNewParameterTypesById = mutableMapOf<Any, String>()
|
||||
for (oldParameter in oldSignature.parameters) {
|
||||
val id = oldParameter.id
|
||||
val newParameter = newSignature.parameterById(id) ?: continue
|
||||
val oldIndex = oldSignature.parameterIndex(oldParameter)
|
||||
val newIndex = newSignature.parameterIndex(newParameter)
|
||||
val (oldType, newType) = refineType(
|
||||
oldParameter.type,
|
||||
newParameter.type,
|
||||
descriptorWithOldSignature.valueParameters[oldIndex].type,
|
||||
descriptorWithNewSignature.valueParameters[newIndex].type
|
||||
)
|
||||
improvedOldParameterTypesById[id] = oldType!!
|
||||
improvedNewParameterTypesById[id] = newType!!
|
||||
}
|
||||
val oldParameters = oldSignature.parameters.map { it.copy(type = improvedOldParameterTypesById[it.id] ?: it.type) }
|
||||
val newParameters = newSignature.parameters.map { it.copy(type = improvedNewParameterTypesById[it.id] ?: it.type) }
|
||||
|
||||
val oldAdditionalData = oldSignature.additionalData as KotlinSignatureAdditionalData?
|
||||
val newAdditionalData = newSignature.additionalData as KotlinSignatureAdditionalData?
|
||||
val (oldReceiverType, newReceiverType) = refineType(
|
||||
oldAdditionalData?.receiverType,
|
||||
newAdditionalData?.receiverType,
|
||||
descriptorWithOldSignature.extensionReceiverParameter?.type,
|
||||
descriptorWithNewSignature.extensionReceiverParameter?.type
|
||||
)
|
||||
|
||||
return state.copy(
|
||||
oldSignature = Signature.create(
|
||||
oldSignature.name,
|
||||
oldReturnType,
|
||||
oldParameters,
|
||||
oldAdditionalData?.copy(receiverType = oldReceiverType)
|
||||
)!!,
|
||||
newSignature = Signature.create(
|
||||
newSignature.name,
|
||||
newReturnType,
|
||||
newParameters,
|
||||
newAdditionalData?.copy(receiverType = newReceiverType)
|
||||
)!!
|
||||
)
|
||||
}
|
||||
|
||||
private fun refineType(
|
||||
oldTypeInCode: String?,
|
||||
newTypeInCode: String?,
|
||||
oldTypeResolved: KotlinType?,
|
||||
newTypeResolved: KotlinType?
|
||||
): Pair<String?, String?> {
|
||||
if (oldTypeResolved?.isError == true || newTypeResolved?.isError == true) {
|
||||
return oldTypeInCode to newTypeInCode
|
||||
}
|
||||
|
||||
val oldTypeFQ = oldTypeResolved?.fqText()
|
||||
val newTypeFQ = newTypeResolved?.fqText()
|
||||
|
||||
if (oldTypeInCode != newTypeInCode) {
|
||||
if (oldTypeFQ == newTypeFQ) {
|
||||
return newTypeInCode to newTypeInCode
|
||||
}
|
||||
} else {
|
||||
if (oldTypeFQ != newTypeFQ) {
|
||||
return oldTypeFQ to newTypeFQ
|
||||
}
|
||||
}
|
||||
|
||||
return oldTypeInCode to newTypeInCode
|
||||
}
|
||||
|
||||
private fun KotlinType.fqText() = DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(this)
|
||||
|
||||
override fun detectAvailableRefactoring(state: SuggestedRefactoringState): SuggestedRefactoringData? {
|
||||
val declaration = state.declaration
|
||||
if (declaration !is KtCallableDeclaration || KotlinSuggestedRefactoringSupport.isOnlyRenameSupported(declaration)) {
|
||||
return SuggestedRenameData(declaration as PsiNamedElement, state.oldSignature.name)
|
||||
}
|
||||
|
||||
val oldSignature = state.oldSignature
|
||||
val newSignature = state.newSignature
|
||||
val updateUsagesData = SuggestedChangeSignatureData.create(state, USAGES)
|
||||
|
||||
if (hasParameterAddedRemovedOrReordered(oldSignature, newSignature)) return updateUsagesData
|
||||
|
||||
// for non-virtual function we can add or remove receiver for usages but not change its type
|
||||
if ((oldSignature.receiverType == null) != (newSignature.receiverType == null)) return updateUsagesData
|
||||
|
||||
val (nameChanges, renameData) = nameChanges(oldSignature, newSignature, declaration, declaration.valueParameters)
|
||||
|
||||
if (hasTypeChanges(oldSignature, newSignature)) {
|
||||
return if (nameChanges > 0)
|
||||
updateUsagesData
|
||||
else
|
||||
declaration.overridesName()?.let { updateUsagesData.copy(nameOfStuffToUpdate = it) }
|
||||
}
|
||||
|
||||
return when {
|
||||
renameData != null -> renameData
|
||||
nameChanges > 0 -> updateUsagesData
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtCallableDeclaration.overridesName(): String? {
|
||||
return when {
|
||||
hasModifier(KtTokens.ABSTRACT_KEYWORD) -> IMPLEMENTATIONS
|
||||
hasModifier(KtTokens.OPEN_KEYWORD) -> OVERRIDES
|
||||
containingClassOrObject?.isInterfaceClass() == true -> if (hasBody()) OVERRIDES else IMPLEMENTATIONS
|
||||
hasModifier(KtTokens.EXPECT_KEYWORD) -> "actual declarations"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasTypeChanges(oldSignature: Signature, newSignature: Signature): Boolean {
|
||||
return super.hasTypeChanges(oldSignature, newSignature) || oldSignature.receiverType != newSignature.receiverType
|
||||
}
|
||||
|
||||
override fun hasParameterTypeChanges(oldParam: Parameter, newParam: Parameter): Boolean {
|
||||
return super.hasParameterTypeChanges(oldParam, newParam) || oldParam.modifiers != newParam.modifiers
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.refactoring.suggested.SuggestedChangeSignatureData
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringExecution
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.types.isError
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
class KotlinSuggestedRefactoringExecution(
|
||||
refactoringSupport: KotlinSuggestedRefactoringSupport
|
||||
) : SuggestedRefactoringExecution(refactoringSupport) {
|
||||
|
||||
private data class PrepareChangeSignatureResult(
|
||||
val returnTypeInfo: KotlinTypeInfo,
|
||||
val parameterTypeInfos: List<KotlinTypeInfo>
|
||||
)
|
||||
|
||||
override fun prepareChangeSignature(data: SuggestedChangeSignatureData): Any {
|
||||
val declaration = data.declaration as KtCallableDeclaration
|
||||
val descriptorWithNewSignature = declaration.resolveToDescriptorIfAny() as CallableDescriptor
|
||||
val newSignature = data.newSignature
|
||||
|
||||
val returnType = descriptorWithNewSignature.returnType
|
||||
val returnTypeInfo = if (returnType != null && !returnType.isError) {
|
||||
KotlinTypeInfo(type = returnType, isCovariant = true)
|
||||
} else {
|
||||
KotlinTypeInfo(text = newSignature.type, isCovariant = true)
|
||||
}
|
||||
|
||||
require(descriptorWithNewSignature.valueParameters.size == newSignature.parameters.size)
|
||||
val parameterTypeInfos = descriptorWithNewSignature.valueParameters.zip(newSignature.parameters)
|
||||
.map { (parameterDescriptor, parameterData) ->
|
||||
val type = parameterDescriptor.varargElementType ?: parameterDescriptor.type
|
||||
if (!type.isError) {
|
||||
KotlinTypeInfo(type = type, isCovariant = true)
|
||||
} else {
|
||||
KotlinTypeInfo(text = parameterData.type, isCovariant = true)
|
||||
}
|
||||
}
|
||||
|
||||
return PrepareChangeSignatureResult(returnTypeInfo, parameterTypeInfos)
|
||||
}
|
||||
|
||||
override fun performChangeSignature(
|
||||
data: SuggestedChangeSignatureData,
|
||||
newParameterValues: List<NewParameterValue>,
|
||||
preparedData: Any?
|
||||
) {
|
||||
val (returnTypeInfo, parameterTypeInfos) = preparedData as PrepareChangeSignatureResult
|
||||
|
||||
val declaration = data.declaration as KtDeclaration
|
||||
val descriptor = declaration.resolveToDescriptorIfAny() as CallableDescriptor
|
||||
val project = declaration.project
|
||||
|
||||
val configuration = object : KotlinChangeSignatureConfiguration {
|
||||
override fun performSilently(affectedFunctions: Collection<PsiElement>) = true
|
||||
}
|
||||
val changeSignature = KotlinChangeSignature(project, descriptor, configuration, declaration, null)
|
||||
val methodDescriptor = changeSignature.adjustDescriptor(listOf(descriptor))!!
|
||||
|
||||
val parameters = mutableListOf<KotlinParameterInfo>()
|
||||
var newParameterValueIndex = 0
|
||||
|
||||
val receiver: KotlinParameterInfo? = if (data.newSignature.receiverType != null) {
|
||||
val newTypeInfo = KotlinTypeInfo(text = data.newSignature.receiverType, isCovariant = false)
|
||||
if (data.oldSignature.receiverType != null) {
|
||||
methodDescriptor.receiver!!.apply { currentTypeInfo = newTypeInfo }
|
||||
} else {
|
||||
KotlinParameterInfo(descriptor, -1, "", newTypeInfo)
|
||||
.withDefaultValue(newParameterValues[newParameterValueIndex++])
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
parameters.addIfNotNull(receiver)
|
||||
|
||||
val psiFactory = KtPsiFactory(project)
|
||||
|
||||
for ((index, parameter) in data.newSignature.parameters.withIndex()) {
|
||||
val initialIndex = data.oldSignature.parameterById(parameter.id)
|
||||
?.let { data.oldSignature.parameterIndex(it) }
|
||||
|
||||
val defaultValue = parameter.defaultValue?.let { psiFactory.createExpression(it) }
|
||||
|
||||
val modifierList = parameter.modifiers
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.let { psiFactory.createModifierList(it) }
|
||||
|
||||
val parameterInfo = if (initialIndex == null) {
|
||||
KotlinParameterInfo(
|
||||
descriptor,
|
||||
-1,
|
||||
parameter.name,
|
||||
parameterTypeInfos[index],
|
||||
defaultValueForParameter = defaultValue,
|
||||
modifierList = modifierList
|
||||
).withDefaultValue(newParameterValues[newParameterValueIndex++])
|
||||
} else {
|
||||
KotlinParameterInfo(
|
||||
descriptor,
|
||||
initialIndex,
|
||||
parameter.name,
|
||||
methodDescriptor.parameters[initialIndex].originalTypeInfo,
|
||||
defaultValueForParameter = defaultValue,
|
||||
modifierList = modifierList
|
||||
).apply {
|
||||
currentTypeInfo = parameterTypeInfos[index]
|
||||
}
|
||||
}
|
||||
parameters.add(parameterInfo)
|
||||
}
|
||||
|
||||
val changeInfo = KotlinChangeInfo(
|
||||
methodDescriptor,
|
||||
context = declaration,
|
||||
name = data.newSignature.name,
|
||||
newReturnTypeInfo = returnTypeInfo,
|
||||
parameterInfos = parameters,
|
||||
receiver = receiver
|
||||
)
|
||||
val processor = KotlinChangeSignatureProcessor(project, changeInfo, "")
|
||||
processor.run()
|
||||
}
|
||||
|
||||
//TODO: we can't just set defaultValueForCall due to bug in KotlinParameterInfo
|
||||
private fun KotlinParameterInfo.withDefaultValue(value: NewParameterValue): KotlinParameterInfo {
|
||||
when (value) {
|
||||
is NewParameterValue.AnyVariable -> {
|
||||
isUseAnySingleVariable = true
|
||||
return this
|
||||
}
|
||||
|
||||
is NewParameterValue.Expression -> {
|
||||
val defaultValueForCall = value.expression as KtExpression
|
||||
return KotlinParameterInfo(
|
||||
callableDescriptor, originalIndex, name, originalTypeInfo,
|
||||
defaultValueForParameter, defaultValueForCall, valOrVar, modifierList
|
||||
).apply {
|
||||
currentTypeInfo = this@withDefaultValue.currentTypeInfo
|
||||
}
|
||||
}
|
||||
|
||||
is NewParameterValue.None -> {
|
||||
defaultValueForCall = null
|
||||
return this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringState
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringStateChanges
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Parameter
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.psiUtil.children
|
||||
|
||||
class KotlinSuggestedRefactoringStateChanges(refactoringSupport: SuggestedRefactoringSupport) :
|
||||
SuggestedRefactoringStateChanges(refactoringSupport)
|
||||
{
|
||||
override fun createInitialState(declaration: PsiElement): SuggestedRefactoringState? {
|
||||
declaration as KtDeclaration
|
||||
if (declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return null // currently not supported
|
||||
if (declaration.hasModifier(KtTokens.ACTUAL_KEYWORD)) return null // currently not supported
|
||||
return super.createInitialState(declaration)
|
||||
}
|
||||
|
||||
override fun signature(declaration: PsiElement, prevState: SuggestedRefactoringState?): Signature? {
|
||||
declaration as KtDeclaration
|
||||
val name = declaration.name ?: return null
|
||||
if (declaration !is KtCallableDeclaration || KotlinSuggestedRefactoringSupport.isOnlyRenameSupported(declaration)) {
|
||||
return Signature.create(name, null, emptyList(), null)
|
||||
}
|
||||
|
||||
val parameters = declaration.valueParameters.map { it.extractParameterData() ?: return null }
|
||||
val type = declaration.typeReference?.text
|
||||
val receiverType = declaration.receiverTypeReference?.text
|
||||
val signature = Signature.create(
|
||||
name,
|
||||
type,
|
||||
parameters,
|
||||
KotlinSignatureAdditionalData(DeclarationType.fromDeclaration(declaration), receiverType)
|
||||
) ?: return null
|
||||
|
||||
return if (prevState == null) signature else matchParametersWithPrevState(signature, declaration, prevState)
|
||||
}
|
||||
|
||||
override fun parameterMarkerRanges(declaration: PsiElement): List<TextRange?> {
|
||||
if (declaration !is KtFunction) return emptyList()
|
||||
return declaration.valueParameters.map { it.colon?.textRange }
|
||||
}
|
||||
|
||||
private fun KtParameter.extractParameterData(): Parameter? {
|
||||
val modifiers = modifierList?.node?.children()
|
||||
?.filter { it.elementType is KtModifierKeywordToken && it.text in modifiersToInclude }
|
||||
?.joinToString(separator = " ") { it.text } ?: ""
|
||||
return Parameter(
|
||||
Any(),
|
||||
name ?: return null,
|
||||
typeReference?.text ?: return null,
|
||||
KotlinParameterAdditionalData(defaultValue?.text/*TODO: strip comments etc*/, modifiers)
|
||||
)
|
||||
}
|
||||
|
||||
private val modifiersToInclude = setOf(KtTokens.VARARG_KEYWORD.value)
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Parameter
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
|
||||
class KotlinSuggestedRefactoringSupport : SuggestedRefactoringSupport {
|
||||
override fun isDeclaration(psiElement: PsiElement): Boolean {
|
||||
if (psiElement !is KtDeclaration) return false
|
||||
if (psiElement is KtParameter && psiElement.ownerFunction != null) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun signatureRange(declaration: PsiElement): TextRange? {
|
||||
when (declaration) {
|
||||
is KtPrimaryConstructor -> return declaration.textRange
|
||||
|
||||
is KtSecondaryConstructor -> return declaration.valueParameterList?.textRange
|
||||
|
||||
is KtCallableDeclaration -> {
|
||||
if (isOnlyRenameSupported(declaration)) {
|
||||
return declaration.nameIdentifier?.textRange
|
||||
}
|
||||
|
||||
val start = declaration.receiverTypeReference?.textRange?.startOffset
|
||||
?: declaration.nameIdentifier?.textRange?.startOffset
|
||||
?: return null
|
||||
val end = (declaration.typeReference ?: declaration.valueParameterList ?: declaration.nameIdentifier)
|
||||
?.textRange?.endOffset
|
||||
?: return null
|
||||
return TextRange(start, end)
|
||||
}
|
||||
|
||||
is KtNamedDeclaration -> return declaration.nameIdentifier?.textRange
|
||||
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun importsRange(psiFile: PsiFile): TextRange? {
|
||||
return (psiFile as KtFile).importList?.textRange
|
||||
}
|
||||
|
||||
override fun nameRange(declaration: PsiElement): TextRange? {
|
||||
val identifier = when (declaration) {
|
||||
is KtPrimaryConstructor -> declaration.containingClassOrObject?.nameIdentifier
|
||||
is KtSecondaryConstructor -> declaration.getConstructorKeyword()
|
||||
is KtNamedDeclaration -> declaration.nameIdentifier
|
||||
else -> null
|
||||
}
|
||||
return identifier?.textRange
|
||||
}
|
||||
|
||||
override fun hasSyntaxError(declaration: PsiElement): Boolean {
|
||||
if (super.hasSyntaxError(declaration)) return true
|
||||
|
||||
// do not suggest renaming of local variable which has neither type nor initializer
|
||||
// it's important because such variable declarations may appear on typing "val name = " before an expression
|
||||
if (declaration is KtProperty && declaration.isLocal) {
|
||||
if (declaration.typeReference == null && declaration.initializer == null) return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isIdentifierStart(c: Char) = c.isJavaIdentifierStart()
|
||||
override fun isIdentifierPart(c: Char) = c.isJavaIdentifierPart()
|
||||
|
||||
override val stateChanges = KotlinSuggestedRefactoringStateChanges(this)
|
||||
override val availability = KotlinSuggestedRefactoringAvailability(this)
|
||||
override val ui get() = KotlinSuggestedRefactoringUI
|
||||
override val execution = KotlinSuggestedRefactoringExecution(this)
|
||||
|
||||
companion object {
|
||||
fun isOnlyRenameSupported(declaration: KtCallableDeclaration): Boolean {
|
||||
// for local variable - only rename
|
||||
return declaration is KtVariableDeclaration && KtPsiUtil.isLocal(declaration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DeclarationType {
|
||||
FUN {
|
||||
override val prefixKeyword get() = "fun"
|
||||
override val isFunction get() = true
|
||||
},
|
||||
VAL {
|
||||
override val prefixKeyword get() = "val"
|
||||
override val isFunction get() = false
|
||||
},
|
||||
VAR {
|
||||
override val prefixKeyword get() = "var"
|
||||
override val isFunction get() = false
|
||||
},
|
||||
PRIMARY_CONSTRUCTOR {
|
||||
override val prefixKeyword: String?
|
||||
get() = null
|
||||
override val isFunction get() = true
|
||||
},
|
||||
SECONDARY_CONSTRUCTOR {
|
||||
override val prefixKeyword: String?
|
||||
get() = null
|
||||
override val isFunction get() = true
|
||||
},
|
||||
OTHER {
|
||||
override val prefixKeyword: String?
|
||||
get() = null
|
||||
override val isFunction get() = false
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
abstract val prefixKeyword: String?
|
||||
abstract val isFunction: Boolean
|
||||
|
||||
companion object {
|
||||
fun fromDeclaration(declaration: KtDeclaration): DeclarationType = when (declaration) {
|
||||
is KtPrimaryConstructor -> PRIMARY_CONSTRUCTOR
|
||||
|
||||
is KtSecondaryConstructor -> SECONDARY_CONSTRUCTOR
|
||||
|
||||
is KtNamedFunction -> FUN
|
||||
|
||||
is KtProperty -> if (declaration.isVar) VAR else VAL
|
||||
|
||||
else -> OTHER
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal data class KotlinSignatureAdditionalData(
|
||||
val declarationType: DeclarationType,
|
||||
val receiverType: String?
|
||||
) : SuggestedRefactoringSupport.SignatureAdditionalData
|
||||
|
||||
internal data class KotlinParameterAdditionalData(
|
||||
val defaultValue: String?,
|
||||
val modifiers: String
|
||||
) : SuggestedRefactoringSupport.ParameterAdditionalData
|
||||
|
||||
internal val Signature.receiverType: String?
|
||||
get() = (additionalData as KotlinSignatureAdditionalData?)?.receiverType
|
||||
|
||||
internal val Parameter.defaultValue: String?
|
||||
get() = (additionalData as KotlinParameterAdditionalData?)?.defaultValue
|
||||
|
||||
internal val Parameter.modifiers: String
|
||||
get() = (additionalData as KotlinParameterAdditionalData?)?.modifiers ?: ""
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.psi.PsiCodeFragment
|
||||
import com.intellij.refactoring.suggested.SuggestedChangeSignatureData
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringExecution.NewParameterValue
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringUI
|
||||
import com.intellij.refactoring.suggested.SignaturePresentationBuilder
|
||||
import org.jetbrains.kotlin.psi.KtExpressionCodeFragment
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
|
||||
object KotlinSuggestedRefactoringUI : SuggestedRefactoringUI() {
|
||||
override fun createSignaturePresentationBuilder(
|
||||
signature: Signature,
|
||||
otherSignature: Signature,
|
||||
isOldSignature: Boolean
|
||||
): SignaturePresentationBuilder {
|
||||
return KotlinSignaturePresentationBuilder(signature, otherSignature, isOldSignature)
|
||||
}
|
||||
|
||||
override fun extractNewParameterData(data: SuggestedChangeSignatureData): List<NewParameterData> {
|
||||
val newParameters = mutableListOf<NewParameterData>()
|
||||
|
||||
val declaration = data.declaration
|
||||
val factory = KtPsiFactory(declaration.project)
|
||||
|
||||
fun createCodeFragment() = factory.createExpressionCodeFragment("", declaration)
|
||||
|
||||
if (data.newSignature.receiverType != null && data.oldSignature.receiverType == null) {
|
||||
newParameters.add(NewParameterData("<receiver>", createCodeFragment(), false/*TODO*/))
|
||||
}
|
||||
|
||||
for (parameter in data.newSignature.parameters) {
|
||||
if (data.oldSignature.parameterById(parameter.id) == null) {
|
||||
newParameters.add(NewParameterData(parameter.name, createCodeFragment(), false/*TODO*/))
|
||||
}
|
||||
}
|
||||
|
||||
return newParameters
|
||||
}
|
||||
|
||||
override fun extractValue(fragment: PsiCodeFragment): NewParameterValue.Expression? {
|
||||
return (fragment as KtExpressionCodeFragment).getContentElement()
|
||||
?.let { NewParameterValue.Expression(it) }
|
||||
}
|
||||
}
|
||||
+874
@@ -0,0 +1,874 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Parameter
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature
|
||||
import com.intellij.refactoring.suggested.BaseSignatureChangePresentationTest
|
||||
|
||||
class KotlinSignatureChangePresentationTest : BaseSignatureChangePresentationTest() {
|
||||
override val refactoringSupport = KotlinSuggestedRefactoringSupport()
|
||||
|
||||
private fun signature(
|
||||
name: String,
|
||||
type: String?,
|
||||
parameters: List<Parameter>,
|
||||
receiverType: String? = null
|
||||
): Signature? {
|
||||
return Signature.create(
|
||||
name,
|
||||
type,
|
||||
parameters,
|
||||
KotlinSignatureAdditionalData(DeclarationType.FUN, receiverType)
|
||||
)
|
||||
}
|
||||
|
||||
fun testAddParameters() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
"String",
|
||||
listOf(Parameter(0, "p1", "Int")),
|
||||
"Any"
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
"String",
|
||||
listOf(
|
||||
Parameter(Any(), "p0", "Any"),
|
||||
Parameter(0, "p1", "Int"),
|
||||
Parameter(Any(), "p2", "Long")
|
||||
),
|
||||
"Any"
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'Any.'
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
': String'
|
||||
New:
|
||||
'fun '
|
||||
'Any.'
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group (added):
|
||||
'p0'
|
||||
': '
|
||||
'Any'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group (added):
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
': String'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testSwapParameters() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int"),
|
||||
Parameter(1, "p2", "Long")
|
||||
)
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(0, "p1", "Int")
|
||||
)
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group (moved):
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group (moved):
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testMoveParameter() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int"),
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(2, "p3", "Any")
|
||||
)
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(2, "p3", "Any"),
|
||||
Parameter(0, "p1", "Int")
|
||||
)
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group (moved):
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p3'
|
||||
': '
|
||||
'Any'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p3'
|
||||
': '
|
||||
'Any'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group (moved):
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testReorderParameters() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int"),
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(2, "p3", "Any"),
|
||||
Parameter(3, "p4", "Any")
|
||||
)
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(3, "p4", "Any"),
|
||||
Parameter(2, "p3", "Any"),
|
||||
Parameter(0, "p1", "Int")
|
||||
)
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group (id = 0, moved):
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group (id = 2, moved):
|
||||
'p3'
|
||||
': '
|
||||
'Any'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p4'
|
||||
': '
|
||||
'Any'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p4'
|
||||
': '
|
||||
'Any'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group (id = 2, moved):
|
||||
'p3'
|
||||
': '
|
||||
'Any'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group (id = 0, moved):
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeFunctionName() {
|
||||
val oldSignature = signature("foo", null, emptyList())!!
|
||||
val newSignature = signature("bar", null, emptyList())!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo' (modified)
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'bar' (modified)
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeReturnType() {
|
||||
val oldSignature = signature("foo", "Any", emptyList())!!
|
||||
val newSignature = signature("foo", "String", emptyList())!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
': '
|
||||
'Any' (modified)
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
': '
|
||||
'String' (modified)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testAddReturnType() {
|
||||
val oldSignature = signature("foo", null, emptyList())!!
|
||||
val newSignature = signature("foo", "String", emptyList())!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
': String' (added)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testRemoveReturnType() {
|
||||
val oldSignature = signature("foo", "Any", emptyList())!!
|
||||
val newSignature = signature("foo", null, emptyList())!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
': Any' (removed)
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterName() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int"),
|
||||
Parameter(1, "p2", "Long")
|
||||
)
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1New", "Int"),
|
||||
Parameter(1, "p2", "Long")
|
||||
)
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p1' (modified)
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p1New' (modified)
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeTwoParameterNames() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int"),
|
||||
Parameter(1, "p2", "Long")
|
||||
)
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1New", "Int"),
|
||||
Parameter(1, "p2New", "Long")
|
||||
)
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p1' (modified)
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2' (modified)
|
||||
': '
|
||||
'Long'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p1New' (modified)
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2New' (modified)
|
||||
': '
|
||||
'Long'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testMoveAndRenameParameter() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int"),
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(2, "p3", "Any")
|
||||
)
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(2, "p3", "Any"),
|
||||
Parameter(0, "p1New", "Int")
|
||||
)
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group (moved):
|
||||
'p1' (modified)
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p3'
|
||||
': '
|
||||
'Any'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p3'
|
||||
': '
|
||||
'Any'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group (moved):
|
||||
'p1New' (modified)
|
||||
': '
|
||||
'Int'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testMoveParameterAndChangeFunctionName() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int"),
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(2, "p3", "Any")
|
||||
)
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"fooNew",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(1, "p2", "Long"),
|
||||
Parameter(2, "p3", "Any"),
|
||||
Parameter(0, "p1", "Int")
|
||||
)
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo' (modified)
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group (id = 0, moved):
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p3'
|
||||
': '
|
||||
'Any'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'fooNew' (modified)
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Long'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p3'
|
||||
': '
|
||||
'Any'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group (id = 0, moved):
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testAddReceiver() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
emptyList()
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
emptyList(),
|
||||
"Any"
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'Any.' (added)
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testRemoveReceiver() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
emptyList(),
|
||||
"Any"
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
emptyList()
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'Any.' (removed)
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeReceiverType() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
emptyList(),
|
||||
"Any"
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
emptyList(),
|
||||
"String"
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'Any' (modified)
|
||||
'.'
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'String' (modified)
|
||||
'.'
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeDefaultValues() {
|
||||
val oldSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int", KotlinParameterAdditionalData("1", "")),
|
||||
Parameter(1, "p2", "Int", KotlinParameterAdditionalData("2", "")),
|
||||
Parameter(2, "p3", "Int", KotlinParameterAdditionalData("3", "")),
|
||||
Parameter(3, "p4", "Int")
|
||||
)
|
||||
)!!
|
||||
val newSignature = signature(
|
||||
"foo",
|
||||
null,
|
||||
listOf(
|
||||
Parameter(0, "p1", "Int", KotlinParameterAdditionalData("1", "")),
|
||||
Parameter(1, "p2", "Int", KotlinParameterAdditionalData("22", "")),
|
||||
Parameter(2, "p3", "Int"),
|
||||
Parameter(3, "p4", "Int", KotlinParameterAdditionalData("4", ""))
|
||||
)
|
||||
)!!
|
||||
doTest(
|
||||
oldSignature,
|
||||
newSignature,
|
||||
"""
|
||||
Old:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
' = '
|
||||
'1'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Int'
|
||||
' = '
|
||||
'2' (modified)
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p3'
|
||||
': '
|
||||
'Int'
|
||||
' = ' (removed)
|
||||
'3' (removed)
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p4'
|
||||
': '
|
||||
'Int'
|
||||
LineBreak('', false)
|
||||
')'
|
||||
New:
|
||||
'fun '
|
||||
'foo'
|
||||
'('
|
||||
LineBreak('', true)
|
||||
Group:
|
||||
'p1'
|
||||
': '
|
||||
'Int'
|
||||
' = '
|
||||
'1'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p2'
|
||||
': '
|
||||
'Int'
|
||||
' = '
|
||||
'22' (modified)
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p3'
|
||||
': '
|
||||
'Int'
|
||||
','
|
||||
LineBreak(' ', true)
|
||||
Group:
|
||||
'p4'
|
||||
': '
|
||||
'Int'
|
||||
' = ' (added)
|
||||
'4' (added)
|
||||
LineBreak('', false)
|
||||
')'
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.openapi.fileTypes.LanguageFileType
|
||||
import com.intellij.refactoring.suggested.BaseSuggestedRefactoringAvailabilityTest
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
|
||||
class KotlinSuggestedRefactoringAvailabilityTest : BaseSuggestedRefactoringAvailabilityTest() {
|
||||
override val fileType: LanguageFileType
|
||||
get() = KotlinFileType.INSTANCE
|
||||
|
||||
override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||
|
||||
fun testNotAvailableWithSyntaxError() {
|
||||
doTest(
|
||||
"""
|
||||
fun foo(p1: Int<caret>) {
|
||||
foo(1)
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
foo(2)
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
myFixture.type(", p2: Any")
|
||||
},
|
||||
{
|
||||
myFixture.type(", p")
|
||||
},
|
||||
expectedAvailability = Availability.Disabled
|
||||
)
|
||||
}
|
||||
|
||||
fun testInsertTrailingComma() {
|
||||
doTest(
|
||||
"""
|
||||
fun foo(p1: Int<caret>) {
|
||||
foo(1)
|
||||
}
|
||||
|
||||
fun bar() {
|
||||
foo(2)
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
myFixture.type(",")
|
||||
},
|
||||
expectedAvailability = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeNonVirtualPropertyType() {
|
||||
doTest(
|
||||
"val v: <caret>String = \"\"",
|
||||
{
|
||||
replaceTextAtCaret("String", "Any")
|
||||
},
|
||||
expectedAvailability = Availability.Disabled
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterTypeNonVirtual() {
|
||||
doTest(
|
||||
"fun foo(p: <caret>String) {}",
|
||||
{
|
||||
replaceTextAtCaret("String", "Any")
|
||||
},
|
||||
expectedAvailability = Availability.Disabled
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeReturnTypeNonVirtual() {
|
||||
doTest(
|
||||
"fun foo(): <caret>String = \"\"",
|
||||
{
|
||||
replaceTextAtCaret("String", "Any")
|
||||
},
|
||||
expectedAvailability = Availability.Disabled
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeLocalVariableType() {
|
||||
doTest(
|
||||
"""
|
||||
fun foo() {
|
||||
val local: <caret>Int
|
||||
local = 10
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("Int", "Long")
|
||||
},
|
||||
expectedAvailability = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testAddLocalVariableType() {
|
||||
doTest(
|
||||
"""
|
||||
fun foo() {
|
||||
var local<caret> = 10
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
myFixture.type(": Long")
|
||||
},
|
||||
expectedAvailability = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testTypeLocalVariableBeforeExpression() {
|
||||
doTest(
|
||||
"""
|
||||
""".trimIndent(),
|
||||
{
|
||||
myFixture.type("val ")
|
||||
},
|
||||
{
|
||||
myFixture.type("cod")
|
||||
},
|
||||
{
|
||||
myFixture.type("e = ")
|
||||
},
|
||||
expectedAvailability = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterTypeAndName() {
|
||||
doTest(
|
||||
"""
|
||||
interface I {
|
||||
fun foo(p: <caret>Int)
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("Int", "String")
|
||||
editor.caretModel.moveToOffset(editor.caretModel.offset - "p: ".length)
|
||||
replaceTextAtCaret("p", "pNew")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "usages"))
|
||||
)
|
||||
}
|
||||
|
||||
fun testRenameTwoParameters() {
|
||||
doTest(
|
||||
"""
|
||||
interface I {
|
||||
fun foo(<caret>p1: Int, p2: Int)
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("p1", "p1New")
|
||||
},
|
||||
{
|
||||
editor.caretModel.moveToOffset(editor.caretModel.offset + "p1New: Int, ".length)
|
||||
replaceTextAtCaret("p2", "p2New")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "usages"))
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterType() {
|
||||
doTest(
|
||||
"""
|
||||
class C {
|
||||
open fun foo(p1: <caret>Int, p2: Int) {
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("Int", "Any")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "overrides"))
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterTypeForAbstract() {
|
||||
doTest(
|
||||
"""
|
||||
abstract class C {
|
||||
abstract fun foo(p1: <caret>Int, p2: Int)
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("Int", "Any")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "implementations"))
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterTypeInInterface() {
|
||||
doTest(
|
||||
"""
|
||||
interface I {
|
||||
fun foo(p1: <caret>Int, p2: Int)
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("Int", "Any")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "implementations"))
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterTypeInInterfaceWithBody() {
|
||||
doTest(
|
||||
"""
|
||||
interface I {
|
||||
fun foo(p1: <caret>Int, p2: Int) {}
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("Int", "Any")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "overrides"))
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeTypeOfPropertyWithImplementationInInterface() {
|
||||
doTest(
|
||||
"""
|
||||
interface I {
|
||||
val p: <caret>Int
|
||||
get() = 0
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("Int", "Any")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("p", "overrides"))
|
||||
)
|
||||
}
|
||||
|
||||
fun testSpecifyExplicitType() {
|
||||
doTest(
|
||||
"""
|
||||
open class C {
|
||||
open fun foo()<caret> = 1
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
myFixture.type(": Int")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "overrides")),
|
||||
expectedAvailabilityAfterResolve = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testRemoveExplicitType() {
|
||||
doTest(
|
||||
"""
|
||||
open class C {
|
||||
open fun foo(): Int<caret> = 1
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
deleteTextBeforeCaret(": Int")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "overrides")),
|
||||
expectedAvailabilityAfterResolve = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testImportNestedClass() {
|
||||
doTest(
|
||||
"""
|
||||
package ppp
|
||||
|
||||
class C {
|
||||
class Nested
|
||||
}
|
||||
|
||||
interface I {
|
||||
fun foo(): <caret>C.Nested
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
addImport("ppp.C.Nested")
|
||||
},
|
||||
{
|
||||
replaceTextAtCaret("C.Nested", "Nested")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "implementations")),
|
||||
expectedAvailabilityAfterResolve = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testImportNestedClassForReceiverType() {
|
||||
doTest(
|
||||
"""
|
||||
package ppp
|
||||
|
||||
class C {
|
||||
class Nested
|
||||
}
|
||||
|
||||
interface I {
|
||||
fun <caret>C.Nested.foo()
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
addImport("ppp.C.Nested")
|
||||
},
|
||||
{
|
||||
replaceTextAtCaret("C.Nested", "Nested")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "implementations")),
|
||||
expectedAvailabilityAfterResolve = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testImportNestedClassForParameterType() {
|
||||
doTest(
|
||||
"""
|
||||
package ppp
|
||||
|
||||
class C {
|
||||
class Nested
|
||||
}
|
||||
|
||||
interface I {
|
||||
fun foo(p: <caret>C.Nested)
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
addImport("ppp.C.Nested")
|
||||
},
|
||||
{
|
||||
replaceTextAtCaret("C.Nested", "Nested")
|
||||
},
|
||||
expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "implementations")),
|
||||
expectedAvailabilityAfterResolve = Availability.NotAvailable
|
||||
)
|
||||
}
|
||||
|
||||
fun testImportAnotherType() {
|
||||
doTest(
|
||||
"""
|
||||
import java.util.Date
|
||||
|
||||
interface I {
|
||||
fun foo(): <caret>Date
|
||||
}
|
||||
""".trimIndent(),
|
||||
{
|
||||
replaceTextAtCaret("Date", "java.sql.Date")
|
||||
},
|
||||
{
|
||||
removeImport("java.util.Date")
|
||||
addImport("java.sql.Date")
|
||||
},
|
||||
{
|
||||
replaceTextAtCaret("java.sql.Date", "Date")
|
||||
},
|
||||
expectedAvailability = Availability.NotAvailable,
|
||||
expectedAvailabilityAfterResolve = Availability.Available((changeSignatureAvailableTooltip("foo", "implementations")))
|
||||
)
|
||||
}
|
||||
|
||||
private fun addImport(fqName: String) {
|
||||
(file as KtFile).importList!!.add(KtPsiFactory(project).createImportDirective(ImportPath.fromString(fqName)))
|
||||
}
|
||||
|
||||
private fun removeImport(fqName: String) {
|
||||
(file as KtFile).importList!!.imports.first { it.importedFqName?.asString() == fqName }.delete()
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.lang.Language
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.command.executeCommand
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Parameter
|
||||
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import com.intellij.refactoring.suggested.BaseSuggestedRefactoringChangeCollectorTest
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinSuggestedRefactoringChangeCollectorTest : BaseSuggestedRefactoringChangeCollectorTest<KtNamedFunction>() {
|
||||
override val fileType: FileType
|
||||
get() = KotlinFileType.INSTANCE
|
||||
|
||||
override val language: Language
|
||||
get() = KotlinLanguage.INSTANCE
|
||||
|
||||
override fun addDeclaration(file: PsiFile, text: String): KtNamedFunction {
|
||||
val psiFactory = KtPsiFactory(project)
|
||||
return (file as KtFile).add(psiFactory.createDeclaration(text)) as KtNamedFunction
|
||||
}
|
||||
|
||||
override fun Signature.presentation(labelForParameterId: (Any) -> String?): String {
|
||||
return buildString {
|
||||
append("fun ")
|
||||
val receiverType = (additionalData as KotlinSignatureAdditionalData?)?.receiverType
|
||||
if (receiverType != null) {
|
||||
append(receiverType)
|
||||
append(".")
|
||||
}
|
||||
append(name)
|
||||
append("(")
|
||||
parameters.joinTo(this, separator = ", ") { it.presentation(labelForParameterId(it.id)) }
|
||||
append(")")
|
||||
if (type != null) {
|
||||
append(": ")
|
||||
append(type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Parameter.presentation(label: String?): String {
|
||||
return buildString {
|
||||
if (modifiers.isNotEmpty()) {
|
||||
append(modifiers)
|
||||
append(" ")
|
||||
}
|
||||
append(name)
|
||||
append(": ")
|
||||
append(type)
|
||||
if (label != null) {
|
||||
append(" (")
|
||||
append(label)
|
||||
append(")")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createType(text: String): KtTypeReference {
|
||||
return KtPsiFactory(project).createType(text)
|
||||
}
|
||||
|
||||
private fun createParameter(text: String): KtParameter {
|
||||
return KtPsiFactory(project).createParameter(text)
|
||||
}
|
||||
|
||||
fun testAddParameter() {
|
||||
doTest(
|
||||
"fun foo(p1: Int) {}",
|
||||
{ it.valueParameterList!!.addParameter(createParameter("p2: Int")) },
|
||||
expectedOldSignature = "fun foo(p1: Int)",
|
||||
expectedNewSignature = "fun foo(p1: Int (initialIndex = 0), p2: Int (new))"
|
||||
)
|
||||
}
|
||||
|
||||
fun testRemoveParameter() {
|
||||
doTest(
|
||||
"fun foo(p1: Int, p2: Int) {}",
|
||||
{ it.valueParameterList!!.removeParameter(0) },
|
||||
expectedOldSignature = "fun foo(p1: Int, p2: Int)",
|
||||
expectedNewSignature = "fun foo(p2: Int (initialIndex = 1))"
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterType() {
|
||||
doTest(
|
||||
"fun foo(p1: Int, p2: Int) {}",
|
||||
{ it.valueParameters[1].typeReference = createType("Any?") },
|
||||
expectedOldSignature = "fun foo(p1: Int, p2: Int)",
|
||||
expectedNewSignature = "fun foo(p1: Int (initialIndex = 0), p2: Any? (initialIndex = 1))"
|
||||
)
|
||||
}
|
||||
|
||||
fun testChangeParameterNames() {
|
||||
doTest(
|
||||
"fun foo(p1: Int, p2: Int) {}",
|
||||
{ it.valueParameters[0].setName("newP1") },
|
||||
{ it.valueParameters[1].setName("newP2") },
|
||||
expectedOldSignature = "fun foo(p1: Int, p2: Int)",
|
||||
expectedNewSignature = "fun foo(newP1: Int (initialIndex = 0), newP2: Int (initialIndex = 1))"
|
||||
)
|
||||
}
|
||||
|
||||
fun testReplaceParameter() {
|
||||
doTest(
|
||||
"fun foo(p1: Int, p2: Int) {}",
|
||||
{ it.valueParameters[0].replace(createParameter("newP1: Long")) },
|
||||
expectedOldSignature = "fun foo(p1: Int, p2: Int)",
|
||||
expectedNewSignature = "fun foo(newP1: Long (new), p2: Int (initialIndex = 1))"
|
||||
)
|
||||
}
|
||||
|
||||
fun testReorderParametersChangeTypesAndNames() {
|
||||
doTest(
|
||||
"fun foo(p1: Int, p2: Int, p3: Int) {}",
|
||||
{
|
||||
editor.caretModel.moveToOffset(it.valueParameters[2].textOffset)
|
||||
myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT)
|
||||
myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT)
|
||||
},
|
||||
{
|
||||
executeCommand {
|
||||
runWriteAction {
|
||||
it.valueParameters[0].typeReference = createType("Any?")
|
||||
it.valueParameters[1].typeReference = createType("Long")
|
||||
it.valueParameters[2].typeReference = createType("Double")
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
executeCommand {
|
||||
runWriteAction {
|
||||
it.valueParameters[1].setName("newName")
|
||||
}
|
||||
}
|
||||
},
|
||||
wrapIntoCommandAndWriteAction = false,
|
||||
expectedOldSignature = "fun foo(p1: Int, p2: Int, p3: Int)",
|
||||
expectedNewSignature = "fun foo(p3: Any? (initialIndex = 2), newName: Long (initialIndex = 0), p2: Double (initialIndex = 1))"
|
||||
)
|
||||
}
|
||||
|
||||
fun testReorderParametersByCutPaste() {
|
||||
doTest(
|
||||
"fun foo(p1: Int, p2: String, p3: Char)",
|
||||
{
|
||||
val offset = it.valueParameters[1].textRange.endOffset
|
||||
editor.caretModel.moveToOffset(offset)
|
||||
editor.selectionModel.setSelection(offset, offset + ", p3: Char".length)
|
||||
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_CUT)
|
||||
},
|
||||
{
|
||||
val offset = it.valueParameters[0].textRange.endOffset
|
||||
editor.caretModel.moveToOffset(offset)
|
||||
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE)
|
||||
},
|
||||
wrapIntoCommandAndWriteAction = false,
|
||||
expectedOldSignature = "fun foo(p1: Int, p2: String, p3: Char)",
|
||||
expectedNewSignature = "fun foo(p1: Int (initialIndex = 0), p3: Char (initialIndex = 2), p2: String (initialIndex = 1))"
|
||||
)
|
||||
}
|
||||
|
||||
fun testReorderParametersByCutPasteAfterChangingName() {
|
||||
doTest(
|
||||
"fun foo(p1: Int, p2: String, p3: Char)",
|
||||
{
|
||||
executeCommand {
|
||||
runWriteAction {
|
||||
it.valueParameters[2].setName("p3New")
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
val offset = it.valueParameters[1].textRange.endOffset
|
||||
editor.caretModel.moveToOffset(offset)
|
||||
editor.selectionModel.setSelection(offset, offset + ", p3New: Char".length)
|
||||
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_CUT)
|
||||
},
|
||||
{
|
||||
val offset = it.valueParameters[0].textRange.endOffset
|
||||
editor.caretModel.moveToOffset(offset)
|
||||
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE)
|
||||
},
|
||||
wrapIntoCommandAndWriteAction = false,
|
||||
expectedOldSignature = "fun foo(p1: Int, p2: String, p3: Char)",
|
||||
expectedNewSignature = "fun foo(p1: Int (initialIndex = 0), p3New: Char (initialIndex = 2), p2: String (initialIndex = 1))"
|
||||
)
|
||||
}
|
||||
}
|
||||
+395
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring.suggested
|
||||
|
||||
import com.intellij.openapi.actionSystem.IdeActions
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.command.executeCommand
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import com.intellij.refactoring.suggested.BaseSuggestedRefactoringChangeListenerTest
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.ImportPath
|
||||
|
||||
class KotlinSuggestedRefactoringChangeListenerTest : BaseSuggestedRefactoringChangeListenerTest() {
|
||||
override val fileType: FileType
|
||||
get() = KotlinFileType.INSTANCE
|
||||
|
||||
fun test1() {
|
||||
setup("fun foo(<caret>) {}")
|
||||
|
||||
perform("editingStarted: 'foo()'") { myFixture.type("p") }
|
||||
|
||||
perform("nextSignature: 'foo(p)'") { commitAll() }
|
||||
perform { myFixture.type(":") }
|
||||
perform { myFixture.type(" S") }
|
||||
perform { myFixture.type("tr") }
|
||||
perform("nextSignature: 'foo(p: Str)'") { commitAll() }
|
||||
perform { myFixture.type("ing") }
|
||||
perform("nextSignature: 'foo(p: String)'") { commitAll() }
|
||||
|
||||
perform {
|
||||
perform { myFixture.type(", ") }
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testCompletion() {
|
||||
setup("fun foo(<caret>) {}")
|
||||
|
||||
perform("editingStarted: 'foo()'") { myFixture.type("p: DoubleArra") }
|
||||
perform("nextSignature: 'foo(p: DoubleArra)'", "nextSignature: 'foo(p: DoubleArray)'") { myFixture.completeBasic() }
|
||||
}
|
||||
|
||||
fun testChangeOutsideSignature() {
|
||||
setup("fun foo(<caret>) {}")
|
||||
|
||||
perform("editingStarted: 'foo()'") { myFixture.type("p: A") }
|
||||
perform("reset") {
|
||||
insertString(editor.document.textLength, "\nval")
|
||||
}
|
||||
}
|
||||
|
||||
fun testEditOtherSignature() {
|
||||
setup("fun foo(<caret>) {}\nfun bar() = 0")
|
||||
|
||||
val otherFunction = (file as KtFile).declarations[1] as KtNamedFunction
|
||||
val offset = otherFunction.valueParameterList!!.startOffset + 1
|
||||
val marker = editor.document.createRangeMarker(offset, offset)
|
||||
|
||||
perform("editingStarted: 'foo()'") { myFixture.type("p: A") }
|
||||
perform("nextSignature: 'foo(p: A)'") { commitAll() }
|
||||
|
||||
perform("reset", "editingStarted: 'bar()'", "nextSignature: 'bar(p1: String)'") {
|
||||
assert(marker.isValid)
|
||||
insertString(marker.startOffset, "p1: String")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testChangeInAnotherFile() {
|
||||
setup("fun foo(<caret>) {}")
|
||||
|
||||
perform("editingStarted: 'foo()'") { myFixture.type("p: A") }
|
||||
perform("reset") {
|
||||
setup("")
|
||||
myFixture.type(" ")
|
||||
}
|
||||
}
|
||||
|
||||
fun testAddImport() {
|
||||
setup("fun foo(<caret>) {}")
|
||||
|
||||
perform("editingStarted: 'foo()'", "nextSignature: 'foo(p: Any)'") {
|
||||
myFixture.type("p: Any")
|
||||
commitAll()
|
||||
}
|
||||
perform("nextSignature: 'foo(p: Any)'", "nextSignature: 'foo(p: Any)'") {
|
||||
addImport("java.util.ArrayList")
|
||||
}
|
||||
perform("nextSignature: 'foo(p: Any, p2: String)'") {
|
||||
myFixture.type(", p2: String")
|
||||
commitAll()
|
||||
}
|
||||
perform("nextSignature: 'foo(p: Any, p2: String)'", "nextSignature: 'foo(p: Any, p2: String)'") {
|
||||
addImport("java.util.Date")
|
||||
}
|
||||
}
|
||||
|
||||
fun testAddImportWithBlankLineInsertion() {
|
||||
setup(
|
||||
"""
|
||||
import foo.bar
|
||||
fun foo(<caret>) {}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform("editingStarted: 'foo()'", "nextSignature: 'foo(p: ArrayList)'") {
|
||||
myFixture.type("p: ArrayList")
|
||||
commitAll()
|
||||
}
|
||||
perform("nextSignature: 'foo(p: ArrayList)'", "nextSignature: 'foo(p: ArrayList)'") {
|
||||
addImport("java.util.ArrayList")
|
||||
}
|
||||
perform("nextSignature: 'foo(p: ArrayList<String>)'") {
|
||||
myFixture.type("<String>")
|
||||
commitAll()
|
||||
}
|
||||
perform("nextSignature: 'foo(p: ArrayList<String>, p2: Any)'") {
|
||||
myFixture.type(", p2: Any")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testAddImportWithBlankLinesRemoval() {
|
||||
setup(
|
||||
"""
|
||||
import foo.bar
|
||||
|
||||
|
||||
|
||||
fun foo(<caret>) {}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform("editingStarted: 'foo()'", "nextSignature: 'foo(p: ArrayList)'") {
|
||||
myFixture.type("p: ArrayList")
|
||||
commitAll()
|
||||
}
|
||||
perform("nextSignature: 'foo(p: ArrayList)'", "nextSignature: 'foo(p: ArrayList)'") {
|
||||
addImport("java.util.ArrayList")
|
||||
}
|
||||
perform("nextSignature: 'foo(p: ArrayList<String>)'") {
|
||||
myFixture.type("<String>")
|
||||
commitAll()
|
||||
}
|
||||
perform("nextSignature: 'foo(p: ArrayList<String>, p2: Any)'") {
|
||||
myFixture.type(", p2: Any")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testReorderParameters() {
|
||||
setup("fun foo(p1: String, p2: Any, p3<caret>: Int) {}")
|
||||
|
||||
perform("editingStarted: 'foo(p1: String, p2: Any, p3: Int)'") {
|
||||
myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT)
|
||||
}
|
||||
perform("nextSignature: 'foo(p1: String, p3: Int, p2: Any)'") {
|
||||
myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT)
|
||||
}
|
||||
perform("nextSignature: 'foo(p3: Int, p1: String, p2: Any)'") {
|
||||
commitAll()
|
||||
}
|
||||
perform("nextSignature: 'foo(p1: String, p3: Int, p2: Any)'") {
|
||||
myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_RIGHT)
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testAddParameterViaPsi() {
|
||||
setup("fun foo(p1: Int) {}")
|
||||
|
||||
val function = (file as KtFile).declarations.single() as KtFunction
|
||||
perform(
|
||||
"editingStarted: 'foo(p1: Int)'",
|
||||
"nextSignature: 'foo(p1: Int,)'",
|
||||
"nextSignature: 'foo(p1: Int,p2: Int)'",
|
||||
"nextSignature: 'foo(p1: Int, p2: Int)'"
|
||||
) {
|
||||
executeCommand {
|
||||
runWriteAction {
|
||||
function.valueParameterList!!.addParameter(KtPsiFactory(project).createParameter("p2: Int"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testCommentTyping() {
|
||||
setup("fun foo(<caret>) {}")
|
||||
|
||||
perform("editingStarted: 'foo()'", "nextSignature: 'foo(p1: Any)'") {
|
||||
myFixture.type("p1: Any")
|
||||
commitAll()
|
||||
}
|
||||
|
||||
perform {
|
||||
myFixture.type("/*")
|
||||
commitAll()
|
||||
}
|
||||
|
||||
perform {
|
||||
myFixture.type(" this is comment for parameter")
|
||||
commitAll()
|
||||
}
|
||||
|
||||
perform("nextSignature: 'foo(p1: Any/* this is comment for parameter*/)'") {
|
||||
myFixture.type("*/")
|
||||
commitAll()
|
||||
}
|
||||
|
||||
perform {
|
||||
myFixture.type(", p2: Int /*")
|
||||
commitAll()
|
||||
}
|
||||
|
||||
perform {
|
||||
myFixture.type("this is comment for another parameter")
|
||||
commitAll()
|
||||
}
|
||||
|
||||
perform("nextSignature: 'foo(p1: Any/* this is comment for parameter*/, p2: Int /*this is comment for another parameter*/)'") {
|
||||
myFixture.type("*/")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testAddReturnType() {
|
||||
setup(
|
||||
"""
|
||||
interface I {
|
||||
fun foo()<caret>
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform("editingStarted: 'foo()'") { myFixture.type(": String") }
|
||||
|
||||
perform("nextSignature: 'foo(): String'") { commitAll() }
|
||||
}
|
||||
|
||||
fun testNewLocal() {
|
||||
setup(
|
||||
"""
|
||||
fun foo() {
|
||||
<caret>
|
||||
print(a)
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform {
|
||||
myFixture.type("val a")
|
||||
commitAll()
|
||||
myFixture.type("bcd")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testNewFunction() {
|
||||
setup(
|
||||
"""
|
||||
interface I {
|
||||
<caret>
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform {
|
||||
myFixture.type("fun foo_bar123(_p1: Int)")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testNewProperty() {
|
||||
setup(
|
||||
"""
|
||||
interface I {
|
||||
<caret>
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform {
|
||||
myFixture.type("val prop: I")
|
||||
commitAll()
|
||||
|
||||
myFixture.type("nt")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testNewLocalWithNewUsage() {
|
||||
setup(
|
||||
"""
|
||||
fun foo() {
|
||||
<caret>
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform {
|
||||
myFixture.type("val a = 10")
|
||||
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_ENTER)
|
||||
myFixture.type("print(a)")
|
||||
commitAll()
|
||||
}
|
||||
|
||||
perform("editingStarted: 'a'", "nextSignature: 'abcd'") {
|
||||
val variable = file.findDescendantOfType<KtProperty>()!!
|
||||
myFixture.editor.caretModel.moveToOffset(variable.nameIdentifier!!.endOffset)
|
||||
myFixture.type("bcd")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testNewLocalBeforeExpression() {
|
||||
setup(
|
||||
"""
|
||||
fun foo(p: Int) {
|
||||
<caret>p * p
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform {
|
||||
myFixture.type("val a")
|
||||
commitAll()
|
||||
}
|
||||
perform {
|
||||
myFixture.type("bcd = ")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testNewClassWithConstructor() {
|
||||
setup("")
|
||||
|
||||
perform {
|
||||
myFixture.type("class C")
|
||||
commitAll()
|
||||
}
|
||||
perform {
|
||||
myFixture.type("(p: Int)")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testNewSecondaryConstructor() {
|
||||
setup(
|
||||
"""
|
||||
class C {
|
||||
<caret>
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform {
|
||||
myFixture.type("constructor(p1: Int)")
|
||||
commitAll()
|
||||
}
|
||||
perform {
|
||||
myFixture.type("(, p2: String)")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
fun testRenameComponentVar() {
|
||||
setup(
|
||||
"""
|
||||
fun f() {
|
||||
val (<caret>a, b) = f()
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
perform("editingStarted: 'a'", "nextSignature: 'newa'") {
|
||||
myFixture.type("new")
|
||||
commitAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addImport(fqName: String) {
|
||||
executeCommand {
|
||||
runWriteAction {
|
||||
(file as KtFile).importList!!.add(KtPsiFactory(project).createImportDirective(ImportPath.fromString(fqName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1553
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user