diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetMutableMethodDescriptor.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetMutableMethodDescriptor.kt index 251169a7f1b..d489c882943 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetMutableMethodDescriptor.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/changeSignature/JetMutableMethodDescriptor.kt @@ -33,10 +33,6 @@ public class JetMutableMethodDescriptor(val original: JetMethodDescriptor): JetM parameters.add(parameter) } - public fun setParameter(index: Int, parameter: JetParameterInfo) { - parameters[index] = parameter - } - public fun removeParameter(index: Int) { parameters.remove(index) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt index c640806dde9..a67d731825b 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt @@ -83,13 +83,25 @@ public class KotlinInplaceParameterIntroducer( private val LOG = Logger.getInstance(javaClass()) } - object PreviewDecorator { - protected val textAttributes: TextAttributes = with(TextAttributes()) { - setEffectType(EffectType.ROUNDED_BOX) - setEffectColor(JBColor.RED) - this + enum class PreviewDecorator { + FOR_ADD: PreviewDecorator() { + override val textAttributes: TextAttributes = with(TextAttributes()) { + setEffectType(EffectType.ROUNDED_BOX) + setEffectColor(JBColor.RED) + this + } } + FOR_REMOVAL: PreviewDecorator() { + override val textAttributes: TextAttributes = with(TextAttributes()) { + setEffectType(EffectType.STRIKEOUT) + setEffectColor(Color.BLACK) + this + } + } + + protected abstract val textAttributes: TextAttributes + fun applyToRange(range: TextRange, markupModel: MarkupModel) { markupModel.addRangeHighlighter(range.getStartOffset(), range.getEndOffset(), @@ -104,6 +116,7 @@ public class KotlinInplaceParameterIntroducer( private fun updatePreview(currentName: String?, currentType: String?) { with (descriptor) { + val rangesToRemove = ArrayList() var addedRange: TextRange? = null val builder = StringBuilder() @@ -129,6 +142,9 @@ public class KotlinInplaceParameterIntroducer( if (parameter == addedParameter) { addedRange = range } + else if (parameter in parametersToRemove) { + rangesToRemove.add(range) + } if (i < parameters.lastIndex) { builder.append(", ") @@ -145,7 +161,8 @@ public class KotlinInplaceParameterIntroducer( val markupModel = DocumentMarkupModel.forDocument(document, myProject, true) markupModel.removeAllHighlighters() - addedRange?.let { PreviewDecorator.applyToRange(it, markupModel) } + rangesToRemove.forEach { PreviewDecorator.FOR_REMOVAL.applyToRange(it, markupModel) } + addedRange?.let { PreviewDecorator.FOR_ADD.applyToRange(it, markupModel) } } revalidate() } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt index 0bd0516bccf..a8307205ead 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt @@ -24,6 +24,7 @@ import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.search.LocalSearchScope import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor @@ -35,18 +36,26 @@ import org.jetbrains.kotlin.idea.refactoring.changeSignature.* import org.jetbrains.kotlin.idea.refactoring.introduce.KotlinIntroduceHandlerBase import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetParent import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHintByKey +import org.jetbrains.kotlin.idea.search.usagesSearch.DefaultSearchHelper +import org.jetbrains.kotlin.idea.search.usagesSearch.UsagesSearchTarget +import org.jetbrains.kotlin.idea.search.usagesSearch.search import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.approximateWithResolvableType +import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiUnifier +import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList +import org.jetbrains.kotlin.psi.psiUtil.getValueParameters +import org.jetbrains.kotlin.psi.psiUtil.isAncestor import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils import org.jetbrains.kotlin.types.JetType +import java.util.Collections import kotlin.test.fail public data class IntroduceParameterDescriptor( @@ -54,7 +63,8 @@ public data class IntroduceParameterDescriptor( val callable: JetNamedDeclaration, val callableDescriptor: FunctionDescriptor, val addedParameter: JetParameter, - val parameterType: JetType + val parameterType: JetType, + val parametersToRemove: List ) { val valVar: JetValVar @@ -81,10 +91,13 @@ public data class IntroduceParameterDescriptor( fun IntroduceParameterDescriptor.performRefactoring() { runWriteAction { JetPsiUtil.deleteElementWithDelimiters(addedParameter) - + val config = object: JetChangeSignatureConfiguration { override fun configure(originalDescriptor: JetMethodDescriptor, bindingContext: BindingContext): JetMethodDescriptor { return originalDescriptor.modify { + val parameters = callable.getValueParameters() + parametersToRemove.map { parameters.indexOf(it) }.sortDescending().forEach { removeParameter(it) } + val parameterInfo = JetParameterInfo(name = addedParameter.getName()!!, type = parameterType, defaultValueForParameter = JetPsiUtil.deparenthesize(originalExpression), @@ -103,7 +116,7 @@ fun IntroduceParameterDescriptor.performRefactoring() { } public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() { - public fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) { + fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) { val psiFactory = JetPsiFactory(project) val parameterList = targetParent.getValueParameterList() @@ -119,15 +132,25 @@ public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() { val expressionType = context[BindingContext.EXPRESSION_TYPE, expression] ?: KotlinBuiltIns.getInstance().getAnyType() val parameterType = expressionType.approximateWithResolvableType(JetScopeUtils.getResolutionScope(targetParent, context), false) - val validatorContainer = - when (targetParent) { - is JetFunction -> targetParent.getBodyExpression() - is JetClass -> targetParent.getBody() - else -> null - } ?: throw AssertionError("Body element is not found: ${JetPsiUtil.getElementTextWithContext(targetParent)}") - val nameValidator = JetNameValidatorImpl(validatorContainer, null, JetNameValidatorImpl.Target.PROPERTIES) + val body = when (targetParent) { + is JetFunction -> targetParent.getBodyExpression() + is JetClass -> targetParent.getBody() + else -> null + } ?: throw AssertionError("Body element is not found: ${JetPsiUtil.getElementTextWithContext(targetParent)}") + val nameValidator = JetNameValidatorImpl(body, null, JetNameValidatorImpl.Target.PROPERTIES) val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(parameterType, nameValidator, "p")) + val parametersToRemove = (parameterList?.getParameters() ?: Collections.emptyList()).filter { + if (!it.hasValOrVarNode()) { + val usages = DefaultSearchHelper() + .newRequest(UsagesSearchTarget(element = it)) + .search() + .toList() + usages.isNotEmpty() && usages.all { expression.isAncestor(it.getElement()) } + } + else false + } + project.executeCommand(INTRODUCE_PARAMETER) { val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(parameterType) val newParameter = psiFactory.createParameter("${suggestedNames.first()}: $renderedType") @@ -160,7 +183,8 @@ public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() { targetParent, functionDescriptor, addedParameter, - parameterType) + parameterType, + parametersToRemove) if (editor.getSettings().isVariableInplaceRenameEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) { with(PsiDocumentManager.getInstance(project)) { commitDocument(editor.getDocument()) diff --git a/idea/testData/refactoring/introduceParameter/classParameterUsedOutside.kt b/idea/testData/refactoring/introduceParameter/classParameterUsedOutside.kt new file mode 100644 index 00000000000..9961047e603 --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/classParameterUsedOutside.kt @@ -0,0 +1,8 @@ +// TARGET: +class A(val a: Int, s: String) { + fun foo() = (a + 1) * 2 +} + +fun test() { + val t = A(1, "2").a +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/classParameterUsedOutside.kt.after b/idea/testData/refactoring/introduceParameter/classParameterUsedOutside.kt.after new file mode 100644 index 00000000000..82238f9d79b --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/classParameterUsedOutside.kt.after @@ -0,0 +1,8 @@ +// TARGET: +class A(val a: Int, s: String, val i: Int = a + 1) { + fun foo() = (i) * 2 +} + +fun test() { + val t = A(1, "2").a +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/classUnusedParameter.kt b/idea/testData/refactoring/introduceParameter/classUnusedParameter.kt new file mode 100644 index 00000000000..977ff6cd911 --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/classUnusedParameter.kt @@ -0,0 +1,8 @@ +// TARGET: +class A(val a: Int, s: String) { + fun foo() = (a + 1) * 2 +} + +fun test() { + A(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/classUnusedParameter.kt.after b/idea/testData/refactoring/introduceParameter/classUnusedParameter.kt.after new file mode 100644 index 00000000000..2f518f190bb --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/classUnusedParameter.kt.after @@ -0,0 +1,8 @@ +// TARGET: +class A(val a: Int, s: String, val i: Int = a + 1) { + fun foo() = (i) * 2 +} + +fun test() { + A(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/classUsedParameter.kt b/idea/testData/refactoring/introduceParameter/classUsedParameter.kt new file mode 100644 index 00000000000..1bd37591140 --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/classUsedParameter.kt @@ -0,0 +1,10 @@ +// TARGET: +class A(val a: Int, s: String) { + val x = a + 2 + + fun foo() = (a + 1) * 2 +} + +fun test() { + A(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/classUsedParameter.kt.after b/idea/testData/refactoring/introduceParameter/classUsedParameter.kt.after new file mode 100644 index 00000000000..8dee0121998 --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/classUsedParameter.kt.after @@ -0,0 +1,10 @@ +// TARGET: +class A(val a: Int, s: String, val i: Int = a + 1) { + val x = a + 2 + + fun foo() = (i) * 2 +} + +fun test() { + A(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/functionMultipleUnusedParameters.kt b/idea/testData/refactoring/introduceParameter/functionMultipleUnusedParameters.kt new file mode 100644 index 00000000000..0e8d0db313f --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/functionMultipleUnusedParameters.kt @@ -0,0 +1,7 @@ +fun foo(a: Int, s: String, b: Int): Int { + return (a + b) * 2 +} + +fun test() { + foo(1, "2", 3) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/functionMultipleUnusedParameters.kt.after b/idea/testData/refactoring/introduceParameter/functionMultipleUnusedParameters.kt.after new file mode 100644 index 00000000000..7659c69870b --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/functionMultipleUnusedParameters.kt.after @@ -0,0 +1,7 @@ +fun foo(s: String, i: Int = a + b): Int { + return (i) * 2 +} + +fun test() { + foo("2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt b/idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt new file mode 100644 index 00000000000..42a2399bdcf --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt @@ -0,0 +1,7 @@ +fun foo(a: Int, s: String): Int { + return (a + 1) * 2 +} + +fun test() { + foo(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt.after b/idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt.after new file mode 100644 index 00000000000..dc3fd3383dc --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt.after @@ -0,0 +1,7 @@ +fun foo(s: String, i: Int = a + 1): Int { + return (i) * 2 +} + +fun test() { + foo("2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/functionUsedParameter.kt b/idea/testData/refactoring/introduceParameter/functionUsedParameter.kt new file mode 100644 index 00000000000..d2f43779538 --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/functionUsedParameter.kt @@ -0,0 +1,7 @@ +fun foo(a: Int, s: String): Int { + return (a + 1) * a +} + +fun test() { + foo(1, "2") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceParameter/functionUsedParameter.kt.after b/idea/testData/refactoring/introduceParameter/functionUsedParameter.kt.after new file mode 100644 index 00000000000..44968b5386e --- /dev/null +++ b/idea/testData/refactoring/introduceParameter/functionUsedParameter.kt.after @@ -0,0 +1,7 @@ +fun foo(a: Int, s: String, i: Int = a + 1): Int { + return (i) * a +} + +fun test() { + foo(1, "2") +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java index 67047cf3cd7..4e7411b8657 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/JetExtractionTestGenerated.java @@ -2215,12 +2215,48 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest { doIntroduceParameterTest(fileName); } + @TestMetadata("classParameterUsedOutside.kt") + public void testClassParameterUsedOutside() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classParameterUsedOutside.kt"); + doIntroduceParameterTest(fileName); + } + + @TestMetadata("classUnusedParameter.kt") + public void testClassUnusedParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classUnusedParameter.kt"); + doIntroduceParameterTest(fileName); + } + + @TestMetadata("classUsedParameter.kt") + public void testClassUsedParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classUsedParameter.kt"); + doIntroduceParameterTest(fileName); + } + @TestMetadata("defaultValueInParens.kt") public void testDefaultValueInParens() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/defaultValueInParens.kt"); doIntroduceParameterTest(fileName); } + @TestMetadata("functionMultipleUnusedParameters.kt") + public void testFunctionMultipleUnusedParameters() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionMultipleUnusedParameters.kt"); + doIntroduceParameterTest(fileName); + } + + @TestMetadata("functionUnusedParameter.kt") + public void testFunctionUnusedParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt"); + doIntroduceParameterTest(fileName); + } + + @TestMetadata("functionUsedParameter.kt") + public void testFunctionUsedParameter() throws Exception { + String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionUsedParameter.kt"); + doIntroduceParameterTest(fileName); + } + @TestMetadata("functionWithApproximatedType.kt") public void testFunctionWithApproximatedType() throws Exception { String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionWithApproximatedType.kt");