Introduce Parameter: Duplicate search & replace

This commit is contained in:
Alexey Sedunov
2015-04-07 18:43:20 +03:00
parent 3bb86a63cc
commit 13776f5bbd
27 changed files with 281 additions and 34 deletions
@@ -36,6 +36,7 @@ import com.intellij.psi.PsiElement
import com.intellij.refactoring.rename.inplace.InplaceRefactoring
import com.intellij.ui.DottedBorder
import com.intellij.ui.JBColor
import com.intellij.ui.NonFocusableCheckBox
import org.jetbrains.kotlin.idea.JetFileType
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.refactoring.changeSignature.*
@@ -49,11 +50,13 @@ import org.jetbrains.kotlin.psi.JetPsiFactory
import org.jetbrains.kotlin.psi.JetPsiUtil
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.resolve.BindingContext
import org.jetbrains.kotlin.types.JetType
import java.awt.BorderLayout
import java.awt.Color
import java.util.ArrayList
import java.util.Collections
import java.util.LinkedHashSet
import javax.swing.BorderFactory
import javax.swing.JPanel
@@ -62,21 +65,21 @@ import javax.swing.border.LineBorder
import kotlin.properties.Delegates
public class KotlinInplaceParameterIntroducer(
val descriptor: IntroduceParameterDescriptor,
val originalDescriptor: IntroduceParameterDescriptor,
editor: Editor,
project: Project
): KotlinInplaceVariableIntroducer<JetParameter>(
descriptor.addedParameter,
originalDescriptor.addedParameter,
editor,
project,
INTRODUCE_PARAMETER,
JetExpression.EMPTY_ARRAY,
null,
false,
descriptor.addedParameter,
originalDescriptor.addedParameter,
false,
true,
descriptor.parameterType,
originalDescriptor.parameterType,
false
) {
companion object {
@@ -112,6 +115,7 @@ public class KotlinInplaceParameterIntroducer(
}
}
private var descriptor = originalDescriptor
private var previewer: EditorEx? = null
private fun updatePreview(currentName: String?, currentType: String?) {
@@ -205,6 +209,29 @@ public class KotlinInplaceParameterIntroducer(
previewerPanel
}
val occurrenceCount = descriptor.occurrencesToReplace.size()
if (occurrenceCount > 1) {
addPanelControl {
val replaceAllCheckBox = NonFocusableCheckBox("Replace all occurrences ($occurrenceCount)")
replaceAllCheckBox.setSelected(true)
replaceAllCheckBox.setMnemonic('R')
replaceAllCheckBox.addActionListener {
descriptor = descriptor.copy(
occurrencesToReplace = with(originalDescriptor) {
if (replaceAllCheckBox.isSelected()) {
occurrencesToReplace
}
else {
Collections.singletonList(originalOccurrence)
}
}
)
updatePreview(null, null)
}
replaceAllCheckBox
}
}
}
private var myDocumentAdapter: DocumentAdapter? = null
@@ -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.PsiReference
import com.intellij.psi.search.LocalSearchScope
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -64,9 +65,13 @@ public data class IntroduceParameterDescriptor(
val callableDescriptor: FunctionDescriptor,
val addedParameter: JetParameter,
val parameterType: JetType,
val parametersToRemove: List<JetParameter>
val parametersUsages: Map<JetParameter, List<PsiReference>>,
val occurrencesToReplace: List<JetExpression>
) {
val originalOccurrence: JetExpression
get() = occurrencesToReplace.first { it.isAncestor(originalExpression) }
val valVar: JetValVar
val parametersToRemove: List<JetParameter>
init {
valVar = if (callable is JetClass) {
@@ -82,9 +87,18 @@ public data class IntroduceParameterDescriptor(
false
}
}
if (originalExpression.parents().any(modifierIsUnnecessary)) JetValVar.None else JetValVar.Val
if (occurrencesToReplace.all { it.parents().any(modifierIsUnnecessary) }) JetValVar.None else JetValVar.Val
}
else JetValVar.None
val occurrenceRanges = occurrencesToReplace.map { it.getTextRange() }
parametersToRemove = parametersUsages.entrySet()
.filter {
it.value.all { paramRef ->
occurrenceRanges.any { occurrenceRange -> occurrenceRange.contains(paramRef.getElement().getTextRange()) }
}
}
.map { it.key }
}
}
@@ -110,12 +124,15 @@ fun IntroduceParameterDescriptor.performRefactoring() {
override fun performSilently(affectedFunctions: Collection<PsiElement>): Boolean = true
}
if (runChangeSignature(callable.getProject(), callableDescriptor, config, callable.analyze(), callable, INTRODUCE_PARAMETER)) {
originalExpression.replace(JetPsiFactory(callable).createSimpleName(addedParameter.getName()!!))
val paramRef = JetPsiFactory(callable).createSimpleName(addedParameter.getName()!!)
occurrencesToReplace.forEach { it.replace(paramRef) }
}
}
}
public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() {
public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() {
open fun configure(descriptor: IntroduceParameterDescriptor): IntroduceParameterDescriptor = descriptor
fun invoke(project: Project, editor: Editor, expression: JetExpression, targetParent: JetNamedDeclaration) {
val psiFactory = JetPsiFactory(project)
@@ -140,16 +157,28 @@ public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() {
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
}
val parametersUsages = targetParent.getValueParameters()
.filter { !it.hasValOrVarNode() }
.map {
it to DefaultSearchHelper<JetParameter>()
.newRequest(UsagesSearchTarget(element = it))
.search()
.toList()
}
.filter { it.second.isNotEmpty() }
.toMap()
val occurrencesToReplace = expression.toRange()
.match(body, JetPsiUnifier.DEFAULT)
.map {
val matchedElement = it.range.elements.singleOrNull()
when (matchedElement) {
is JetExpression -> matchedElement
is JetStringTemplateEntryWithExpression -> matchedElement.getExpression()
else -> null
} as? JetExpression
}
.filterNotNull()
project.executeCommand(INTRODUCE_PARAMETER) {
val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(parameterType)
@@ -179,12 +208,13 @@ public class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() {
}
val introduceParameterDescriptor =
IntroduceParameterDescriptor(JetPsiUtil.deparenthesize(expression)!!,
targetParent,
functionDescriptor,
addedParameter,
parameterType,
parametersToRemove)
configure(IntroduceParameterDescriptor(JetPsiUtil.deparenthesize(expression)!!,
targetParent,
functionDescriptor,
addedParameter,
parameterType,
parametersUsages,
occurrencesToReplace))
if (editor.getSettings().isVariableInplaceRenameEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) {
with(PsiDocumentManager.getInstance(project)) {
commitDocument(editor.getDocument())
@@ -0,0 +1,10 @@
// TARGET:
class A(val a: Int, s: String) {
val x = a + 1
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 = i
fun foo() = i * 2
}
fun test() {
A(1, "2")
}
@@ -0,0 +1,10 @@
// TARGET:
class A(val a: Int, s: String) {
val x = <selection>a + 1</selection>
fun foo() = (a + 1) * 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 = i
fun foo() = i * 2
}
fun test() {
A(1, "2")
}
@@ -0,0 +1,10 @@
// TARGET:
class A(val a: Int, s: String) {
val x = a + 1
fun foo() = (<selection>a + 1</selection>) * 2
}
fun test() {
val t = A(1, "2").a
}
@@ -0,0 +1,10 @@
// TARGET:
class A(val a: Int, s: String, val i: Int = a + 1) {
val x = i
fun foo() = i * 2
}
fun test() {
val t = A(1, "2").a
}
@@ -0,0 +1,11 @@
// SINGLE_REPLACE
// TARGET:
class A(val a: Int, s: String) {
val x = a + 1
fun foo() = (<selection>a + 1</selection>) * 2
}
fun test() {
A(1, "2")
}
@@ -0,0 +1,11 @@
// SINGLE_REPLACE
// TARGET:
class A(val a: Int, s: String, val i: Int = a + 1) {
val x = a + 1
fun foo() = i * 2
}
fun test() {
A(1, "2")
}
@@ -0,0 +1,11 @@
// SINGLE_REPLACE
// TARGET:
class A(val a: Int, s: String) {
val x = <selection>a + 1</selection>
fun foo() = (a + 1) * 2
}
fun test() {
A(1, "2")
}
@@ -0,0 +1,11 @@
// SINGLE_REPLACE
// TARGET:
class A(val a: Int, s: String, i: Int = a + 1) {
val x = i
fun foo() = (a + 1) * 2
}
fun test() {
A(1, "2")
}
@@ -1,6 +1,6 @@
// TARGET:
class A(val a: Int, s: String, val i: Int = a + 1) {
fun foo() = (i) * 2
fun foo() = i * 2
}
fun test() {
@@ -1,6 +1,6 @@
// TARGET:
class A(val a: Int, s: String, val i: Int = a + 1) {
fun foo() = (i) * 2
fun foo() = i * 2
}
fun test() {
@@ -2,7 +2,7 @@
class A(val a: Int, s: String, val i: Int = a + 1) {
val x = a + 2
fun foo() = (i) * 2
fun foo() = i * 2
}
fun test() {
@@ -1,5 +1,5 @@
fun foo(a: Int, i: Int = a + 1): Int {
val b = (i) * 2
val b = i * 2
return a + b
}
@@ -1,5 +1,5 @@
fun foo(s: String, i: Int = a + b): Int {
return (i) * 2
return i * 2
}
fun test() {
@@ -0,0 +1,8 @@
fun foo(a: Int, s: String): Int {
val t = (a + 1) * 2
return <selection>a + 1</selection> - t
}
fun test() {
foo(1, "2")
}
@@ -0,0 +1,8 @@
fun foo(s: String, i: Int = a + 1): Int {
val t = i * 2
return i - t
}
fun test() {
foo("2")
}
@@ -0,0 +1,9 @@
// SINGLE_REPLACE
fun foo(a: Int, s: String): Int {
val t = (a + 1) * 2
return <selection>a + 1</selection> - t
}
fun test() {
foo(1, "2")
}
@@ -0,0 +1,9 @@
// SINGLE_REPLACE
fun foo(a: Int, s: String, i: Int = a + 1): Int {
val t = (a + 1) * 2
return i - t
}
fun test() {
foo(1, "2")
}
@@ -1,5 +1,5 @@
fun foo(s: String, i: Int = a + 1): Int {
return (i) * 2
return i * 2
}
fun test() {
@@ -1,5 +1,5 @@
fun foo(a: Int, s: String, i: Int = a + 1): Int {
return (i) * a
return i * a
}
fun test() {
@@ -1,5 +1,5 @@
fun foo(a: Int, i: Int = a + 1): Int {
val b = (i) * 2
val b = i * 2
return a + b
}
@@ -1,6 +1,6 @@
class A {
constructor(a: Int, i: Int = a + 1) {
val b = (i) * 2
val b = i * 2
val t = a + b
}
}
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.refactoring.JetRefactoringUtil
import org.jetbrains.kotlin.idea.refactoring.introduce.extractFunction.ExtractKotlinFunctionHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.IntroduceParameterDescriptor
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter.KotlinIntroduceParameterHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.KotlinIntroducePropertyHandler
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinIntroduceVariableHandler
@@ -65,7 +66,16 @@ public abstract class AbstractJetExtractionTest() : JetLightCodeInsightFixtureTe
protected fun doIntroduceParameterTest(path: String) {
doTest(path) { file ->
with (KotlinIntroduceParameterHandler()) {
val handler = object: KotlinIntroduceParameterHandler() {
override fun configure(descriptor: IntroduceParameterDescriptor): IntroduceParameterDescriptor {
val fileText = file.getText()
val singleReplace = InTextDirectivesUtils.isDirectiveDefined(fileText, "// SINGLE_REPLACE")
return with (descriptor) {
copy(occurrencesToReplace = if (singleReplace) Collections.singletonList(originalOccurrence) else occurrencesToReplace)
}
}
}
with (handler) {
val target = file.findElementByComment("// TARGET:") as? JetNamedDeclaration
if (target != null) {
JetRefactoringUtil.selectExpression(fixture.getEditor(), file, true) { expression ->
@@ -2203,6 +2203,36 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
doIntroduceParameterTest(fileName);
}
@TestMetadata("classMultipleUsages1.kt")
public void testClassMultipleUsages1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsages1.kt");
doIntroduceParameterTest(fileName);
}
@TestMetadata("classMultipleUsages2.kt")
public void testClassMultipleUsages2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsages2.kt");
doIntroduceParameterTest(fileName);
}
@TestMetadata("classMultipleUsagesOutsideScope.kt")
public void testClassMultipleUsagesOutsideScope() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsagesOutsideScope.kt");
doIntroduceParameterTest(fileName);
}
@TestMetadata("classMultipleUsagesSingleReplace1.kt")
public void testClassMultipleUsagesSingleReplace1() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsagesSingleReplace1.kt");
doIntroduceParameterTest(fileName);
}
@TestMetadata("classMultipleUsagesSingleReplace2.kt")
public void testClassMultipleUsagesSingleReplace2() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classMultipleUsagesSingleReplace2.kt");
doIntroduceParameterTest(fileName);
}
@TestMetadata("classNoParams.kt")
public void testClassNoParams() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/classNoParams.kt");
@@ -2245,6 +2275,18 @@ public class JetExtractionTestGenerated extends AbstractJetExtractionTest {
doIntroduceParameterTest(fileName);
}
@TestMetadata("functionMultipleUsages.kt")
public void testFunctionMultipleUsages() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionMultipleUsages.kt");
doIntroduceParameterTest(fileName);
}
@TestMetadata("functionMultipleUsagesSingleReplace.kt")
public void testFunctionMultipleUsagesSingleReplace() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionMultipleUsagesSingleReplace.kt");
doIntroduceParameterTest(fileName);
}
@TestMetadata("functionUnusedParameter.kt")
public void testFunctionUnusedParameter() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/functionUnusedParameter.kt");