Introduce Parameter: Support conversion of local variables
#KT-9170 Fixed
This commit is contained in:
@@ -425,6 +425,7 @@ public class KotlinRefactoringUtil {
|
||||
if (expressions.size() == 0) {
|
||||
if (failOnEmptySuggestion) throw new IntroduceRefactoringException(
|
||||
KotlinRefactoringBundle.message("cannot.refactor.not.expression"));
|
||||
callback.run(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+12
-11
@@ -19,21 +19,16 @@ package org.jetbrains.kotlin.idea.refactoring.introduce
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer
|
||||
import com.intellij.refactoring.rename.inplace.InplaceRefactoring
|
||||
import com.intellij.ui.NonFocusableCheckBox
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.core.replaced
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtNamedDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypeAndBranch
|
||||
import java.awt.BorderLayout
|
||||
|
||||
public abstract class AbstractKotlinInplaceIntroducer<D: KtNamedDeclaration>(
|
||||
@@ -77,10 +72,16 @@ public abstract class AbstractKotlinInplaceIntroducer<D: KtNamedDeclaration>(
|
||||
): KtExpression? {
|
||||
if (exprText == null || !declaration.isValid()) return null
|
||||
|
||||
return containingFile
|
||||
.findElementAt(marker.getStartOffset())
|
||||
?.getNonStrictParentOfType<KtSimpleNameExpression>()
|
||||
?.replaced(KtPsiFactory(myProject).createExpression(exprText))
|
||||
val leaf = containingFile.findElementAt(marker.startOffset) ?: return null
|
||||
|
||||
leaf.getParentOfTypeAndBranch<KtProperty> { nameIdentifier }?.let {
|
||||
return it.replaced(KtPsiFactory(myProject).createDeclaration(exprText))
|
||||
}
|
||||
|
||||
val occurrenceExprText = (myExpr as? KtProperty)?.name ?: exprText
|
||||
return leaf
|
||||
.getNonStrictParentOfType<KtSimpleNameExpression>()
|
||||
?.replaced(KtPsiFactory(myProject).createExpression(occurrenceExprText))
|
||||
}
|
||||
|
||||
override fun updateTitle(declaration: D?) = updateTitle(declaration, null)
|
||||
|
||||
+16
-1
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter
|
||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.RangeMarker
|
||||
import com.intellij.openapi.editor.impl.DocumentMarkupModel
|
||||
import com.intellij.openapi.editor.markup.EffectType
|
||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||
@@ -26,6 +27,7 @@ import com.intellij.openapi.editor.markup.MarkupModel
|
||||
import com.intellij.openapi.editor.markup.TextAttributes
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.NonFocusableCheckBox
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.createPrimaryConstructorParameterListIfAbsent
|
||||
@@ -38,6 +40,7 @@ import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getValueParameters
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.singletonList
|
||||
@@ -135,7 +138,9 @@ public class KotlinInplaceParameterIntroducer(
|
||||
val parameterType = currentType ?: parameter.getTypeReference()!!.getText()
|
||||
descriptor = descriptor.copy(newParameterName = parameterName!!, newParameterTypeText = parameterType)
|
||||
val modifier = if (valVar != KotlinValVar.None) "${valVar.keywordName} " else ""
|
||||
val defaultValue = if (withDefaultValue) " = ${newArgumentValue.getText()}" else ""
|
||||
val defaultValue = if (withDefaultValue) {
|
||||
" = ${if (newArgumentValue is KtProperty) newArgumentValue.name else newArgumentValue.text}"
|
||||
} else ""
|
||||
|
||||
"$modifier$parameterName: $parameterType$defaultValue"
|
||||
}
|
||||
@@ -239,6 +244,16 @@ public class KotlinInplaceParameterIntroducer(
|
||||
revalidate()
|
||||
}
|
||||
|
||||
override fun getRangeToRename(element: PsiElement): TextRange {
|
||||
if (element is KtProperty) return element.nameIdentifier!!.textRange.shiftRight(-element.startOffset)
|
||||
return super.getRangeToRename(element)
|
||||
}
|
||||
|
||||
override fun createMarker(element: PsiElement): RangeMarker {
|
||||
if (element is KtProperty) return super.createMarker(element.nameIdentifier)
|
||||
return super.createMarker(element)
|
||||
}
|
||||
|
||||
override fun performIntroduce() {
|
||||
getDescriptorToRefactor(isReplaceAllOccurrences()).performRefactoring()
|
||||
}
|
||||
|
||||
+48
-21
@@ -27,6 +27,7 @@ import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer
|
||||
import com.intellij.refactoring.listeners.RefactoringEventListener
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
@@ -36,7 +37,6 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
|
||||
import org.jetbrains.kotlin.idea.core.NewDeclarationNameValidator
|
||||
import org.jetbrains.kotlin.idea.util.getResolutionScope
|
||||
import org.jetbrains.kotlin.idea.core.moveInsideParenthesesAndReplaceWith
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.removeTemplateEntryBracesIfPossible
|
||||
import org.jetbrains.kotlin.idea.core.refactoring.runRefactoringWithPostprocessing
|
||||
@@ -50,6 +50,7 @@ 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.getResolutionScope
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiRange
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.KotlinPsiUnifier
|
||||
import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange
|
||||
@@ -61,6 +62,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||
import org.jetbrains.kotlin.types.typeUtil.isNothing
|
||||
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import java.util.*
|
||||
import kotlin.test.fail
|
||||
|
||||
@@ -147,10 +149,11 @@ fun IntroduceParameterDescriptor.performRefactoring() {
|
||||
.forEach { methodDescriptor.removeParameter(it) }
|
||||
}
|
||||
|
||||
val defaultValue = if (newArgumentValue is KtProperty) (newArgumentValue as KtProperty).initializer else newArgumentValue
|
||||
val parameterInfo = KotlinParameterInfo(callableDescriptor = callableDescriptor,
|
||||
name = newParameterName,
|
||||
defaultValueForCall = if (withDefaultValue) null else newArgumentValue,
|
||||
defaultValueForParameter = if (withDefaultValue) newArgumentValue else null,
|
||||
defaultValueForCall = if (withDefaultValue) null else defaultValue,
|
||||
defaultValueForParameter = if (withDefaultValue) defaultValue else null,
|
||||
valOrVar = valVar)
|
||||
parameterInfo.currentTypeText = newParameterTypeText
|
||||
methodDescriptor.addParameter(parameterInfo)
|
||||
@@ -214,9 +217,20 @@ public open class KotlinIntroduceParameterHandler(
|
||||
): KotlinIntroduceHandlerBase() {
|
||||
open fun invoke(project: Project, editor: Editor, expression: KtExpression, targetParent: KtNamedDeclaration) {
|
||||
val physicalExpression = expression.substringContextOrThis
|
||||
if (physicalExpression is KtProperty && physicalExpression.isLocal && physicalExpression.nameIdentifier == null) {
|
||||
showErrorHintByKey(project, editor, "cannot.refactor.no.expression", INTRODUCE_PARAMETER)
|
||||
return
|
||||
}
|
||||
|
||||
val context = physicalExpression.analyze()
|
||||
|
||||
val expressionType = expression.extractableSubstringInfo?.type ?: context.getType(physicalExpression)
|
||||
val expressionType = if (physicalExpression is KtProperty && physicalExpression.isLocal) {
|
||||
context[BindingContext.VARIABLE, physicalExpression]?.type
|
||||
}
|
||||
else {
|
||||
expression.extractableSubstringInfo?.type ?: context.getType(physicalExpression)
|
||||
}
|
||||
|
||||
if (expressionType == null) {
|
||||
showErrorHint(project, editor, "Expression has no type", INTRODUCE_PARAMETER)
|
||||
return
|
||||
@@ -241,7 +255,13 @@ public open class KotlinIntroduceParameterHandler(
|
||||
else -> null
|
||||
} ?: throw AssertionError("Body element is not found: ${targetParent.getElementTextWithContext()}")
|
||||
val nameValidator = NewDeclarationNameValidator(body, sequenceOf(body), NewDeclarationNameValidator.Target.VARIABLES)
|
||||
val suggestedNames = KotlinNameSuggester.suggestNamesByType(replacementType, nameValidator, "p")
|
||||
|
||||
val suggestedNames = SmartList<String>().apply {
|
||||
if (physicalExpression is KtProperty && !ApplicationManager.getApplication().isUnitTestMode) {
|
||||
addIfNotNull(physicalExpression.name)
|
||||
}
|
||||
addAll(KotlinNameSuggester.suggestNamesByType(replacementType, nameValidator, "p"))
|
||||
}
|
||||
|
||||
val parametersUsages = findInternalUsagesOfParametersAndReceiver(targetParent, functionDescriptor)
|
||||
|
||||
@@ -252,21 +272,27 @@ public open class KotlinIntroduceParameterHandler(
|
||||
else {
|
||||
Collections.emptyList()
|
||||
}
|
||||
val occurrencesToReplace = expression.toRange()
|
||||
.match(body, KotlinPsiUnifier.DEFAULT)
|
||||
.filterNot {
|
||||
val textRange = it.range.getPhysicalTextRange()
|
||||
forbiddenRanges.any { it.intersects(textRange) }
|
||||
}
|
||||
.mapNotNull {
|
||||
val matchedElement = it.range.elements.singleOrNull()
|
||||
when (matchedElement) {
|
||||
is KtExpression -> matchedElement
|
||||
is KtStringTemplateEntryWithExpression -> matchedElement.getExpression()
|
||||
else -> null
|
||||
} as? KtExpression
|
||||
}
|
||||
.map { it.toRange() }
|
||||
|
||||
val occurrencesToReplace = if (expression is KtProperty) {
|
||||
ReferencesSearch.search(expression).mapNotNullTo(SmartList(expression.toRange())) { it.element?.toRange() }
|
||||
}
|
||||
else {
|
||||
expression.toRange()
|
||||
.match(body, KotlinPsiUnifier.DEFAULT)
|
||||
.filterNot {
|
||||
val textRange = it.range.getPhysicalTextRange()
|
||||
forbiddenRanges.any { it.intersects(textRange) }
|
||||
}
|
||||
.mapNotNull {
|
||||
val matchedElement = it.range.elements.singleOrNull()
|
||||
val matchedExpr = when (matchedElement) {
|
||||
is KtExpression -> matchedElement
|
||||
is KtStringTemplateEntryWithExpression -> matchedElement.expression
|
||||
else -> null
|
||||
} as? KtExpression
|
||||
matchedExpr?.toRange()
|
||||
}
|
||||
}
|
||||
|
||||
project.executeCommand(
|
||||
INTRODUCE_PARAMETER,
|
||||
@@ -296,11 +322,12 @@ public open class KotlinIntroduceParameterHandler(
|
||||
withDefaultValue = false,
|
||||
parametersUsages = parametersUsages,
|
||||
occurrencesToReplace = occurrencesToReplace,
|
||||
occurrenceReplacer = {
|
||||
occurrenceReplacer = replacer@ {
|
||||
val expressionToReplace = it.elements.single() as KtExpression
|
||||
val replacingExpression = psiFactory.createExpression(newParameterName)
|
||||
val substringInfo = expressionToReplace.extractableSubstringInfo
|
||||
val result = when {
|
||||
expressionToReplace is KtProperty -> return@replacer expressionToReplace.delete()
|
||||
expressionToReplace.isLambdaOutsideParentheses() -> {
|
||||
expressionToReplace
|
||||
.getStrictParentOfType<KtLambdaArgument>()!!
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.intellij.openapi.editor.ScrollType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
@@ -122,6 +121,11 @@ fun selectElementsWithTargetParent(
|
||||
}
|
||||
else {
|
||||
if (!editor.getSelectionModel().hasSelection()) {
|
||||
val elementAtCaret = file.findElementAt(editor.caretModel.offset)
|
||||
elementAtCaret?.getParentOfTypeAndBranch<KtProperty> { nameIdentifier }?.let {
|
||||
return@selectExpression selectTargetContainer(listOf(it))
|
||||
}
|
||||
|
||||
editor.getSelectionModel().selectLineAtCaret()
|
||||
}
|
||||
selectMultipleExpressions()
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
fun foo(): Int {
|
||||
<selection>val x = 1</selection>
|
||||
|
||||
return 1 + x
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
Cannot introduce parameter of type 'Unit'
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo(i: Int) {
|
||||
val <caret>t = 1
|
||||
val u = t + i
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun foo(i: Int, i1: Int = 1) {
|
||||
val u = i1 + i
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo(i: Int) {
|
||||
<selection>val t = 1</selection>
|
||||
val u = t + i
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fun foo(i: Int, i1: Int = 1) {
|
||||
val u = i1 + i
|
||||
}
|
||||
+21
-6
@@ -3370,12 +3370,6 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
|
||||
doIntroduceSimpleParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("localVar.kt")
|
||||
public void testLocalVar() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/localVar.kt");
|
||||
doIntroduceSimpleParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("partialSubstitution.kt")
|
||||
public void testPartialSubstitution() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/partialSubstitution.kt");
|
||||
@@ -3666,6 +3660,27 @@ public class ExtractionTestGenerated extends AbstractExtractionTest {
|
||||
doIntroduceSimpleParameterTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/introduceParameter/variableConversion")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class VariableConversion extends AbstractExtractionTest {
|
||||
public void testAllFilesPresentInVariableConversion() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceParameter/variableConversion"), Pattern.compile("^(.+)\\.(kt|kts)$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("caretAtIdentifier.kt")
|
||||
public void testCaretAtIdentifier() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/variableConversion/caretAtIdentifier.kt");
|
||||
doIntroduceSimpleParameterTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("fullSelection.kt")
|
||||
public void testFullSelection() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceParameter/variableConversion/fullSelection.kt");
|
||||
doIntroduceSimpleParameterTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/refactoring/introduceLambdaParameter")
|
||||
|
||||
Reference in New Issue
Block a user