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)
}
public fun setParameter(index: Int, parameter: JetParameterInfo) {
parameters[index] = parameter
}
public fun removeParameter(index: Int) {
parameters.remove(index)
}
@@ -83,13 +83,25 @@ public class KotlinInplaceParameterIntroducer(
private val LOG = Logger.getInstance(javaClass<KotlinInplaceParameterIntroducer>())
}
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<TextRange>()
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()
}
@@ -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<JetParameter>
) {
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<JetParameter>()
.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())
@@ -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);
}
@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");