Introduce Parameter: Remove parameters which become unused after occurrence replacement

This commit is contained in:
Alexey Sedunov
2015-04-06 21:07:42 +03:00
parent 53a6827ad8
commit 3bb86a63cc
16 changed files with 188 additions and 21 deletions
@@ -33,10 +33,6 @@ public class JetMutableMethodDescriptor(val original: JetMethodDescriptor): JetM
parameters.add(parameter) parameters.add(parameter)
} }
public fun setParameter(index: Int, parameter: JetParameterInfo) {
parameters[index] = parameter
}
public fun removeParameter(index: Int) { public fun removeParameter(index: Int) {
parameters.remove(index) parameters.remove(index)
} }
@@ -83,13 +83,25 @@ public class KotlinInplaceParameterIntroducer(
private val LOG = Logger.getInstance(javaClass<KotlinInplaceParameterIntroducer>()) private val LOG = Logger.getInstance(javaClass<KotlinInplaceParameterIntroducer>())
} }
object PreviewDecorator { enum class PreviewDecorator {
protected val textAttributes: TextAttributes = with(TextAttributes()) { FOR_ADD: PreviewDecorator() {
setEffectType(EffectType.ROUNDED_BOX) override val textAttributes: TextAttributes = with(TextAttributes()) {
setEffectColor(JBColor.RED) setEffectType(EffectType.ROUNDED_BOX)
this 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) { fun applyToRange(range: TextRange, markupModel: MarkupModel) {
markupModel.addRangeHighlighter(range.getStartOffset(), markupModel.addRangeHighlighter(range.getStartOffset(),
range.getEndOffset(), range.getEndOffset(),
@@ -104,6 +116,7 @@ public class KotlinInplaceParameterIntroducer(
private fun updatePreview(currentName: String?, currentType: String?) { private fun updatePreview(currentName: String?, currentType: String?) {
with (descriptor) { with (descriptor) {
val rangesToRemove = ArrayList<TextRange>()
var addedRange: TextRange? = null var addedRange: TextRange? = null
val builder = StringBuilder() val builder = StringBuilder()
@@ -129,6 +142,9 @@ public class KotlinInplaceParameterIntroducer(
if (parameter == addedParameter) { if (parameter == addedParameter) {
addedRange = range addedRange = range
} }
else if (parameter in parametersToRemove) {
rangesToRemove.add(range)
}
if (i < parameters.lastIndex) { if (i < parameters.lastIndex) {
builder.append(", ") builder.append(", ")
@@ -145,7 +161,8 @@ public class KotlinInplaceParameterIntroducer(
val markupModel = DocumentMarkupModel.forDocument(document, myProject, true) val markupModel = DocumentMarkupModel.forDocument(document, myProject, true)
markupModel.removeAllHighlighters() 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() revalidate()
} }
@@ -24,6 +24,7 @@ import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile import com.intellij.psi.PsiFile
import com.intellij.psi.search.LocalSearchScope
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor 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.KotlinIntroduceHandlerBase
import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetParent import org.jetbrains.kotlin.idea.refactoring.introduce.selectElementsWithTargetParent
import org.jetbrains.kotlin.idea.refactoring.introduce.showErrorHintByKey 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.IdeDescriptorRenderers
import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.executeCommand
import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.approximateWithResolvableType 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.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList 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.psi.psiUtil.parents
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils import org.jetbrains.kotlin.resolve.scopes.JetScopeUtils
import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.types.JetType
import java.util.Collections
import kotlin.test.fail import kotlin.test.fail
public data class IntroduceParameterDescriptor( public data class IntroduceParameterDescriptor(
@@ -54,7 +63,8 @@ public data class IntroduceParameterDescriptor(
val callable: JetNamedDeclaration, val callable: JetNamedDeclaration,
val callableDescriptor: FunctionDescriptor, val callableDescriptor: FunctionDescriptor,
val addedParameter: JetParameter, val addedParameter: JetParameter,
val parameterType: JetType val parameterType: JetType,
val parametersToRemove: List<JetParameter>
) { ) {
val valVar: JetValVar val valVar: JetValVar
@@ -81,10 +91,13 @@ public data class IntroduceParameterDescriptor(
fun IntroduceParameterDescriptor.performRefactoring() { fun IntroduceParameterDescriptor.performRefactoring() {
runWriteAction { runWriteAction {
JetPsiUtil.deleteElementWithDelimiters(addedParameter) JetPsiUtil.deleteElementWithDelimiters(addedParameter)
val config = object: JetChangeSignatureConfiguration { val config = object: JetChangeSignatureConfiguration {
override fun configure(originalDescriptor: JetMethodDescriptor, bindingContext: BindingContext): JetMethodDescriptor { override fun configure(originalDescriptor: JetMethodDescriptor, bindingContext: BindingContext): JetMethodDescriptor {
return originalDescriptor.modify { return originalDescriptor.modify {
val parameters = callable.getValueParameters()
parametersToRemove.map { parameters.indexOf(it) }.sortDescending().forEach { removeParameter(it) }
val parameterInfo = JetParameterInfo(name = addedParameter.getName()!!, val parameterInfo = JetParameterInfo(name = addedParameter.getName()!!,
type = parameterType, type = parameterType,
defaultValueForParameter = JetPsiUtil.deparenthesize(originalExpression), defaultValueForParameter = JetPsiUtil.deparenthesize(originalExpression),
@@ -103,7 +116,7 @@ fun IntroduceParameterDescriptor.performRefactoring() {
} }
public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() { 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 psiFactory = JetPsiFactory(project)
val parameterList = targetParent.getValueParameterList() val parameterList = targetParent.getValueParameterList()
@@ -119,15 +132,25 @@ public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() {
val expressionType = context[BindingContext.EXPRESSION_TYPE, expression] ?: KotlinBuiltIns.getInstance().getAnyType() val expressionType = context[BindingContext.EXPRESSION_TYPE, expression] ?: KotlinBuiltIns.getInstance().getAnyType()
val parameterType = expressionType.approximateWithResolvableType(JetScopeUtils.getResolutionScope(targetParent, context), false) val parameterType = expressionType.approximateWithResolvableType(JetScopeUtils.getResolutionScope(targetParent, context), false)
val validatorContainer = val body = when (targetParent) {
when (targetParent) { is JetFunction -> targetParent.getBodyExpression()
is JetFunction -> targetParent.getBodyExpression() is JetClass -> targetParent.getBody()
is JetClass -> targetParent.getBody() else -> null
else -> null } ?: throw AssertionError("Body element is not found: ${JetPsiUtil.getElementTextWithContext(targetParent)}")
} ?: throw AssertionError("Body element is not found: ${JetPsiUtil.getElementTextWithContext(targetParent)}") val nameValidator = JetNameValidatorImpl(body, null, JetNameValidatorImpl.Target.PROPERTIES)
val nameValidator = JetNameValidatorImpl(validatorContainer, null, JetNameValidatorImpl.Target.PROPERTIES)
val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(parameterType, nameValidator, "p")) val suggestedNames = linkedSetOf(*JetNameSuggester.suggestNames(parameterType, nameValidator, "p"))
val parametersToRemove = (parameterList?.getParameters() ?: Collections.emptyList()).filter {
if (!it.hasValOrVarNode()) {
val usages = DefaultSearchHelper<JetParameter>()
.newRequest(UsagesSearchTarget(element = it))
.search()
.toList()
usages.isNotEmpty() && usages.all { expression.isAncestor(it.getElement()) }
}
else false
}
project.executeCommand(INTRODUCE_PARAMETER) { project.executeCommand(INTRODUCE_PARAMETER) {
val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(parameterType) val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(parameterType)
val newParameter = psiFactory.createParameter("${suggestedNames.first()}: $renderedType") val newParameter = psiFactory.createParameter("${suggestedNames.first()}: $renderedType")
@@ -160,7 +183,8 @@ public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() {
targetParent, targetParent,
functionDescriptor, functionDescriptor,
addedParameter, addedParameter,
parameterType) parameterType,
parametersToRemove)
if (editor.getSettings().isVariableInplaceRenameEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) { if (editor.getSettings().isVariableInplaceRenameEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) {
with(PsiDocumentManager.getInstance(project)) { with(PsiDocumentManager.getInstance(project)) {
commitDocument(editor.getDocument()) commitDocument(editor.getDocument())
@@ -0,0 +1,8 @@
// TARGET:
class A(val a: Int, s: String) {
fun foo() = (<selection>a + 1</selection>) * 2
}
fun test() {
val t = A(1, "2").a
}
@@ -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
}
@@ -0,0 +1,8 @@
// TARGET:
class A(val a: Int, s: String) {
fun foo() = (<selection>a + 1</selection>) * 2
}
fun test() {
A(1, "2")
}
@@ -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")
}
@@ -0,0 +1,10 @@
// TARGET:
class A(val a: Int, s: String) {
val x = a + 2
fun foo() = (<selection>a + 1</selection>) * 2
}
fun test() {
A(1, "2")
}
@@ -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")
}
@@ -0,0 +1,7 @@
fun foo(a: Int, s: String, b: Int): Int {
return (<selection>a + b</selection>) * 2
}
fun test() {
foo(1, "2", 3)
}
@@ -0,0 +1,7 @@
fun foo(s: String, i: Int = a + b): Int {
return (i) * 2
}
fun test() {
foo("2")
}
@@ -0,0 +1,7 @@
fun foo(a: Int, s: String): Int {
return (<selection>a + 1</selection>) * 2
}
fun test() {
foo(1, "2")
}
@@ -0,0 +1,7 @@
fun foo(s: String, i: Int = a + 1): Int {
return (i) * 2
}
fun test() {
foo("2")
}
@@ -0,0 +1,7 @@
fun foo(a: Int, s: String): Int {
return (<selection>a + 1</selection>) * a
}
fun test() {
foo(1, "2")
}
@@ -0,0 +1,7 @@
fun foo(a: Int, s: String, i: Int = a + 1): Int {
return (i) * a
}
fun test() {
foo(1, "2")
}
@@ -2215,12 +2215,48 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
doIntroduceParameterTest(fileName); 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") @TestMetadata("defaultValueInParens.kt")
public void testDefaultValueInParens() throws Exception { public void testDefaultValueInParens() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/defaultValueInParens.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/defaultValueInParens.kt");
doIntroduceParameterTest(fileName); 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") @TestMetadata("functionWithApproximatedType.kt")
public void testFunctionWithApproximatedType() throws Exception { public void testFunctionWithApproximatedType() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionWithApproximatedType.kt"); String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionWithApproximatedType.kt");