Introduce Parameter: Support conversion of local variables

#KT-9170 Fixed
This commit is contained in:
Alexey Sedunov
2015-12-10 19:35:37 +03:00
parent fff011f60a
commit d0b9b6a3b4
12 changed files with 117 additions and 46 deletions
@@ -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;
}
@@ -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)
@@ -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()
}
@@ -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'
@@ -0,0 +1,4 @@
fun foo(i: Int) {
val <caret>t = 1
val u = t + i
}
@@ -0,0 +1,3 @@
fun foo(i: Int, i1: Int = 1) {
val u = i1 + i
}
@@ -0,0 +1,4 @@
fun foo(i: Int) {
<selection>val t = 1</selection>
val u = t + i
}
@@ -0,0 +1,3 @@
fun foo(i: Int, i1: Int = 1) {
val u = i1 + i
}
@@ -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")