From 1c7a952ca68f341b6203cbd4c39b148d7e2e5404 Mon Sep 17 00:00:00 2001 From: Valentin Kipyatkov Date: Wed, 26 Feb 2020 09:38:24 +0100 Subject: [PATCH] Suggested Rename and Change Signature implementation for Kotlin (cherry picked from commit b9a9000569ff0927e16d88a9f7ef8ff36ed705c9) --- idea/resources/META-INF/plugin.xml.201 | 1 + .../KotlinSignaturePresentationBuilder.kt.201 | 77 + ...linSuggestedRefactoringAvailability.kt.201 | 161 ++ ...KotlinSuggestedRefactoringExecution.kt.201 | 157 ++ ...linSuggestedRefactoringStateChanges.kt.201 | 66 + .../KotlinSuggestedRefactoringSupport.kt.201 | 153 ++ .../KotlinSuggestedRefactoringUI.kt.201 | 46 + ...tlinSignatureChangePresentationTest.kt.201 | 874 ++++++++++ ...uggestedRefactoringAvailabilityTest.kt.201 | 371 ++++ ...estedRefactoringChangeCollectorTest.kt.201 | 201 +++ ...gestedRefactoringChangeListenerTest.kt.201 | 395 +++++ .../KotlinSuggestedRefactoringTest.kt.201 | 1553 +++++++++++++++++ 12 files changed, 4055 insertions(+) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSignaturePresentationBuilder.kt.201 create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringAvailability.kt.201 create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringExecution.kt.201 create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringStateChanges.kt.201 create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringSupport.kt.201 create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringUI.kt.201 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSignatureChangePresentationTest.kt.201 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringAvailabilityTest.kt.201 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeCollectorTest.kt.201 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeListenerTest.kt.201 create mode 100644 idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringTest.kt.201 diff --git a/idea/resources/META-INF/plugin.xml.201 b/idea/resources/META-INF/plugin.xml.201 index a2bcb259c67f1..853da692ca44d 100644 --- a/idea/resources/META-INF/plugin.xml.201 +++ b/idea/resources/META-INF/plugin.xml.201 @@ -87,5 +87,6 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio. + diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSignaturePresentationBuilder.kt.201 b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSignaturePresentationBuilder.kt.201 new file mode 100644 index 0000000000000..46d2003fa70f7 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSignaturePresentationBuilder.kt.201 @@ -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) + } + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringAvailability.kt.201 b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringAvailability.kt.201 new file mode 100644 index 0000000000000..33839c735353f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringAvailability.kt.201 @@ -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() + val improvedNewParameterTypesById = mutableMapOf() + 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 { + 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 + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringExecution.kt.201 b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringExecution.kt.201 new file mode 100644 index 0000000000000..8bd21c848fe9f --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringExecution.kt.201 @@ -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 + ) + + 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, + 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) = true + } + val changeSignature = KotlinChangeSignature(project, descriptor, configuration, declaration, null) + val methodDescriptor = changeSignature.adjustDescriptor(listOf(descriptor))!! + + val parameters = mutableListOf() + 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 + } + } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringStateChanges.kt.201 b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringStateChanges.kt.201 new file mode 100644 index 0000000000000..f68d8a8427abc --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringStateChanges.kt.201 @@ -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 { + 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) +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringSupport.kt.201 b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringSupport.kt.201 new file mode 100644 index 0000000000000..94420d9898e8c --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringSupport.kt.201 @@ -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 ?: "" diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringUI.kt.201 b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringUI.kt.201 new file mode 100644 index 0000000000000..5c736f3efcabd --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringUI.kt.201 @@ -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 { + val newParameters = mutableListOf() + + 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("", 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) } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSignatureChangePresentationTest.kt.201 b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSignatureChangePresentationTest.kt.201 new file mode 100644 index 0000000000000..a7c571591dab4 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSignatureChangePresentationTest.kt.201 @@ -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, + 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() + ) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringAvailabilityTest.kt.201 b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringAvailabilityTest.kt.201 new file mode 100644 index 0000000000000..2ee42f89742a1 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringAvailabilityTest.kt.201 @@ -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) { + foo(1) + } + + fun bar() { + foo(2) + } + """.trimIndent(), + { + myFixture.type(", p2: Any") + }, + { + myFixture.type(", p") + }, + expectedAvailability = Availability.Disabled + ) + } + + fun testInsertTrailingComma() { + doTest( + """ + fun foo(p1: Int) { + foo(1) + } + + fun bar() { + foo(2) + } + """.trimIndent(), + { + myFixture.type(",") + }, + expectedAvailability = Availability.NotAvailable + ) + } + + fun testChangeNonVirtualPropertyType() { + doTest( + "val v: String = \"\"", + { + replaceTextAtCaret("String", "Any") + }, + expectedAvailability = Availability.Disabled + ) + } + + fun testChangeParameterTypeNonVirtual() { + doTest( + "fun foo(p: String) {}", + { + replaceTextAtCaret("String", "Any") + }, + expectedAvailability = Availability.Disabled + ) + } + + fun testChangeReturnTypeNonVirtual() { + doTest( + "fun foo(): String = \"\"", + { + replaceTextAtCaret("String", "Any") + }, + expectedAvailability = Availability.Disabled + ) + } + + fun testChangeLocalVariableType() { + doTest( + """ + fun foo() { + val local: Int + local = 10 + } + """.trimIndent(), + { + replaceTextAtCaret("Int", "Long") + }, + expectedAvailability = Availability.NotAvailable + ) + } + + fun testAddLocalVariableType() { + doTest( + """ + fun foo() { + var local = 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: 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(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: Int, p2: Int) { + } + } + """.trimIndent(), + { + replaceTextAtCaret("Int", "Any") + }, + expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "overrides")) + ) + } + + fun testChangeParameterTypeForAbstract() { + doTest( + """ + abstract class C { + abstract fun foo(p1: Int, p2: Int) + } + """.trimIndent(), + { + replaceTextAtCaret("Int", "Any") + }, + expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "implementations")) + ) + } + + fun testChangeParameterTypeInInterface() { + doTest( + """ + interface I { + fun foo(p1: Int, p2: Int) + } + """.trimIndent(), + { + replaceTextAtCaret("Int", "Any") + }, + expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "implementations")) + ) + } + + fun testChangeParameterTypeInInterfaceWithBody() { + doTest( + """ + interface I { + fun foo(p1: Int, p2: Int) {} + } + """.trimIndent(), + { + replaceTextAtCaret("Int", "Any") + }, + expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "overrides")) + ) + } + + fun testChangeTypeOfPropertyWithImplementationInInterface() { + doTest( + """ + interface I { + val p: Int + get() = 0 + } + """.trimIndent(), + { + replaceTextAtCaret("Int", "Any") + }, + expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("p", "overrides")) + ) + } + + fun testSpecifyExplicitType() { + doTest( + """ + open class C { + open fun foo() = 1 + } + """.trimIndent(), + { + myFixture.type(": Int") + }, + expectedAvailability = Availability.Available(changeSignatureAvailableTooltip("foo", "overrides")), + expectedAvailabilityAfterResolve = Availability.NotAvailable + ) + } + + fun testRemoveExplicitType() { + doTest( + """ + open class C { + open fun foo(): Int = 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(): 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 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: 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(): 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() + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeCollectorTest.kt.201 b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeCollectorTest.kt.201 new file mode 100644 index 0000000000000..fde7692cf276c --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeCollectorTest.kt.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() { + 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))" + ) + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeListenerTest.kt.201 b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeListenerTest.kt.201 new file mode 100644 index 0000000000000..1d335a060d7d0 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringChangeListenerTest.kt.201 @@ -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() {}") + + 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() {}") + + perform("editingStarted: 'foo()'") { myFixture.type("p: DoubleArra") } + perform("nextSignature: 'foo(p: DoubleArra)'", "nextSignature: 'foo(p: DoubleArray)'") { myFixture.completeBasic() } + } + + fun testChangeOutsideSignature() { + setup("fun foo() {}") + + perform("editingStarted: 'foo()'") { myFixture.type("p: A") } + perform("reset") { + insertString(editor.document.textLength, "\nval") + } + } + + fun testEditOtherSignature() { + setup("fun foo() {}\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() {}") + + perform("editingStarted: 'foo()'") { myFixture.type("p: A") } + perform("reset") { + setup("") + myFixture.type(" ") + } + } + + fun testAddImport() { + setup("fun foo() {}") + + 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() {} + """.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)'") { + myFixture.type("") + commitAll() + } + perform("nextSignature: 'foo(p: ArrayList, p2: Any)'") { + myFixture.type(", p2: Any") + commitAll() + } + } + + fun testAddImportWithBlankLinesRemoval() { + setup( + """ + import foo.bar + + + + fun foo() {} + """.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)'") { + myFixture.type("") + commitAll() + } + perform("nextSignature: 'foo(p: ArrayList, p2: Any)'") { + myFixture.type(", p2: Any") + commitAll() + } + } + + fun testReorderParameters() { + setup("fun foo(p1: String, p2: Any, p3: 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() {}") + + 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() + } + """.trimIndent() + ) + + perform("editingStarted: 'foo()'") { myFixture.type(": String") } + + perform("nextSignature: 'foo(): String'") { commitAll() } + } + + fun testNewLocal() { + setup( + """ + fun foo() { + + print(a) + } + """.trimIndent() + ) + + perform { + myFixture.type("val a") + commitAll() + myFixture.type("bcd") + commitAll() + } + } + + fun testNewFunction() { + setup( + """ + interface I { + + } + """.trimIndent() + ) + + perform { + myFixture.type("fun foo_bar123(_p1: Int)") + commitAll() + } + } + + fun testNewProperty() { + setup( + """ + interface I { + + } + """.trimIndent() + ) + + perform { + myFixture.type("val prop: I") + commitAll() + + myFixture.type("nt") + commitAll() + } + } + + fun testNewLocalWithNewUsage() { + setup( + """ + fun foo() { + + } + """.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()!! + myFixture.editor.caretModel.moveToOffset(variable.nameIdentifier!!.endOffset) + myFixture.type("bcd") + commitAll() + } + } + + fun testNewLocalBeforeExpression() { + setup( + """ + fun foo(p: Int) { + 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 { + + } + """.trimIndent() + ) + + perform { + myFixture.type("constructor(p1: Int)") + commitAll() + } + perform { + myFixture.type("(, p2: String)") + commitAll() + } + } + + fun testRenameComponentVar() { + setup( + """ + fun f() { + val (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))) + } + } + } +} diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringTest.kt.201 b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringTest.kt.201 new file mode 100644 index 0000000000000..c27a76c46b34b --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/suggested/KotlinSuggestedRefactoringTest.kt.201 @@ -0,0 +1,1553 @@ +/* + * 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.LanguageFileType +import com.intellij.psi.PsiDocumentManager +import com.intellij.refactoring.suggested.SuggestedRefactoringExecution +import com.intellij.refactoring.suggested.BaseSuggestedRefactoringTest +import com.intellij.refactoring.suggested._suggestedChangeSignatureNewParameterValuesForTests +import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.resolve.ImportPath + +class KotlinSuggestedRefactoringTest : BaseSuggestedRefactoringTest() { + override val fileType: LanguageFileType + get() = KotlinFileType.INSTANCE + + override fun setUp() { + super.setUp() + _suggestedChangeSignatureNewParameterValuesForTests = { + SuggestedRefactoringExecution.NewParameterValue.Expression(KtPsiFactory(project).createExpression("default$it")) + } + } + + override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE + + fun testAddParameter() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + fun foo(p1: Int) { + foo(1) + } + + fun bar() { + foo(2) + } + """.trimIndent(), + """ + fun foo(p1: Int, p2: Any) { + foo(1, default0) + } + + fun bar() { + foo(2, default0) + } + """.trimIndent(), + "usages", + { myFixture.type(", p2: Any") } + ) + } + + fun testRemoveParameter() { + doTestChangeSignature( + """ + fun foo(p1: Any?, p2: Int) { + foo(null, 1) + } + + fun bar() { + foo(1, 2) + } + """.trimIndent(), + """ + fun foo(p1: Any?) { + foo(null) + } + + fun bar() { + foo(1) + } + """.trimIndent(), + "usages", + { + deleteTextBeforeCaret(", p2: Int") + } + ) + } + + fun testReorderParameters1() { + doTestChangeSignature( + """ + fun foo(p1: Any?, p2: Int, p3: Boolean) { + foo(null, 1, true) + } + + fun bar() { + foo(1, 2, false) + } + """.trimIndent(), + """ + fun foo(p3: Boolean, p1: Any?, p2: Int) { + foo(true, null, 1) + } + + fun bar() { + foo(false, 1, 2) + } + """.trimIndent(), + "usages", + { myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT) }, + { myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_LEFT) }, + wrapIntoCommandAndWriteAction = false + ) + } + + fun testReorderParameters2() { + doTestChangeSignature( + """ + fun foo(p1: Any?, p2: Int, p3: Boolean) { + foo(null, 1, true) + } + + fun bar() { + foo(1, 2, false) + } + """.trimIndent(), + """ + fun foo(p2: Int, p1: Any?, p3: Boolean) { + foo(1, null, true) + } + + fun bar() { + foo(2, 1, false) + } + """.trimIndent(), + "usages", + { + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_CUT) + }, + { + editor.caretModel.moveToOffset(editor.caretModel.offset - 10) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE) + }, + { + myFixture.type(", ") + }, + { + editor.caretModel.moveToOffset(editor.caretModel.offset + 10) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) + }, + wrapIntoCommandAndWriteAction = false + ) + } + + fun testChangeParameterType() { + doTestChangeSignature( + """ + interface I { + fun foo(p: String) + } + + class C : I { + override fun foo(p: String) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(p: Any) + } + + class C : I { + override fun foo(p: Any) { + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "Any") + } + ) + } + + fun testChangeParameterTypeExpectFunction() { + ignoreErrorsBefore = true + ignoreErrorsAfter = true + doTestChangeSignature( + "expect fun foo(p: String)", + "expect fun foo(p: Any)", + "actual declarations", + { + replaceTextAtCaret("String", "Any") + } + ) + } + + fun testUnresolvedParameterType() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + interface I { + fun foo(p: String) + } + + class C : I { + override fun foo(p: String) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(p: XXX) + } + + class C : I { + override fun foo(p: XXX) { + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "XXX") + } + ) + } + + fun testChangeParameterTypeWithImportInsertion() { + myFixture.addFileToProject( + "X.kt", + """ + package xxx + class X + """.trimIndent() + ) + + val otherFile = myFixture.addFileToProject( + "Other.kt", + """ + class D : I { + override fun foo(p: String) { + } + } + """.trimIndent() + ) + + doTestChangeSignature( + """ + interface I { + fun foo(p: String) + } + + class C : I { + override fun foo(p: String) { + } + } + """.trimIndent(), + """ + import xxx.X + + interface I { + fun foo(p: X) + } + + class C : I { + override fun foo(p: X) { + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "X") + }, + { + addImport("xxx.X") + } + ) + + assertEquals( + """ + import xxx.X + + class D : I { + override fun foo(p: X) { + } + } + """.trimIndent(), + otherFile.text + ) + } + + fun testChangeReturnType() { + doTestChangeSignature( + """ + interface I { + fun foo(): String + } + + class C : I { + override fun foo(): String = "" + } + """.trimIndent(), + """ + interface I { + fun foo(): Any + } + + class C : I { + override fun foo(): Any = "" + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "Any") + } + ) + } + + fun testAddReturnType() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() {} + } + """.trimIndent(), + """ + interface I { + fun foo(): String + } + + class C : I { + override fun foo(): String {} + } + """.trimIndent(), + "implementations", + { + myFixture.type(": String") + } + ) + } + + fun testRemoveReturnType() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + interface I { + fun foo(): String + } + + class C : I { + override fun foo(): String = "" + } + """.trimIndent(), + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() = "" + } + """.trimIndent(), + "implementations", + { + deleteTextAtCaret(": String") + } + ) + } + + fun testRenameAndAddReturnType() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() {} + } + """.trimIndent(), + """ + interface I { + fun fooNew(): String + } + + class C : I { + override fun fooNew(): String {} + } + """.trimIndent(), + "usages", + { + val offset = editor.caretModel.offset + editor.document.insertString(offset, "New") + }, + { + val offset = editor.caretModel.offset + editor.document.insertString(offset + "New()".length, ": String") + } + ) + } + + fun testChangeParameterTypeWithImportReplaced() { + myFixture.addFileToProject( + "XY.kt", + """ + package xxx + class X + class Y + """.trimIndent() + ) + + val otherFile = myFixture.addFileToProject( + "Other.kt", + """ + import xxx.X + + class D : I { + override fun foo(p: X) { + } + } + """.trimIndent() + ) + + doTestChangeSignature( + """ + import xxx.X + + interface I { + fun foo(p: X) + } + """.trimIndent(), + """ + import xxx.Y + + interface I { + fun foo(p: Y) + } + """.trimIndent(), + "implementations", + { + val offset = editor.caretModel.offset + editor.document.replaceString(offset, offset + "X".length, "Y") + }, + { + addImport("xxx.Y") + removeImport("xxx.X") + } + ) + + assertEquals( + """ + import xxx.Y + + class D : I { + override fun foo(p: Y) { + } + } + """.trimIndent(), + otherFile.text + ) + } + + fun testPreserveCommentsAndFormatting() { + doTestChangeSignature( + """ + class A : I { + override fun foo() { + } + } + + interface I { + fun foo() + } + """.trimIndent(), + """ + class A : I { + override fun foo(p1: Int, p2: Long, p3: Any?) { + } + } + + interface I { + fun foo(p1: Int/*comment 1*/, p2: Long/*comment 2*/, + p3: Any?/*comment 3*/) + } + """.trimIndent(), + "usages", + { + myFixture.type("p1: Int/*comment 1*/") + }, + { + myFixture.type(", p2: Long/*comment 2*/") + }, + { + myFixture.type(",") + myFixture.performEditorAction(IdeActions.ACTION_EDITOR_ENTER) + myFixture.type("p3: Any?/*comment 3*/") + }, + wrapIntoCommandAndWriteAction = false + ) + } + + fun testParameterCompletion() { + ignoreErrorsAfter = true + doTestChangeSignature( + "package ppp\nclass Abcdef\nfun foo(p: Int) { }\nfun bar() { foo(1) }", + "package ppp\nclass Abcdef\nfun foo(abcdef: Abcdef, p: Int) { }\nfun bar() { foo(default0, 1) }", + "usages", + { + executeCommand { + runWriteAction { + myFixture.type("abcde") + PsiDocumentManager.getInstance(project).commitAllDocuments() + } + } + }, + { + myFixture.completeBasic() + }, + { + myFixture.finishLookup('\n') + }, + { + executeCommand { + runWriteAction { + myFixture.type(", ") + PsiDocumentManager.getInstance(project).commitAllDocuments() + } + } + }, + wrapIntoCommandAndWriteAction = false + ) + } + + fun testRenameTwoParameters() { + doTestChangeSignature( + """ + fun foo(p1: Int, p2: String) { + p1.hashCode() + p2.hashCode() + } + """.trimIndent(), + """ + fun foo(p1New: Int, p2New: String) { + p1New.hashCode() + p2New.hashCode() + } + """.trimIndent(), + "usages", + { + val function = (file as KtFile).declarations.single() as KtFunction + function.valueParameters[0].setName("p1New") + function.valueParameters[1].setName("p2New") + } + ) + } + + fun testChangePropertyType() { + doTestChangeSignature( + """ + interface I { + var v: String + } + + class C : I { + override var v: String = "" + } + """.trimIndent(), + """ + interface I { + var v: Any + } + + class C : I { + override var v: Any = "" + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "Any") + }, + expectedPresentation = """ + Old: + 'var ' + 'v' + ': ' + 'String' (modified) + New: + 'var ' + 'v' + ': ' + 'Any' (modified) + """.trimIndent() + ) + } + + fun testRenameClass() { + doTestRename( + """ + var v: C? = null + + interface C + """.trimIndent(), + """ + var v: CNew? = null + + interface CNew + """.trimIndent(), + "C", + "CNew", + { + myFixture.type("New") + } + ) + } + + fun testRenameLocalVar() { + doTestRename( + """ + fun foo() { + var v = 0 + v++ + } + """.trimIndent(), + """ + fun foo() { + var vNew = 0 + vNew++ + } + """.trimIndent(), + "v", + "vNew", + { + myFixture.type("New") + } + ) + } + + fun testRenameComponentVar() { + doTestRename( + """ + data class Pair(val t1: T1, val t2: T2) + + fun f(): Pair = Pair("a", 1) + + fun g() { + val (a, b) = f() + b.hashCode() + } + """.trimIndent(), + """ + data class Pair(val t1: T1, val t2: T2) + + fun f(): Pair = Pair("a", 1) + + fun g() { + val (a, b1) = f() + b1.hashCode() + } + """.trimIndent(), + "b", + "b1", + { + myFixture.type("1") + } + ) + } + + fun testRenameLoopVar() { + doTestRename( + """ + fun f(list: List) { + for (s in list) { + s.hashCode() + } + } + """.trimIndent(), + """ + fun f(list: List) { + for (s1 in list) { + s1.hashCode() + } + } + """.trimIndent(), + "s", + "s1", + { + myFixture.type("1") + } + ) + } + + fun testRenameParameter() { + doTestRename( + """ + fun foo(p: String) { + p.hashCode() + } + """.trimIndent(), + """ + fun foo(pNew: String) { + pNew.hashCode() + } + """.trimIndent(), + "p", + "pNew", + { + myFixture.type("New") + } + ) + } + + fun testRenameParametersInOverrides() { + doTestRename( + """ + interface I { + fun foo(p: String) + } + + class C : I { + override fun foo(p: String) { + p.hashCode() + } + } + """.trimIndent(), + """ + interface I { + fun foo(pNew: String) + } + + class C : I { + override fun foo(pNew: String) { + pNew.hashCode() + } + } + """.trimIndent(), + "p", + "pNew", + { + myFixture.type("New") + } + ) + } + + fun testAddPrimaryConstructorParameter() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + class C(private val x: String, y: Int) + + fun foo() { + val c = C("a", 1) + } + """.trimIndent(), + """ + class C(private val x: String, y: Int, z: Any) + + fun foo() { + val c = C("a", 1, default0) + } + """.trimIndent(), + "usages", + { myFixture.type(", z: Any") }, + expectedPresentation = """ + Old: + 'C' + '(' + LineBreak('', true) + Group: + 'x' + ': ' + 'String' + ',' + LineBreak(' ', true) + Group: + 'y' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'C' + '(' + LineBreak('', true) + Group: + 'x' + ': ' + 'String' + ',' + LineBreak(' ', true) + Group: + 'y' + ': ' + 'Int' + ',' + LineBreak(' ', true) + Group (added): + 'z' + ': ' + 'Any' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testAddSecondaryConstructorParameter() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + class C { + constructor(x: String) + } + + fun foo() { + val c = C("a") + } + """.trimIndent(), + """ + class C { + constructor(x: String, y: Int) + } + + fun foo() { + val c = C("a", default0) + } + """.trimIndent(), + "usages", + { myFixture.type(", y: Int") }, + expectedPresentation = """ + Old: + 'constructor' + '(' + LineBreak('', true) + Group: + 'x' + ': ' + 'String' + LineBreak('', false) + ')' + New: + 'constructor' + '(' + LineBreak('', true) + Group: + 'x' + ': ' + 'String' + ',' + LineBreak(' ', true) + Group (added): + 'y' + ': ' + 'Int' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testAddReceiver() { + doTestChangeSignature( + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() { + } + } + """.trimIndent(), + """ + interface I { + fun Int.foo() + } + + class C : I { + override fun Int.foo() { + } + } + """.trimIndent(), + "usages", + { myFixture.type("Int.") }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', false) + ')' + New: + 'fun ' + 'Int.' (added) + 'foo' + '(' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testAddReceiverAndParameter() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + fun foo() { + } + + fun bar() { + foo() + } + """.trimIndent(), + """ + fun Int.foo(o: Any) { + } + + fun bar() { + default0.foo(default1) + } + """.trimIndent(), + "usages", + { myFixture.type("Int.") }, + { repeat("foo(".length) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_MOVE_CARET_RIGHT) } }, + { myFixture.type("o: Any") } + ) + } + + fun testRemoveReceiver() { + doTestChangeSignature( + """ + fun Int.foo() { + } + + fun bar() { + 1.foo() + } + """.trimIndent(), + """ + fun foo() { + } + + fun bar() { + foo() + } + """.trimIndent(), + "usages", + { + repeat(4) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) } + } + ) + } + + fun testChangeReceiverType() { + doTestChangeSignature( + """ + interface I { + fun Int.foo() + } + + class C : I { + override fun Int.foo() { + } + } + + fun I.f() { + 1.foo() + } + """.trimIndent(), + """ + interface I { + fun Any.foo() + } + + class C : I { + override fun Any.foo() { + } + } + + fun I.f() { + 1.foo() + } + """.trimIndent(), + "implementations", + { + repeat("Int".length) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) } + myFixture.type("Any") + }, + expectedPresentation = """ + Old: + 'fun ' + 'Int' (modified) + '.' + 'foo' + '(' + LineBreak('', false) + ')' + New: + 'fun ' + 'Any' (modified) + '.' + 'foo' + '(' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testChangeReceiverTypeAndRemoveParameter() { + doTestChangeSignature( + """ + fun Int.foo(p: Any) {} + + fun bar() { + 1.foo("a") + } + """.trimIndent(), + """ + fun Any.foo() {} + + fun bar() { + 1.foo() + } + """.trimIndent(), + "usages", + { + repeat("Int".length) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) } + myFixture.type("Any") + }, + { + editor.caretModel.moveToOffset(editor.caretModel.offset + ".foo(".length) + repeat("p: Any".length) { myFixture.performEditorAction(IdeActions.ACTION_EDITOR_DELETE) } + } + ) + } + + fun testAddVarargParameter() { + doTestChangeSignature( + """ + interface I { + fun foo(p: Int) + } + + class C : I { + override fun foo(p: Int) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(p: Int, vararg s: String) + } + + class C : I { + override fun foo(p: Int, vararg s: String) { + } + } + """.trimIndent(), + "usages", + { myFixture.type(", vararg s: String") }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p' + ': ' + 'Int' + ',' + LineBreak(' ', true) + Group (added): + 'vararg' + ' ' + 's' + ': ' + 'String' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + //TODO +/* + fun testAddVarargModifier() { + doTestChangeSignature( + """ + interface I { + fun foo(p: Int) + } + + class C : I { + override fun foo(p: Int) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(vararg p: Int) + } + + class C : I { + override fun foo(vararg p: Int) { + } + } + """.trimIndent(), + { myFixture.type("vararg ") }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'vararg' (added) + ' ' + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testRemoveVarargModifier() { + doTestChangeSignature( + """ + interface I { + fun foo(vararg p: Int) + } + + class C : I { + override fun foo(vararg p: Int) { + } + } + """.trimIndent(), + """ + interface I { + fun foo(p: Int) + } + + class C : I { + override fun foo(p: Int) { + } + } + """.trimIndent(), + { + deleteStringAtCaret("vararg ") + }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'vararg' (removed) + ' ' + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p' + ': ' + 'Int' + LineBreak('', false) + ')' + """.trimIndent() + ) + } +*/ + + fun testSwapConstructorParameters() { + doTestChangeSignature( + """ + class C( + p1: Int, + p2: String + ) + + fun foo() { + C(1, "") + } + """.trimIndent(), + """ + class C( + p2: String, + p1: Int + ) + + fun foo() { + C("", 1) + } + """.trimIndent(), + "usages", + { + myFixture.performEditorAction(IdeActions.ACTION_MOVE_STATEMENT_UP_ACTION) + } + ) + } + + fun testChangeParameterTypeOfVirtualExtensionMethod() { + doTestChangeSignature( + """ + abstract class Base { + protected abstract fun Int.foo(p: String) + + fun bar() { + 1.foo("a") + } + } + + class Derived : Base() { + override fun Int.foo(p: String) { + } + } + """.trimIndent(), + """ + abstract class Base { + protected abstract fun Int.foo(p: Any) + + fun bar() { + 1.foo("a") + } + } + + class Derived : Base() { + override fun Int.foo(p: Any) { + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("String", "Any") + } + ) + } + + fun testAddParameterToVirtualExtensionMethod() { + ignoreErrorsAfter = true + doTestChangeSignature( + """ + abstract class Base { + protected abstract fun Int.foo(p: String) + + fun bar() { + 1.foo("a") + } + } + + class Derived : Base() { + override fun Int.foo(p: String) { + } + } + """.trimIndent(), + """ + abstract class Base { + protected abstract fun Int.foo(p: String, p1: Int) + + fun bar() { + 1.foo("a", default0) + } + } + + class Derived : Base() { + override fun Int.foo(p: String, p1: Int) { + } + } + """.trimIndent(), + "usages", + { + myFixture.type(", p1: Int") + } + ) + } + + fun testAddParameterWithFullyQualifiedType() { + doTestChangeSignature( + """ + interface I { + fun foo() + } + + class C : I { + override fun foo() { + } + } + """.trimIndent(), + """ + import java.io.InputStream + + interface I { + fun foo(p: java.io.InputStream) + } + + class C : I { + override fun foo(p: InputStream) { + } + } + """.trimIndent(), + "usages", + { myFixture.type("p: java.io.InputStream") } + ) + } + + fun testAddParameterWithDefaultValue() { + doTestChangeSignature( + """ + interface I { + fun foo(p1: Int) + } + + class C : I { + override fun foo(p1: Int) { + } + } + + fun f(i: I) { + i.foo(1) + } + """.trimIndent(), + """ + interface I { + fun foo(p1: Int, p2: Int = 10) + } + + class C : I { + override fun foo(p1: Int, p2: Int) { + } + } + + fun f(i: I) { + i.foo(1) + } + """.trimIndent(), + "usages", + { + myFixture.type(", p2: Int = 10") + }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p1' + ': ' + 'Int' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p1' + ': ' + 'Int' + ',' + LineBreak(' ', true) + Group (added): + 'p2' + ': ' + 'Int' + ' = ' + '10' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testReorderParameterWithDefaultValue() { + doTestChangeSignature( + """ + interface I { + fun foo(p1: Int, p2: Int = 10) + } + + class C : I { + override fun foo(p1: Int, p2: Int) { + } + } + + fun f(i: I) { + i.foo(2) + } + """.trimIndent(), + """ + interface I { + fun foo(p2: Int = 10, p1: Int) + } + + class C : I { + override fun foo(p2: Int, p1: Int) { + } + } + + fun f(i: I) { + i.foo(p1 = 2) + } + """.trimIndent(), + "usages", + { + myFixture.performEditorAction(IdeActions.MOVE_ELEMENT_RIGHT) + }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group (moved): + 'p1' + ': ' + 'Int' + ',' + LineBreak(' ', true) + Group: + 'p2' + ': ' + 'Int' + ' = ' + '10' + LineBreak('', false) + ')' + New: + 'fun ' + 'foo' + '(' + LineBreak('', true) + Group: + 'p2' + ': ' + 'Int' + ' = ' + '10' + ',' + LineBreak(' ', true) + Group (moved): + 'p1' + ': ' + 'Int' + LineBreak('', false) + ')' + """.trimIndent() + ) + } + + fun testReplaceTypeWithItsAlias() { + doTestChangeSignature( + """ + typealias StringToUnit = (String) -> Unit + + interface I { + fun foo(): (String) -> Unit + } + + class C : I { + override fun foo(): (String) -> Unit { + throw UnsupportedOperationException() + } + } + """.trimIndent(), + """ + typealias StringToUnit = (String) -> Unit + + interface I { + fun foo(): StringToUnit + } + + class C : I { + override fun foo(): StringToUnit { + throw UnsupportedOperationException() + } + } + """.trimIndent(), + "implementations", + { + replaceTextAtCaret("(String) -> Unit", "StringToUnit") + }, + expectedPresentation = """ + Old: + 'fun ' + 'foo' + '(' + LineBreak('', false) + ')' + ': ' + '(String) -> Unit' (modified) + New: + 'fun ' + 'foo' + '(' + LineBreak('', false) + ')' + ': ' + 'StringToUnit' (modified) + """.trimIndent() + ) + } + + 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() + } +}