Introduce Parameter: Duplicate search & replace
This commit is contained in:
+31
-4
@@ -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
|
||||
|
||||
+50
-20
@@ -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")
|
||||
}
|
||||
+11
@@ -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")
|
||||
}
|
||||
+11
@@ -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
-1
@@ -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")
|
||||
}
|
||||
+9
@@ -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
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+11
-1
@@ -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 ->
|
||||
|
||||
+42
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user